LaTeX Immediate Evaluation

latex

I'm trying to write a command in LaTeX that takes a string such as 8:00A and converts it into the number of minutes, as part of a script to draw a class schedule using TikZ. However, I'm running into some issues – it seems that LaTeX doesn't actually evaluate the contents of a command.

My command is currently:

\newcommand{\timetominutes}[1]{
  \IfSubStr{#1}{P}{720}{0}+\IfSubStr{#1}{P}{\StrBetween{#1}{:}{P}}{\StrBetween{#1}{:}{A}}+60*\StrBefore{#1}{:}
}

If you print out the text from it, it will correctly calculate the number of minutes from midnight. However, if used inside another function, it becomes apparent that it doesn't actually run any of those commands – it merely returns text including those commands. So if I write:

\myfunc{\timetominutes{8:00A}}

Instead of \myfunc seeing something useful like 0+00+60*8, it sees \IfSubStr{8:00A}{P}{720}{0}+\IfSubStr{8:00A}{P}{\StrBetween{8:00A}{:}{P}}{\StrBetween{8:00A}{:}{A}}+60*\StrBefore{8:00A}{:}. This is absolutely useless to me and I can't seen to find a way to force LaTeX to execute subcommands before the main one. I assume there's a way to do it, but LaTeX documentation is scarce and I can't seem to find anything.

Alternatively, if there's a way to get LaTeX to stop complaining about too many }s (when I have the correct number), that could work.

Best Answer

Unfortunately, no. As the xstring package states:

The macros of this package are not purely expandable

(Section 3.2 of xtring_doc_en.pdf.)

This "expandable" concept, if you're not familiar with it, is quite a hairy subject in TeX. Simply put, something that is not expandable cannot be evaluated as an argument. Anything that uses an assignment somewhere is guaranteed not to be expandable in most TeX varieties, but other non-expandable triggers exist as well. The solution to such problems is rather difficult for anyone that's not familiar with the inner workings of TeX's "mouth" (the part of TeX that handles things like expansion).

Hint: If the LaTeX code is generated by a script: use the script to convert the time expressions, because just about any programming language is easier to use than TeX when it comes to string manipulation. (Or just about anything else for that matter.)


The xstring package does hint at a way out: You can store the result of most operations in a variable, by adding [\variable] to the end of the calls. This means you'd need to rewrite \timetominutes to something that builds up the result piece by piece, and then store that result in a command sequence for use later on.

Usage:

\timetominutesinto\somevar{8:00A} % \somevar contains 48
\expandafter\myfunc\expandafter{\somevar} % calls \myfunc{48}

Note the use of \expandafter, which tells TeX to do a simple one-level expansion (evaluation) of a command sequence after the next. If you didn't use the two \expandafters, you'd get \somevar as an argument to \myfunc, not 48.

(Caution: ugly TeX code ahead!)

\makeatletter % allow @ in command names
\def\timetominutesinto#1#2{% 
  % #1 = command to store the result in
  % #2 = the text to parse
  % \ttm@tempa and \ttm@tempb are temporary variables for this macro
  \let\ttm@tempa\empty % make the command empty
  \IfSubStr{#2}{P}{%
    \def\ttm@tempa{720+}% set tempa to "720+"
    \StrBetween{#2}{:}{P}[\ttm@tempb]% store substring between : and P into tempb
    \edef\ttm@tempa{\ttm@tempa \ttm@tempb}% set tempa to tempa + tempb
  }{%
    \def\ttm@tempa{0+}% set tempa to 0+
    \StrBetween{#2}{:}{A}[\ttm@tempb]% store substring between : and A into tempb
    \edef\ttm@tempa{\ttm@tempa \ttm@tempb}% set tempa to tempa + tempb
  }%
  \edef\ttm@tempa{\ttm@tempa+60*}% set tempa to tempa + "+60*"
  \StrBefore{#2}{:}[\ttm@tempb]% store substring before : into tempb
  % now, set #1 to the result of evaluating the formula returned by concatenating
  % tempa and tempb
  \edef#1{\numexpr \ttm@tempa \ttm@tempb}%
}

\def is the TeX primitive corresponding to LaTeX's \newcommand/\renewcommand. \edef means Expanding \def, which evaluates the definition before assigning the result to a command sequence. \numexpr evaluates a simple number expression, like x + m + h * 60 created by the command above.

It is also possible to calculate the result immediately as a number, without building up the formula, by using integer arithmetic. But that would make the code even more remote from your original intent.

It is possible to do these string manipulations through TeX itself, without using the xstring package (even expandible in this particular case). But that's pretty low level stuff, which cannot easily be repeated if you're not a TeX wizzard.