8

I have a date (in the format YYYY.MM.DD) and a time (format HH:MM).

From these, I want to compute a kind of TimeStamp/Total of minutes.

\documentclass{article}

\makeatletter

% Timestamp
\newcommand{\timeStamp}[2]{%{#1-Date (YYYY.MM.DD)}{#2-Time (HH:MM)}
    \expandafter\timeStamp@t#1 #2\@nil%
}%
\def\timeStamp@t#1.#2.#3 #4:#5\@nil{%
    \the\numexpr#5+#4*60+(#3-1)*60*24+(#2-1)*60*24*31+(#1-2017)*60*24*31*365\relax%
}%

\makeatother

\begin{document}
\def\tOne{03:00}%
\def\dOne{2017.08.01}%
\timeStamp{\dOne}{\tOne}\\%
\end{document}

The compiler says: "Runaway argument?"

This is probably due to a problem with the extension of the second argument, because this call (\timeStamp{\dOne}{03:00}) is Ok. What am I doing wrong?

7

\expandafter only expands one token after the token that follows (unless there are arguments). In your case this is just #1. One way to get your result, is to collect #1 #2 expanded in a macro before insertion:

Sample output

\documentclass{article}

\makeatletter

% Timestamp
\newcommand{\timeStamp}[2]{%{#1-Date (YYYY.MM.DD)}{#2-Time (HH:MM)}
  \edef\mytmp{#1 #2}\expandafter\timeStamp@t\mytmp\@nil%
}%
\def\timeStamp@t#1.#2.#3 #4:#5\@nil{%
    \the\numexpr#5+#4*60+(#3-1)*60*24+(#2-1)*60*24*31+(#1-2017)*60*24*31*365\relax%
}%

\makeatother

\begin{document}
\tracingmacros=2\tracingcommands=2
\def\tOne{03:00}%
\def\dOne{2017.08.01}%
\timeStamp{\dOne}{\tOne}
\end{document}

This approach should generalise well to more than two arguments.

  • Thx. Clear and concise. – Joseph Marie Jun 18 '18 at 11:13
  • But not fully expandable. – egreg Jun 18 '18 at 11:23
  • Indeed @egreg. I faced this problem in my "real" code, so I switched to a more complicated solution. – Joseph Marie Jun 19 '18 at 7:38
6

One can do some juggling with \expandafter, but a direct approach may be better:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\cs_new:Nn \joseph_time_stamp:nn
 {
  \__joseph_time_stamp:w #1.#2\q_stop
 }
\cs_generate_variant:Nn \joseph_time_stamp:nn { ff }
% a devious trick for the colon
\use:x
 {
  \cs_new:Npn
   \exp_not:N \__joseph_time_stamp:w
   ##1.##2.##3.##4\token_to_str:N :##5
   \exp_not:N \q_stop
 }
 {
  \int_eval:n
   {
    #5+#4*60+(#3-1)*60*24+(#2-1)*60*24*31+(#1-2017)*60*24*31*365
   }
 }
\NewExpandableDocumentCommand{\timeStamp}{mm}
 {
  \joseph_time_stamp:ff { #1 } { #2 }
 }
\ExplSyntaxOff

\begin{document}

\def\tOne{03:00}
\def\dOne{2017.08.01}

\timeStamp{\dOne}{\tOne}

\timeStamp{2017.1.1}{0:0} % should print 0

\end{document}

If the time specification didn't include the colon, it would be easier; the problem is that : is special in the scope of \ExplSyntaxOn, so the direct

\cs_new:Npn \__joseph_time_stamp:w #1.#2.#3.#4:#5\q_stop
 {
  \int_eval:n
   {
    #5+#4*60+(#3-1)*60*24+(#2-1)*60*24*31+(#1-2017)*60*24*31*365
   }
 }

would not work and an indirect method is needed in order to “stringify” the colon.

enter image description here

An expandable solution with \expandafter and argument juggling (OS for “old style”):

\makeatletter
\newcommand{\timeStampOS}[2]{%
  \expandafter\timeStampOS@a\expandafter{#2}{#1}%
}
\newcommand{\timeStampOS@a}[2]{%
  \expandafter\timeStampOS@b\expandafter{#2}{#1}%
}
\newcommand{\timeStampOS@b}[2]{\timeStampOS@c #1 #2\@nil}
\def\timeStampOS@c #1.#2.#3 #4:#5\@nil{%
  \the\numexpr
    #5+#4*60+(#3-1)*60*24+(#2-1)*60*24*31+(#1-2017)*60*24*31*365
  \relax
}
\makeatother
  • Thanks a lot for all and excuse me for all. I have added the link for the traslation. +1. – Sebastiano Jun 18 '18 at 11:38
4

expand #1 and #2

% Timestamp
\newcommand\timeStamp[2]{%
    \expandafter\timeStamp@t#2 #1\@nil}%
\def\timeStamp@t#1:#2 #3\@nil{\expandafter\timeStamp@@t#3 #1:#2\@nil}%
\def\timeStamp@@t#1.#2.#3 #4:#5\@nil{%
  \the\numexpr#5+#4*60+(#3-1)*60*24+(#2-1)*60*24*31+(#1-2017)*60*24*31*365\relax%
}%
  • Thx. But now, I am completely confused about '\expandafter'... – Joseph Marie Jun 18 '18 at 11:07
  • @JosephMarieit expands the first token after the macro after \expandafter, there are two tokens here. – daleif Jun 18 '18 at 11:11
2

It is clearer to:

  • devote macros to sub-computations, possibly reusable elsewhere,

  • indicate in the macro name the expansion type it does on its arguments.

Like this: (O meaning "expands once the argument"; often one would prefer "F" for f-type expansion which expands repeatedly the first token, as this allows nesting. Else one would need e.g. T for "expands twice the argument", because with \def\foo{\the\numexpr...}, one needs two expansions for fully expanding).

enter image description here

\documentclass{article}

\newcommand{\TimeStampOO}[2]
   {\the\numexpr\expandafter\DateToMinutes\expandafter{#1}+
                \expandafter\TimeToMinutes\expandafter{#2}\relax}

\makeatletter
\newcommand{\DateToMinutes}[1]{\the\numexpr\Date@ToMinutes#1\relax}
\def\Date@ToMinutes#1.#2.#3\relax
   {(#3-1)*60*24+(#2-1)*60*24*31+(#1-2017)*60*24*31*365\relax}
\newcommand{\TimeToMinutes}[1]{\the\numexpr\Time@ToMinutes#1\relax}
\def\Time@ToMinutes#1:#2\relax
   {#2+#1*60\relax}
\makeatother

\begin{document}
\def\tOne{03:00}%
\def\dOne{2017.08.01}%
\TimeStampOO{\dOne}{\tOne}%
\end{document}

This is expandable.

  • the auxiliary macros could use bare \numexpr, but with \the\numexpr (which adds some overhead in the global computation) they can be used top-level. – user4686 Jun 18 '18 at 13:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.