команда для отображения времени (длительности) в удобной для восприятия форме

команда для отображения времени (длительности) в удобной для восприятия форме

Я потратил 3 часа, пытаясь написать команду для форматирования времени (длительности), выраженного в виде плавающего числа (в секундах), в удобной форме, как показано ниже:

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)

Таким образом, 1,25678 дает 1,256, 125,35 дает 2:05, 18249 (что равно 5*3600+4*60+9) дает 5:04:09.

Я пробовал несколько пакетов для сравнения/форматирования. Безуспешно. Может кто-нибудь мне помочь? Спасибо!

решение1

Это можно сделать с помощью expl3и его 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}

введите описание изображения здесь

При необходимости вот полностью расширяемая версия:

\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}

введите описание изображения здесь

решение2

Использование \pgfmathparseпроблематично для этого вопроса, поскольку целочисленное деление завершается ошибкой «Слишком большая размерность» около 16500. В частности, без некоторой дополнительной осторожности любое решение с использованием \pgfmathparseне сможет справиться с примером OP, 18249поскольку это число слишком велико.

Для больших (целочисленных) вычисленийxintсемейство пакетов трудно превзойти (к сожалению, мне сложно разобраться в его документации, поскольку приведено мало примеров). Использованиеxinttoolsиxintfrac, вместе сsiunitxмы можем ответить на этот вопрос и, в частности, справиться с «большим» примером 18249.

Код

\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}

Выход

Это дает желаемый результат:

введите описание изображения здесь

Некоторые объяснения

Учитывая, что мне было немного трудно разобраться, как использовать xintкоманды, будет уместно дать несколько пояснений.

  1. Макрос \xintiiDivisionвозвращает частноеиостаток и затем \xintAssignиспользуется для назначения (этих двух выходов) макросам.

  2. Оператор сравнения \xintifLtпринимает десятичные числа, тогда как \xintiiifGtбыстрее и ожидает целое число. Спасибо @jfbu за объяснение этого в комментариях.

  3. Команда \numотsiunitxиспользуется для форматирования вывода и, в частности, дополнения начальными нулями с помощью minimum-integer-digits=2.

Вполне возможно, что xintкоманды можно использовать более эффективно!

решение3

Вот решение с использованием пакета apnum:

\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

РедактироватьИ второе решение. Оно используеттолько примитивы TeXи \newcountмакрос. И идея та же, что и выше. Время теперь ограничено 2^31 секундами, то есть 68 лет.

\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

решение4

(Обновил код Lua после того, как автор разъяснил, чтоокругление(В расчетах длительности вместо усечения следует применять секунды.)

Вот решение на основе LuaLaTeX. Преамбула устанавливает как функцию Lua с именем, formatted_durationтак и макрос TeX с именем \fdur. Макрос TeX принимает один аргумент (длительность в секундах и долях секунды), который он передает функции Lua для выполнения вычислений час-минута-секунда и необходимого переформатирования.

введите описание изображения здесь

\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} 

Связанный контент