# Why are my height and width calculations incorrect when aligning TikZ Calendar days on a page?

I am trying to put together a dynamically sized calendar for the current year. I just need to specify these things

• width of calendar (set to \textwidth for dynamic resizing)
• height of calendar (set to \textheight for dynamic resizing)
• how many months per page
• total number of days per month to display (I used 32, 31 max days in month + 1 for month label)

The year is set automatically to the current year.

# Questions

• Why is the total horizontal width of my days (each \daywd) not equal to the \textwidth?

• Why must I set the \dayht to equal only 98% of the \textheight?

# Code

\documentclass{article}
\usepackage{geometry}
\usepackage{showframe}
\usepackage{tikz}
\usepackage{tikzpagenodes} % Adds nodes around page boxes (e.g. body)
\usetikzlibrary{calendar}

\usepackage{fancyhdr}
\pagestyle{fancy}

\usepackage{datetime}
\renewcommand{\dateseparator}{-}

\def\mntcount{2}% Set month count
\setlength{\parindent}{0pt}
\begin{document}
\pgfmathparse{(\textheight / 32)*.98} % How can I set this to 100% \textheight and still get it on one page?
\pgfmathsetlengthmacro\dayht{\pgfmathresult}
\pgfmathparse{(\textwidth / \mntcount)}
\pgfmathsetlengthmacro\daywd{\pgfmathresult}

%\count255 = 1
%\loop
\begin{tikzpicture}[every node={inner sep=0pt,outer sep=0pt}]
\calendar
[anchor=north west,dates=\the\year-1-01 to
\the\year-\mntcount-last,
every day/.style={baseline=(current bounding box.north west),inner sep=0pt,outer sep=0pt},
% Slightly modify each day's node
day code={
\node [name=\pgfcalendarsuggestedname,shape=rectangle,draw,minimum height=\dayht,minimum width=\daywd,text width=\daywd]
{\tikzdaytext};
},
execute before day scope={
%\ifdate{day of month=1}{\tikzmonthcode}{},
%every month/.append style={anchor=north,yshift=2em}
% Print month name
\ifdate{day of month=1}
{
% Shift right, coordinate system right(first iteration is ok because points are relative)
\pgftransformxshift{\daywd}
\draw (0,0) node [shape=rectangle, draw = red, font=\color{orange},text centered,text width=\daywd]
{\pgfcalendarmonthname{\pgfcalendarcurrentmonth}};
}{}{},every month/.append style={anchor=south,yshift=2cm}
},
% Each day is placed at (0,0), so shift entire coordinate system downwards after each day
%execute after day scope={\pgftransformyshift{-\dayht}}, % Initial Working Attempt
execute at begin day scope={\pgftransformyshift{-\dayht*\pgfcalendarcurrentday pt}} %
] at (current page text area.north west);
\end{tikzpicture}
% \ifnum\count255 < 12
%\repeat
\end{document}


# TO DO

1. Make Text scale when natural width is above or below a certain threshold based on ratios.

2. No easy interface for multiple languages

3. No easy interface for importing holidays from .ics

• @cfr I thought minimum width would be redundant here due to text width. I just threw it in there in case I wanted to make the text width less than the minimum width someday. – Jonathan Komar Aug 30 '16 at 14:49
• @cfr If text width=3cm and you do a inner sep=0pt, then the node is not wider than text width I think. – Jonathan Komar Aug 30 '16 at 15:00

You need to account for the width and height of rules and either ensure that inner sep=0pt is actually applied - in your MWE, the every day style has no effect at all - or stick to minimum width as text width=<x> will produce a node with width greater than <x>.

If you do that, it all fits:

\documentclass{article}
\usepackage{geometry}
\usepackage{showframe}
\usepackage{tikz}
\usepackage{tikzpagenodes} % Adds nodes around page boxes (e.g. body)
\usetikzlibrary{calendar}
\usepackage{fancyhdr}
\pagestyle{fancy}
\usepackage{datetime}
\renewcommand{\dateseparator}{-}
\def\mntcount{2}% Set month count
\setlength{\parindent}{0pt}
\begin{document}
\begin{tikzpicture}[every node={inner sep=0pt,outer sep=0pt}]
\pgfmathsetlengthmacro\dayht{((\textheight-2*\pgflinewidth)/ 32)}%
\pgfmathsetlengthmacro\daywd{(\textwidth / \mntcount)-\pgflinewidth}%
\calendar
[anchor=north west,dates=\the\year-1-01 to
\the\year-\mntcount-last,
every day/.style={baseline=(current bounding box.north west), inner sep=0pt, outer sep=0pt},
% Slightly modify each day's node
day code={%
\node [every day, name=\pgfcalendarsuggestedname, draw, align=left, minimum height=\dayht, minimum width=\daywd, text width=\daywd]
{\tikzdaytext};
},
execute before day scope={%
%\ifdate{day of month=1}{\tikzmonthcode}{},
%every month/.append style={anchor=north,yshift=2em}
% Print month name
\ifdate{day of month=1}
{%
% Shift right, coordinate system right(first iteration is ok because points are relative)
\pgftransformxshift{\daywd}%
\draw (0,0) node [draw = red, text=orange, minimum height=\dayht, minimum width=\daywd]
{\pgfcalendarmonthname{\pgfcalendarcurrentmonth}};
}{}{},
every month/.append style={anchor=south, yshift=2cm }%
},
% Each day is placed at (0,0), so shift entire coordinate system downwards after each day
%execute after day scope={\pgftransformyshift{-\dayht}}, % Initial Working Attempt
execute at begin day scope={\pgftransformyshift{-\dayht*\pgfcalendarcurrentday pt}}%
] at (current page text area.north west);
\end{tikzpicture}
\end{document}


# EDIT

This is a follow-up based on looking at the code you posted in your answer in light of the discussion in comments there.

I think you are making life more difficult for yourself than you need to. At least, I don't see why all the \dd stuff is required. The following example doesn't handle the language stuff and it lacks a couple of bells and whistles, but it does demonstrate how to produce a 12-month list of days with dates and month headings which is roughly similar to yours, but a bit more straightforward in terms of implementation.

The calendar is based on 2 colours. One is the background colour (white by default). The other is the highlight colour used for filling weekend days, labelling months etc. (gray by default; specialred from your answer in the example).

\colorlet{calbkcolour}{white}
\colorlet{calcolour}{gray}


We set some keys for use with calendars.

\tikzset{%


First, we'll use cal colour to change the highlight colour and cal back colour to change the background colour.

  cal colour/.code={%
\colorlet{calcolour}{#1}%s
},
cal back colour/.code={%
\colorlet{calbkcolour}{#1}%s
},


In your code, you set inner sep to 0pt and then have to compensate explicitly in the content using \mboxes. This doesn't seem the simplest solution. We'll use a key to set a consistent, configurable inner sep for our calendars so that, like the two colours above, it is always the same within a calendar but can vary between them.

  cal inner sep/.store in=\calinnersep,
cal inner sep=2.5pt,


We'll do the same for the number of months.

  cal month count/.store in=\calmntcount,
cal month count=2,


Now, we'll set up a key cal config to execute some code, when necessary, to do the calculations for us. I'm not sure if this is the ideal solution, but it seems to work reasonably well.

  cal config/.code={%


First, calculating the height and width of each node, roughly as before.

    \pgfmathsetlengthmacro\caldayht{((\textheight-\pgflinewidth)/ 32)}%
\pgfmathsetlengthmacro\caldaywd{(\textwidth / \calmntcount)-\pgflinewidth}%


We're going to use another length macro for the width of the text within the node. Although we don't need it here, the commented code could be used for the height of the node text, if desired.

    %     \pgfmathsetlengthmacro\caldaytxtht{((\textheight-\pgflinewidth)/ 32)-2*\calinnersep}% currently unused
\pgfmathsetlengthmacro\caldaytxtwd{(\textwidth / \calmntcount)-\pgflinewidth-2*\calinnersep}%
},


We trigger this code to make sure everything has some default value - just in case.

  cal config,
}


Now for the \calendar. I have to admit I find this macro strange, hard to use and the documentation difficult to understand.

  \calendar
[
anchor=north west,
cal month count=12,


This needs to come before cal config. If you wish to change cal inner sep it should be done first, too.

    cal colour=specialred,


Change the highlight colour from gray.

    cal config,


We need to do this because we've changed the number of months from the default (2) to 12. Otherwise, we could skip it to save the calculation time.

Some of the following could be added to the cal config if it will not vary between calendars.

    dates=\the\year-1-01 to \the\year-\calmntcount-last,


The specialised day code - aside from the stuff to add the month label - was not really doing anything, so drop it and just use the every day style.

    every day/.style={baseline=(current bounding box.north west), fill=calbkcolour, inner sep=\calinnersep, outer sep=0pt, draw, align=left, minimum height=\caldayht, minimum width=\caldaywd, text width=\caldaytxtwd},


The trick here is the fill=calbkcolour which will get changed for weekends. Note that calbkcolour is a colour name and not a macro, as explained above.

Similarly, we can simplify the month stuff by relying on the every month style.

    every month/.style={font=\bfseries, text=calcolour, minimum height=\caldayht, inner sep=\calinnersep, minimum width=\caldaywd},


Here we're setting the text colour to calcolour. Again, it is a colour name rather than a macro. Note, too, \bfseries. \bf and friends have been deprecated for use in LaTeX for 20+ years and ought not be used in new code.

Here's the simplified month code.

    month code={%
\pgftransformxshift{\caldaywd}%
\node [every month] {\tikzmonthtext};
},


This is the simplified execute before day scope.

    execute before day scope={%
% Print month name
\ifdate{day of month=1}
{%
\tikzmonthcode
}{},
},
execute at begin day scope={\pgftransformyshift{-\caldayht*\pgfcalendarcurrentday pt}},
]


Now for the weekends.

  if (Sunday) [cal back colour=calcolour!30]
if (Saturday) [cal back colour=calcolour!20]


Notice that this is setting calbkcolour to a mix based on the highlight colour, calcolour. This is then effective in the fill=calbkcolour in the every day style, but will be limited, of course, to the days which satisfy the conditionals.

This strange indirect method is due to the fact that it will not work, for example to say if (Sunday) [fill=red] even though it does work to say if (Sunday) [red] for red text. The issue has nothing to do with the at.

  at (current page text area.north west)% note that this makes no difference in this example


However, the at isn't necessary and could be eliminated. I've just left it in to show it isn't a problem combining it with the conditional formatting, as you'd thought.

  ;


That's it.

Complete code:

\documentclass[a3paper,landscape]{article}
\usepackage{geometry}
\usepackage{tikz}
\usepackage{tikzpagenodes} % Adds nodes around page boxes (e.g. body)
\usetikzlibrary{calendar}
\pagestyle{empty}
\setlength{\parindent}{0pt}
\definecolor{specialred}{cmyk}{.07,.81,.59,.04}
\colorlet{calbkcolour}{white}
\colorlet{calcolour}{gray}
\tikzset{%
cal colour/.code={%
\colorlet{calcolour}{#1}%s
},
cal back colour/.code={%
\colorlet{calbkcolour}{#1}%s
},
cal inner sep/.store in=\calinnersep,
cal inner sep=2.5pt,
cal month count/.store in=\calmntcount,
cal month count=2,
cal config/.code={%
\pgfmathsetlengthmacro\caldayht{((\textheight-\pgflinewidth)/ 32)}%
\pgfmathsetlengthmacro\caldaywd{(\textwidth / \calmntcount)-\pgflinewidth}%
%     \pgfmathsetlengthmacro\caldaytxtht{((\textheight-\pgflinewidth)/ 32)-2*\calinnersep}% currently unused
\pgfmathsetlengthmacro\caldaytxtwd{(\textwidth / \calmntcount)-\pgflinewidth-2*\calinnersep}%
},
cal config,
}
\begin{document}
\begin{tikzpicture}
\calendar
[
anchor=north west,
cal month count=12,
cal colour=specialred,
cal config,
dates=\the\year-1-01 to \the\year-\calmntcount-last,
every day/.style={baseline=(current bounding box.north west), fill=calbkcolour, inner sep=\calinnersep, outer sep=0pt, draw, align=left, minimum height=\caldayht, minimum width=\caldaywd, text width=\caldaytxtwd},
every month/.style={font=\bfseries, text=calcolour, minimum height=\caldayht, inner sep=\calinnersep, minimum width=\caldaywd},
month code={%
\pgftransformxshift{\caldaywd}%
\node [every month] {\tikzmonthtext};
},
execute before day scope={%
% Print month name
\ifdate{day of month=1}
{%
\tikzmonthcode
}{},
},
execute at begin day scope={\pgftransformyshift{-\caldayht*\pgfcalendarcurrentday pt}},
]
if (Sunday) [cal back colour=calcolour!30]
if (Saturday) [cal back colour=calcolour!20]
at (current page text area.north west)% note that this makes no difference in this example
;
\end{tikzpicture}
\end{document}

• My only gripe is that you used a fixed value for the rules instead of a variable. That makes the calendar no longer dynamic. (the .4pt and .8pt). Any fix for that in case the rule sizes are different than default? – Jonathan Komar Aug 30 '16 at 15:02
• It is just a minimal example. You can use the current line width or a variable or whatever. .4pt is just the standard line width so I used it for example. – cfr Aug 30 '16 at 15:04
• Yea I was asking where .4pt and .8pt are set. – Jonathan Komar Aug 30 '16 at 15:05
• That's just the default and twice the default. – cfr Aug 30 '16 at 15:06
• I just noticed that the days align center now with the removal of text width. This is undesired. – Jonathan Komar Aug 30 '16 at 15:15

In case anybody is interested, here is my code after some touching up. I work with minority languages, so it is both useful and fun to be able to customize calendars like this.

# Create a language file

Ideally you'd create a langauge switcher function that sets variables like language code, native language name, etc. For a quick and dirty solution, just hardcode everything like I did here:

## Welsh

% Setup Month Names
\def\pgfcalendarmonthname#1{%
\translate{\ifcase#1\or Ionawr\or Chwefror\or Mawrth\or Ebrill\or
Mai\or Mehefin\or Gorffennaf\or Awst\or Medi\or Hydref\or
Tachwedd\or Rhagfyr\fi}%
}
% Setup Weekday Short Names
\def\pgfcalendarweekdayshortname#1{%
\translate{\ifcase#1 Ll.\or Ma.\or Me.\or
Ia.\or Gw.\or Sa. \or Su.\fi}}


## Upland Tyrolean

% Setup Month Names
\def\pgfcalendarmonthname#1{%
\translate{\ifcase#1\or Jänner\or Feber\or März\or April\or
Mai\or Juni\or Juli\or August\or September\or Oktober\or
November\or Dezember\fi}%
}
% Setup Weekday Short Names
\def\pgfcalendarweekdayshortname#1{%
\translate{\ifcase#1 Ma.\or Eh.\or Mi.\or
Do.\or Fr.\or Sa. \or Su.\fi}}


# Calendar File vertical_serialized_days_per_month_calendar.tex

\documentclass{article}
\usepackage{fontspec}
%\setmainfont{Liberation Sans}
%\usepackage{showframe}
\usepackage{tikz}
\usepackage{tikzpagenodes} % Adds nodes around page boxes (e.g. body)
\usetikzlibrary{calendar,calc}
\usepackage{fancyhdr}
\cfoot{}
\rfoot{Tiroler Oberland}
\pagestyle{fancy}
\usepackage{datetime}
\renewcommand{\dateseparator}{-}

\definecolor{specialred}{cmyk}{.07,.81,.59,.04}
\def\calcolor{specialred}
\def\mntcount{12}% Set month count
\setlength{\parindent}{0pt}

% Setup Month Names
% Setup Weekday Short Names
%\input{welsh.tex}
%\input{oberland_tirolerisch.tex}
\def\pgfcalendarmonthname#1{%
\translate{\ifcase#1\or Jänner\or Feber\or März\or April\or
Mai\or Juni\or Juli\or August\or September\or Oktober\or
November\or Dezember\fi}%
}
% Setup Weekday Short Names
\def\pgfcalendarweekdayshortname#1{%
\translate{\ifcase#1 Ma.\or Eh.\or Mi.\or
Do.\or Fr.\or Sa. \or Su.\fi}}

\begin{document}
\begin{tikzpicture}[every node={inner sep=0pt,outer sep=0pt}]
\pgfmathsetlengthmacro\dayht{((\textheight-2*\pgflinewidth)/ 32)}%
\pgfmathsetlengthmacro\daywd{(\textwidth / \mntcount)-\pgflinewidth}%
\calendar
[anchor=north west,dates=\the\year-1-01 to
\the\year-\mntcount-last,
every day/.style={baseline=(current bounding box.north west), inner sep=0pt, outer sep=0pt},
% Slightly modify each day's node
day code={%
\def\dddescription{}%default description
\def\ddcolor{white}%default day color
\ifdate {Sunday}
{%
\def\dddescription{}
\def\ddcolor{\calcolor!30}
}%true
{}%false
\ifdate {Saturday}
{%
\def\dddescription{}
\def\ddcolor{\calcolor!20}
}%true
{}%false
\ifdate {workday}
{%
\def\dddescription{}
\def\ddcolor{white}
}%true
{}%false
% Generated with script
\ifdate{equals=2016-01-01}{\def\dddescription{Neujahr}}{}
\ifdate{equals=2016-01-06}{\def\dddescription{Heilige Drei Könige}}{}
\ifdate{equals=2016-03-19}{\def\dddescription{St. Josef}}{}
\ifdate{equals=2016-05-01}{\def\dddescription{Staatsfeiertag}}{}
\ifdate{equals=2016-05-04}{\def\dddescription{St. Florian}}{}
\ifdate{equals=2016-08-15}{\def\dddescription{Mariä Himmelfahrt}}{}
\ifdate{equals=2016-09-24}{\def\dddescription{St. Rupert}}{}
\ifdate{equals=2016-10-10}{\def\dddescription{Tag der Volksabstimmung}}{}
\ifdate{equals=2016-10-26}{\def\dddescription{Nationalfeiertag}}{}
\ifdate{equals=2016-11-01}{\def\dddescription{Allerheiligen}}{}
\ifdate{equals=2016-11-11}{\def\dddescription{St. Martin}}{}
\ifdate{equals=2016-11-15}{\def\dddescription{St. Leopold}}{}
\ifdate{equals=2016-12-08}{\def\dddescription{Mariä Empfängnis}}{}
\ifdate{equals=2016-12-24}{\def\dddescription{Heiliger Abend}}{}
\ifdate{equals=2016-12-25}{\def\dddescription{Weihnachten}}{}
\ifdate{equals=2016-12-26}{\def\dddescription{Stefanitag}}{}
\ifdate{equals=2016-12-31}{\def\dddescription{Silvester}}{}
\node [%
every day,
name=\pgfcalendarsuggestedname,
draw,
fill=\ddcolor,
align=left,
minimum height=\dayht,
minimum width=\daywd,
text width=\daywd
]%
(\pgfcalendarcurrentmonth-\pgfcalendarcurrentweekday)%unique label format mm-dd
{\mbox{ }\tikzdaytext\mbox{ }{\tiny\pgfcalendarweekdayshortname{\pgfcalendarcurrentweekday}}};
\node [anchor=south east,font=\fontsize{3}{4}\selectfont,inner sep=0pt,outer sep=0pt] at ($(\pgfcalendarcurrentmonth-\pgfcalendarcurrentweekday.south east)+(-.2ex,.2ex)$) {\dddescription};
},
execute before day scope={%
%\ifdate{day of month=1}{\tikzmonthcode}{},
%every month/.append style={anchor=north,yshift=2em}
% Print month name
\ifdate{day of month=1}
{%
% Shift right, coordinate system right(first iteration is ok because points are relative)
\pgftransformxshift{\daywd}%
\draw (0,0) node [text=\calcolor, font=\bf,minimum height=\dayht, minimum width=\daywd]
{\pgfcalendarmonthname{\pgfcalendarcurrentmonth}};
}{}{},
},
% Each day is placed at (0,0), so shift entire coordinate system downwards after each day
%execute after day scope={\pgftransformyshift{-\dayht}}, % Initial Working Attempt
execute at begin day scope={\pgftransformyshift{-\dayht*\pgfcalendarcurrentday pt}}%
] at (current page text area.north west);
\end{tikzpicture}
\end{document}


# Upland Tyrolean

Updated to include holidays from .ics file. I will add a link to my script in the future.

# Notes

As reference from /usr/local/texlive/2016/texmf-dist/tex/generic/pgf/frontendlayer/tikz/libraries/tikzlibrarycalendar.code.tex

line 86 contains the important day code node default definition

 71 % Templates for typesetting days, month, years
72
73 \tikzoption{day code}{\def\tikzdaycode{#1}}
74 \tikzoption{day text}{\def\tikzdaytext{#1}}
75 \tikzoption{days}{\tikzstyle{every day}+=[#1]}
76 \tikzstyle{every day}=[anchor=base east]
77
78 \tikzoption{month code}{\def\tikzmonthcode{#1}}
79 \tikzoption{month text}{\def\tikzmonthtext{#1}}
80 \tikzstyle{every month}=[]
81
82 \tikzoption{year code}{\def\tikzyearcode{#1}}
83 \tikzoption{year text}{\def\tikzyeartext{#1}}
84 \tikzstyle{every year}=[]
85
86 \def\tikzdaycode{\node[name=\pgfcalendarsuggestedname,every day]{\tikzdaytext};}
87 \def\tikzmonthcode{\node[every month]{\tikzmonthtext};}
88 \def\tikzyearcode{\node[every year]{\tikzyeartext};}
89
90 \def\tikzdaytext{\%d-}
91 \def\tikzmonthtext{\%mt}
92 \def\tikzyeartext{\%y0}


I could significantly increase typesetting time and reduce memory by including the coloring logic in the day code like this:

  day code={%
\def\dddescription{}%default description
\def\ddcolor{white}%default day color
\ifdate {Sunday}
{%
\def\dddescription{Sunday}
\def\ddcolor{\calcolor!30}
}%true
{}%false
\ifdate {Saturday}
{%
\def\dddescription{Saturday}
\def\ddcolor{\calcolor!20}
}%true
{}%false
\ifdate {workday}
{%
\def\dddescription{Workday}
\def\ddcolor{white}
}%true
{}%false
\node [%
every day,
name=\pgfcalendarsuggestedname,
draw,
fill=\ddcolor,
align=left,
minimum height=\dayht,
minimum width=\daywd,
text width=\daywd
]%
{\space\tikzdaytext\space{\tiny\pgfcalendarweekdayshortname{\pgfcalendarcurrentweekday}\dddescription}};
},

• Can you do one for Welsh? ;) – cfr Aug 30 '16 at 21:06
• @cfr Done. Do you know why so much memory is needed for the coloring of days? Is there a cleverer way of setting up day coloring logic? – Jonathan Komar Aug 31 '16 at 7:24
• Oh, wow! I was only joking, really. That's neat. Thank you! I have no idea about the memory. What are you going by exactly? Do you mean the message about how much memory TeX used goes up when you add the colour? – cfr Aug 31 '16 at 12:42
• The abbreviation for Monday should really be Llu. to match the others, but that's a minor quibble ;). (ll is one letter.) – cfr Aug 31 '16 at 12:45
• @cfr I mean if you comment out the ifday is saturday etc, compilation time is much faster. I think pgf is coloring all days per rule. Each rule overwrites the other. So 360 color applications per coditional rule. There must be a way to abstract the logic from the coloring. Haha no prob. Thanks for catching the ll. – Jonathan Komar Aug 31 '16 at 12:45