-
Notifications
You must be signed in to change notification settings - Fork 111
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add support for Dates.FixedPeriod * Fix tests on Julia < 1.2 * Fix more tests * Delete unused method * Add some more tests * Fix tests on 32-bit master * Add three-argument div * Add support for CompoundPeriod * Fix tests on 32bit * Add Quantity(::FixedPeriod) docstring * Fix some tests * Fix a test * Add documentation page
- Loading branch information
Showing
8 changed files
with
1,121 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
```@meta | ||
DocTestSetup = quote | ||
using Unitful | ||
end | ||
``` | ||
# Interoperability with the `Dates` standard library | ||
|
||
[Julia's `Dates` standard library](https://docs.julialang.org/en/v1/stdlib/Dates/) provides data types for representing specific points in time `Date`/`DateTime` and differences between them, i.e., periods. Unitful provides methods for using period types from the `Dates` standard library together with `Quantity`s. | ||
|
||
## Support for `Dates.FixedPeriod`s | ||
|
||
The `Dates.FixedPeriod` union type includes all `Dates.Period`s that represent a fixed period of time, i.e., `Dates.Week`, `Dates.Day`, `Dates.Hour`, `Dates.Minute`, `Dates.Second`, `Dates.Millisecond`, `Dates.Microsecond`, and `Dates.Nanosecond`. These types can be converted to `Quantity`s or used in place of them. | ||
|
||
!!! note | ||
`Dates.Year` does not represent a fixed period and cannot be converted to a `Quantity`. While Unitful's `yr` unit is exactly equal to 365.25 days, a `Dates.Year` may contain 365 or 366 days. | ||
|
||
Each `FixedPeriod` is considered equivalent to a `Quantity`. For example, `Dates.Millisecond(5)` corresponds to the quantity `Int64(5)*u"ms"`. A `FixedPeriod` can be converted to the equivalent `Quantity` with a constructor: | ||
|
||
```@docs | ||
Unitful.Quantity(::Dates.FixedPeriod) | ||
``` | ||
|
||
In most respects, `FixedPeriod`s behave like their equivalent quantities. They can be converted to other units using `uconvert`, used in arithmetic operations with other quantities, and they have a `unit` and `dimension`: | ||
|
||
```jldoctest | ||
julia> using Dates: Hour | ||
julia> p = Hour(3) | ||
3 hours | ||
julia> uconvert(u"s", p) | ||
10800 s | ||
julia> p == 180u"minute" | ||
true | ||
julia> p < 1u"d" | ||
true | ||
julia> 5u"s" + p | ||
10805 s | ||
julia> 210u"km" / p | ||
70.0 km hr^-1 | ||
julia> unit(p) === u"hr" | ||
true | ||
julia> dimension(p) | ||
𝐓 | ||
``` | ||
|
||
Conversely, a `FixedPeriod` can be created from a quantity using the appropriate constructor, `convert`, or `round` methods. This will fail (i.e., throw an `InexactError`) if the resulting value cannot be represented as an `Int64`: | ||
|
||
```jldoctest | ||
julia> using Dates: Day, Hour, Millisecond | ||
julia> Millisecond(1.5u"s") | ||
1500 milliseconds | ||
julia> convert(Hour, 1u"yr") | ||
8766 hours | ||
julia> Day(1u"yr") | ||
ERROR: InexactError: Int64(1461//4) | ||
[...] | ||
julia> round(Day, 1u"yr") | ||
365 days | ||
``` | ||
|
||
## Support for `Dates.CompoundPeriod`s | ||
|
||
The `Dates` standard library provides the `Dates.CompoundPeriod` type to represent sums of periods of different types: | ||
|
||
```@repl | ||
using Dates: Day, Second | ||
Day(5) + Second(1) | ||
typeof(ans) | ||
``` | ||
|
||
Unitful provides facilities to work with `CompoundPeriod`s as long as they consist only of `FixedPeriod`s. Such `CompoundPeriod`s can be converted to `Quantity`s using `convert`, `uconvert`, or `round`: | ||
|
||
```@jldoctest | ||
julia> using Dates: Day, Second | ||
julia> p = Day(5) + Second(1) | ||
5 days, 1 second | ||
julia> uconvert(u"s", p) | ||
432001//1 s | ||
julia> convert(typeof(1.0u"yr"), p) | ||
0.01368928562374832 yr | ||
julia> round(u"d", p) | ||
5//1 d | ||
julia> q = Month(1) + Day(1) # Month is not a fixed period | ||
1 month, 1 day | ||
julia> uconvert(u"s", q) | ||
ERROR: MethodError: no method matching Quantity{Rational{Int64},𝐓,Unitful.FreeUnits{(s,),𝐓,nothing}}(::Month) | ||
[...] | ||
``` | ||
|
||
However, not all operations that are defined for `FixedPeriod`s support `CompoundPeriod`s as well. | ||
The reason for that is that a `CompoundPeriod` does not correspond to a specific unit: | ||
|
||
```@jldoctest | ||
julia> p = Day(365) + Hour(6) | ||
365 days, 6 hours | ||
julia> unit(p) # A CompoundPeriod does not have a corresponding unit ... | ||
ERROR: MethodError: no method matching unit(::Dates.CompoundPeriod) | ||
[...] | ||
julia> dimension(p) # ... but it does have a dimension | ||
𝐓 | ||
julia> Quantity(p) # As a result, there is no Quantity type associated with it ... | ||
ERROR: MethodError: no method matching Quantity(::Int64) | ||
[...] | ||
julia> T = typeof(1.0u"hr"); T(p) # ... but it can be converted to a concrete time quantity | ||
8766.0 hr | ||
``` | ||
|
||
Consequently, any operation whose result would depend on the input unit is not supported by `CompoundPeriod`s. For example: | ||
|
||
* `+(::Quantity, ::CompoundPeriod)` and `+(::CompoundPeriod, ::Quantity)` error, since the unit of the result depends on the units of both arguments. | ||
* `div(::Quantity, ::CompoundPeriod)` and `div(::CompoundPeriod, ::Quantity)` work, since the result is a dimensionless number. | ||
* `mod(::CompoundPeriod, ::Quantity)` works, but `mod(::Quantity, ::CompoundPeriod)` does not, since the second argument determines the unit of the returned quantity. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
# Conversion from and to types from the `Dates` stdlib | ||
|
||
# Dates.FixedPeriod | ||
|
||
for (period, unit) = ((Dates.Week, wk), (Dates.Day, d), (Dates.Hour, hr), | ||
(Dates.Minute, minute), (Dates.Second, s), (Dates.Millisecond, ms), | ||
(Dates.Microsecond, μs), (Dates.Nanosecond, ns)) | ||
@eval unit(::Type{$period}) = $unit | ||
@eval (::Type{$period})(x::AbstractQuantity) = $period(ustrip(unit($period), x)) | ||
end | ||
|
||
dimension(p::Dates.FixedPeriod) = dimension(typeof(p)) | ||
dimension(::Type{<:Dates.FixedPeriod}) = 𝐓 | ||
|
||
""" | ||
unit(x::Dates.FixedPeriod) | ||
unit(x::Type{<:Dates.FixedPeriod}) | ||
Return the units that correspond to a particular period. | ||
# Examples | ||
```julia | ||
julia> unit(Second(15)) == u"s" | ||
true | ||
julia> unit(Hour) == u"hr" | ||
true | ||
``` | ||
""" | ||
unit(p::Dates.FixedPeriod) = unit(typeof(p)) | ||
|
||
numtype(x::Dates.FixedPeriod) = numtype(typeof(x)) | ||
numtype(::Type{T}) where {T<:Dates.FixedPeriod} = Int64 | ||
|
||
quantitytype(::Type{T}) where {T<:Dates.FixedPeriod} = | ||
Quantity{numtype(T),dimension(T),typeof(unit(T))} | ||
|
||
ustrip(p::Dates.FixedPeriod) = Dates.value(p) | ||
|
||
""" | ||
Quantity(period::Dates.FixedPeriod) | ||
Create a `Quantity` that corresponds to the given `period`. The numerical value of the | ||
resulting `Quantity` is of type `Int64`. | ||
# Example | ||
```jldoctest | ||
julia> using Dates: Second | ||
julia> Quantity(Second(5)) | ||
5 s | ||
``` | ||
""" | ||
Quantity(period::Dates.FixedPeriod) = Quantity(ustrip(period), unit(period)) | ||
|
||
uconvert(u::Units, period::Dates.FixedPeriod) = uconvert(u, Quantity(period)) | ||
|
||
(T::Type{<:AbstractQuantity})(period::Dates.FixedPeriod) = T(Quantity(period)) | ||
|
||
convert(T::Type{<:AbstractQuantity}, period::Dates.FixedPeriod) = T(period) | ||
convert(T::Type{<:Dates.FixedPeriod}, x::AbstractQuantity) = T(x) | ||
|
||
round(T::Type{<:Dates.FixedPeriod}, x::AbstractQuantity, r::RoundingMode=RoundNearest) = | ||
T(round(numtype(T), ustrip(unit(T), x), r)) | ||
round(u::Units, period::Dates.FixedPeriod, r::RoundingMode=RoundNearest; kwargs...) = | ||
round(u, Quantity(period), r; kwargs...) | ||
round(T::Type{<:Number}, u::Units, period::Dates.FixedPeriod, r::RoundingMode=RoundNearest; | ||
kwargs...) = round(T, u, Quantity(period), r; kwargs...) | ||
round(T::Type{<:AbstractQuantity}, period::Dates.FixedPeriod, r::RoundingMode=RoundNearest; | ||
kwargs...) = round(T, Quantity(period), r; kwargs...) | ||
|
||
for (f, r) in ((:floor,:RoundDown), (:ceil,:RoundUp), (:trunc,:RoundToZero)) | ||
@eval $f(T::Type{<:Dates.FixedPeriod}, x::AbstractQuantity) = round(T, x, $r) | ||
@eval $f(u::Units, period::Dates.FixedPeriod; kwargs...) = | ||
round(u, period, $r; kwargs...) | ||
@eval $f(T::Type{<:Number}, u::Units, period::Dates.FixedPeriod; kwargs...) = | ||
round(T, u, period, $r; kwargs...) | ||
@eval $f(T::Type{<:AbstractQuantity}, period::Dates.FixedPeriod; kwargs...) = | ||
round(T, period, $r; kwargs...) | ||
end | ||
|
||
for op = (:+, :-, :*, :/, ://, :fld, :cld, :mod, :rem, :atan, | ||
:(==), :isequal, :<, :isless, :≤) | ||
@eval $op(x::Dates.FixedPeriod, y::AbstractQuantity) = $op(Quantity(x), y) | ||
@eval $op(x::AbstractQuantity, y::Dates.FixedPeriod) = $op(x, Quantity(y)) | ||
end | ||
for op = (:*, :/, ://) | ||
@eval $op(x::Dates.FixedPeriod, y::Units) = $op(Quantity(x), y) | ||
@eval $op(x::Units, y::Dates.FixedPeriod) = $op(x, Quantity(y)) | ||
end | ||
div(x::Dates.FixedPeriod, y::AbstractQuantity, r...) = div(Quantity(x), y, r...) | ||
div(x::AbstractQuantity, y::Dates.FixedPeriod, r...) = div(x, Quantity(y), r...) | ||
|
||
isapprox(x::Dates.FixedPeriod, y::AbstractQuantity; kwargs...) = | ||
isapprox(Quantity(x), y; kwargs...) | ||
isapprox(x::AbstractQuantity, y::Dates.FixedPeriod; kwargs...) = | ||
isapprox(x, Quantity(y); kwargs...) | ||
|
||
function isapprox(x::AbstractArray{<:AbstractQuantity}, y::AbstractArray{T}; | ||
kwargs...) where {T<:Dates.Period} | ||
if isconcretetype(T) | ||
y′ = reinterpret(quantitytype(T), y) | ||
else | ||
y′ = Quantity.(y) | ||
end | ||
isapprox(x, y′; kwargs...) | ||
end | ||
isapprox(x::AbstractArray{<:Dates.FixedPeriod}, y::AbstractArray{<:AbstractQuantity}; | ||
kwargs...) = isapprox(y, x; kwargs...) | ||
|
||
Base.promote_rule(::Type{Quantity{T,𝐓,U}}, ::Type{S}) where {T,U,S<:Dates.FixedPeriod} = | ||
promote_type(Quantity{T,𝐓,U}, quantitytype(S)) | ||
|
||
# Dates.CompoundPeriod | ||
|
||
dimension(p::Dates.CompoundPeriod) = dimension(typeof(p)) | ||
dimension(::Type{<:Dates.CompoundPeriod}) = 𝐓 | ||
|
||
uconvert(u::Units, period::Dates.CompoundPeriod) = | ||
Quantity{promote_type(Int64,typeof(convfact(u,ns))),dimension(u),typeof(u)}(period) | ||
|
||
try_uconvert(u::Units, period::Dates.CompoundPeriod) = nothing | ||
function try_uconvert(u::TimeUnits, period::Dates.CompoundPeriod) | ||
T = Quantity{promote_type(Int64,typeof(convfact(u,ns))),dimension(u),typeof(u)} | ||
val = zero(T) | ||
for p in period.periods | ||
p isa Dates.FixedPeriod || return nothing | ||
val += T(p) | ||
end | ||
val | ||
end | ||
|
||
(T::Type{<:AbstractQuantity})(period::Dates.CompoundPeriod) = | ||
mapreduce(T, +, period.periods, init=zero(T)) | ||
|
||
convert(T::Type{<:AbstractQuantity}, period::Dates.CompoundPeriod) = T(period) | ||
|
||
round(u::Units, period::Dates.CompoundPeriod, r::RoundingMode=RoundNearest; kwargs...) = | ||
round(u, uconvert(u, period), r; kwargs...) | ||
round(T::Type{<:Number}, u::Units, period::Dates.CompoundPeriod, | ||
r::RoundingMode=RoundNearest; kwargs...) = | ||
round(T, u, uconvert(u, period), r; kwargs...) | ||
round(T::Type{<:AbstractQuantity}, period::Dates.CompoundPeriod, | ||
r::RoundingMode=RoundNearest; kwargs...) = | ||
round(T, T(period), r; kwargs...) | ||
|
||
for (f, r) in ((:floor,:RoundDown), (:ceil,:RoundUp), (:trunc,:RoundToZero)) | ||
@eval $f(u::Units, period::Dates.CompoundPeriod; kwargs...) = | ||
round(u, period, $r; kwargs...) | ||
@eval $f(T::Type{<:Number}, u::Units, period::Dates.CompoundPeriod; kwargs...) = | ||
round(T, u, period, $r; kwargs...) | ||
@eval $f(T::Type{<:AbstractQuantity}, period::Dates.CompoundPeriod; kwargs...) = | ||
round(T, period, $r; kwargs...) | ||
end | ||
|
||
for op = (:fld, :cld, :atan, :<, :isless, :≤) | ||
@eval $op(x::Dates.CompoundPeriod, y::AbstractQuantity) = $op(uconvert(unit(y),x), y) | ||
@eval $op(x::AbstractQuantity, y::Dates.CompoundPeriod) = $op(x, uconvert(unit(x),y)) | ||
end | ||
div(x::Dates.CompoundPeriod, y::AbstractQuantity, r...) = div(uconvert(unit(y),x), y, r...) | ||
div(x::AbstractQuantity, y::Dates.CompoundPeriod, r...) = div(x, uconvert(unit(x),y), r...) | ||
mod(x::Dates.CompoundPeriod, y::AbstractQuantity) = mod(uconvert(unit(y),x), y) | ||
rem(x::Dates.CompoundPeriod, y::AbstractQuantity) = rem(uconvert(unit(y),x), y) | ||
for op = (:(==), :isequal) | ||
@eval $op(x::Dates.CompoundPeriod, y::AbstractQuantity{T,𝐓,U}) where {T,U} = | ||
$op(try_uconvert(U(), x), y) | ||
@eval $op(x::AbstractQuantity{T,𝐓,U}, y::Dates.CompoundPeriod) where {T,U} = | ||
$op(x, try_uconvert(U(), y)) | ||
end | ||
|
||
isapprox(x::Dates.CompoundPeriod, y::AbstractQuantity; kwargs...) = | ||
dimension(y) === 𝐓 ? isapprox(uconvert(unit(y), x), y; kwargs...) : false | ||
isapprox(x::AbstractQuantity, y::Dates.CompoundPeriod; kwargs...) = | ||
dimension(x) === 𝐓 ? isapprox(x, uconvert(unit(x), y); kwargs...) : false | ||
|
||
function isapprox(x::AbstractArray{<:AbstractQuantity}, | ||
y::AbstractArray{Dates.CompoundPeriod}; kwargs...) | ||
if dimension(eltype(x)) === 𝐓 | ||
isapprox(x, uconvert.(unit(eltype(x)), y); kwargs...) | ||
else | ||
false | ||
end | ||
end | ||
|
||
isapprox(x::AbstractArray{Dates.CompoundPeriod}, y::AbstractArray{<:AbstractQuantity}; | ||
kwargs...) = isapprox(y, x; kwargs...) |
Oops, something went wrong.