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
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:
produces:
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{} \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 \endparsepdfdatetimeThe 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:
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:
with
where \parsepdftimezone
is defined as:
(\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:
This has the syntax:
For example:
produces:
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:
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 example extends the custom package described in Example 40. I've added the LaTeX internal command:
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:
\RequirePackage{etoolbox}
Now the user command
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:
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:
Alternatively if you want a numeric date independent of the definition
of \today
:
Similarly define a command with the syntax:
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}
Add a command to the mycustomdatetime package described in Example 41 that has the syntax:
This command should be equivalent to:
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).