# How to compare a date with a set of dates

I am creating a schedule for weekly lectures using the following code

\documentclass{article}

%... Set the first lecture date
\ThisYear{2019}
\ThisMonth{3}
\ThisDay{1}

\newif\iffirst
\firsttrue
\newcommand{\nextlec}{%
\iffirst
\firstfalse
\else
\fi
\section*{\today}
}

\begin{document}
\nextlec
Lecture 01.

\nextlec
Lecture 02.

\nextlec
Lecture 03.

\nextlec
Lecture 04.

\nextlec
Lecture 05.

\end{document}


The code works fine. Now, let's suppose there are holidays on March 08, March 29, April 18, May 17, and June 18.

My question is: how to redefine the \nextlec to skip a holiday and return the next non-holiday date?

Note: this is not a duplicate from here, since I want to compare a calculated date to a set of several dates that I want to define.

EDITS:

After Jeffrey J Weimer suggestion (see comments), I tried to implemente the following piece code in my original one, but got a lot of error.

\def\holidays#1{\def\@holidays{#1}}
\newcommand{\theholidays}{ {\@holidays} }

\newif\iffirst
\firsttrue

\newcommand{\jumpweek}{%
\iffirst
\firstfalse
\else
\fi
}

\newcommand\nextlecbool{true}

\newcommand{\nextlec}{%
\jumpweek
\foeach \holiday in \theholidays
\while{\nextlecbool}
\ifnum (10000*\the\year + 100*\the\month + \the\day) = \holiday
\section*{\today}
\jumpweek
\else
\renewcommand{\nextlecbool}{false}
\fi
\EndWhile
;
\renewcommand{\nextlecbool}{true}
}

• Where are you defining the dates? In an external file that is loaded? If so, I almost have to think the datatool package will provide a viable approach. – Jeffrey J Weimer Nov 21 '18 at 15:07
• @JeffreyJWeimer I would like to define the date at the same tex file, but I don't know how to do this. – Brasil Nov 21 '18 at 17:03
• Great @JeffreyJWeimer. It's really good idea. I will try it. You could make your comment an anwer. Then, I will accept it after getting positive reault... thank you! – Brasil Nov 22 '18 at 2:38
• The calendars package may do what you want. – Jeffrey J Weimer Nov 22 '18 at 15:48
• Unfortunately, this is a bit out of my league. I tried with datatool but the date parsing is above my level and it may require the equivalent of a double for-loop construction. I had high hopes for the calendars package but it is too poorly and obtusely commented to be of use for anyone but the bravest of heart. I see others have examples in the meantime that may be a solution. – Jeffrey J Weimer Nov 25 '18 at 18:20

You can make a list of the holidays and check whether the current date as set by \AdvDate appears in the list:

\documentclass[twocolumn]{article}
\usepackage{xparse}

\ExplSyntaxOn
\clist_new:N \g_brasil_holiday_clist

\NewDocumentCommand{\holidays}{m}
{
\clist_gset:Nn \g_brasil_holiday_clist { #1 }
}
\NewDocumentCommand{\nextlec}{}
{
\brasil_check_holiday:
\section*{\today}
}

\cs_new_protected:Nn \brasil_check_holiday:
{
\clist_if_in:NxT \g_brasil_holiday_clist
{ \int_eval:n {\day} / \int_eval:n {\month} }
{ \brasil_check_holiday: } % redo the test
}
\cs_generate_variant:Nn \clist_if_in:NnT { Nx }
\ExplSyntaxOff

\holidays{8/3,29/3,18/4,17/5,18/6}

%... Set the first lecture date

\ThisYear{2019}
\ThisMonth{3}
\ThisDay{1}

\begin{document}
\nextlec
Lecture 01.

\nextlec
Lecture 02.

\nextlec
Lecture 03.

\nextlec
Lecture 04.

\nextlec
Lecture 05.

\nextlec
Lecture 06.

\nextlec
Lecture 07.

\nextlec
Lecture 08.

\nextlec
Lecture 09.

\nextlec
Lecture 10.

\nextlec
Lecture 11.

\nextlec
Lecture 12.

\nextlec
Lecture 13.

\nextlec
Lecture 14.

\nextlec
Lecture 15.

\nextlec
Lecture 16.

\nextlec
Lecture 17.

\nextlec
Lecture 18.

\end{document}


Here I use twocolumn just for reducing the output to a single page.

How does this work?

The command \holidays saves the list of holidays in a clist variable (just a sequence of items separated by commas, that can be queried later).

The main command \nextlec first calls \brasil_check_holiday: that performs the check.

This is the really tricky part. The package advdate stores the current date as set by \AdvanceDate using the standard counters \day, \month and \year. We however need to transform them into explicit numbers, which is accomplished by \int_eval:n {\day} and \int_eval:n {\month}.

The \clist_if_in:NnT function takes three arguments:

1. a clist variable
2. a token list that's checked to be in the variable
3. the code to execute if the test is successful

However, we need to do the expansion mentioned above, so I define a variant

\clist_if_in:NxT


that will expand the second argument before performing the check. Before checking, the date is advanced by a week.

What happens if the test is successful, that is, the current date is a holiday? The \brazil_check_holiday: function is executed again and this will go on until a non holiday is found.

At this point \section*{\today} is executed and the job is finished.

• Hi @egreg. This code seems very cool! I just read about the xparse package and as far as I could understand, it is like a programming language with features that apply for LaTeX. So, let's go with my questions: (a) would it be possible to do the same task without xparse? (b) about the commands \ExplSyntaxOn, ´\clist, \NewDocumentCommand, and \cs, are they LaTeX native, or definitions inside the xparse? Thank you very much! – Brasil Nov 22 '18 at 17:44
• @JeffreyJWeimer No CS degree. ;-) I added some explanations. – egreg Nov 25 '18 at 23:09
• Thank you, @egreg. It is really cool and clear! – Brasil Nov 26 '18 at 14:38

Perhaps this exhibits some tricks about recursive iteration and about using delimited arguments for taking apart strings of pattern

<word for month><space><number of day>,<space><year>

\documentclass{article}
\usepackage{datenumber}

\makeatletter
%-----------------------------------------------------------------
% Section: Code for comparing dates.
%-----------------------------------------------------------------
% \monthtonumber{<name of a month>} yields the number of that
% month (range 1..12; January -> 1, February -> 2 ,...,
% December -> 12)
% \monthfork just selects/"spits out" an undelimited argument that
% comes  behind a delimited argument's delimiter which is formed by
% a list of all months.
% In any case there will be only one place where the list
% is correct and where the delimiter will match...
% ! is used both for making visibly distinguishing month-phrases
% from each other more easy and for denoting where the
% remainder which is to be removed by the !!!!-delimited argument
% ends.
%-----------------------------------------------------------------
\newcommand*\monthtonumber[1]{%
\monthfork
!#1!February!March!April!May!June!July!August!September!October!November!December!{1}%
!January!#1!March!April!May!June!July!August!September!October!November!December!{2}%
!January!February!#1!April!May!June!July!August!September!October!November!December!{3}%
!January!February!March!#1!May!June!July!August!September!October!November!December!{4}%
!January!February!March!April!#1!June!July!August!September!October!November!December!{5}%
!January!February!March!April!May!#1!July!August!September!October!November!December!{6}%
!January!February!March!April!May!June!#1!August!September!October!November!December!{7}%
!January!February!March!April!May!June!July!#1!September!October!November!December!{8}%
!January!February!March!April!May!June!July!August!#1!October!November!December!{9}%
!January!February!March!April!May!June!July!August!September!#1!November!December!{10}%
!January!February!March!April!May!June!July!August!September!October!#1!December!{11}%
!January!February!March!April!May!June!July!August!September!October!November!#1!{12}%
!!!!%
}%
%-----------------------------------------------------------------
% \monthfork grabs the undelimited argument behind that
% list of months that is correct/in correct order due to insertion
% of the name of the month by \monthtonumber.
% As \monthfork needs to be a macro that processes
% delimited arguments, it needs to be defined in terms of
% \def. Nonetheless provide a dummy-definition in terms
% of \newcommand before for triggering an error-message
% in case one is about to override a macro that already
% is defined, e.g., by some package.
%-----------------------------------------------------------------
\newcommand\monthfork{}%
\def\monthfork#1!January!February!March!April!May!June!%
July!August!September!October!November!%
December!#2#3!!!!{#2}%
%
%-----------------------------------------------------------------
% \comparedatetotoday{<month as word><space><day as number>,<space><year as number>}%
% {<tokens in case dates do not differ>}%
% {<tokens in case dates differ>}%
% converts <month as word> to a number and compares that to the value of \month,
% compares <day as number> to the value of \day,
% compares <year as number> to the value of \year
% In case a difference is detected delivers <tokens in case dates differ>
% In case no difference is detected delivers <tokens in case dates do not differ>
% The macro \splitdateandcomparecomponents is used for obtaining
% the components of the date.
% As the date is provided in the pattern
% <month as word><space><day as number>,<space><year as number>
% a space is attached at the end which yields the pattern
% <month as word><space><day as number>,<space><year as number><space>
% and then the components of that pattern are obtained by the macro
% \splitdateandcomparecomponents where the three arguments are delimited
% accordingly. Again first a \newcommand-dummy-definition for
% triggering error-message in case of overriding an already  existing
% command.
%-----------------------------------------------------------------
\newcommand\comparedatetotoday[1]{\splitdateandcomparecomponents#1 }%
\newcommand\splitdateandcomparecomponents{}%
\def\splitdateandcomparecomponents#1 #2, #3 {%
\ifnum\the\month=\monthtonumber{#1} %
\expandafter\@firstofone\else\expandafter\@secondoftwo\fi
{%
\ifnum\the\day=#2 %
\expandafter\@firstofone\else\expandafter\@secondoftwo\fi
{%
\ifnum\the\year=#3 %
\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
{\@firstoftwo}%
}%
}%
\@secondoftwo
}%
%-----------------------------------------------------------------
% \comparedatestotoday{%
%    {{<month 1 as word><space><day 1 as number>,<space><year 1 as number>}}%<-date1
%    {{<month 2 as word><space><day 2 as number>,<space><year 2 as number>}}%<-date2
%    ...
%    {{<month k as word><space><day k as number>,<space><year k as number>}}%<-datek
% }%
% {<tokens in case at least one date does not differ from the date formed by
%  \year, \month and \day>}%
% {<tokens in case all dates do differ from the date formed by
%  \year, \month and \day>}%
%
%  \comparedatestotoday attaches the token \relax to the list of
%  holiday-dates and prepends an argument holding the token
%  \@secondoftwo and calls  \comparedatestotodayloop.
%
%  \comparedatestotodayloop in a recursive loop compares with all
%  elements of the list of holiday-dates compares the date-components to
%  \month, \day, \year for finding out if \month, \day and \year
%  denote one of these holiday-dates.
%
%  \comparedatestotodayloop's recursive loop works as follows:
%
%  \comparedatestotodayloop processes two arguments.
%  The first argument denotes the action in case the end of the list is reached.
%  The second argument either is an element of the list of dates or is the
%  attached token \relax.
%  Thus for finding out whether the end of the list is reached, one can
%  check whether the second argument equals \relax.
%  If so, just spit out the first argument which denotes the action in
%  case the end of the list is reached.
%  If not so, compare the date held in the second argument to
%  \month, \day, \year (which are components of today) for finding out
%  if \today is a holiday and therefore the first argument needs to be
%  replaced by \@firstoftwo before doing the next iteration by
%  calling \comparedatestotodayloop again...
%-----------------------------------------------------------------
\newcommand\comparedatestotoday[1]{%
\comparedatestotodayloop{\@secondoftwo}#1\relax
}%
% \comparedatestotodayloop{<token in case end of list is reached>}%
%                         {<either element of list of holiday-dates
%                           or \relax which was attached for marking
%                           the end of the list of holiday-dates>}%
\newcommand\comparedatestotodayloop[2]{%
\ifx\relax#2\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
{#1}{%
\comparedatetotoday{#2}%
{\comparedatestotodayloop{\@firstoftwo}}%
{\comparedatestotodayloop{#1}}%
}%
}%
%-----------------------------------------------------------------
% Section: Code for printing lists of dates.
%-----------------------------------------------------------------
% \PrintListOfHolidays[<macro for formatting a date>]%
%                     {<separator between dates>}%
%                     {<separator for the last date>}%
%                     {<macro holding the list of dates>}
\newcommand\exchange[2]{#2#1}
\expandafter\exchange\expandafter{#4}{\Holidayloop{#1}{}{}{#2}{#3}}\relax\relax
}%
%-----------------------------------------------------------------
% \Holidayloop{<macro for formatting a date>}%
%              {<separator for this date in case it is not the last date of the list>}%
%              {<separator for this date in case it is the last date of the list>}%
%              {<separator between dates>}%
%              {<separator for the last date>}%
%              {<month as word><space><day as number>,>space><year as number>}
% At first glimpse the arguments <separator for this date in case it is not the last date of the list>
% and  <separator between dates> respective
% <separator for this date in case it is the last date of the list> and
% <separator for the last date> seem to be the same.
% But we must consider the case of the list being empty or holding
% only one element: In these cases we don't want any separator, thus
% we need a possibility to pass empty arguments for the first
% iteration...
%
\newcommand\Holidayloop[7]{%
\ifx\relax#6\expandafter\@gobble\else\expandafter\@firstofone\fi
{%
\ifx\relax#7\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
{#3#1{#6}\Holidayloop{#1}{#4}{#5}{#4}{#5}{#7}}%<-case: last date of the list
{#2#1{#6}\Holidayloop{#1}{#4}{#5}{#4}{#5}{#7}}%<-case: not the last date of the list
}%
}%
% macros for formatting the date.
% They all take the date and use "inner" macros that by means of
% delimited arguments split it into components and do the desired
% things to the components.
%
\newcommand\RemoveYear[1]{\innerRemoveyear#1\relax}
\newcommand\innerRemoveyear{}%
\def\innerRemoveyear#1 #2,#3\relax{\mbox{#1 \number#2}}%
%
\setdatenumber{#3}{\monthtonumber{#1}}{#2}%
\mbox{\datedayname, #1 #2, #3}%
}%
%
% As we need to compare each \today/each combination of
% \year, \month and \day to the list of holidays for
% for finding out whether a \today is on a holiday, we can
% maintain a list of such \todays that are on holidays.
% Whenever such a \today is found, it will added to the macro
% \@DaysAffectedByHolidays. That macro in turn is used at the
% end of the document for writing to .aux-file a directive
% for defining a macro \DaysAffectedByHolidays that holds the
% list of only those holidays that "collide" with lecture-days.
%
\newcommand\DaysAffectedByHolidays{}%
\newcommand\@DaysAffectedByHolidays{}%
\AtEndDocument{%
\immediate\write\@auxout{%
\string\gdef\string\DaysAffectedByHolidays{\@DaysAffectedByHolidays}%
}%
}%

\newcounter{lecture}

\newlength\scratchlength

\newcommand{\nextlec}{%
% Compare the advanced \today to each element of the list of holidays:
\expandafter\comparedatestotoday
\expandafter{\Holidaylist}{%
%
% Tokens in case the advanced \today denotes one of the holidays:
%
%\setdatenumber{\year}{\month}{\day}%
%\section*{Holiday: \datedayname, \today} Holiday.%
\xdef\@DaysAffectedByHolidays{\@DaysAffectedByHolidays{\today}}%
% calculate another lecture-day...
\nextlec
}{%
%
% Tokens in case the advanced \today does not denote one of the holidays:
%
\stepcounter{lecture}%
\setdatenumber{\year}{\month}{\day}%
\section*{Lecture~\arabic{lecture}: \datedayname, \today}%
}%
}%

\makeatother

%... Set the first lecture date
\ThisYear{2019}
\ThisMonth{3}
\ThisDay{1}

\newcommand*\Holidaylist{%
{March 08, 2019}%
{March 29, 2019}%
{April 18, 2019}%
{May 17, 2019}%
{June 18,  2019}%
}%

\begin{document}

\settowidth\scratchlength{\hbox{\textbf{All holidays: }}}
\setlength\scratchlength{-\scratchlength}

% The macros holding the lists of dates of holidays do not
% contain the token \relax. Therefore if they are not empty
% as there is at least one holiday, things will end up in
% the \else-branch of the \if-comparison.
%
\if\relax\Holidaylist\relax\else
\hbox{%
\hbox{\textbf{All holidays: }}%
\hbox to\scratchlength{\vtop{%
\hsize=\scratchlength\noindent
\PrintListOfHolidays{; }{; }{\Holidaylist}.\strut%
}}%
}%
\fi

\if\relax\Holidaylist\relax\else
\hbox{%
\hbox{\textbf{All holidays: }}%
\hbox to\scratchlength{\vtop{%
\hsize=\scratchlength\noindent
\PrintListOfHolidays[\RemoveYear]{, }{ and }{\Holidaylist}.\strut%
}}%
}%
\fi

\if\relax\Holidaylist\relax\else
\hbox{%
\hbox{\textbf{All holidays: }}%
\hbox to\scratchlength{\vtop{%
\hsize=\scratchlength\noindent
\PrintListOfHolidays[\AddDayOfWeek]{, }{ and }{\Holidaylist}.\strut%
}}%
}%
\fi

% These occur after the 2nd LaTeX-run:

\settowidth\scratchlength{\hbox{\textbf{Holidays that affect our lecture-plan: }}}
\setlength\scratchlength{-\scratchlength}

\if\relax\DaysAffectedByHolidays\relax\else
\hbox{%
\hbox{\textbf{Holidays that affect our lecture-plan: }}%
\hbox to\scratchlength{\vtop{%
\hsize=\scratchlength\noindent
\PrintListOfHolidays{; }{; }{\DaysAffectedByHolidays}.\strut%
}}%
}%
\fi

\if\relax\DaysAffectedByHolidays\relax\else
\hbox{%
\hbox{\textbf{Holidays that affect our lecture-plan: }}%
\hbox to\scratchlength{\vtop{%
\hsize=\scratchlength\noindent
\PrintListOfHolidays[\RemoveYear]{, }{ and }{\DaysAffectedByHolidays}.\strut%
}}%
}%
\fi

\if\relax\DaysAffectedByHolidays\relax\else
\hbox{%
\hbox{\textbf{Holidays that affect our lecture-plan: }}%
\hbox to\scratchlength{\vtop{%
\hsize=\scratchlength\noindent
\PrintListOfHolidays[\AddDayOfWeek]{, }{ and }{\DaysAffectedByHolidays}.\strut%
}}%
}%
\fi

\newpage

\nextlec
Lecture 01.

\nextlec
Lecture 02.

\nextlec
Lecture 03.

\nextlec
Lecture 04.

\nextlec
Lecture 05.

\nextlec
Lecture 06.

\nextlec
Lecture 07.

\nextlec
Lecture 08.

\nextlec
Lecture 09.

\nextlec
Lecture 10.

\end{document}


• Thank you @UlrichDiez. Your suggestion seems to work (I didn't try yet). However, it is to big and complex for me (I am just a junior in LaTeX). Could you, please, document or explain the role of pieces of this code? (For instance, whats the meaning of !? Or, why \def\monthfork#1 after \newcommand{\monthfork}{}?) Sorry for my inconvenience and thank you again. – Brasil Nov 22 '18 at 11:59
• @Brasil I tried to add some commenting. ;-) – Ulrich Diez Nov 22 '18 at 19:09
• Presuming this is not to be used to run through an exhaustive database, I will take a trade off of a loss of memory allocation at run time as a return for a shorter code that prefers to avoid TeX primitives. I respect the prowess and power in what you wrote. It is not my cup of tea, as one might say. – Jeffrey J Weimer Nov 26 '18 at 14:02

# Method 1

This tackles the problem with packages xifthen and pgffor.

\documentclass{article}

\usepackage{etoolbox, xifthen, pgffor, advdate}

%% counter for number of lecture
\newcounter{nlecture}\setcounter{nlecture}{1}

%% denote holiday when found
\newbool{holiday}

\newcommand{\DoLecture}{%
\global\boolfalse{holiday}%%%
\foreach \D in \holidaylist
{%
\ifthenelse{\equal{\today}{\D}}
{%
\global\booltrue{holiday}%%%
\breakforeach
}
{}%%%
}%%%
\ifbool{holiday}
{%
Holiday - \today

\DoLecture
}
{%%%
Lecture \thenlecture{} - \today
\stepcounter{nlecture}%%%
}%%%
}

%% start date for lectures
\ThisYear{2019}\ThisMonth{3}\ThisDay{1}

%% holiday list
\newcommand{\holidaylist}{{March 8, 2019}, {March 22, 2019}, {March 29, 2019}}

\renewcommand{\baselinestretch}{1.5}

\begin{document}

\DoLecture

\DoLecture

\DoLecture

\DoLecture

\DoLecture

\DoLecture

\end{document}


This gives the result below.

CAVEAT 1 - As noted elsewhere, doing string comparisons in \ifthenelse eats up TeX memory.

I leave as an exercise for the reader the development of a method to put the \DoLecture command in a loop, perhaps using \foreach. One problem is that \AdvanceDate does not work directly within a \foreach loop.

# Method 2

This tackles the problem with the package datatool. I prefer this approach.

\documentclass{article}

%% counter for number of lecture
\newcounter{nlecture}\setcounter{nlecture}{1}

%% denote holiday when found
\newbool{holiday}

\newcommand*{\theholiday}[1]{}

%% step through lectures
\newcommand{\StepThruLectures}[2]{%
\global\boolfalse{holiday}%%%
\DTLforeach*{holidays}
{\hdate=date, \event=event}
{%
\DTLifstringeq*{\today{}}{\hdate{}}
{%
\global\booltrue{holiday}%%%
\renewcommand{\theholiday}{\event}%%%
\dtlbreak
}
{}%%%
}%%%
\ifbool{holiday}
{%

\StepThruLectures{#1}{#2}%%%
}
{%

\stepcounter{nlecture}%%%
}%%%
}

%% generate lecture and holiday statements
\newcommand{\LectureHeader}[4]{#1: Lecture #2 - Topic: #3 / Reading: #4}
\newcommand{\HolidayHeader}[2]{#1: Holiday - #2}

%% start a date
\ThisYear{2019}\ThisMonth{3}\ThisDay{1}

% set the lecture topics
\begin{filecontents}{lectures.csv}
Introduction, none
Math is Fun, Chapter 1
Math is Hard, Chapter 2
\LaTeX{} is Preferred, Chapter 3
Conclusions, none
\end{filecontents}

% set the holidays
\begin{filecontents}{holidaylist.csv}
date, event
{March 8, 2019}, my birthday
{March 22, 2019}, a special day
{March 29, 2019}, tornados
\end{filecontents}

\renewcommand{\baselinestretch}{1.5}

\begin{document}

% uncomment next lines to show databases
%\DTLdisplaydb{lectures}
%\DTLdisplaydb{holidays}

\begin{DTLenvforeach}{lectures}%

\end{DTLenvforeach}

\end{document}


Here is the output.

The advantage of this approach is that you can generate your lecture topics and have them ready in a CSV file that is immutable each term. All you need to do in any given term is set the start date and the CSV for the holidays. The compilation will automatically generate your lecture schedule for that term.

CAVEAT - As noted with datatool`, when the database is large, this will be slow.

## Other Notes

I still believe the calendars package could likely do this and more, perhaps with a lot more grace.