Befehl zur Anzeige der Zeit (Dauer) in menschenlesbarer Form

Befehl zur Anzeige der Zeit (Dauer) in menschenlesbarer Form

Ich habe 3 Stunden damit verbracht, einen Befehl zu schreiben, um eine als Gleitkommazahl (in Sekunden) ausgedrückte Zeit (Dauer) in einer ansprechenden Form wie folgt zu formatieren:

if the time is < 60 write as a float with 3 decimals (i.e. milliseconds) 
else let h,m,s be the hours, minutes and seconds corresponding to the time
     if h=0 then write as m:ss (ss always with 2 digits, m can have only 1)
     else write h:mm:ss (mm and ss with 2 digits, h can have only 1)

Also ergibt 1,25678 1,256, 125,35 ergibt 2:05, 18249 (was 5*3600+4*60+9 entspricht) ergibt 5:04:09.

Ich habe mehrere Pakete zum Vergleichen/Formatieren ausprobiert. Kein Erfolg. Kann mir jemand helfen? Danke!

Antwort1

expl3Sie können dies mit und seinem Modul tun fp.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\NewDocumentCommand{\duration}{m}
 {
  \didou_duration:n { #1 }
 }

\fp_new:N \l_didou_duration_hrs_fp
\fp_new:N \l_didou_duration_min_fp
\fp_new:N \l_didou_duration_sec_fp

\cs_new_protected:Nn \didou_duration:n
 {
  \fp_compare:nTF { #1 < 60 }
   {% easy case
    \fp_eval:n { round(#1,3) }
   }
   {
    \fp_compare:nTF { #1 < 3600 }
     {% only minutes
      \didou_duration_minutes:n { #1 }
     }
     {% hours
      \didou_duration_hours:n { #1 }
     }
   }
 }

\cs_new_protected:Nn \didou_duration_minutes:n
 {
  \fp_set:Nn \l_didou_duration_min_fp { trunc( #1/60,0 ) }
  \fp_set:Nn \l_didou_duration_sec_fp
   {
    round( #1-\l_didou_duration_min_fp * 60,0 )
   }
  % now print
  \fp_eval:n { \l_didou_duration_min_fp }
  :
  \fp_compare:nT { \l_didou_duration_sec_fp < 10 } { 0 } % two digits
  \fp_eval:n { \l_didou_duration_sec_fp }
 }
\cs_generate_variant:Nn \didou_duration_minutes:n { V }

\cs_new_protected:Nn \didou_duration_hours:n
 {
  \fp_set:Nn \l_didou_duration_hrs_fp { trunc( #1/3600,0 ) }
  \fp_set:Nn \l_didou_duration_min_fp { #1 - \l_didou_duration_hrs_fp * 3600 }
  % print the hours
  \fp_eval:n { \l_didou_duration_hrs_fp } :
  % go to minutes
  \fp_compare:nT { \l_didou_duration_min_fp < 600 } { 0 }
  \didou_duration_minutes:V \l_didou_duration_min_fp
 }

\ExplSyntaxOff

\begin{document}

\duration{1.25678}

\duration{125.35}

\duration{249}

\duration{18249}

\duration{2318249}

\end{document}

Bildbeschreibung hier eingeben

Bei Bedarf hier eine vollständig erweiterbare Version:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\DeclareExpandableDocumentCommand{\duration}{m}
 {
  \didou_duration:n { #1 }
 }


\cs_new:Nn \didou_duration:n
 {
  \fp_compare:nTF { #1 < 60 }
   {% easy case
    \fp_eval:n { round(#1,3) }
   }
   {
    \fp_compare:nTF { #1 < 3600 }
     {% only minutes
      \didou_duration_minutes:n { #1 }
     }
     {% hours
      \didou_duration_hours:n { #1 }
     }
   }
 }

\cs_new:Nn \didou_duration_minutes:n
 {
  \fp_eval:n { trunc( #1/60,0 ) }
  :
  \fp_compare:nT { round( #1 - trunc( #1/60,0 ) * 60,0 ) < 10 } { 0 } % two digits
  \fp_eval:n { round( #1- trunc( #1/60,0 ) * 60,0 ) }
 }
\cs_generate_variant:Nn \didou_duration_minutes:n { f }

\cs_new:Nn \didou_duration_hours:n
 {
  % print the hours
  \fp_eval:n { trunc( #1/3600,0 ) } :
  % go to minutes
  \fp_compare:nT { trunc( #1/3600,0 ) < 600 } { 0 }
  \didou_duration_minutes:f { \fp_eval:n { #1 - trunc( #1/3600,0 ) * 3600} }
 }

\ExplSyntaxOff

\begin{document}

\duration{1.25678}

\duration{125.35}

\duration{249}

\duration{18249}

\duration{2318249}

\edef\test{\duration{2318249}}\texttt{\meaning\test}

\end{document}

Bildbeschreibung hier eingeben

Antwort2

Die Verwendung \pgfmathparseist für diese Frage problematisch, da die Ganzzahldivision bei etwa 16500 mit einem „Dimension zu groß“-Fehler fehlschlägt. Insbesondere \pgfmathparsewird ohne zusätzliche Sorgfalt keine Lösung mit dem Beispiel des OP zurechtkommen, 18249da diese Zahl zu groß ist.

Für große (ganzzahlige) BerechnungenxintFamilie von Paketen ist schwer zu schlagen (leider finde ich die Dokumentation schwer verständlich, da nur wenige Beispiele angegeben sind). MitwerkzeugUndxintfrac, zusammen mitAbonnierenWir können diese Frage beantworten und insbesondere mit dem "großen" Beispiel von umgehen 18249.

Code

\documentclass{article}
\usepackage{siunitx,xinttools,xintfrac}

\newcommand\Duration[1]{%
  \xintifLt{#1}{60}{% less than 60 => print as seconds, with milliseconds
      \num[round-mode=places,round-precision=3]{#1}%
  }{% have minutes seconds and possibly hours
      \xintAssign\xintiiDivision{\xintNum{#1}}{3600}\to\hours\minutes%
      \xintAssign\xintiiDivision{\minutes}{60}\to\minutes\seconds%
      \xintiiifGt{\hours}{0}{% hours>0%
         \hours:\num[minimum-integer-digits=2]{\minutes}:\num[minimum-integer-digits=2]{\seconds}%
      }{% only minutes and seconds
         \minutes:\num[minimum-integer-digits=2]{\seconds}%
      }%
  }%
}

\begin{document}

  1.25678: \Duration{1.25678}

  125.35: \Duration{125.35}

  18249: \Duration{18249}

\end{document}

Ausgabe

Dies erzeugt die gewünschte Ausgabe:

Bildbeschreibung hier eingeben

Einige Erklärungen

Da es für mich ein wenig schwierig war, herauszufinden, wie die xintBefehle zu verwenden sind, sind ein paar erklärende Worte angebracht.

  1. Das Makro \xintiiDivisiongibt den Quotienten zurückUndDer Rest wird dann \xintAssignverwendet, um (diese beiden Ausgänge) Makros zuzuweisen.

  2. Der Vergleichsoperator \xintifLtakzeptiert Dezimalzahlen, \xintiiifGtist jedoch schneller und erwartet eine Ganzzahl. Vielen Dank an @jfbu für die Erklärung in den Kommentaren.

  3. Der \numBefehl vonAbonnierendient zum Formatieren der Ausgabe und insbesondere zum Auffüllen mit führenden Nullen mittels minimum-integer-digits=2.

Möglicherweise xintkönnen die Befehle effizienter genutzt werden!

Antwort3

Hier ist eine Lösung mit dem apnum-Paket:

\input apnum

\def\duration#1{{\apFRAC=0         % "/" means integer division
   \def\zero{0}%
   \evaldef\S{#1/1}%               \S: number of seconds (without fraction part) 
   \evaldef\H{\S/3600}%            \H: number of hours
   \evaldef\Hrest{\S - 3600*\H}%   \Hrest: number of seconds without hours
   \evaldef\M{\Hrest/60}%          \M: minutes in \Hrest
   \evaldef\Mrest{\Hrest - 60*\M}% \Mrest: number of seconds without minutes
   \ifx\H\zero
     \ifx\M\zero \miliseconds#1...\end         % print: seconds.miliseconds  
     \else \M:\twodigits\Mrest \fi             % print: minutes:seconds
   \else \H:\twodigits\M:\twodigits\Mrest \fi  % print: hours:minutes:seconds
}}
\def\twodigits#1{\ifnum#1<10 0\fi#1}
\def\miliseconds #1.#2#3#4\end{#1\ifx.#2\else.#2\ifx.#3\else#3\fi\fi}

1.25678: \duration{1.25678}  % prints: 1.25

125.35:  \duration{125.35}   % prints: 2:05

18249:   \duration{18249}    % prints: 5:04:09

\bye

BearbeitenUnd die zweite Lösung. Es verwendetnur TeX-Grundelementeund \newcountMakro. Und die Idee ist die gleiche wie oben. Die Zeit ist jetzt auf 2^31 Sekunden begrenzt, das bedeutet 68 Jahre.

\newcount\S \newcount\H  \newcount\Hrest  \newcount\M  \newcount\Mrest

\def\duration#1{%
   \setS#1.\end
   \H=\S \divide\H by3600
   \Hrest=\H \multiply\Hrest by-3600 \advance\Hrest by\S
   \M=\Hrest \divide\M by60
   \Mrest=\M \multiply\Mrest by-60 \advance\Mrest by\Hrest
   \ifnum \H=0
     \ifnum\M=0 \miliseconds#1...\end             % print: seconds.miliseconds  
     \else \the\M:\twodigits\Mrest \fi            % print: minutes:seconds
   \else \the\H:\twodigits\M:\twodigits\Mrest \fi % print: hours:minutes:seconds
}
\def\setS#1.#2\end{\S=#1\relax}
\def\twodigits#1{\ifnum#1<10 0\fi\the#1}
\def\miliseconds #1.#2#3#4\end{#1\ifx.#2\else.#2\ifx.#3\else#3\fi\fi}

1.25678: \duration{1.25678}

125.35:  \duration{125.35}

18249:   \duration{18249}

\bye

Antwort4

(Der Lua-Code wurde aktualisiert, nachdem der OP klargestellt hatte, dassRundunganstatt einer Kürzung sollte auf die Sekunden in den Dauerberechnungen angewendet werden.)

Hier ist eine LuaLaTeX-basierte Lösung. Die Präambel richtet sowohl eine Lua-Funktion namens formatted_durationals auch ein TeX-Makro namens ein \fdur. Das TeX-Makro nimmt ein Argument (die Dauer in Sekunden und Sekundenbruchteilen), das es an die Lua-Funktion übergibt, um die Stunden-Minuten-Sekunden-Berechnungen und die erforderliche Neuformatierung durchzuführen.

Bildbeschreibung hier eingeben

\documentclass{article}

%% Lua-side code
\usepackage{luacode}
\begin{luacode}
function round(num, idp)
  local mult = 10^(idp or 0)
  return math.floor(num * mult + 0.5) / mult
end

function formatted_duration ( n ) 
  local h=0; local m=0; local s
  n = tonumber ( n )
  -- First, calculate hours, minutes, and seconds
  if n >= 3600 then
    h = math.floor (n / 3600)
    n = n - h*3600
  end
  if n >= 60 then
    m = math.floor ( n / 60 )
    n = n - m*60
  end 
  s = round ( n , 3 )

  -- Next, format h, m and s as needed, then return the formatted string
  if h>0 then
     return tex.sprint ( h .. ":" .. string.format("%02d", m ) .. 
                              ":" .. string.format("%02d", round (s,0) ) )
  elseif m>0 then
     return tex.sprint ( m .. ":" .. string.format("%02d", round (s,0) ) )
  else
     return tex.sprint ( string.format ( "%.3f", s ) )
  end
end 
\end{luacode}

%% TeX-side code
\newcommand\fdur[1]{\directlua{formatted_duration(\luastring{#1})}}

\begin{document}
\begin{tabular}{ll}
Input & Output of \texttt{\string\fdur} \\[1ex]
1 & \fdur{1}             \\ % -> "1.000" 
1.25678 & \fdur{1.25678} \\ % -> "1.257" 
125.35  & \fdur{125.35}  \\ % -> "2:05"  
18249   & \fdur{18249}   \\ % -> "5:04:09"
\end{tabular}
\end{document} 

verwandte Informationen