22

I am trying to make a two sided holiday calendar (DIN A4, two pages for one school year). For this I started with an example from Robert Krause and tried to modify it. Here is how it looks so far:

% DIN-A4 doublesided year calendar
% Author: Robert Krause
% License : Creative Commons attribution license
% Submitted to TeXample.net on 13 July 2018

% Modified by julia 2018

\documentclass[a4paper, ngerman, 10pt]{scrartcl}
\usepackage[utf8]{inputenc}
\usepackage[ngerman]{babel}
\usepackage[T1]{fontenc}
\usepackage{tikz,xparse}            % Use the calendar.sty style

\usepackage{translator} % German Month and Day names
\usepackage{fancyhdr}       % header and footer
\usepackage{fix-cm}     % Large year in header

\usepackage[ headheight = 0.8cm, hmargin=.5cm,
  top = 1.7cm, nofoot,bottom=0cm]{geometry}
\usetikzlibrary{calc}
\usetikzlibrary{calendar}
\renewcommand*\familydefault{\sfdefault}


\makeatletter
\long\def\ifnodedefined#1#2#3{%
    \@ifundefined{pgf@sh@ns@#1}{#3}{#2}%
}
\makeatother

% Names of Holidays are inserted by employing this macro
\def\termin#1#2{
  \ifnodedefined{cal-#1}{
  \node [anchor=north west, text width= 3.4cm] at
  ($(cal-#1.north west)+(3em, 0em)$) {\tiny{#2}};
  }{}
}


\newcounter{week}
\setcounter{week}{1}
\newcommand\woche[2]{
  \node [anchor=north east, align=right] at
    ($(#1.north east)+(0em, 0em)$) {\tiny{SW #2}.};}


\newcommand{\holidays}{
between=2018-10-29 and 2018-11-04,
between=2019-02-28 and 2019-02-28,
between=2019-03-01 and 2019-03-08,
between=2019-04-15 and 2019-04-26,
between=2019-06-10 and 2019-06-02,
between=2019-07-29 and 2019-09-01,
}

%Header
\renewcommand{\headrulewidth}{0.0pt}
\setlength{\headheight}{0.8cm}
\chead{
 \Huge 2018/2019
 \Large\textbf{Termine}\hfill
}
\cfoot{}

\newcommand{\kheight}{0.82}
\newcommand{\kwidth}{3.0}
\newcommand{\kshift}{3.4}
\newcommand{\calstartdate}{2018-09-10}

\newcommand{\kal}[2]{
\vspace*{-1cm}
\begin{tikzpicture}[every day/.style={anchor = north}]
\calendar[
  dates=#1,
  name=cal,
  day yshift = 3em,
  day code=
  {
    \node[name=\pgfcalendarsuggestedname,every day,shape=rectangle,
    minimum height= \kheight cm, text width = \kwidth cm, draw =
    gray]{\tikzdaytext \enskip
      \pgfcalendarweekdayshortname{\pgfcalendarcurrentweekday}};
    \ifdate{Monday}{          
    }{}
  },
  execute before day scope=
  {
    \ifdate{day of month=1}
    {
      % Shift right
      \pgftransformxshift{\kshift cm}
      % Print month name 
      \draw (0,0)node [shape=rectangle, minimum height= \kheight cm,
      text width = \kwidth cm, fill = red, text= white, draw = red, text centered]
      {\textbf{\pgfcalendarmonthname{\pgfcalendarcurrentmonth}}};
    }{}
    \ifdate{workday}
    {
      % normal days are white
      \tikzset{every day/.style={fill=white}}
      % Vacation (Germany, Baden-Wuerrtemberg) gray background
      \ifdate{#2}{%
        \tikzset{every day/.style={fill=gray!30}}
      }{}
    }{}
    % Saturdays and half holidays (Christma's and New year's eve)
    \ifdate{Saturday}{\tikzset{every day/.style={fill=red!10}}}{}
    % % Sundays and full holidays
    \ifdate{Sunday}{\tikzset{every day/.style={fill=red!20}}}{}
    %Tag der Arbeit
    \ifdate{equals=2018-10-03}{\tikzset{every
        day/.style={fill=red!20}}}{}
    % Christi Himmelfahrt
    \ifdate{equals=2019-05-30}{\tikzset{every
        day/.style={fill=red!20}}}{}
  },
 execute at begin day scope=
  {
    % each day is shifted down according to the day of month
    \pgftransformyshift{-\kheight*\pgfcalendarcurrentday cm}
  }
  ];
  % % Some Dates
  \termin{2018-10-03}{Tag der dt. Einheit}
  \termin{2019-01-01}{Neujahr}
  \termin{2019-01-06}{Heilige Drei Könige}
  \termin{2019-04-19}{Karfreitag}
  \termin{2019-04-21}{Ostersonntag}
  \termin{2019-04-22}{Ostermontag}
  \termin{2019-05-01}{Tag der Arbeit}
  \termin{2019-05-30}{Christi Himmelfahrt}
  \termin{2019-06-09}{Pfingstsonntag}
  \termin{2019-06-20}{Pfingstmontag}

\end{tikzpicture}
}

\begin{document}
\pagestyle{fancy}
\begin{center}
\kal{2018-09-10 to 2019-02-28}{
  between=2018-10-03 and 2018-10-05,
  between=2018-10-29 and 2018-11-04,
  between=2019-02-28 and 2019-02-28,
  between=2019-03-01 and 2019-03-08,
  between=2019-04-15 and 2019-04-26,
  between=2019-06-10 and 2019-06-02,
  between=2019-07-29 and 2019-09-01,
}
\kal{2019-03-01 to 2019-08-30}{
  between=2018-10-03 and 2018-10-05,
  between=2018-10-29 and 2018-11-04,
  between=2019-02-28 and 2019-02-28,
  between=2019-03-01 and 2019-03-08,
  between=2019-04-15 and 2019-04-26,
  between=2019-06-10 and 2019-06-02,
  between=2019-07-29 and 2019-09-01,
}
\pagebreak
%\kal{2019-03-01 to 2019-08-30}{\holidays}
\end{center}

\end{document}

There are some points where I need help:

  1. The school weeks should be numbered automatically, so in my example on Mo. 2018-09-10 there should be a small "SW 1" in the upper right corner of the day node. In Mo. 2018-09-17 there should be "SW 2" etc, Mo. 2018-10-01 would be "SW 4" and on Mo. 2018-10-29 there should be no mark since it is in the holidays, so "SW 8" would be on Mo. 2018-11-05 (and not "SW 9" since the SW counter shouldn't increment in the holidays).

If at least one day in a week is not a holiday day it should count as a "school week" and a particular "SW x" should be placed in the upper right corner of the first day in this week which is not a holiday day (in my example this would be always a Monday, but for a general solution it might be the case that for example Monday and Tuesday are holiday days but Wednesday, Thursday and Friday are not, in this case "SW x" should be printed on the Wednesday).

I tried the following code inside the day code, but it seems to be nonsense.

\ifdate{Monday}{
       \tikzset{
    loop over item/.code args={####1/####2/####3}{%
      \ifdate{between=####1 and ####2}{%
      }{
        \woche{\pgfcalendarsuggestedname}{\theweek}
        \stepcounter{week}    
      }},
    loop over item/.list/.expanded=\ferien
  }          
}{}

where the week command is defined as:

\newcounter{week}
\setcounter{week}{1}
\newcommand\woche[2]{
  \node [anchor=north east, align=right] at
    ($(#1.north east)+(0em, 0em)$) {\tiny{SW #2}.};}
  1. The calender week number should be on each monday in the right lower corner.

  2. The code redundancy should be reduced and the syntax for giving dates and holidays simplified as much as possible, in particular I want to give the list of holidays globally in just one place and another list of special dates in another global place (instead of writing \termin each time). However it should be possible to define several categories of dates (with different lists and styles).

You may have noticed that for example the date "Heilige Drei Könige" is printed on 2019-01-06 and as well in 2019-06-06 (which is wrong) since I specified the date once for both calendars. This should in particular be fixed (I know that I can fix it by duplicating the calendar code and set the date in the first part of the calendar only, but as written above I want to reduce the code redudancy and not increase it).

3
  • Is it a hard requirement to use TeX? The production of calendars seems to be sufficiently common to have encouraged the production of specific tools. Are you aware of pcal and gocal? I am the author of Gocal. I haven't worked on it for a while, but I am willing to add whatever fancy bells and whistles you'd like to have in your calendar. Your requirement look pretty specific. I have never heard of "School weeks"... Commented Oct 10, 2018 at 8:16
  • @StefanSchroeder Yes I want the result in LaTeX (and the layout as in my example). However it would be ok, if any lua or python backend would be used in an intermediate step.
    – Julia
    Commented Oct 10, 2018 at 9:30
  • just a tip: I was working on a latex solution with the help of python's workalendar but one of the dependent packages (ehem) fails for me at the moment.
    – Bart
    Commented Oct 20, 2018 at 13:53

2 Answers 2

11
+200

I think this addresses all the issues...

  1. For the School week thing, I used two conditionals: \ifWeekStarted, and \ifPrintSW. The first one is triggered every Monday to signal a week has started. Later on, if we're not in a holiday, and \ifWeekStarted, the \ifPrintSW switch is turned on. If this last switch is on, when PGF is drawing the node, it will step the schoolweek counter and write SW X. Now it will mark the earliest day of every week that it can. Now we only have to exclude possible holidays, so if the holiday falls on a Monday, it will hold \ifWeekStarted switch and wait for Tuesday to see if it can enable \ifPrintSW. We'll get to the holidays later.

  2. Similar to 1, but much easier, we only write a number every week. Here, however, we (may) have a problem. If you want both School Week and Calendar Week to start at 1, then the code is trivial. However, if you want the Calendar Week to start in January 1st, then I don't know if it's possible (with a reasonable amount of effort) without LuaTeX... I implemented a solution that with pdfTeX and XeTeX starts the Calendar Week numbering from 1 with the School Weeks, and with LuaTeX it calls os.date() to get the week number. Since you said it's OK, then OK :)

  3. I defined a command \addholiday which takes three arguments: an identifier for the holidays, a comma-separated list of holiday entries, and a style setting (which will be executed by \tikzset). Each holiday entry is of the form <date conditional>/<holiday name>. For example, the "Heilige Drei Könige" day is given as equals = 2019-01-06 / Heilige Drei Könige. All other valid PGF calendar day-selection keys are valid, for example: between = 2018-10-08 and 2018-10-09 / Seven-day weekend!!!. The code will then loop through the given holidays and print them when appropriate. Additionally, one can use the starred variant, \addholiday*, to define a "special" day, which is not a holiday per se, so it still counts as a school week, but such special days can have a header printed on them and have a special formatting.

Here's the first page when compiled with LuaTeX:

enter image description here

Note that the weeks are numbered from 37 because the output of date -d 2018-09-10 +%V (on a Linux machine) says that this is the 37th week of the year. When compiled with pdfTeX or XeTeX it will start from 1.

Notice that due to the Seven-day weekend!!! the label SW 5. appears only on Wednesday. Note also that, because for week number 42 I used \addholiday*, then the week still counts as School Week (SW 6.), but it gets a "Holiday Label" and gets formatted accordingly.


The first version I created (if someone is interested you can find it in the edit history -- it is too long to post two codes) used low-level definitions and argument handling to define the \addholiday macro, so it soon became far too awkward to maintain or modify anything (mostly because I was bodging-in features as I was writing the command. Don't recommend that :).

The current version uses expl3's property lists to store the holidays. This allows me to easily add properties to the holidays, query the existing properties, and use them in the drawing code.

If you call, say, this command:

\addholiday*{HDAY}{%
 , between = 2018-10-15 and 2018-10-19 / Special week!
 , equals = 2019-01-06 / Heilige Drei Könige
}
{every day/.style={fill=green!30}}

then the property tree created will be like this:

enter image description here

(I am deeply sorry for my attempt using forest. Code adapted from this answer).

When you do that, the \addholiday command creates a \l__julia_HDAY_holiday property list which will contain three properties: special, style, and dates. The special property will contain a boolean, true if the starred variant was used, false otherwise. The style property will hold the last argument to \addholiday which will do the formatting of this set of dates. The last property, dates, will contain the reference to another property list, \l__julia_HDAY_holiday_dates. This property list will contain as many properties as the number of entries you passed to \addholiday. Each property will be the name of the holiday, and the corresponding value will be the PGF Calendar test to find the dates.

Now the command \holidaycheck iterates through the defined holiday lists and calls \__julia_holiday_check:nnn. This last macro is responsible for checking if the current date is a holiday (with \ifdate), and executing the provided code if that's the case. With all this structure set up, implementing a verification to print only the first instance of a holiday's name is close to trivial :)

For each holiday (for example Heilige Drei Könige), we check if a command called \is_Heilige Drei Könige_printed is defined. If it is not, then we set the boolean \WriteName to true, write the holiday's name, and define the command \is_Heilige Drei Könige_printed (the definition is empty). If that command is defined (by the previous step) then we set \WriteName to false and don't write the name.

Code:

% DIN-A4 doublesided year calendar
% Author: Robert Krause
% License : Creative Commons attribution license
% Submitted to TeXample.net on 13 July 2018

% Modified by julia 2018

\documentclass[a4paper, ngerman, 10pt]{scrartcl}
\usepackage[utf8]{inputenc}
\usepackage[ngerman]{babel}
\usepackage[T1]{fontenc}
\usepackage{tikz,xparse}            % Use the calendar.sty style
\usepackage{iftex}
\makeatletter
\ifLuaTeX
  \expandafter\@firstofone
\else
  \expandafter\@gobble
\fi
  {\usepackage{luacode}}

\usepackage{translator} % German Month and Day names
\usepackage{fancyhdr}       % header and footer
\usepackage{fix-cm}     % Large year in header
\usepackage{xparse}

\usepackage[ headheight = 0.8cm, hmargin=.5cm,
  top = 1.7cm, nofoot,bottom=0cm]{geometry}

\usetikzlibrary{calc}
\usetikzlibrary{calendar}
\renewcommand*\familydefault{\sfdefault}

% Names of Holidays are inserted by employing this macro
\def\termin#1#2{%
  \node [anchor=north west, text width= 3.4cm] at
    ($(#1.north west)+(3em, 0em)$) {\tiny{#2}};
}

\ifLuaTeX
\begin{luacode*}
function get_week_number(y,m,d)
  return os.date("%V",os.time{year=y,month=m,day=d})
end
\end{luacode*}
\fi

\newcounter{calweek}
\newcounter{schoolweek}

\newcommand\woche[2]{%
  \node [anchor=south east, align=right] at
    ($(#1.south east)+(0em, 0em)$) {\tiny{#2}};}

\newcommand\schulwoche[2]{%
  \node [anchor=north east, align=right] at
    ($(#1.north east)+(0em, 0em)$) {\tiny{SW #2}.};}

\ExplSyntaxOn
\bool_new:N \WriteName
\prop_new:N \l__julia_holiday_list_prop
\bool_new:N \l__julia_special_bool
\cs_new:Npn \exp_args:NNnc { \::N \::n \::c \::: }
\NewDocumentCommand\addholiday
  { s m m m }
  {
    \IfBooleanTF { #1 }
      { \bool_set_true:N \l__julia_special_bool }
      { \bool_set_false:N \l__julia_special_bool }
    \__julia_add_holiday:nnn { #2 } { #3 } { #4 }
  }
\cs_new:Npn \__julia_add_holiday:nnn #1 #2 #3
  {
    \prop_clear_new:c { l__julia_#1_holiday }
    \prop_clear_new:c { l__julia_#1_holiday_dates }
    \__julia_add_holiday_aux:ccnnn
      { l__julia_#1_holiday } { l__julia_#1_holiday_dates }
      { #1 } { #2 } { #3 }
  }
\cs_new:Npn \__julia_add_holiday_aux:NNnnn #1 #2 #3 #4 #5
  {
    \prop_put:Nnn \l__julia_holiday_list_prop { #3 } { #1 }
    \exp_args:NNnV
    \prop_put:Nnn #1 { special } \l__julia_special_bool
    \prop_put:Nnn #1 { dates } { #2 }
    \prop_put:Nnn #1 { style } { #5 }
    \clist_map_inline:nn { #4 }
      { \__julia_prop_split_add:Nn #2 { ##1 } }
    % \prop_show:N #1
  }
\cs_generate_variant:Nn \__julia_add_holiday_aux:NNnnn { ccnnn }
\cs_new:Npn \__julia_prop_split_add:Nn #1 #2
  { \__julia_prop_split_add:Nw #1 #2 \q_stop }
\cs_new:Npn \__julia_prop_split_add:Nw #1 #2 / #3 \q_stop
  {
    \exp_args:NNff
    \prop_put:Nnn #1
      { \tl_trim_spaces:n { #3 } }
      { \tl_trim_spaces:n { #2 } }
  }
\cs_new:Npn \holidaycheck #1
  {
    \cs_gset_eq:NN \HolidayStyle \c_empty_tl
    \prop_map_inline:Nn \l__julia_holiday_list_prop
      {
        \prop_get:NnN ##2 { dates   } \HLprop
        \prop_get:NnN ##2 { style   } \HLstyle
        \prop_get:NnN ##2 { special } \HLtype
        \prop_map_inline:Nn \HLprop
          {
            \__julia_holiday_check:nnn
              { #1 } { ####1 } { ####2 }
          }
      }
  }
\cs_new:Npn \__julia_holiday_check:nnn #1 #2 #3
  {
    % #1 is the code passed to \holidaycheck
    % #2 is the holiday name
    % #3 is the holiday date
    \ifdate { #3 }
      {
        \cs_set:Npn \name { #2 }
        \cs_if_exist:cTF { is_ #2 _printed }
          { \bool_set_false:N \WriteName }
          { \bool_set_true:N \WriteName }
        #1
      }
      { }
  }
\cs_new:Npn \HolidaySetUsed #1
  { \cs_gset_eq:cN { is_ #1 _printed } \c_empty_tl }
\ExplSyntaxOff

%Header
\renewcommand{\headrulewidth}{0.0pt}
\setlength{\headheight}{0.8cm}
\chead{%
 \Huge 2018/2019
 \Large\textbf{Termine}\hfill
}
\cfoot{}

\newcommand{\kheight}{0.82}
\newcommand{\kwidth}{3.0}
\newcommand{\kshift}{3.4}
\newcommand{\calstartdate}{2018-09-10}

\newif\ifWeekStarted
\WeekStartedfalse
\newif\ifPrintSW
\PrintSWfalse
\newif\ifHasHoliday

\newcommand{\kal}[2]{%
\vspace*{-1cm}
\begin{tikzpicture}[every day/.style={anchor = north}]
\calendar[
  dates = #1,
  name = cal,
  day yshift = 3em,
  day code = 
    {%
      \holidaycheck{%
        \global\let\HolidayStyle\HLstyle
      }%
      \expandafter\tikzset\expandafter{\HolidayStyle}%
      \node [
        name = \pgfcalendarsuggestedname,
        every day,
        shape = rectangle,
        minimum height = \kheight cm,
        text width = \kwidth cm,
        draw = gray]
        {\tikzdaytext\enskip
         \pgfcalendarweekdayshortname{\pgfcalendarcurrentweekday}%
        };%
      \holidaycheck{%
        \IfBooleanF{\HLtype}{\global\PrintSWfalse} % A holiday
        \IfBooleanTF{\WriteName}
          {%
            \termin{\pgfcalendarsuggestedname}{\name}{}%
            \HolidaySetUsed{\name}%
          }%
          {}%
      }%
      \ifPrintSW
        \stepcounter{schoolweek}%
        \global\WeekStartedfalse
        \global\PrintSWfalse
        \schulwoche{\pgfcalendarsuggestedname}{\arabic{schoolweek}}%
      \fi
      \ifdate{Monday}{%
        \ifLuaTeX
          \setcounter{calweek}{%
            \directlua{%
              tex.print(
                get_week_number(
                  \pgfcalendarcurrentyear,
                  \pgfcalendarcurrentmonth,
                  \pgfcalendarcurrentday
                )
              )
            }%
          }%
        \else
          \stepcounter{calweek}%
        \fi
        \woche{\pgfcalendarsuggestedname}{\arabic{calweek}}%
      }{}%
    },
  execute before day scope=
    {%
      \ifdate{day of month=1}
      {%
        % Shift right
        \pgftransformxshift{\kshift cm}
        % Print month name 
        \draw (0,0) node [shape=rectangle, minimum height= \kheight cm,
          text width = \kwidth cm, fill = red, text= white, draw = red, text centered]
          {\textbf{\pgfcalendarmonthname{\pgfcalendarcurrentmonth}}};
      }{}%
      \ifdate{Monday}{%
        \global\WeekStartedtrue
      }{}
      \ifdate{workday}
      {%
        % normal days are white
        \tikzset{every day/.style={fill=white}}
        % Vacation (Germany, Baden-Wuerrtemberg) gray background
        \expandafter\ifdate\expandafter{#2}{%
          \tikzset{every day/.style={fill=gray!30}}
        }{%
          \ifWeekStarted
            \global\PrintSWtrue
          \fi
        }%
      }{}%
      % Saturdays and half holidays (Christma's and New year's eve)
      \ifdate{Saturday}{%
        \tikzset{every day/.style={fill=red!10}}%
      }{}%
      % % Sundays and full holidays
      \ifdate{Sunday}{%
        \tikzset{every day/.style={fill=red!20}}%
      }{}%
    },
  execute at begin day scope=
    {%
      % each day is shifted down according to the day of month
      \pgftransformyshift{-\kheight*\pgfcalendarcurrentday cm}
    }
  ];
\end{tikzpicture}
}

\addholiday{normal holidays}{%
 , between = 2018-10-08 and 2018-10-09 / Seven-day weekend!!!
 , equals = 2018-10-03 / Tag der dt. Einheit
 , equals = 2019-01-01 / Neujahr
 , equals = 2019-01-06 / Heilige Drei Könige
 , equals = 2019-04-19 / Karfreitag
 , equals = 2019-04-21 / Ostersonntag
 , equals = 2019-04-22 / Ostermontag
 , equals = 2019-05-01 / Tag der Arbeit
 , equals = 2019-05-30 / Christi Himmelfahrt
 , equals = 2019-06-09 / Pfingstsonntag
 , equals = 2019-06-20 / Pfingstmontag
}
{every day/.style={fill=gray!30}}

\addholiday*{very special week}{%
 , between = 2018-10-15 and 2018-10-19 / Special week!
}
{every day/.style={fill=green!30}}

\newcommand{\HolidayList}{%
  between=2018-10-03 and 2018-10-05,
  between=2018-10-29 and 2018-11-04,
  between=2019-02-28 and 2019-02-28,
  between=2019-03-01 and 2019-03-08,
  between=2019-04-15 and 2019-04-26,
  between=2019-06-10 and 2019-06-02,
  between=2019-07-29 and 2019-09-01,
}

\begin{document}
\pagestyle{fancy}
\begin{center}
\kal{2018-09-10 to 2019-02-28}{\HolidayList}
\kal{2019-03-01 to 2019-08-30}{\HolidayList}
\pagebreak
% \kal{2019-03-01 to 2019-08-30}{\holidays}
\end{center}

\end{document}
4
  • Thank you very much, great answer! Is there any way to reduce the code redundancy that you have th repeat the holiday list for the second page?
    – Julia
    Commented Oct 25, 2018 at 17:36
  • @Julia Glad you liked it :) I forgot about the last holiday list. This one is fairly simple: you can store the holidays in a macro and pass it to the \kal command. You just have to make sure that the macro is expanded once, so I added two more \expandafter to ensure this. Commented Oct 25, 2018 at 17:56
  • How can I modify it, that the holiday name of the \addholiday command is printed just once on the first day of that holiday?
    – Julia
    Commented Oct 26, 2018 at 11:35
  • @Julia Done :) I had to completely rework the innards of \addholiday, as it was far too messy to add more features. With the new version you only have the first day of the holiday printed and it should be much easier to add more features :) Commented Oct 26, 2018 at 17:25
1

We can do this with PGF/TikZ and TeX.

I'm using

  • my qrr.calendar libraries [1, 2] for
    • the /pgf/calendar/and key that first showed up in another answer of mine
    • the week numbering (shorthand: \%n- = 1, \%n= = ␣1, \%n0= 01),
    • the if key that can be used inside another if,
  • a LaTeX counter schoolweek which simply gets incremented for every school week,
  • a LaTeX counter lastschoolweek which stores the week number of the last typeset school week.
  • The /utils/if key is used to check whether a school week marking was already typeset for the current week.

Using lists to apply styles and labels.

The handlers /.list and /.expand once are used with the list macros that just store the dates or ranges. This way, you can say

\newcommand*\termine{
  equals=12-25 / Christmas Day / Holiday,
  …}

And with a key setup like this

\tikzset{termine/.style args={#1/#2/#3}{
  if={(#1) [days={label:#2,#3}]}

you can do termine/.list/.expand once=\termine and PGF/TikZ just checks all of them and, if true, it applies the label #2 and the style #3.

This is the power of the /tikz/if key because that way one can control both conditionals as well as the actions.

This is not possible with the “bare” if (<cond>) [<true>] pseudo path operator.

Numbering the school weeks

The [de]activate SW keys are used to only (de)activate the possibly school week label (all markings are implemented as TikZ labels):

activate SW/.style={
  mark SW/.style = {% only mark SW if we didn't already set this yearweek's SW
    /utils/if={\value{lastschoolweek}!=\the\numexpr\pgfcalendarcurrentweeknumber\relax}{
      /utils/exec=\setcounter{lastschoolweek}{\%n0},
      label = {[anchor=north east, every SW mark/.try]
        north east:SW~\stepcounter{schoolweek}\theschoolweek}}{}}},
deactivate SW/.style = {mark SW/.style=},

For this, the mark SW style must executed last when the node options are applies. That's way the last condition is

if = (workday) [ days = mark SW ]

The styles for holidays (Feiertag, Ferien, …) must have deactivate SW in them to disable the mark SW style.

and much more

If we also set up

/pgf/calendar/strip first/.style args={#1/#2}{#1},
/pgf/calendar/strip first yesterday/.style args={#1/#2}{yesterday={#1}}

we can extend the bereiche key as follows:

bereiche/.style args={#1--#2/#3/#4}{
  if = {(between = #1 and #2) [
    if = {(workday)                    [days = {style=#4}]},
    if = {(and = {workday, equals=#1}) [days = {mark up = {#3}}]},
    if = {(and = {Monday,  between=#1+1 and #2, not = {strip first/.list/.expand once=\termine}})
      [days = {mark up = {#3 (Fs.)}} ]
    else [
      if = {(and={between=#1+2 and #2, workday, not=Monday,
             not={not={strip first yesterday/.list/.expand once=\termine}}})
        [days = {mark up = {#3 (Fs.)}}] }
    ]}
  ]}}

And we get markings like Osterferien (Fs.) but only on the days that follow after weekends and holidays.

Code

\documentclass[ngerman,multi=tikzpicture,varwidth=false]{standalone}
\usepackage{translator,tikz,babel,libertine}
\usetikzlibrary{calendar,qrr.calendar}
\newcounter{schoolweek}
\newcounter{lastschoolweek}
\makeatletter
\pgfkeys{/utils/if/.code n args={3}{%
  \pgfmathparse{#1}\ifdim\pgfmathresult pt=0pt\relax
    \expandafter\pgfutil@firstoftwo\else\expandafter\pgfutil@secondoftwo\fi
    {\pgfkeysalso{#3}}{\pgfkeysalso{#2}}}}
\makeatother
\tikzset{
  %%% the actual calendar style
  month columns/.style={% Mondays get yearweek number, workdays possibly schoolweek number
    month width/.initial=47.5mm, day height/.initial=6.2mm,% (297-10)/6 and (210-10)/32.2
    day code={\node[every day]{\tikzdaytext};}, day text={\%d=~~\%w.},
    month code={\node[every month]{\tikzmonthtext};},
    textual/.style            = {
      draw=gray, font=\strut, text depth=+0pt, outer sep=+0pt, anchor=south,
      text width=\pgfkeysvalueof{/tikz/month width}-2*(\pgfkeysvalueof{/pgf/inner xsep})},
    every day/.append style   = {textual, align=left, minimum height=\pgfkeysvalueof{/tikz/day height}},
    every month/.append style = {textual, align=center, minimum height=1.2*(\pgfkeysvalueof{/tikz/day height}), font=\Large\bfseries, fill=red, text=white},
    execute before day scope  = {%
      \ifdate{day of month=1, equals=\pgfcalendarbeginiso}{\tikzmonthcode}{}%
      \ifdate{equals=\pgfcalendarbeginiso}{\pgftransformyshift{-\numexpr\pgfcalendarifdateday-1\relax*(\pgfkeysvalueof{/tikz/day height})}}{}%
      \pgftransformyshift{-(\pgfkeysvalueof{/tikz/day height})}},
    execute after day scope   = {%
      \ifdate{end of month=1}{%
        \pgftransformyshift{(\numexpr\pgfcalendarifdateday\relax)*(\pgfkeysvalueof{/tikz/day height})}%
        \pgftransformxshift{\pgfkeysvalueof{/tikz/month width}}}{}}}}
  
\newcommand*\termine{% repeating fixed day
  equals=01-01 / Neujahr             / Feiertag,
  equals=01-06 / Heilige Drei Könige / chrFeiertag,
  equals=03-08 / Int. Frauentag      / Feiertag,
  equals=05-01 / Tag der Arbeit      / Feiertag,
  equals=10-03 / Tag der dt. Einheit / Feiertag,
  equals=12-25 / 1. Weihnachtstag    / chrFeiertag,
  equals=12-26 / 2. Weihnachtstag    / chrFeiertag,
  Easter=-2    / Karfreitag          / chrFeiertag,
  Easter= 0    / Ostersonntag        / chrFeiertag,
  Easter= 1    / Ostermontag         / chrFeiertag,
  Easter=39    / Christi Himmelfahrt / chrFeiertag,
  Easter=49    / Pfingstsonntag      / chrFeiertag,
  Easter=50    / Pfingstmontag       / chrFeiertag}
\newcommand*\bereiche{% holidays
  2022-07-07--2022-08-20 / Sommerferien          / Ferien,
  2022-09-06--2022-09-08 / Extraferien           / {Ferien, fill=blue!50},
  2022-09-14--2022-09-20 / keine Ferien          / keineFerien,
  2022-10-24--2022-11-05 / Herbstferien          / Ferien,
  2022-12-22--2023-01-02 / Weihnachtsferien      / Ferien,
  2023-01-30--2023-02-04 / Winterferien          / Ferien,
  2023-04-03--2023-04-14 / Osterferien           / Ferien,
  2023-05-19--2023-05-19 / Brückentag            / Ferien,
  2023-05-30--2023-05-30 / Pfingstferien         / Ferien,
  2023-07-13--2023-08-25 / Sommerferien          / Ferien}
\tikzset{
  %%% set the first appearing week to the given number
  start SW/.code=\setcounter{schoolweek}{\numexpr#1-1\relax},
  %%% marking styles
  mark up/.style       = {label = {[anchor=north west, every mark up]   north west: #1}},
  mark down/.style     = {label = {[anchor=south west, every mark down] south west: #1}},
  mark KW/.style       = {label = {[anchor=south east, every KW mark]   south east: KW~\%n-}},
  every K- mark/.style = {font = \scriptsize, text = black, text depth = +0pt, inner ysep = .3em},
  every KW mark/.style = {every K- mark, text = gray!50!black}, every SW mark/.style = every K- mark,
  every mark/.style    = {font = \scriptsize, text = black, xshift = +3.3em, text depth = +0pt, inner ysep = .3em},
  every mark up/.style = every mark, every mark down/.style = every mark,
  %
  %%% SW marking is special, let's have keys that (de)activate this
  activate SW/.style={
    mark SW/.style = {% only mark SW if we didn't already set this yearweek's SW
      /utils/if={\value{lastschoolweek}!=\the\numexpr\pgfcalendarcurrentweek\relax}{
        /utils/exec=\setcounter{lastschoolweek}{\%n0},
        label = {[anchor=north east, every SW mark/.try]
          north east:SW~\stepcounter{schoolweek}\theschoolweek}}{}}},
  deactivate SW/.style = {mark SW/.style=},
  %
  %%% Parsing
  bereiche/.style args={#1--#2/#3/#4}{
    if = {(between = #1 and #2) [
      if = {(workday)                    [days = {style=#4}]},
      if = {(and = {workday, equals=#1}) [days = {mark up = {#3}}]}
    ]},
  },
  termine/.style args = {#1/#2/#3}{if = {(#1)    [days = {mark down = {#2},style=#3} ]}},
  %
  %%% visualization
  Feiertag/.style     = {fill = red!75,         deactivate SW},
  chrFeiertag/.style  = {fill = red!75!blue!50, deactivate SW},
  Ferien/.style       = {fill = gray!25,        deactivate SW},
  keineFerien/.style  = {fill = green!50}}
\tikzset{
  my calendar/.style = {
    month columns, if = {(Monday) [days=mark KW]}, if = (workday) [activate SW],
    bereiche/.list/.expand once=\bereiche, termine/.list/.expand once=\termine,
    if = {(Sunday)   [days={fill=red!50}]}, if = {(Saturday) [days={fill=red!25}]},
    if = {(workday)  [days={mark SW/.try}]}}}
\begin{document}\sffamily\small
\tikz\calendar[dates = 2022-08-01 to 2023-01-31, my calendar, start SW = 1];
\tikz\calendar[dates = 2023-02-01 to 2023-07-31, my calendar];
\end{document}

Output

enter image description here

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .