How to Print/Parse a TimePeriod with a Specific Format?

Hi!

I want to parse and print durations in the format "HH:MM:SS.sss". Parsing seems to be pretty simple. if I do for example

julia> str = "1:02:23.234"
"1:02:23.234"
julia> t = Time(str)
01:02:23.234

this seems to work just fine. However, this is not a TimePeriod but a time.

If I want to go the other way and print TimePeriods in that format I’m running into problems:

julia> Dates.format(Time(Second(32)), "HH:MM:SS.sss")
"00:00:32.000"

works fine but

julia> Dates.format(Time(Second(432)), "HH:MM:SS.sss")
ERROR: ArgumentError: Second: 432 out of range (0:59)

throws an error. Dates.format does not take TimePeriods for an argument:

julia> Dates.format(Second(432), "HH:MM:SS.sss")
ERROR: MethodError: no method matching format(::Second, ::String)

Working with CompoundPeriods also does not work:

julia> d = Minute(2) + Second(10)
2 minutes, 10 seconds

julia> Dates.format(Time(d), "HH:MM:SS.sss")
ERROR: MethodError: no method matching Int64(::Dates.CompoundPeriod)

Does anyone know if there’s a built in way to print my duration in the desired format, is there any package that deals with this, or do I have to write a custom function?

You can use Time(0) + Second(…) instead of Time(Second(…)):

julia> Time(0) + Second(432)
00:07:12

julia> Dates.format(Time(0) + Second(432), "HH:MM:SS.sss")
"00:07:12.000"
1 Like

That’s it! Thanks! :slight_smile:

Actually, using the CompoundPeriods package it does:

using Dates, CompoundPeriods
d = Minute(2) + Second(10)
Dates.format(Time(d), "HH:MM:SS.sss")
"00:02:10.000"

There is an easier (more elegant?) solution.

  • You have to know that only Time, Date and DateTime types are printable (aka formatable)

Once you know this, you convert to one of these types. In your case, you want to convert to a Time type using the constructor function Dates.Time(...).

So do something like this

using Dates
d = Minute(2) + Second(10)
println(typeof(d)) # Dates.CompoundPeriod

time  = Dates.Time(canonicalize(Dates.CompoundPeriod(d)).periods...)
time_str::String = Dates.format(time, "HH:MM:SS")
println("$(time_str)")

The “special sauce” here is twofold

  • canonicalize converts any CompoundPeriod to the canonical form, the trick here being to created it from the splayed .periods field (this is what the d.periods... is about
  • you can then create a Time object from this CompountPeriod object

This doesn’t work for periods longer than a day:

julia> using Dates

julia> myformat(sec::Dates.Second, fmt::AbstractString) = Dates.Time(Dates.canonicalize(sec).periods...);

julia> myformat(Second(500), "HH:MM:SS")
00:08:20

julia> myformat(Second(5000), "HH:MM:SS")
01:23:20

julia> myformat(Second(50000), "HH:MM:SS")
13:53:20

julia> myformat(Second(500000), "HH:MM:SS")
ERROR: MethodError: no method matching Int64(::Day)
The type `Int64` exists, but no method is defined for this combination of argument types when trying to construct it.

Closest candidates are:
  Int64(::Float64)
   @ Base float.jl:919
  Int64(::Float32)
   @ Base float.jl:919
  Int64(::Float16)
   @ Base float.jl:919
  ...

Stacktrace:
 [1] Time(h::Day, mi::Hour, s::Minute, ms::Second, us::Int64, ns::Int64, ampm::Dates.AMPM)
   @ Dates ~/.julia/juliaup/julia-1.12.4+0.aarch64.apple.darwin14/share/julia/stdlib/v1.12/Dates/src/types.jl:423
 [2] myformat(sec::Second, fmt::String)
   @ Main ./REPL[2]:1
 [3] top-level scope
   @ REPL[6]:1

Meanwhile Python has no problem printing large time periods:

>>> from datetime import timedelta
>>> str(timedelta(seconds=800000000.9))
'9259 days, 6:13:20.900000'

(However, “9259 days” makes no sense; at this point it should be split into weeks, months and years)


Using nanoseconds doesn’t work either:

julia> using Dates

julia> begin
               myformat(period::Dates.TimePeriod, fmt::AbstractString) =
                       Dates.Time(Dates.canonicalize(period).periods...)
               myformat(sec::Real, fmt::AbstractString) =
                       myformat(Dates.Nanosecond(round(Int, sec * 1e9)), fmt)
       end;

julia> myformat(100306.25350200786, "HH:MM:SS")
ERROR: MethodError: no method matching Time(::Day, ::Hour, ::Minute, ::Second, ::Millisecond, ::Microsecond, ::Nanosecond)
The type `Time` exists, but no method is defined for this combination of argument types when trying to construct it.

Closest candidates are:
  Time(::TimePeriod, ::TimePeriod...)
   @ Dates ~/.julia/juliaup/julia-1.12.4+0.aarch64.apple.darwin14/share/julia/stdlib/v1.12/Dates/src/types.jl:378
  Time(::Any, ::Any, ::Any, ::Any, ::Any, ::Any)
   @ Dates ~/.julia/juliaup/julia-1.12.4+0.aarch64.apple.darwin14/share/julia/stdlib/v1.12/Dates/src/types.jl:423
  Time(::Any, ::Any, ::Any, ::Any, ::Any, ::Any, ::Dates.AMPM)
   @ Dates ~/.julia/juliaup/julia-1.12.4+0.aarch64.apple.darwin14/share/julia/stdlib/v1.12/Dates/src/types.jl:423
  ...

Stacktrace:
 [1] myformat
   @ ./REPL[3]:2 [inlined]
 [2] myformat(sec::Float64, fmt::String)
   @ Main ./REPL[3]:4
 [3] top-level scope
   @ REPL[13]:1

Apparently Day is not a TimePeriod:

julia> Day <: TimePeriod
false

…which is why Time(period::TimePeriod, periods::TimePeriod...) is not called. Instead, Time(h, mi=0, s=0, ms=0, us=0, ns=0, ampm::AMPM=TWENTYFOURHOUR) is called.

If Day is not a TimePeriod, what is it, then??? Same for Week, Month, etc: are they not time periods?

julia> supertypes(Day)

(Day, DatePeriod, Period, Dates.AbstractTime, Any)

julia> subtypes(Dates.AbstractTime)
5-element Vector{Any}:
 Dates.Calendar
 Dates.CompoundPeriod
 Dates.Instant
 Period
 TimeType

julia> subtypes(Period)
2-element Vector{Any}:
 DatePeriod
 TimePeriod

julia> supertypes(Week)
(Week, DatePeriod, Period, Dates.AbstractTime, Any)

julia> supertypes(Month)
(Month, DatePeriod, Period, Dates.AbstractTime, Any)

I know, those were rhetorical questions. I was trying to say that all of them are time periods.

What I don’t understand is two things:

  1. Why is there “no method matching format(::Dates.CompoundPeriod, ::String)”? Why can I not format CompoundPeriods? The default string representation is too long, like 82673 weeks, 4 days, 2 hours, 56 minutes, 20 seconds (why does it not go further, to months and years? I cannot immediately comprehend how long 82673 weeks is)
  2. Why is it not possible to have fractional seconds/minutes/etc? Second(1.234) gives InexactError: Int64(1.234). Sure, but now I have to go convert this to nanoseconds, round that to an integer, then call canonicalize, only to face the inability to have custom formats for Dates.CompoundPeriod

Because months and years are variable length, and it does not have the context of the absolute date to give an accurate count of months or years.

2 Likes

This was a design decision that was made early on, which is somewhat controversial but is difficult to change. See the long discussion in `DateTime` arithmetic on `Microsecond` (or smaller) scale

1 Like