%20em%20formato%20leg%C3%ADvel%20por%20humanos.png)
Passei 3 horas tentando escrever um comando para formatar um tempo (duração) expresso como um número flutuante (em segundos) de uma forma agradável, como segue:
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)
Portanto, 1,25678 dá 1,256, 125,35 dá 2:05, 18249 (que é igual a 5*3600+4*60+9) dá 5:04:09.
Tentei vários pacotes para comparação/formatação. Sem sucesso. Alguém pode me ajudar ? Obrigado !
Responder1
Você pode fazer isso com expl3
seu fp
módulo.
\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}
Se necessário, aqui está uma versão totalmente expansível:
\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}
Responder2
O uso \pgfmathparse
é problemático para esta questão porque a divisão inteira falha com um erro "Dimensão muito grande" em torno de 16500. Em particular, sem alguns cuidados adicionais, qualquer solução que use \pgfmathparse
não será capaz de lidar com o exemplo do OP 18249
porque esse número é muito grande.
Para cálculos grandes (inteiros), oxintfamília de pacotes é difícil de superar (infelizmente, acho difícil acompanhar sua documentação porque poucos exemplos são dados). Usandoxinttoolsexintfrac, junto comsiunitxpodemos responder a esta questão e, em particular, lidar com o "grande" exemplo de 18249
.
Código
\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}
Saída
Isso produz a saída desejada:
Alguma explicação
Dado que me esforcei um pouco para descobrir como usar os xint
comandos, algumas palavras de explicação são necessárias.
A macro
\xintiiDivision
retorna o quocienteeo restante e então\xintAssign
é usado para atribuir (essas duas saídas) às macros.O operador de comparação
\xintifLt
aceita números decimais, mas\xintiiifGt
é mais rápido e espera um número inteiro. Obrigado a @jfbu por explicar isso nos comentários.O
\num
comando desiunitxé usado para formatar a saída e, em particular, preencher com zeros à esquerda viaminimum-integer-digits=2
.
Muito possivelmente os xint
comandos podem ser usados de forma mais eficiente!
Responder3
Aqui está uma solução usando o pacote 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
EditarE segunda solução. Ele usaapenas primitivas TeXe \newcount
macro. E a ideia é a mesma acima. O tempo está limitado a 2 ^ 31 segundos agora, o que significa 68 anos.
\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
Responder4
(Atualizado o código Lua depois que o OP esclareceu quearredondamentoem vez de truncamento, deve ser aplicado aos segundos nos cálculos de duração.)
Aqui está uma solução baseada em LuaLaTeX. O preâmbulo configura uma função Lua chamada formatted_duration
e uma macro TeX chamada \fdur
. A macro TeX leva um argumento (a duração em segundos e frações de segundo), que passa para a função Lua para realizar os cálculos de hora-minuto-segundo e a reformatação necessária.
\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}