teaching machines

LaTeX Calendar Generator

My wife is crafting together a calendar full of family photos, and she asked me to print off some simple 2016 calendar grids that she found online. I looked at the templates and vomited. They had URLs on them.

Because I suffer from developerism, I asked my wife if she’d let me try and generate some calendar pages for her. She agreed and even sat and watched me code up the script that I share below. She is rarely the direct benefactor of my career and skill set, and we will forever treasure this night when she watched me code up a calendar generator. Especially when we run it each year for the rest of our lives.

A page from the end results looks some like this:

Screen Shot 2015-12-23 at 9.24.19 AM

 

The generator is written in Ruby and its output is LaTeX source, which can be fed into a LaTeX interpreter like pdflatex.

#!/usr/bin/env ruby

# ---------------------------------------------------------------------------- 
# FILE:   caltex                                                       
# AUTHOR: Chris Johnson                                                        
# DATE:   Dec 22 2015                                                          
#                                                                              
# Generates the LaTeX source for a calendar for a given month. Written with
# my wife watching.
#
# Usage: ./caltex year [start-month [end-month]]
#
# To generate a PDF for all the months of 2016, run the following:
#
#   ./caltex 2016 > 2016.tex
#   pdflatex 2016.tex
# ---------------------------------------------------------------------------- 

require 'date'

def emit_month_page year, month
  starts_on = Date.new(year, month, 1)
  ends_on = Date.new(year, month, -1)

  # We need enough boxes for all the days of the month plus whatever number of
  # leading empty boxes in the first week before the first day. These will be
  # chunked into rows of 7.
  nweeks = ((ends_on.day + starts_on.wday) / 7.0).ceil

  # Echo the month name, centered and bold and huge.
  puts '\begin{center}\Huge\bf'
  puts starts_on.strftime('%B')
  puts '\end{center}'

  # Now for the grid.
  puts '\begin{tabularx}{\linewidth}{|X|X|X|X|X|X|X|} \hline'

  # Center the weekdays in their cells.
  weekdays = %w{Sunday Monday Tuesday Wednesday Thursday Friday Saturday}
  print weekdays.map { |weekday| "\\centering\\arraybackslash{}#{weekday}" }.join('&')
  puts '\\\\ \hline'

  # Walk the grid. Translate the (week, weekday) 2D coordinate into a logical
  # day of month. Only the ones in [1, 28|29|30|31] get a label. The rule is to
  # push the label down a bit.
  height = nweeks == 6 ? 58 : 75
  (0...nweeks).each do |week|
    labels = (0..6).map do |weekday|
      day_of_month = week * 7 + weekday - starts_on.wday + 1
      if day_of_month >= 1 && day_of_month <= ends_on.day
        "\\rule{0pt}{3.5ex}\\large#{day_of_month}"
      else
        ""
      end
    end
    print labels.join(' & ')
    puts " \\\\[#{height}pt] \\hline"
  end

  puts '\end{tabularx}\clearpage'
end

case ARGV.size
  when 1
    year = ARGV[0].to_i
    start_month = 1
    end_month = 12
  when 2
    year = ARGV[0].to_i
    start_month = ARGV[1].to_i
    end_month = start_month
  when 3
    year = ARGV[0].to_i
    start_month = ARGV[1].to_i
    end_month = ARGV[2].to_i
  else
    STDERR.puts "Usage: #{$0} year [start-month [end-month]]"
    exit 1
end

puts <<'EOF'
\documentclass[landscape]{article}
\usepackage[margin=0.7in]{geometry}
\setlength{\pdfpagewidth}{\paperwidth}
\setlength{\pdfpageheight}{\paperheight}
\usepackage{tabularx}
\pagestyle{empty}
\renewcommand{\familydefault}{\sfdefault}

\begin{document}
EOF

(start_month..end_month).each do |month|
  emit_month_page(year, month)
end

puts <<'EOF'
\end{document}
EOF

Happy new year!

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *