12

I'm trying to parse a date (say, a due date) in a DD-MMM-YYYY format, e.g., 06-May-2012 and create macros \dueday, \duemonth, and \dueyear. I use xstring's commands to extract the substrings. Then I use xstring's \IfStrEqCase to generate the numeric \duemonth:

\documentclass{article}

\usepackage{xstring}

\begin{document}
\newcommand{\duedate}{06-May-2012}

\newcommand{\dueday}{\StrBefore{\duedate}{-}}
\newcommand{\duemonthname}{\StrBetween[1,2]{\duedate}{-}{-}}
\newcommand{\dueyear}{\StrBehind[2]{\duedate}{-}}

Reconstruction: \dueday-\duemonthname-\dueyear.

%\renewcommand{\duemonthname}{May}   % This appears to solve the problem.

\IfStrEqCase{\duemonthname}{%
    {Jan}{\newcommand{\duemonth}{1}}%
    {Feb}{\newcommand{\duemonth}{2}}%
    {Mar}{\newcommand{\duemonth}{3}}%
    {Apr}{\newcommand{\duemonth}{4}}%
    {May}{\newcommand{\duemonth}{5}}%
    {Jun}{\newcommand{\duemonth}{6}}%
    {Jul}{\newcommand{\duemonth}{7}}%
    {Aug}{\newcommand{\duemonth}{8}}%
    {Sep}{\newcommand{\duemonth}{9}}%
    {Oct}{\newcommand{\duemonth}{10}}%
    {Nov}{\newcommand{\duemonth}{11}}%
    {Dec}{\newcommand{\duemonth}{12}}%
    }

\duemonth   % This should output 5.

\end{document}

which fails with an error message TeX capacity exceeded. The \IfStrEqCase doesn't seem to be the problem, because if I issue \renewcommand{\duemonthname}{May} right before it, everything is fine. This suggests that the error is due to splitting commands, but their outputs are what I expect them to be (at least their "visible" outputs are). What is causing the error then?

1
  • 2
    Use \StrBefore{\duedate}{-}[\dueday]\StrBetween[1,2]{\duedate}{-}{-}[\duemonthname]. Every command of xstring has an optional argument which saves the result in a command. See documentation. May 6, 2012 at 8:44

3 Answers 3

14

No, you don't need xstring for that. TeX's syntax is sufficient.

\def in TeX use a parameter template to extract any format of argument. The syntax is (c.f. TeX for the Impatient):

\def \foo parameter text {replacement text}

Thus you can directly use

\def\parsedate #1-#2-#3\stopmark{Use #1 and #2 and #3 as you wish}

to get the different arguments in a date. This is even simpler (and more effective) than xstring package.

To get a number from the name of a month, you can define a sequence of macros. For example, define

\def\theFeb{2}

then you can use \theFeb to get the number. What's more, \csname ...\endcsname allows you to use \csname the#1\endcsname to get \theFeb when #1 is Feb.

\parsedate above needs a \stopmark (it can be any thing) to denote the end of the argument, so it's convenient to define a new macro to use it:

\def\parsedateHelper #1-#2-#3\stopmark{Use #1 and #2 and #3 as you wish}
\def\parsedate#1{\parsedateHelper #1\stopmark}
% use as \parsedate{06-May-2012}

You may also meet some expansion issues when you use a macro as the argument of \parsedate, so it is better to store the argument into another macro and use it with help of \expandafter. It is a bit tricky:

\def\parsedate#1{%
  \edef\savedargument{#1}% #1 is expanded by \edef
  \expandafter\parsedateHelper\savedargument\stopmark}

Make everything together:

\documentclass{article}

\def\parsedate#1{\edef\temp{#1}%
  \expandafter\parsedateX\temp\relax}
\def\parsedateX #1-#2-#3\relax{%
  \def\dueday{#1}%
  \edef\duemonth{\csname the#2\endcsname}%
  \def\dueyear{#3}}
\def\theJan{1}
\def\theFeb{2}
\def\theMar{3}
\def\theApr{4}
\def\theMay{5}
\def\theJun{6}
\def\theJul{7}
\def\theAug{8}
\def\theSep{9}
\def\theOct{10}
\def\theNov{11}
\def\theDec{12}


\begin{document}
\parsedate{06-May-2012}
\dueyear/\duemonth/\dueday

\def\duedate{01-Aug-2003}
\parsedate{\duedate}
\dueyear/\duemonth/\dueday

\end{document}
4
  • 2
    I was thinking of posting a TeX-based answer myself, but then went with explaining the original problem. You might want to explain the steps here a little, so that new users have a chance of understanding what's going on. May 6, 2012 at 8:59
  • Very elegant, @Leo. Thanks. Seems I do need to learn TeX. \def does what I exactly I was looking for and doe it more elegantly. At one point I even wanted to try regex, but thought it was overkill. Then I learned of xstring in my search on TeX.SX, which does the job (with the fixes), but not nearly as elegantly as TeX itself, although I have to admit the interface it provides is very programming language--like. May 6, 2012 at 18:59
  • How do I do this in a robust way? I.e. I want to check first if \duedate is of the form dd-mon-yyy and if not, i dont want to parse it.
    – user56452
    Nov 18, 2014 at 8:39
  • 1
    @user56452: To check the form, you can use \parsedateHelper #1--\stopmark to add the separators if there aren't. Then you can check the arguments of \parsedateHelper as you wish. The basic idea can be found in the definition of \@for in the source of LaTeX2e. That's tricky, anyway.
    – Leo Liu
    Nov 19, 2014 at 3:14
7

The mandatory LaTeX3 solution:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\parsedate}{ >{ \SplitArgument { 2 } { - } } m }
 {
  \parsedate_main:nnn #1
 }
\cs_new_protected:Npn \parsedate_main:nnn #1 #2 #3
 {
  \cs_gset:Npn \dueday { #1 }
  \cs_gset:Npx \duemonth { \prop_get:Nn \g_parsedate_months_prop { #2 } }
  \cs_gset:Npn \dueyear { #3 }
 }
\prop_new:N \g_parsedate_months_prop
\prop_gput:Nnn \g_parsedate_months_prop { Jan } { 01 }
\prop_gput:Nnn \g_parsedate_months_prop { Feb } { 02 }
\prop_gput:Nnn \g_parsedate_months_prop { Mar } { 03 }
\prop_gput:Nnn \g_parsedate_months_prop { Apr } { 04 }
\prop_gput:Nnn \g_parsedate_months_prop { May } { 05 }
\prop_gput:Nnn \g_parsedate_months_prop { Jun } { 06 }
\prop_gput:Nnn \g_parsedate_months_prop { Jul } { 07 }
\prop_gput:Nnn \g_parsedate_months_prop { Aug } { 08 }
\prop_gput:Nnn \g_parsedate_months_prop { Sep } { 09 }
\prop_gput:Nnn \g_parsedate_months_prop { Oct } { 10 }
\prop_gput:Nnn \g_parsedate_months_prop { Nov } { 11 }
\prop_gput:Nnn \g_parsedate_months_prop { Dec } { 12 }
\ExplSyntaxOff

\begin{document}
\parsedate{06-May-2012}
\dueday/\duemonth/\dueyear
\end{document}

This will print

06/05/2012

The argument is given as 06-May-2012, but the \SplitArgument facility presents it to \parsedate_main:nnn already in the form {06}{May}{2012}. Thus it's easy to extract the day and the year. For the month we use a property list: we associate to each month its number and retrieve this one with \prop_get:Nn.

Some words of explanation

An argument passed to a command defined with \NewDocumentCommand can be "processed" before being presented to the macros doing the actual work. In this case we ask that the argument is split into three parts at the - with

\SplitArgument{2}{-}

(it means that two - are expected). So what's denoted with #1 in the body of the definition is to be thought of as {chunk1}{chunk2}{chunk3}. This is passed to \parsedate_main:nnn which indeed has three arguments.

Its duty is to assign a meaning to \dueday, \duemonth and \dueyear. The first and third are easy: just (globally) define them as parameterless macros.

For \duemonth we have to translate the month abbreviation into a number. The easiest method would be with \prg_case_str:nnn:

\cs_gset:Npx \duemonth
  {
   \prg_case_str:nnn { #2 }
    {
     { Jan } { 01 }
     { Feb } { 02 }
     ...
     { Dec } { 12 }
    }{}
   }

but the approach with property lists has an advantage: one might define an entire set of property lists for various languages and decide at runtime which one to rename to \g_parsedate_month_prop for being used in \parsedate_main:nnn, thus adapting easily the same macro to a multilingual environment.

So \duemonth is defined with \prop_get:Nn, in an "expanded definition": the entire

\prop_get:Nn \g_parsedate_months_prop { #2 }

is expanded because we use \cs_gset:Npx (the final x means "expansion", it's the good old \xdef). In the case #2 is May, the property list is looked at for the value corresponding to the property May, which is 05, so this becomes the replacement text of \duemonth.

4
  • Why do you use prop instead of a simple case? May 6, 2012 at 10:13
  • @MarcoDaniel Just to show that they exist. :) Seriously, one can define many of them (for multilingual support, for example) and use which one is appropriate. This wouldn't work using \prg_case_str:nnn.
    – egreg
    May 6, 2012 at 10:44
  • @egreg, it seems there are at least three ways to do something in LaTeX: 1) something that simply works (using packages, like what I was trying to do with Martin and Marco's fix), 2) something that's elegant (using TeX primitives, like Leo's solution), and 3) something that's very structured (using LaTeX3 material, like your solution)! If only I had enough LaTeX literacy to understand how you solution works... May 6, 2012 at 18:30
  • Note that as of September 2014, \prop_get:Nn has been renamed to \prop_item:Nn
    – egreg
    Sep 17, 2014 at 17:54
6

You make the common mistake of placing the \StrBefore etc. macros in the macro definition. These operations are not expandable and can't be used as input to other string macros. Instead use the trailing optional argument to store the extracted string into a macro. This way you don't re-extract the string every time you are using the macros and you can use them in other string manipulation macros.

\documentclass{article}

\usepackage{xstring}

\begin{document}
\newcommand{\duedate}{06-May-2012}

\StrBefore{\duedate}{-}[\dueday]
\StrBetween[1,2]{\duedate}{-}{-}[\duemonthname]
\StrBehind[2]{\duedate}{-}[\dueyear]

Reconstruction: \dueday-\duemonthname-\dueyear.

%\renewcommand{\duemonthname}{May}   % This appears to solve the problem.

\IfStrEqCase{\duemonthname}{%
    {Jan}{\newcommand{\duemonth}{1}}%
    {Feb}{\newcommand{\duemonth}{2}}%
    {Mar}{\newcommand{\duemonth}{3}}%
    {Apr}{\newcommand{\duemonth}{4}}%
    {May}{\newcommand{\duemonth}{5}}%
    {Jun}{\newcommand{\duemonth}{6}}%
    {Jul}{\newcommand{\duemonth}{7}}%
    {Aug}{\newcommand{\duemonth}{8}}%
    {Sep}{\newcommand{\duemonth}{9}}%
    {Oct}{\newcommand{\duemonth}{10}}%
    {Nov}{\newcommand{\duemonth}{11}}%
    {Dec}{\newcommand{\duemonth}{12}}%
}

\duemonth   % This should output 5.

\end{document}
2
  • thanks. This fully answers my question, which puts me in a dilemma as to which answer to "accept"---yours or Leo's. May 6, 2012 at 18:48
  • @AliMehrizi: I'm fine with any answer you pick. Mine answered your answer more directly and explained the reason why it originally didn't worked, while the others shown more lower-level ways to do the same. May 6, 2012 at 18:52

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.