# Parsing String to Dates.CompoundPeriod

**URL:** <https://discourse.julialang.org/t/parsing-string-to-dates-compoundperiod/25484>\
**Category:** General Usage\
**Created:** [June 20, 2019, 1:46pm UTC](https://discourse.julialang.org/t/parsing-string-to-dates-compoundperiod/25484 "2019-06-20T13:46:28Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![scelles](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/scelles/32/220408_2.png) [@scelles](https://discourse.julialang.org/u/scelles)\
**Post date:** [June 20, 2019, 1:46pm UTC](https://discourse.julialang.org/t/parsing-string-to-dates-compoundperiod/25484/1 "2019-06-20T13:46:28Z")

</div>

Hello,

I have Strings representing duration like `"0.123"` (in seconds, with millisecond resolution), `"1:2.123"` (`minute:second.millisecond`), `"1:2:3.456"` (`hour:minute:second.millisecond`)  
I wonder if some functions to parse `String` to `Dates.CompoundPeriod` exists.

I did my own functions

```julia
using Test
using Dates
import Base: parse, tryparse

function parse(::Type{Second}, s)
    # s.xxxxxxxxx
    n = count(i->(i=='.'), s)
    if n == 0
        Second(s)
    elseif n == 1
        s, ns = split(s, ".")
        if length(ns) > 9
            throw(InexactError)
        else
            while(length(ns) < 9)
                ns = ns * "0"
            end
        end
        Second(s) + Millisecond(ns[1:3]) + Microsecond(ns[4:6]) + Nanosecond(ns[7:9])
    else
        throw(ArgumentError("invalid s.ms \"$s\""))
    end
end

function tryparse(::Type{Second}, s, args... ; kwargs...)
    try
        parse(Second, s, args... ; kwargs...)
    catch
        nothing
    end
end

@test parse(Second, "4") == Second(4)
@test parse(Second, "4.321") == Second(4) + Millisecond(321)
@test parse(Second, "4.3") == Second(4) + Millisecond(300)
@test parse(Second, "4.300") == Second(4) + Millisecond(300)
@test parse(Second, "4.003") == Second(4) + Millisecond(3)
@test parse(Second, "4.003002") == Second(4) + Millisecond(3) + Microsecond(2)
@test parse(Second, "4.003002001") == Second(4) + Millisecond(3) + Microsecond(2) + Nanosecond(1)
@test parse(Second, "0") == Second(0)
@test parse(Second, "0.0") == Second(0)
@test parse(Second, "0.0") == Dates.CompoundPeriod()

function parse(::Type{Dates.CompoundPeriod}, s::AbstractString)
    n = count(i->(i==':'), s)
    if n == 0
        parse(Second, s)
    elseif n == 1 # mm:ss
        mm, ss = split(s, ":")
        ss = parse(Second, ss)
        Minute(mm) + ss
    elseif n == 2 # hh:mm:ss
        hh, mm, ss = split(s, ":")
        ss = parse(Second, ss)
        Hour(hh) + Minute(mm) + ss
    else
        throw(ArgumentError("invalid hh:mm:ss \"$s\""))
    end
end

function tryparse(::Type{Dates.CompoundPeriod}, s, args... ; kwargs...)
    try
        parse(Dates.CompoundPeriod, s, args... ; kwargs...)
    catch
        nothing
    end
end

@test parse(Dates.CompoundPeriod, "4.321") == Second(4) + Millisecond(321)
@test parse(Dates.CompoundPeriod, "5:4.321") == Minute(5) + Second(4) + Millisecond(321)
@test parse(Dates.CompoundPeriod, "6:5:4.321") == Hour(6) + Minute(5) + Second(4) + Millisecond(321)
@test parse(Dates.CompoundPeriod, "0") == Dates.CompoundPeriod()

```

but I can’t imagine that it doesn’t exist (better written) in either Base or in an existing package.

I’m not looking for parsing as `Dates.Time` (because hour can exceed 24).

Any idea?

Kind regards

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [June 21, 2019, 10:23am UTC](https://discourse.julialang.org/t/parsing-string-to-dates-compoundperiod/25484/2 "2019-06-21T10:23:24Z")

</div>

> [@scelles](#):
>
> I can’t imagine that it doesn’t exist (better written) in either Base or in an existing package.

There may not be a parse function if the format is not considered standard. Also, defining a method for a type you don’t own is [type piracy](https://docs.julialang.org/en/v1/manual/style-guide/#Avoid-type-piracy-1). It is perfectly fine though to define your own type, eg `CustomCompundParser`, and define a `tryparse` for that.

---

<div class="post-metadata">

**Author:** ![cchderrick](https://avatars.discourse-cdn.com/v4/letter/c/ecd19e/32.png) [@cchderrick](https://discourse.julialang.org/u/cchderrick)\
**Post date:** [June 21, 2019, 1:35pm UTC](https://discourse.julialang.org/t/parsing-string-to-dates-compoundperiod/25484/3 "2019-06-21T13:35:17Z")

</div>

Looks like you have some sort of definition of the string format, when you have the prior knowledge of the input format, you could use regular expression.

Made some quick pattern using [https://regex101.com/](https://regex101.com/), this should get you pretty close. (no guarantee)

```julia
function parse_date(s::AbstractString)
    HHMMSS = r"(((\d*):)?(\d*):)?((\d+)(\.(\d+))?)"
    regex = match(HHMMSS, s)
    
    HH = regex.captures[3] != nothing ? tryparse(Int, regex.captures[3]) : 0
    MM = regex.captures[4] != nothing ? tryparse(Int, regex.captures[4]) : 0
    SS = tryparse(Int,regex.captures[6]) # it should always have something?? idk
    ss = regex.captures[8] != nothing ? tryparse(Int, regex.captures[8]) : 0

    return Hour(HH) + Minute(MM) + Second(SS) + Millisecond(ss)
end

```

One thing about type piracy, I think you can define this special time format as a custom type, like `HHMMSSssString`, then define

```julia
parse(::Type{Dates.CompoundPeriod},s::HHMMSSssString)

```

Technically only you know what this format looks like, and how to parse it into compound period, so

```julia
parse(::Type{Dates.CompoundPeriod}, s::AbstractString)

```

is a bit too greedy.

---

<div class="post-metadata">

**Author:** ![scelles](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/scelles/32/220408_2.png) [@scelles](https://discourse.julialang.org/u/scelles)\
**Post date:** [June 21, 2019, 1:49pm UTC](https://discourse.julialang.org/t/parsing-string-to-dates-compoundperiod/25484/4 "2019-06-21T13:49:18Z")

</div>

Thanks @Tamas_Papp and @cchderrick for your warning about type piracy to avoid to be to “greedy” with type.

@cchderrick thanks for the regex but I don’t think your code is correct

```
6:5:4.32 

```

I haven’t test it now… but I think `32` will become 32 millisecond (instead of 320)

---

<div class="post-metadata">

**Author:** ![cchderrick](https://avatars.discourse-cdn.com/v4/letter/c/ecd19e/32.png) [@cchderrick](https://discourse.julialang.org/u/cchderrick)\
**Post date:** [June 21, 2019, 2:00pm UTC](https://discourse.julialang.org/t/parsing-string-to-dates-compoundperiod/25484/5 "2019-06-21T14:00:43Z")

</div>

You are correct that my code doesn’t work. I was a bit hasty. It looks like it have to do some of your logic on the `ss` string.

```julia
        if length(ns) > 9
            throw(InexactError)
        else
            while(length(ns) < 9)
                ns = ns * "0"
            end
        end

```

and you can probably use `rpad`

```julia
julia> rpad("3",9,"0")
"300000000"

```

or just numerical round

```julia
julia> round(Int,0.3*1000)
300

julia> round(Int,0.32*1000)
320

julia> round(Int,0.321*1000)
321

```

---

<div class="post-metadata">

**Author:** ![scelles](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/scelles/32/220408_2.png) [@scelles](https://discourse.julialang.org/u/scelles)\
**Post date:** [June 21, 2019, 2:09pm UTC](https://discourse.julialang.org/t/parsing-string-to-dates-compoundperiod/25484/6 "2019-06-21T14:09:35Z")

</div>

Using `rpad` is a good idea

but I noticed that using `CompoundPeriod` for storing duration is in fact a bad idea

```julia
julia> Dates.Minute(1) + Dates.Second(1) > Dates.Minute(1)
ERROR: MethodError: no method matching isless(::Minute, ::Dates.CompoundPeriod)
Closest candidates are:
  isless(::Missing, ::Any) at missing.jl:70
  isless(::Union{Day, Hour, Microsecond, Millisecond, Minute, Nanosecond, Second, Week}, ::Union{Month, Year}) at C:\cygwin\home\Administrator\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.1\Dates\src\periods.jl:444
  isless(::P<:Period, ::P<:Period) where P<:Period at C:\cygwin\home\Administrator\buildbot\worker\package_win64\build\usr\share\julia\stdlib\v1.1\Dates\src\periods.jl:70
  ...
Stacktrace:
 [1] <(::Minute, ::Dates.CompoundPeriod) at .\operators.jl:260
 [2] >(::Dates.CompoundPeriod, ::Minute) at .\operators.jl:286
 [3] top-level scope at none:0

```

because I want to compare duration.

What is the Julia equivalent to Python `datetime.timedelta` (to be able to compare duration?)

---

<div class="post-metadata">

**Author:** ![cchderrick](https://avatars.discourse-cdn.com/v4/letter/c/ecd19e/32.png) [@cchderrick](https://discourse.julialang.org/u/cchderrick)\
**Post date:** [June 21, 2019, 3:06pm UTC](https://discourse.julialang.org/t/parsing-string-to-dates-compoundperiod/25484/7 "2019-06-21T15:06:47Z")

</div>

hmm… interesting. I would also have thought it is implemented, given `+`, and `-` are already implemented.

Well, here is my attempt, using differences.

```julia
function duration_gt(x::Dates.CompoundPeriod, y::Dates.CompoundPeriod)
    d = y-x
    # probably bad assumption that the first period is always 
    # the lower time resolution and canonicalized?
    first(d.periods).value < 0 
end
duration_gt(x::Dates.CompoundPeriod, y::Dates.Period) = duration_gt(x,Dates.CompoundPeriod(y))
duration_gt(x::Dates.Period, y::Dates.CompoundPeriod) = duration_gt(Dates.CompoundPeriod(x),y)
duration_gt(x::Dates.Period, y::Dates.Period) = duration_gt(Dates.CompoundPeriod(x),Dates.CompoundPeriod(y))

```

Some quick test cases:

```julia
julia> W1D1 = Week(1)+Day(1)
1 week, 1 day

julia> S2 = Second(2)
2 seconds

julia> MS300 = Microsecond(300)
300 microseconds

julia> duration_gt(W1D1,S2)
true

julia> duration_gt(S2,W1D1)
false

julia> duration_gt(S2,MS300)
true

julia> duration_gt(MS300,S2)
false

```

I think it is not implemented, because `Year` and `Month` are not fixed duration, it doesn’t work with CompoundPeriond with `Month` or `Year` in it.

---

<div class="post-metadata">

**Author:** ![scelles](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/scelles/32/220408_2.png) [@scelles](https://discourse.julialang.org/u/scelles)\
**Post date:** [June 21, 2019, 4:42pm UTC](https://discourse.julialang.org/t/parsing-string-to-dates-compoundperiod/25484/8 "2019-06-21T16:42:51Z")

</div>

In fact to allow such comparison we must assume that we are comparing “fixed” `CompoundPeriod`.

I don’t know if such function exist in `Base` but I would define something like:

```julia
function isfixed(x::Dates.CompoundPeriod)
   typ = map(typeof, x.periods)
   !(Year in typ || Month in typ)
end

```

and will add something like

```
@assert(isfixed(x), "x must be a fixed CompoundPeriod")

```

and

```
@assert(isfixed(y), "y must be a fixed CompoundPeriod")

```

---

<div class="post-metadata">

**Author:** ![scelles](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/scelles/32/220408_2.png) [@scelles](https://discourse.julialang.org/u/scelles)\
**Post date:** [June 21, 2019, 4:54pm UTC](https://discourse.julialang.org/t/parsing-string-to-dates-compoundperiod/25484/9 "2019-06-21T16:54:18Z")

</div>

Issue about implementing `CompoundPeriod` comparison opened

[https://github.com/JuliaLang/julia/issues/32389](https://github.com/JuliaLang/julia/issues/32389)

---

<div class="post-metadata">

**Author:** ![kevbonham](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kevbonham/32/216165_2.png) [@kevbonham](https://discourse.julialang.org/u/kevbonham)\
**Post date:** [June 21, 2019, 5:55pm UTC](https://discourse.julialang.org/t/parsing-string-to-dates-compoundperiod/25484/10 "2019-06-21T17:55:28Z")

</div>

> [@Tamas\_Papp](#):
>
> Also, defining a method for a type you don’t own is [type piracy](https://docs.julialang.org/en/v1/manual/style-guide/#Avoid-type-piracy-1). It is perfectly fine though to define your own type, eg `CustomCompundParser` , and define a `tryparse` for that

It’s also fine if you’re not putting something in a package intended for use by others, right? In my data analysis projects I often commit type piracy, but I don’t ever expect someone to `using MyProject`.

---

<div class="post-metadata">

**Author:** ![cchderrick](https://avatars.discourse-cdn.com/v4/letter/c/ecd19e/32.png) [@cchderrick](https://discourse.julialang.org/u/cchderrick)\
**Post date:** [June 21, 2019, 5:59pm UTC](https://discourse.julialang.org/t/parsing-string-to-dates-compoundperiod/25484/11 "2019-06-21T17:59:45Z")

</div>

Looks like FixedPeriod is defined:  
[https://github.com/JuliaLang/julia/blob/master/stdlib/Dates/src/periods.jl#L382](https://github.com/JuliaLang/julia/blob/master/stdlib/Dates/src/periods.jl#L382)

```julia-repl
julia> Dates.FixedPeriod
Union{Day, Hour, Microsecond, Millisecond, Minute, Nanosecond, Second, Week}

```

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [June 22, 2019, 4:32am UTC](https://discourse.julialang.org/t/parsing-string-to-dates-compoundperiod/25484/12 "2019-06-22T04:32:45Z")

</div>

> [@kevbonham](#):
>
> In my data analysis projects I often commit type piracy, but I don’t ever expect someone to `using MyProject` .

Type piracy can also have unintended consequences if it diverts dispatch of existing methods. It can, of course, be perfectly fine under the right conditions, but I think it is a good habit to reserve it for cases where it is definitely needed.

If you need it often, that may be a code smell.
