2

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?

2 Answers 2

2

You can try this. Exploiting (for once) the fact that \foreach works in a group hence LaTeX does not complain with the \newcommand. HenceElse one would use \renewcommand and an initial \newcommand\foo{}.

\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}
2

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.

enter image description here

You can check that the output for

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

is as follows (no leap year)

enter image description here

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 }
 }
3
  • @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, 2016 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, 2016 at 21:59
  • @UlrichDiez I changed the implementation with a boolean expression, which should be more efficient
    – egreg
    Jan 31, 2016 at 22:13

You must log in to answer this question.

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