About
Shop
LaTeX
Software
Books
Gallery
News
Contact
Blog
Settings
Latest news 2019-11-04: The giveaway of two signed copies of “Quack, quack, quack. Give my hat back!” has closed and the winning entrants have been selected. Thank you to everyone who took part.


7.4 Parsing and Displaying Times

The pgfcalender doesn't provide any time-related utilities. TeX's \time primitive expands to the current time in terms of the number of minutes since midnight. The datetime package works out the current hour and minute by performing some arithmetic on the value of \time, but since \time is an integer number of minutes, there's no information about the number of seconds nor is there any information about the time zone. (The new datetime2 package uses the methods described in this section to determine the current time.) PDFTeX comes with a primitive2 called

\pdfcreationdate

that expands to D:YYYY⟩⟨MM⟩⟨DD⟩⟨hh⟩⟨mm⟩⟨ss⟩⟨time zone⟩ where ⟨YYYY⟩ is the year (four digits), ⟨MM⟩ is the month number (two digits), ⟨DD⟩ is the day of the month (two digits), ⟨hh⟩ is the hour (two digits), ⟨mm⟩ is the number of minutes past the hour (two digits), ⟨ss⟩ is the number of seconds past the minute (two digits) and ⟨time zone⟩ is the time zone, which may be Z (for UTC+00) or +⟨HH⟩'⟨mm⟩' (for UTC+⟨HH⟩:⟨mm⟩) or -⟨HH⟩'⟨mm⟩' (for UTC−⟨HH⟩:⟨mm⟩).

The value of \pdfcreationdate is set at the start of the PDFTeX run.

Example:

\pdfcreationdate

produces:

D:20140319184445Z

Recall the \parsemdydate command defined in §7.2 The pgfcalendar Package Utility Commands used \def to parse a date string. A similar method can be employed here, but unfortunately it's more complicated. At first glance it looks as though we can define a command in the form:

\def\parsepdfdatetime D:#1\endparsepdfdatetime{code}
However if we try this out (ignoring the argument for the time being):

\def\parsepdfdatetime D:#1\endparsepdfdatetime{}
\expandafter\parsepdfdatetime\pdfcreationdate\endparsepdfdatetime

we get an error:

! Use of \parsepdfdatetime doesn't match its definition.
<inserted text> D
                 :20140319185833Z
l.10 \expandafter\parsepdfdatetime\pdfcreationdate
                                               \endparsepdfdatetime
The problem is the initial D as the following works fine:3

\def\parsepdfdatetime#1:#2\endparsepdfdatetime{}
\expandafter\parsepdfdatetime\pdfcreationdate\endparsepdfdatetime

The first argument (#1) will always be D and can be ignored. Since TeX only allows a maximum of nine arguments, this leaves eight arguments left, which can pick up the year (#2#3#4#5), month (#6#7) and day (#8#9) digits. This information is already available from TeX's \year, \month and \day primitives, however PDFTeX provides a similar primitive:

\pdffilemoddate{filename}

that expands to the modification date and time for the file given by ⟨filename⟩, and this uses the same format, so \parsepdfdatetime should still save the year, month and day information to be more generally useful.

In order to get around the nine argument maximum \parsepdfdatetime needs to call another command that will parse the remainder:

\def\parsepdfdatetime#1:#2#3#4#5#6#7#8#9{%
  \def\theyear{#2#3#4#5}%
  \def\themonth{#6#7}%
  \def\theday{#8#9}%
  \parsepdftime
}

Note that the end placeholder token \endparsepdfdatetime is no longer in the argument syntax of \parsepdfdatetime. It's now in the argument syntax of the new \parsepdftime command, which picks up the remaining time information:

\def\parsepdftime#1#2#3#4#5#6#7\endparsepdfdatetime{%
  \def\thehour{#1#2}%
  \def\theminute{#3#4}%
  \def\thesecond{#5#6}%
  \def\thetimezone{#7}%
}

The hour digits are now given by #1#2, the minute digits are #3#4 the second digits are #5#6 and the time zone information is in the final argument #7. This information has been stored in the new commands \thehour, \theminute, \thesecond and \thetimezone. If you want \thetimezone to be in the format ⟨sign⟩⟨HH⟩:⟨mm⟩ (where ⟨sign⟩ is either + or -) then replace:

\def\thetimezone{#7}%

with

  \ifstrequal{#7}{Z}
  {%
    \def\thetimezone{+00:00}%
  }%
  {%
    \parsepdftimezone#7%
  }%

where \parsepdftimezone is defined as:

\def\parsepdftimezone#1'#2'{%
  \def\thetimezone{#1:#2}%
}

(\ifstrequal is defined by etoolbox and tests if two strings are equal, but unlike \ifthenelse{\equal{string1}{string2}}{}{}, \ifstrequal doesn't perform any expansion on the strings.)

As with the \datefmt command defined in the previous section, we can also define an analogous command to format the time:

\newcommand*{\timefmt}[4]{%
  #1:#2:#3#4%
}

This has the syntax:

\timefmt{hour}{minutes}{seconds}{UTC offset}

For example:

\timefmt{11}{03}{01}{+01:00}

produces:

11:03:01 +01:00

Another possible definition is:

\newcommand*{\timefmt}[4]{%
  #1:#2%
  \ifstrempty{#3}% test for empty 3rd argument
  {}% no seconds specified
  {:#3}% seconds
  \ifstrempty{#4}% test for empty 4th argument
  {}% no time zone
  {#4}%
}

This uses etoolbox's \ifstrempty command to determine whether or not ⟨seconds⟩ or ⟨UTC offset⟩ have been specified. Alternatively, the time zone information can be dealt with by another command called, say, \timezonefmt:

\newcommand*{\timefmt}[4]{%
  #1:#2%
  \ifstrempty{#3}% test for empty 3rd argument
  {}% no seconds specified
  {:#3}% seconds
  \timezonefmt{#4}% time zone
}

Since it's possible that the time zone might not be fully expanded (for example, the argument might be \thetimezone), the new \timezonefmt first fully expands its argument before parsing it:

\newcommand*{\timezonefmt}[1]{%
  \edef\thistimezone{#1}%
  \ifdefempty{\thistimezone}%
  {}% empty argument
  {%
    \expandafter\@timezonefmt\thistimezone\@endtimezonefmt
  }%
}

Now the actual parsing is done by a new internal command with the syntax:

\@timezonefmtHH⟩:⟨mm⟩\@endtimezonefmt

Here's one possible definition of \@timezonefmt that just displays “Z” if both ⟨HH⟩ and ⟨mm⟩ are zero, otherwise it either does just ⟨HH⟩ if ⟨mm⟩ is zero or it does ⟨HH⟩:⟨mm

\def\@timezonefmt#1:#2\@endtimezonefmt{%
  \ifnum#2=0\relax
    \ifnum#1=0\relax
      Z%
    \else
      #1%
    \fi
  \else
    #1:#2%
  \fi
}

Example:

Since \pdfcreationdate is set at the start of the LaTeX run, you only need to parse it once:

\expandafter\parsepdfdatetime\pdfcreationdate\endparsepdfdatetime
\let\pdfhour\thehour
\let\pdfminute\theminute
\let\pdfsecond\thesecond
\let\pdftimezone\thetimezone
\newcommand*{\pdfnowtime}{%
  \timefmt{\pdfhour}{\pdfminute}{\pdfsecond}{\pdftimezone}}

The time stamp for the LaTeX run can now be inserted into your document using this new \pdfnowtime command:

This PDF was created at \pdfnowtime.

produces:

This PDF was created at 17:51:22Z.

Example 41. Custom Date and Time Package

This example extends the custom package described in Example 40. I've added the LaTeX internal command:

\two@digits{number}

which ensures ⟨number⟩ has at least two digits. Take care when using commands that print numbers, such as \two@digits or \number, as you can unexpectedly lose following spaces. It's for this reason that I've occasionally used \relax or \␣ (backslash space) in the code below.

This example package is now called mycustomdatetime so it needs to have the filename mycustomdatetime.sty and the package declaration should be modified accordingly:

\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{mycustomdatetime}[2014/03/20 1.0 My custom date 
and time format]

Remember the etoolbox package is required:

Now the user command

\timefmt{hh}{mm}{ss}{HH⟩:⟨SS}

is defined:

\newcommand*{\timefmt}[4]{%
  \two@digits{#1}:\two@digits{#2}%
  \ifstrempty{#3}% test for empty 3rd argument
  {}% no seconds specified
  {:\two@digits{#3}}% seconds
  \timezonefmt{#4}% time zone
  \relax
}

and its helper time zone formatting command:

\timezonefmt{HH⟩:⟨SS}

is defined:

\newcommand*{\timezonefmt}[1]{%
  \edef\thistimezone{#1}%
  \ifdefempty{\thistimezone}%
  {}% empty argument
  {%
     \expandafter\@timezonefmt\thistimezone\@endtimezonefmt
  }%
}

along with its internal command:

\def\@timezonefmt#1:#2\@endtimezonefmt{%
  \ifnum#2=0\relax
    \ifnum#1=0\relax
      Z%
    \else
      #1%
    \fi
  \else
    #1:#2%
  \fi
}

If you want to use \two@digits in the time zone, you need to be careful with the plus or minus sign:

\def\@timezonefmt#1:#2\@endtimezonefmt{%
  \ifnum #2=0\relax
    \ifnum #1=0\relax
      Z%
    \else
      \ifnum #1<0 $-$\two@digits{-#1}\else +\two@digits{#1}\fi
    \fi
  \else
    \ifnum #1<0 $-$\two@digits{-#1}\else +\two@digits{#1}\fi
    :\two@digits{#2}%
  \fi
}

(The minus sign has been placed in math-mode using $-$ to ensure it's displayed as a real minus sign rather than as a hyphen. If for some reason you need to use \timefmt in math-mode, I suggest you put it in inside the argument of amsmath's \text command [1].)

Now for the commands that can parse the PDF date format:

\def\parsepdfdatetime#1:#2#3#4#5#6#7#8#9{%
  \def\theyear{#2#3#4#5}%
  \def\themonth{#6#7}%
  \def\theday{#8#9}%
  \parsepdftime
}

\def\parsepdftime#1#2#3#4#5#6#7\endparsepdfdatetime{%
  \def\thehour{#1#2}%
  \def\theminute{#3#4}%
  \def\thesecond{#5#6}%
  \ifstrequal{#7}{Z}
  {%
    \def\thetimezone{+00:00}%
  }%
  {%
    \parsepdftimezone#7%
  }%
}

\def\parsepdftimezone#1'#2'{%
  \def\thetimezone{#1:#2}%
}

Provide a convenient way of displaying the document build time:

\expandafter\parsepdfdatetime\pdfcreationdate\endparsepdfdatetime
\let\pdfhour\thehour
\let\pdfminute\theminute
\let\pdfsecond\thesecond
\let\pdftimezone\thetimezone

\newcommand*{\pdfnowtime}{%
  \timefmt{\pdfhour}{\pdfminute}{\pdfsecond}{\pdftimezone}}

and for a complete date and time stamp:

\newcommand*{\pdfnow}{\today\␣\pdfnowtime}

Alternatively if you want a numeric date independent of the definition of \today:

Similarly define a command with the syntax:

\filedate{filename}

that will display the time stamp of a file:

\newcommand*{\filedate}[1]{%
  \expandafter\parsepdfdatetime\pdffilemoddate{#1}\endparsepdfdatetime
  \datefmt{\theyear{\themonth}{\theday}\space
  \timefmt{\thehour}{\theminute}{\thesecond}{\thetimezone}}%
}

Alternatively, if you want the day of week information to also be shown:

\newcommand*{\filedate}[1]{%
  \expandafter\parsepdfdatetime\pdffilemoddate{#1}\endparsepdfdatetime
  \printdate{\theyear-\themonth-\theday}\␣
  \timefmt{\thehour}{\theminute}{\thesecond}{\thetimezone}%
}

Or if you just want a numeric format regardless of the definition of \printdate:

\newcommand*{\filedate}[1]{%
  \expandafter\parsepdfdatetime\pdffilemoddate{#1}\endparsepdfdatetime
  \theyear-\two@digits{\themonth}-\two@digits\theday\␣
  \timefmt{\thehour}{\theminute}{\thesecond}{\thetimezone}%
}

The rest of the package code is as described in Example 40, including the definitions of \datefmt and \printdate. (You can download the complete package.) Here's an example document that uses this new package:

\documentclass{article}

\usepackage{mycustomdatetime}

\begin{document}

The file \jobname.tex was last modified on:
\filedate{\jobname.tex}.

The PDF was built by \TeX\␣on: \pdfnow.

Format a specific time (Zulu time):
\timefmt{8}{10}{35}{+0:00}.

Format a specific time (non-Zulu time):
\timefmt{8}{10}{35}{+1:00} or
\timefmt{8}{10}{35}{-4:30} or
\timefmt{8}{10}{35}{+5:45}.

Format a specific time without a time zone:
\timefmt{8}{10}{35}{}.

Format a specific time with a time zone but without seconds:
\timefmt{8}{10}{}{+00:00}.

Format a specific time without a time zone or seconds:
\timefmt{8}{10}{}{}.

\end{document}

You can download or view this document.

Exercise 22. Displaying Times

Add a command to the mycustomdatetime package described in Example 41 that has the syntax:

\printdatetime{YYYY⟩-⟨MM⟩-⟨DD⟩ ⟨hh⟩:⟨mm⟩:⟨ss⟩⟨UTC offset}

This command should be equivalent to:

\printdate{YYYY⟩-⟨MM⟩-⟨DD} \timefmt{hh}{mm}{ss}{UTC offset}

Example usage:

\printdatetime{2014-03-25 01:23:15+00:00}
\printdatetime{2014-03-24 23:31:58+01:00}
\printdatetime{2014-03-24 16:28:56-06:00}
\printdatetime{2014-03-24 14:45:23+08:00}
\printdatetime{2014-03-25 03:12:04-04:30}
\printdatetime{2014-03-24 03:45:24-04:30}
\printdatetime{2014-03-24 21:20:24+05:45}

For the More Adventurous:

Suppose I now need all the times in a common time zone for easier comparison. Make a new command called, say, \printzuludatetime that has the same syntax as \printdatetime but it converts the date and time to Zulu time (UTC+00:00) before displaying it.

You can download or view a solution. (The new datetime2 package [101] now comes with commands that perform a similar conversion in the accompanying datetime2-calc package.)



Footnotes

...primitive2
For further details about PDFTeX primitives see the PDFTeX documentation [106].
... fine:3
It's the category code of the character “D” in the expansion of \pdfcreationdate that's the problem. If it's first changed to “other” (category code 12) before defining \parsepdfdatetime then the error won't occur.

This book is also available as A4 PDF or 12.8cm x 9.6cm PDF or paperback (ISBN 978-1-909440-07-4).

© 2015 Dickimaw Books. "Dickimaw", "Dickimaw Books" and the Dickimaw parrot logo are trademarks. The Dickimaw parrot was painted by Magdalene Pritchett.

Terms of Use Privacy Policy Cookies Site Map FAQs