Skip to content

Commit

Permalink
fix JuliaLang#9216: more consistent units for Period arithmetic
Browse files Browse the repository at this point in the history
  • Loading branch information
stevengj committed Dec 2, 2014
1 parent 2a6a4d9 commit 831f3ad
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 82 deletions.
4 changes: 2 additions & 2 deletions base/dates/arithmetic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ function (-)(dt::Date,z::Month)
end
(+)(x::Date,y::Week) = return Date(UTD(value(x) + 7*value(y)))
(-)(x::Date,y::Week) = return Date(UTD(value(x) - 7*value(y)))
(+)(x::Date,y::Day) = return Date(UTD(value(x) + y))
(-)(x::Date,y::Day) = return Date(UTD(value(x) - y))
(+)(x::Date,y::Day) = return Date(UTD(value(x) + value(y)))
(-)(x::Date,y::Day) = return Date(UTD(value(x) - value(y)))
(+)(x::DateTime,y::Period) = return DateTime(UTM(value(x)+toms(y)))
(-)(x::DateTime,y::Period) = return DateTime(UTM(value(x)-toms(y)))
(+)(y::Period,x::TimeType) = x + y
Expand Down
66 changes: 43 additions & 23 deletions base/dates/periods.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ end
# Now we're safe to define Period-Number conversions
# Anything an Int64 can convert to, a Period can convert to
Base.convert{T<:Number}(::Type{T},x::Period) = convert(T,value(x))
Base.convert{T<:Period}(::Type{T},x::Real) = T(int64(x))
Base.convert{T<:Period}(::Type{T},x::Real) = T(x)

#Print/show/traits
Base.string{P<:Period}(x::P) = string(value(x),_units(x))
Expand All @@ -33,29 +33,46 @@ default{T<:TimePeriod}(p::Union(T,Type{T})) = zero(p)

(-){P<:Period}(x::P) = P(-value(x))
Base.isless{P<:Period}(x::P,y::P) = isless(value(x),value(y))
Base.isless{R<:Real}(x::Period,y::R) = isless(int64(y),value(x))
Base.isless{R<:Real}(y::R,x::Period) = isless(int64(y),value(x))
=={P<:Period}(x::P,y::P) = ===(value(x),value(y))
=={R<:Real}(x::Period,y::R) = ==(int64(y),value(x))
=={R<:Real}(y::R,x::Period) = ==(int64(y),value(x))

#Period Arithmetic:
import Base.div, Base.mod, Base.gcd, Base.lcm
let vec_ops = [:.+,:.-,:.*,:.%,:div]
for op in [:+,:-,:*,:%,:mod,:gcd,:lcm,vec_ops]
@eval begin
#Period-Period
($op){P<:Period}(x::P,y::P) = P(($op)(value(x),value(y)))
#Period-Real
($op){P<:Period}(x::P,y::Real) = P(($op)(value(x),convert(Int64,y)))
($op){P<:Period}(x::Real,y::P) = P(($op)(convert(Int64,x),value(y)))
end
#Vectorized
if op in vec_ops
@eval begin
($op){P<:Period}(x::AbstractArray{P}, y::P) = reshape([$op(i,y) for i in x], size(x))
($op){P<:Period}(y::P, x::AbstractArray{P}) = reshape([$op(i,y) for i in x], size(x))
=={P<:Period}(x::P,y::P) = value(x) == value(y)

# Period Arithmetic, grouped by dimensionality:
import Base: div, mod, rem, gcd, lcm, +, -, *, /, %, .+, .-, .*, .%
for op in (:+,:-,:lcm,:gcd)
@eval ($op){P<:Period}(x::P,y::P) = P(($op)(value(x),value(y)))
end
for op in (:.+, :.-)
op_ = symbol(string(op)[2:end])
@eval begin
($op){P<:Period}(x::P,Y::StridedArray{P}) = ($op)(Y,x)
($op_){P<:Period}(x::P,Y::StridedArray{P}) = ($op)(Y,x)
($op_){P<:Period}(Y::StridedArray{P},x::P) = ($op)(Y,x)
end
end
for op in (:/,:%,:div,:mod)
@eval begin
($op){P<:Period}(x::P,y::P) = ($op)(value(x),value(y))
($op){P<:Period}(x::P,y::Real) = P(($op)(value(x),Int64(y)))
end
end
/{P<:Period}(X::StridedArray{P}, y::P) = X ./ y
%{P<:Period}(X::StridedArray{P}, y::P) = X .% y
*{P<:Period}(x::P,y::Real) = P(value(x) * Int64(y))
*(y::Real,x::Period) = x * y
.*{P<:Period}(y::Real, X::StridedArray{P}) = X .* y
for (op,Ty,Tz) in ((:.+,:P,:P),(:.-,:P,:P), (:.*,Real,:P),
(:./,:P,Float64), (:./,Real,:P),
(:.%,:P,Int64), (:.%,Integer,:P),
(:div,:P,Int64), (:div,Integer,:P),
(:mod,:P,Int64), (:mod,Integer,:P))
sop = string(op)
op_ = sop[1] == '.' ? symbol(sop[2:end]) : op
@eval begin
function ($op){P<:Period}(X::StridedArray{P},y::$Ty)
Z = similar(X, $Tz)
for i = 1:length(X)
@inbounds Z[i] = ($op_)(X[i],y)
end
return Z
end
end
end
Expand All @@ -64,6 +81,9 @@ end
Base.gcdx{T<:Period}(a::T,b::T) = ((g,x,y)=gcdx(value(a),value(b)); return T(g),x,y)
Base.abs{T<:Period}(a::T) = T(abs(value(a)))

# Like Base.steprem in range.jl, but returns the correct type for Periods
Base.steprem(start::Period,stop::Period,step::Period) = (stop-start) % value(step)

periodisless(::Period,::Year) = true
periodisless(::Period,::Month) = true
periodisless(::Year,::Month) = false
Expand Down
7 changes: 7 additions & 0 deletions base/dates/ranges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,10 @@ end
Base.start{T<:TimeType}(r::StepRange{T}) = 0
Base.next{T<:TimeType}(r::StepRange{T}, i::Int) = (r.start+r.step*i,i+1)
Base.done{T<:TimeType,S<:Period}(r::StepRange{T,S}, i::Integer) = length(r) <= i

.+{T<:TimeType}(x::Period, r::Range{T}) = (x+first(r)):step(r):(x+last(r))
.+{T<:TimeType}(r::Range{T},x::Period) = x .+ r
+{T<:TimeType}(r::Range{T},x::Period) = x .+ r
+{T<:TimeType}(x::Period,r::Range{T}) = x .+ r
.-{T<:TimeType}(r::Range{T},x::Period) = (first(r)-x):step(r):(last(r)-x)
-{T<:TimeType}(r::Range{T},x::Period) = r .- x
87 changes: 30 additions & 57 deletions test/dates/periods.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,28 @@
@test Dates.Year(1) > Dates.Year(0)
@test (Dates.Year(1) < Dates.Year(0)) == false
@test Dates.Year(1) == Dates.Year(1)
@test Dates.Year(1) != 1
@test Dates.Year(1) + Dates.Year(1) == Dates.Year(2)
@test Dates.Year(1) - Dates.Year(1) == Dates.Year(0)
@test Dates.Year(1) * Dates.Year(1) == Dates.Year(1)
@test Dates.Year(10) % Dates.Year(4) == Dates.Year(2)
@test div(Dates.Year(10),Dates.Year(3)) == Dates.Year(3)
@test div(Dates.Year(10),Dates.Year(4)) == Dates.Year(2)
@test Dates.Year(1) - Dates.Year(1) == zero(Dates.Year)
@test_throws MethodError Dates.Year(1) * Dates.Year(1) == Dates.Year(1)
@test Dates.Year(10) % Dates.Year(4) == 2
@test gcd(Dates.Year(10), Dates.Year(4)) == Dates.Year(2)
@test lcm(Dates.Year(10), Dates.Year(4)) == Dates.Year(20)
@test div(Dates.Year(10),Dates.Year(3)) == 3
@test div(Dates.Year(10),Dates.Year(4)) == 2
@test Dates.Year(10) / Dates.Year(4) == 2.5
t = Dates.Year(1)
t2 = Dates.Year(2)
@test ([t,t,t,t,t] .+ Dates.Year(1)) == ([t2,t2,t2,t2,t2])
@test (Dates.Year(1) .+ [t,t,t,t,t]) == ([t2,t2,t2,t2,t2])
@test ([t2,t2,t2,t2,t2] .- Dates.Year(1)) == ([t,t,t,t,t])
@test ([t,t,t,t,t] .* Dates.Year(1)) == ([t,t,t,t,t])
@test ([t,t,t,t,t] .% t2) == ([t,t,t,t,t])
@test div([t,t,t,t,t],Dates.Year(1)) == ([t,t,t,t,t])
@test ([t,t,t,t,t] + Dates.Year(1)) == ([t2,t2,t2,t2,t2])
@test (Dates.Year(1) + [t,t,t,t,t]) == ([t2,t2,t2,t2,t2])
@test ([t2,t2,t2,t2,t2] - Dates.Year(1)) == ([t,t,t,t,t])
@test_throws MethodError ([t,t,t,t,t] .* Dates.Year(1)) == ([t,t,t,t,t])
@test ([t,t,t,t,t] * 1) == ([t,t,t,t,t])
@test ([t,t,t,t,t] .% t2) == ([1,1,1,1,1])
@test div([t,t,t,t,t],Dates.Year(1)) == ([1,1,1,1,1])
@test mod([t,t,t,t,t],Dates.Year(2)) == ([1,1,1,1,1])
@test [t,t,t] / t2 == [0.5,0.5,0.5]
@test abs(-t) == t

#Period arithmetic
y = Dates.Year(1)
Expand Down Expand Up @@ -131,29 +139,20 @@ y2 = Dates.Year(2)
@test typemax(Dates.Year) + y == Dates.Year(-9223372036854775808)
@test typemin(Dates.Year) == Dates.Year(-9223372036854775808)
#Period-Real arithmetic
@test y + 1 == Dates.Year(2)
@test 1 + y == Dates.Year(2)
@test y + true == Dates.Year(2)
@test true + y == Dates.Year(2)
@test y + 1.0 == Dates.Year(2)
@test_throws InexactError y + 1.2
@test y + 1f0 == Dates.Year(2)
@test_throws InexactError y + 1.2f0
@test y + BigFloat(1) == Dates.Year(2)
@test_throws InexactError y + BigFloat(1.2)
@test y + 1.0 == Dates.Year(2)
@test_throws InexactError y + 1.2
@test_throws MethodError y + 1 == Dates.Year(2)
@test_throws MethodError y + true == Dates.Year(2)
@test_throws InexactError y + Dates.Year(1.2)
@test y + Dates.Year(1f0) == Dates.Year(2)
@test y * 4 == Dates.Year(4)
@test y * 4f0 == Dates.Year(4)
@test_throws InexactError y * 3//4 == Dates.Year(1)
@test div(y,2) == Dates.Year(0)
@test div(2,y) == Dates.Year(2)
@test div(y,y) == Dates.Year(1)
@test_throws MethodError div(2,y) == Dates.Year(2)
@test div(y,y) == 1
@test y*10 % 5 == Dates.Year(0)
@test 5 % y*10 == Dates.Year(0)
@test (y > 3) == false
@test (4 < y) == false
@test 1 == y
@test_throws MethodError (y > 3) == false
@test_throws MethodError (4 < y) == false
@test 1 != y
t = [y,y,y,y,y]
@test t .+ Dates.Year(2) == [Dates.Year(3),Dates.Year(3),Dates.Year(3),Dates.Year(3),Dates.Year(3)]
dt = Dates.DateTime(2012,12,21)
Expand Down Expand Up @@ -208,52 +207,26 @@ test = ((((((((dt + y) - m) + w) - d) + h) - mi) + s) - ms)
@test Dates.Year(-1) < Dates.Year(1)
@test !(Dates.Year(-1) > Dates.Year(1))
@test Dates.Year(1) == Dates.Year(1)
@test Dates.Year(1) == 1
@test 1 == Dates.Year(1)
@test (Dates.Year(1) < 1) == false
@test (1 < Dates.Year(1)) == false
@test Dates.Year(1) != 1
@test 1 != Dates.Year(1)
@test Dates.Month(-1) < Dates.Month(1)
@test !(Dates.Month(-1) > Dates.Month(1))
@test Dates.Month(1) == Dates.Month(1)
@test Dates.Month(1) == 1
@test 1 == Dates.Month(1)
@test (Dates.Month(1) < 1) == false
@test (1 < Dates.Month(1)) == false
@test Dates.Day(-1) < Dates.Day(1)
@test !(Dates.Day(-1) > Dates.Day(1))
@test Dates.Day(1) == Dates.Day(1)
@test Dates.Day(1) == 1
@test 1 == Dates.Day(1)
@test (Dates.Day(1) < 1) == false
@test (1 < Dates.Day(1)) == false
@test Dates.Hour(-1) < Dates.Hour(1)
@test !(Dates.Hour(-1) > Dates.Hour(1))
@test Dates.Hour(1) == Dates.Hour(1)
@test Dates.Hour(1) == 1
@test 1 == Dates.Hour(1)
@test (Dates.Hour(1) < 1) == false
@test (1 < Dates.Hour(1)) == false
@test Dates.Minute(-1) < Dates.Minute(1)
@test !(Dates.Minute(-1) > Dates.Minute(1))
@test Dates.Minute(1) == Dates.Minute(1)
@test Dates.Minute(1) == 1
@test 1 == Dates.Minute(1)
@test (Dates.Minute(1) < 1) == false
@test (1 < Dates.Minute(1)) == false
@test Dates.Second(-1) < Dates.Second(1)
@test !(Dates.Second(-1) > Dates.Second(1))
@test Dates.Second(1) == Dates.Second(1)
@test Dates.Second(1) == 1
@test 1 == Dates.Second(1)
@test (Dates.Second(1) < 1) == false
@test (1 < Dates.Second(1)) == false
@test Dates.Millisecond(-1) < Dates.Millisecond(1)
@test !(Dates.Millisecond(-1) > Dates.Millisecond(1))
@test Dates.Millisecond(1) == Dates.Millisecond(1)
@test Dates.Millisecond(1) == 1
@test 1 == Dates.Millisecond(1)
@test (Dates.Millisecond(1) < 1) == false
@test (1 < Dates.Millisecond(1)) == false
@test_throws MethodError Dates.Year(1) < Dates.Millisecond(1)

@test Dates.Year("1") == y
Expand Down
6 changes: 6 additions & 0 deletions test/dates/ranges.jl
Original file line number Diff line number Diff line change
Expand Up @@ -491,3 +491,9 @@ testmonthranges2(100000)
# Issue 5
lastdaysofmonth = [Dates.Date(2014,i,Dates.daysinmonth(2014,i)) for i=1:12]
@test [Date(2014,1,31):Dates.Month(1):Date(2015)] == lastdaysofmonth

# Range addition/subtraction:
let d = Dates.Day(1)
@test (Dates.Date(2000):d:Dates.Date(2001))+d == (Dates.Date(2000)+d:d:Dates.Date(2001)+d)
@test (Dates.Date(2000):d:Dates.Date(2001))-d == (Dates.Date(2000)-d:d:Dates.Date(2001)-d)
end

0 comments on commit 831f3ad

Please sign in to comment.