Skip to content

Commit

Permalink
Backport period rounding (#462)
Browse files Browse the repository at this point in the history
* Backport period rounding.

* Added link to bottom of README.
  • Loading branch information
rofinn authored and omus committed Jan 29, 2018
1 parent 169abdd commit ace032f
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 0 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,9 @@ 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


Expand Down Expand Up @@ -495,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
119 changes: 119 additions & 0 deletions src/Compat.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
73 changes: 73 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit ace032f

Please sign in to comment.