%20en%20forma%20legible%20por%20humanos.png)
Pasé 3 horas intentando escribir un comando para formatear un tiempo (duración) expresado como un número flotante (en segundos) de la siguiente manera:
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)
Entonces 1.25678 da 1.256, 125.35 da 2:05, 18249 (que equivale a 5*3600+4*60+9) da 5:04:09.
Probé varios paquetes para comparar/formatear. Sin éxito. Alguien me puede ayudar ? Gracias !
Respuesta1
Puedes hacerlo con expl3
y su 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}
Si es necesario, aquí hay una versión totalmente ampliable:
\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}
Respuesta2
El uso \pgfmathparse
es problemático para esta pregunta porque la división de enteros falla con un error de "Dimensión demasiado grande" alrededor de 16500. En particular, sin un cuidado adicional, cualquier solución que utilice \pgfmathparse
no podrá hacer frente al ejemplo del OP 18249
porque este número es demasiado grande.
Para cálculos grandes (enteros) elxintEsta familia de paquetes es difícil de superar (desafortunadamente, encuentro difícil seguir su documentación porque se dan pocos ejemplos). Usandoherramientas xintyxintfrac, Juntos consiunitxPodemos responder a esta pregunta y, en particular, afrontar el ejemplo "grande" 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}
Producción
Esto produce el resultado deseado:
Alguna explicación
Dado que me costó un poco descubrir cómo usar los xint
comandos, son necesarias unas pocas palabras de explicación.
La macro
\xintiiDivision
devuelve el cociente.yel resto y luego\xintAssign
se usa para asignar (estas dos salidas) a macros.El operador de comparación
\xintifLt
acepta números decimales, mientras que\xintiiifGt
es más rápido y espera un número entero. Gracias a @jfbu por explicar esto en los comentarios.El
\num
comando desiunitxse utiliza para formatear la salida y, en particular, rellenar con ceros a la izquierda medianteminimum-integer-digits=2
.
¡Es muy posible que los xint
comandos se puedan usar de manera más eficiente!
Respuesta3
Aquí hay una solución usando el paquete 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
EditarY segunda solución. Usasólo primitivas TeXy \newcount
macro. Y la idea es la misma que la anterior. El tiempo ahora está limitado a 2^31 segundos, lo que significa 68 años.
\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
Respuesta4
(Se actualizó el código Lua después de que el OP aclarara queredondeoen lugar de truncamiento se debe aplicar a los segundos en los cálculos de duración).
Aquí hay una solución basada en LuaLaTeX. El preámbulo configura tanto una función Lua llamada formatted_duration
como una macro TeX llamada \fdur
. La macro TeX toma un argumento (la duración en segundos y fracciones de segundo), que pasa a la función Lua para realizar los cálculos de hora-minuto-segundo y el reformateo requerido.
\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}