# Error in foreach loop when number range defined with a macro

I am having problems to implement a loop that runs over the months of the year and the days of each month. A simple representative example of my current approach is shown below.

\documentclass{article}

\usepackage{pgffor}
\usepackage{ifthen}

\newcommand\Ndays[1]{\ifthenelse{\equal{#1}{February}}{28}%
{\ifthenelse{\equal{#1}{March}}{31}{30}}}

\begin{document}

\foreach \m in {February,March,April}
{
month:\m \par
\newcommand\Nd{\Ndays{\m}}
ndays: \Nd \par
\foreach \n in {1,...,31}
{
day:\n \par
}
}
\end{document}


Evidently, each month has a different number of days, so I decided to define a dictionary associating each month with the number of days through the "Ndays" macro. Now, I need to include the correct number of days for each month in the example above. If I naively substitute "31" by "\Nd" in the inner loop, an error is found when I compile this code with pdflatex:

! Undefined control sequence.
<argument> \equal
{\m }{February}
l.23   }


I played around with different macro definitions and \expandafter-s, without success. Moreover, if I define the macro as:

\newcommand\Nd{28}


the naive substitution does not raise errors. Is there any simple solution and explanation to this?


\documentclass{article}

\usepackage{pgffor}
\usepackage{ifthen}

\newcommand\Ndays[1]{\ifthenelse{\equal{#1}{February}}
{\newcommand\Nd{28}}%
{\ifthenelse{\equal{#1}{March}}
{\newcommand\Nd{31}}%
{\newcommand\Nd{30}}}}

\begin{document}

\foreach \m in {February,March,April}
{
month:\m \par
\Ndays{\m}%
ndays: \Nd \par
\foreach \n in {1,...,\Nd}
{
day:\n \par
}
}
\end{document}


The obvious glitch (at least for who has some experience in macro expansion) is that \Nd does not expand to the number of days, but to the set of instruction for printing the number of days.

The following macros associate days to months (also taking care of leap years) in an expandable way.

\documentclass{article}
\usepackage{multicol}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\printmonths}{O{\c_sys_year_int}m}
{% #1 is an optional year
% #2 is a comma separated list of months
\clist_map_inline:nn { #2 }
{
\manual_print_month:nn { #1 } { ##1 }
}
}
\cs_new_protected:Nn \manual_print_month:nn
{
\par
Month:~#2 \par
Ndays:~\manual_print_days:nn { #1 } { #2 } \par
\int_step_inline:nnnn { 1 } { 1 } { \manual_print_days:nn { #1 } { #2 } }
{
day:~##1 \par
}
}

\cs_new:Nn \manual_print_days:nn
{
\str_case:nn { #2 }
{
{January}{31}
{February}{\manual_february:n { #1 }}
{March}{31}
{April}{30}
{May}{31}
{June}{30}
{July}{31}
{August}{31}
{September}{30}
{October}{31}
{November}{30}
{December}{31}
}
}

\cs_new:Nn \manual_february:n
{
\bool_if:nTF
{
\int_compare_p:n { \int_mod:nn { #1 } { 4 } != 0 }
||
(
\int_compare_p:n { \int_mod:nn { #1 } { 100 } = 0 }
&&
\int_compare_p:n { \int_mod:nn { #1 } { 400 } != 0 }
)
}
{ 28 } { 29 }
}

\ExplSyntaxOff

\begin{document}

\begin{multicols}{3}

\printmonths[2100]{February,March,April}

\end{multicols}

\end{document}


Apart from the complication for February, the macros are straightforward: we map the argument as a comma separated list. For each item the function \manual_print_month:nn is executed, which steps from 1 to the number of days and does the printing.

You can check that the output for

\printmonths[2100]{February,March,April}


is as follows (no leap year)

A possibly more efficient version of \manual_february:n with lazy evaluation of conditionals:

\cs_new:Nn \manual_february:n
{
\bool_lazy_or:nnTF
{
\int_compare_p:n { \int_mod:nn { #1 } { 4 } != 0 }
}
{
\bool_lazy_and_p:nn
{
\int_compare_p:n { \int_mod:nn { #1 } { 100 } = 0 }
}
{
\int_compare_p:n { \int_mod:nn { #1 } { 400 } != 0 }
}
}
{ 28 } { 29 }
}

• @UlrichDiez Still three comparisons. Maybe some nanoseconds faster, though. ;-) Let's blame pope Gregory XIII. :-) Maybe the team can provide an expandable function taking an integer as argument that returns 1 if the integer corresponds to a leap year and 0 otherwise. – egreg Jan 31 '16 at 21:50
• How about checking divisibility by 4, 16 and 25 instead of 4, 100 and 400? Not divisible by 4 -> Not a leap year. Divisible by 4 and divisible by 16 -> A leap year. Divisible by 4 and not divisible by 16 and divisible by 25 -> Not a leap year. Divisible by 4 and not divisible by 16 and not divisible by 25 -> A leap year. (I hope I got it right...) – user82850 Jan 31 '16 at 21:59
• @UlrichDiez I changed the implementation with a boolean expression, which should be more efficient – egreg Jan 31 '16 at 22:13