8

I want to compute a time difference.

For example:

  • 22:44 - 22:50 -> 00:06
  • 23:00 - 00:10 -> 01:10

In the second case, the time is implicitly about the following day.

My idea is:

  1. Convert the times into their equivalent in total minutes (h*60+m).
  2. Compute the difference.
  3. Convert the difference back in the format hh:mm.

For the moment, I am stuck with a syntax error:

Missing number, treated as zero. Missing ) inserted for expression.

\documentclass{article}

\def\nok#1:#2\relax#3:#4\relax{%
    \def\tStart{\the\numexpr(#1*60+#2)}%
    \def\tEnd{\the\numexpr(#3*60+#4)}%
    \tEnd\ifnum\tEnd<\tStart+1440\fi%
}

\begin{document}

\the\numexpr(\nok22:44\relax22:50\relax)

\end{document}

What am I doing wrong?

  • How about this: https://tex.stackexchange.com/questions/212364/compute-the-difference-between-two-time-points-on-different-dates or this: https://tex.stackexchange.com/questions/203099/how-to-compute-the-difference-between-two-time-points-e-g-1130-am-and-0120 – Steven B. Segletes Oct 25 '17 at 19:11

5 Answers5

11

\numexpr expressions have to expand to the numeric syntax so you can not have unexpandable assignments from \def

I think this is the calculation you intended

\documentclass{article}

\def\nok#1:#2\relax#3:#4\relax{%
\numexpr
 \numexpr(#3)*60+#4\relax
 \ifnum\numexpr(#1)*60+#2\relax<\numexpr(#1)*60+#2\relax+1440\fi
\relax
 }

\begin{document}

\the\numexpr(\nok22:44\relax22:50\relax)\relax

\end{document}
David Carlisle
  • 757,742
  • @jfbu I'll add a top level \relax :-) – David Carlisle Oct 25 '17 at 21:48
  • +1........... then I can still point out that \the\nok22:44\relax22:50\relax works fine ;-) –  Oct 25 '17 at 21:49
  • @jfbu true, but only if you look inside the definition and see that you can prefix \nok with \the – David Carlisle Oct 25 '17 at 21:54
  • any definition starting with \def deserves a peek inside! –  Oct 25 '17 at 21:57
  • I think we got busy discussing \relax but missed that you don't compute a difference here... (you did get the ticks) and now we have 3 additional answers, so I will add another one ;-) –  Oct 26 '17 at 07:34
  • This is the only answer which explains the error from the question... – cgnieder Oct 26 '17 at 08:06
  • @jfbu the question is not about date calculation but why there was an error in the OPs code, this answer uses the same calculation as the OP but repeating that subterms rather than making a definition so it works by expansion. – David Carlisle Oct 26 '17 at 08:28
  • I knew there must have been some reason why I upvoted: you are indeed the only one actually answering the question! ;-). Notice that I waited for all our comrades rolling out their computations for following suit... not my fault here... –  Oct 26 '17 at 09:07
8

You can also use Lua, if you're willing to compile with lualatex instead of pdflatex:

output

\documentclass{article}

\directlua{dofile('timediff.lua')}
\def\wait#1#2{\directlua{tex.print(timediff('#1', '#2'))}}

\begin{document}
The time from 22:44 to 22:50 is \wait{22:44}{22:50}.

The time from 23:00 to 00:10 is \wait{23:00}{00:10}.
\end{document}

where timediff.lua is:

function hhms(s)
   -- Example: hhms('01:42') gives 102
   local _, _, hh, mm = string.find(s, "(%d*):(%d*)")
   return hh * 60 + mm
end

function timediff(time1, time2)
   -- Example: timediff('22:45', '01:15') gives '02:30'
   local t1 = hhms(time1)
   local t2 = hhms(time2)
   if t2 < t1 then t2 = t2 + 1440 end
   local d = t2 - t1
   local mm = d % 60
   local hh = (d - mm) / 60
   return string.format('%02d:%02d', hh, mm)
end

Later you may find Lua's time and date functions useful.

ShreevatsaR
  • 45,428
  • 10
  • 117
  • 149
7

The expected expl3 answer providing the expected padding. This also support (single) macros in the input.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewExpandableDocumentCommand{\timedelta}{mm}
 {
  \joseph_timediff:ff { #1 } { #2 }
 }

\cs_new:Nn \joseph_timediff:nn
 {
  \__joseph_timediff:ff
   { \__joseph_minutes:w #1 \q_stop }
   { \__joseph_minutes:w #2 \q_stop }
 }
\cs_generate_variant:Nn \joseph_timediff:nn { ff }

\cs_new:Nn \__joseph_timediff:nn
 {
  \joseph_output:f
   {
    \int_eval:n { (#2) - (#1) \int_compare:nNnT { #2 } < { #1 } { + 1440 } }
   }
 }
\cs_generate_variant:Nn \__joseph_timediff:nn { ff }

\use:x
 {
  \cs_new:Npn \exp_not:N \__joseph_minutes:w ##1 \token_to_str:N : ##2 \exp_not:N \q_stop
 }
 {
  #1 * 60 + #2
 }

\cs_new:Nn \joseph_output:n
 {
  \int_compare:nNnT { \int_div_truncate:nn { #1 } { 60 } } < { 10 } { 0 }
  \int_div_truncate:nn { #1 } { 60 }
  :
  \int_compare:nNnT { \int_mod:nn { #1 } { 60 } } < { 10 } { 0 }
  \int_mod:nn { #1 } { 60 }
 }
\cs_generate_variant:Nn \joseph_output:n { f }

\ExplSyntaxOff

\newcommand\routeStart{01:27}
\newcommand\routeStop{01:27}

\begin{document}

\timedelta{22:40}{22:50}

\timedelta{23:00}{00:10}

\timedelta{08:24}{19:32}

\timedelta{\routeStart}{01:27}

\timedelta{16:03}{\routeStop}

\timedelta{\routeStart}{\routeStop}

\end{document}

enter image description here

egreg
  • 1,121,712
5

An approach without packages.

\documentclass{article}


\makeatletter
% Will be expandable, expands in two steps
\newcommand{\timedelta}[2]{\romannumeral0\timedelta@i#1:#2:}%
% get arguments and evaluate time difference in minutes
\def\timedelta@i #1:#2:#3:#4:%
   {\expandafter\timedelta@ii\the\numexpr#3*60+#4-#1*60-#2.}%
% correct modulo 24*60 if difference turns out negative
\def\timedelta@ii#1{\expandafter\timedelta@iii\the\numexpr
                    \if-#11440\fi #1}%
% do Euclidean division by 60. Curse \numexpr rounding in passing.
\def\timedelta@iii#1.{\expandafter\timedelta@iv\the\numexpr
                    (#1 + 30)/60 -1.#1.}%
% Get the remainder too, prepare for zero padding
\def\timedelta@iv #1.#2.{\expandafter\timedelta@v\the\numexpr
                    100+#2-60*#1\expandafter.\the\numexpr100+#1.}
% Output final result 
\def\timedelta@v 1#1.1#2.{ #2:#1}
\makeatother

\begin{document}

\timedelta{22:40}{22:50}

\timedelta{23:00}{00:10}

\timedelta{08:24}{19:32}

\end{document}

enter image description here


In practice, one may want to use the \timedelta with arguments being themselves macros. But the above code does not expand the arguments. This led to follow-up question Probably just another gdef-expansion problem.

I provided an answer there which completes the above code with generated variants which expand their arguments.

Let me here simply extend the above code so that \timedelta automatically expands its two arguments. This expansion will be a so-called "full (first token) expansion", and in particular \timedelta can then be used as argument to itself, so that one can compute delta's of delta's etc... à la Newton iterated differences. Just in case you are idle for a while.

\documentclass{article}    

\makeatletter
% Will be expandable, expands in two steps

% Expands its arguments!

\newcommand{\timedelta}[2]%
  {\romannumeral0\expandafter\timedelta@h\expandafter
     {\romannumeral-`0#2}{#1}}%
\def\timedelta@h #1#2{\expandafter\timedelta@i\romannumeral-`0#2:#1:}

% get arguments and evaluate time difference in minutes
\def\timedelta@i #1:#2:#3:#4:%
   {\expandafter\timedelta@ii\the\numexpr#3*60+#4-#1*60-#2.}%
% correct modulo 24*60 if difference turns out negative
\def\timedelta@ii#1{\expandafter\timedelta@iii\the\numexpr
                    \if-#11440\fi #1}%
% do Euclidean division by 60. Curse \numexpr rounding in passing.
\def\timedelta@iii#1.{\expandafter\timedelta@iv\the\numexpr
                    (#1 + 30)/60 -1.#1.}%
% Get the remainder too, prepare for zero padding
\def\timedelta@iv #1.#2.{\expandafter\timedelta@v\the\numexpr
                    100+#2-60*#1\expandafter.\the\numexpr100+#1.}
% Output final result 
\def\timedelta@v 1#1.1#2.{ #2:#1}
\makeatother

\begin{document}

\timedelta{22:40}{22:50}

\timedelta{23:00}{00:10}

\timedelta{08:24}{19:32}

\newcommand\timeStart{00:02}
\newcommand\timeEnd{00:01}

\timedelta{\timeStart}{\timeEnd}

\timedelta{\timeEnd}{\timeStart}

\timedelta{\timedelta{10:00}{14:23}}{\timedelta{10:00}{14:22}}

\end{document}

enter image description here

4

The following assumes that times are of the form HH:MM:

enter image description here

\documentclass{article}

\usepackage{xfp}

\makeatletter

\def\TheHour#1:#2@END{#1}
\def\TheMinute#1:#2@END{#2}

\newcommand{\timediff}[2]{%
  \two@digits{% HOUR
    \fpeval{(\TheHour#2@END)-(\TheHour#1@END)% Hour difference
            +(\ifnum\TheHour#2@END<\TheHour#1@END 24\else 0\fi)% Correction for time spanning midnight
            -(\ifnum\TheMinute#2@END<\TheMinute#1@END 1\else 0\fi)}% Minute correction
  }%
  {:}%
  \two@digits{% MINUTE
    \fpeval{(\TheMinute#2@END)-(\TheMinute#1@END)% Minute difference
            +(\ifnum\TheMinute#2@END<\TheMinute#1@END 60\else 0\fi)}% Minute correction
  }%
}
\makeatother

\begin{document}

$22{:}44 - 22{:}50 = \timediff{22:44}{22:50}$

$23{:}00 - 00{:}10 = \timediff{23:00}{00:10}$

$00{:}00 - 23{:}59 = \timediff{00:00}{23:59}$

$22{:}44 - 00{:}43 = \timediff{22:44}{00:43}$

$12{:}34 - 12{:}34 = \timediff{12:34}{12:34}$

\end{document}
Werner
  • 603,163