From 145977f00dfa0059c5f76b30befc31e515b7e877 Mon Sep 17 00:00:00 2001 From: Rory Finnegan Date: Thu, 18 Jan 2018 20:33:27 -0600 Subject: [PATCH 1/2] Backport period rounding. --- README.md | 2 + src/Compat.jl | 119 +++++++++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 73 +++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+) diff --git a/README.md b/README.md index f2dba8404..f81838f3a 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,8 @@ Currently, the `@compat` macro supports the following syntaxes: * `codeunits(s)` returns an array-like view of the `UInt8` code units of a string and `ncodeunits(s)` returns the number of code units ([#25241]). +* `Dates.Period` rounding (e.g., `round(Dates.Hour(36), Dates.Day, RoundNearestTiesUp) == Dates.Day(2)` ([##24182]). + ## Renaming diff --git a/src/Compat.jl b/src/Compat.jl index 2b110b2c4..872afbdb5 100644 --- a/src/Compat.jl +++ b/src/Compat.jl @@ -776,6 +776,125 @@ else import Libdl end +# https://github.com/JuliaLang/julia/pull/24182 +if VERSION < v"0.7.0-DEV.2402" + const ConvertiblePeriod = Union{Compat.Dates.TimePeriod, Compat.Dates.Week, Compat.Dates.Day} + const TimeTypeOrPeriod = Union{Compat.Dates.TimeType, Compat.ConvertiblePeriod} + + """ + floor(x::Period, precision::T) where T <: Union{TimePeriod, Week, Day} -> T + + Rounds `x` down to the nearest multiple of `precision`. If `x` and `precision` are different + subtypes of `Period`, the return value will have the same type as `precision`. + + For convenience, `precision` may be a type instead of a value: `floor(x, Dates.Hour)` is a + shortcut for `floor(x, Dates.Hour(1))`. + + ```jldoctest + julia> floor(Dates.Day(16), Dates.Week) + 2 weeks + + julia> floor(Dates.Minute(44), Dates.Minute(15)) + 30 minutes + + julia> floor(Dates.Hour(36), Dates.Day) + 1 day + ``` + + Rounding to a `precision` of `Month`s or `Year`s is not supported, as these `Period`s are of + inconsistent length. + """ + function Base.floor(x::Compat.ConvertiblePeriod, precision::T) where T <: Compat.ConvertiblePeriod + Compat.Dates.value(precision) < 1 && throw(DomainError(precision)) + _x, _precision = promote(x, precision) + return T(_x - mod(_x, _precision)) + end + + """ + ceil(x::Period, precision::T) where T <: Union{TimePeriod, Week, Day} -> T + + Rounds `x` up to the nearest multiple of `precision`. If `x` and `precision` are different + subtypes of `Period`, the return value will have the same type as `precision`. + + For convenience, `precision` may be a type instead of a value: `ceil(x, Dates.Hour)` is a + shortcut for `ceil(x, Dates.Hour(1))`. + + ```jldoctest + julia> ceil(Dates.Day(16), Dates.Week) + 3 weeks + + julia> ceil(Dates.Minute(44), Dates.Minute(15)) + 45 minutes + + julia> ceil(Dates.Hour(36), Dates.Day) + 3 days + ``` + + Rounding to a `precision` of `Month`s or `Year`s is not supported, as these `Period`s are of + inconsistent length. + """ + function Base.ceil(x::Compat.ConvertiblePeriod, precision::Compat.ConvertiblePeriod) + f = floor(x, precision) + return (x == f) ? f : f + precision + end + + """ + floorceil(x::Period, precision::T) where T <: Union{TimePeriod, Week, Day} -> (T, T) + + Simultaneously return the `floor` and `ceil` of `Period` at resolution `p`. More efficient + than calling both `floor` and `ceil` individually. + """ + function floorceil(x::Compat.ConvertiblePeriod, precision::Compat.ConvertiblePeriod) + f = floor(x, precision) + return f, (x == f) ? f : f + precision + end + + """ + round(x::Period, precision::T, [r::RoundingMode]) where T <: Union{TimePeriod, Week, Day} -> T + + Rounds `x` to the nearest multiple of `precision`. If `x` and `precision` are different + subtypes of `Period`, the return value will have the same type as `precision`. By default + (`RoundNearestTiesUp`), ties (e.g., rounding 90 minutes to the nearest hour) will be rounded + up. + + For convenience, `precision` may be a type instead of a value: `round(x, Dates.Hour)` is a + shortcut for `round(x, Dates.Hour(1))`. + + ```jldoctest + julia> round(Dates.Day(16), Dates.Week) + 2 weeks + + julia> round(Dates.Minute(44), Dates.Minute(15)) + 45 minutes + + julia> round(Dates.Hour(36), Dates.Day) + 3 days + ``` + + Valid rounding modes for `round(::Period, ::T, ::RoundingMode)` are `RoundNearestTiesUp` + (default), `RoundDown` (`floor`), and `RoundUp` (`ceil`). + + Rounding to a `precision` of `Month`s or `Year`s is not supported, as these `Period`s are of + inconsistent length. + """ + function Base.round(x::Compat.ConvertiblePeriod, precision::Compat.ConvertiblePeriod, r::RoundingMode{:NearestTiesUp}) + f, c = floorceil(x, precision) + _x, _f, _c = promote(x, f, c) + return (_x - _f) < (_c - _x) ? f : c + end + + Base.round(x::Compat.TimeTypeOrPeriod, p::Compat.Dates.Period, r::RoundingMode{:Down}) = Base.floor(x, p) + Base.round(x::Compat.TimeTypeOrPeriod, p::Compat.Dates.Period, r::RoundingMode{:Up}) = Base.ceil(x, p) + + Base.round(::Compat.TimeTypeOrPeriod, p::Compat.Dates.Period, ::RoundingMode) = throw(DomainError(p)) + Base.round(x::Compat.TimeTypeOrPeriod, p::Compat.Dates.Period) = Base.round(x, p, RoundNearestTiesUp) + Base.floor(x::Compat.TimeTypeOrPeriod, ::Type{P}) where P <: Compat.Dates.Period = Base.floor(x, oneunit(P)) + Base.ceil(x::Compat.TimeTypeOrPeriod, ::Type{P}) where P <: Compat.Dates.Period = Base.ceil(x, oneunit(P)) + function Base.round(x::Compat.TimeTypeOrPeriod, ::Type{P}, r::RoundingMode=RoundNearestTiesUp) where P <: Compat.Dates.Period + return Base.round(x, oneunit(P), r) + end +end + if VERSION < v"0.7.0-DEV.3216" const AbstractDateTime = Compat.Dates.TimeType else diff --git a/test/runtests.jl b/test/runtests.jl index 494d86332..7c5e318c5 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1111,6 +1111,79 @@ end @test Compat.AbstractDateTime <: Compat.Dates.TimeType @test Compat.Dates.DateTime <: Compat.AbstractDateTime +# 0.7.0-DEV.2402 + +x = Compat.Dates.Second(172799) +@test floor(x, Compat.Dates.Week) == Compat.Dates.Week(0) +@test floor(x, Compat.Dates.Day) == Compat.Dates.Day(1) +@test floor(x, Compat.Dates.Hour) == Compat.Dates.Hour(47) +@test floor(x, Compat.Dates.Minute) == Compat.Dates.Minute(2879) +@test floor(x, Compat.Dates.Second) == Compat.Dates.Second(172799) +@test floor(x, Compat.Dates.Millisecond) == Compat.Dates.Millisecond(172799000) +@test ceil(x, Compat.Dates.Week) == Compat.Dates.Week(1) +@test ceil(x, Compat.Dates.Day) == Compat.Dates.Day(2) +@test ceil(x, Compat.Dates.Hour) == Compat.Dates.Hour(48) +@test ceil(x, Compat.Dates.Minute) == Compat.Dates.Minute(2880) +@test ceil(x, Compat.Dates.Second) == Compat.Dates.Second(172799) +@test ceil(x, Compat.Dates.Millisecond) == Compat.Dates.Millisecond(172799000) +@test round(x, Compat.Dates.Week) == Compat.Dates.Week(0) +@test round(x, Compat.Dates.Day) == Compat.Dates.Day(2) +@test round(x, Compat.Dates.Hour) == Compat.Dates.Hour(48) +@test round(x, Compat.Dates.Minute) == Compat.Dates.Minute(2880) +@test round(x, Compat.Dates.Second) == Compat.Dates.Second(172799) +@test round(x, Compat.Dates.Millisecond) == Compat.Dates.Millisecond(172799000) + +x = Dates.Nanosecond(2000999999) +@test floor(x, Compat.Dates.Second) == Compat.Dates.Second(2) +@test floor(x, Compat.Dates.Millisecond) == Compat.Dates.Millisecond(2000) +@test floor(x, Compat.Dates.Microsecond) == Compat.Dates.Microsecond(2000999) +@test floor(x, Compat.Dates.Nanosecond) == x +@test ceil(x, Compat.Dates.Second) == Compat.Dates.Second(3) +@test ceil(x, Compat.Dates.Millisecond) == Compat.Dates.Millisecond(2001) +@test ceil(x, Compat.Dates.Microsecond) == Compat.Dates.Microsecond(2001000) +@test ceil(x, Compat.Dates.Nanosecond) == x +@test round(x, Compat.Dates.Second) == Compat.Dates.Second(2) +@test round(x, Compat.Dates.Millisecond) == Compat.Dates.Millisecond(2001) +@test round(x, Compat.Dates.Microsecond) == Compat.Dates.Microsecond(2001000) +@test round(x, Compat.Dates.Nanosecond) == x + + +for x in [Compat.Dates.Week(3), Compat.Dates.Day(14), Compat.Dates.Second(604800)] + local x + for p in [Compat.Dates.Week, Compat.Dates.Day, Compat.Dates.Hour, Compat.Dates.Second, Compat.Dates.Millisecond, Compat.Dates.Microsecond, Compat.Dates.Nanosecond] + local p + @test floor(x, p) == p(x) + @test ceil(x, p) == p(x) + end +end + +x = Compat.Dates.Hour(36) +@test round(x, Compat.Dates.Day, RoundNearestTiesUp) == Compat.Dates.Day(2) +@test round(x, Compat.Dates.Day, RoundUp) == Compat.Dates.Day(2) +@test round(x, Compat.Dates.Day, RoundDown) == Compat.Dates.Day(1) +@test_throws DomainError round(x, Compat.Dates.Day, RoundNearest) +@test_throws DomainError round(x, Compat.Dates.Day, RoundNearestTiesAway) +@test_throws DomainError round(x, Compat.Dates.Day, RoundToZero) +@test round(x, Dates.Day) == round(x, Compat.Dates.Day, RoundNearestTiesUp) + +x = Compat.Dates.Hour(86399) +for p in [Compat.Dates.Week, Compat.Dates.Day, Compat.Dates.Hour, Compat.Dates.Second, Compat.Dates.Millisecond, Compat.Dates.Microsecond, Compat.Dates.Nanosecond] + local p + for v in [-1, 0] + @test_throws DomainError floor(x, p(v)) + @test_throws DomainError ceil(x, p(v)) + @test_throws DomainError round(x, p(v)) + end +end +for p in [Compat.Dates.Year, Compat.Dates.Month] + local p + for v in [-1, 0, 1] + @test_throws MethodError floor(x, p(v)) + @test_throws MethodError ceil(x, p(v)) + @test_throws DomainError round(x, p(v)) + end +end + # 0.7.0-DEV.3025 let c = CartesianIndices(1:3, 1:2), l = LinearIndices(1:3, 1:2) @test LinearIndices(c) == collect(l) From c686c298f57e58ccbd829764ee7679aff6a89c70 Mon Sep 17 00:00:00 2001 From: rofinn Date: Thu, 25 Jan 2018 15:06:33 -0600 Subject: [PATCH 2/2] Added link to bottom of README. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f81838f3a..879be7a28 100644 --- a/README.md +++ b/README.md @@ -245,7 +245,8 @@ Currently, the `@compat` macro supports the following syntaxes: * `codeunits(s)` returns an array-like view of the `UInt8` code units of a string and `ncodeunits(s)` returns the number of code units ([#25241]). -* `Dates.Period` rounding (e.g., `round(Dates.Hour(36), Dates.Day, RoundNearestTiesUp) == Dates.Day(2)` ([##24182]). + +* `Dates.Period` rounding (e.g., `round(Dates.Hour(36), Dates.Day, RoundNearestTiesUp) == Dates.Day(2)` ([#24182]). ## Renaming @@ -497,3 +498,4 @@ includes this fix. Find the minimum version from there. [#25571]: https://github.com/JuliaLang/julia/issues/25571 [#25629]: https://github.com/JuliaLang/julia/issues/25629 [#25654]: https://github.com/JuliaLang/julia/issues/25654 +[#24182]: https://github.com/JuliaLang/julia/issues/24182