diff --git a/src/duration.jl b/src/duration.jl index e72b4e9..1267844 100644 --- a/src/duration.jl +++ b/src/duration.jl @@ -1,6 +1,6 @@ """ - Duration{T} + Duration{T} <: Number A `Duration` represents a period of time, split into an integer number of seconds and a fractional part. @@ -9,54 +9,44 @@ fractional part. - `seconds`: The integer number of seconds. - `fraction`: The fractional part of the duration, where `T` is a subtype of `Number`. """ -struct Duration{T} +struct Duration{T} <: Number seconds::Int fraction::T end -function Duration(seconds::T) where {T<:Number} - i, f = divrem(seconds, 1) - return Duration{T}(i, f) +function Duration{T}(seconds::Number) where {T <: Number} + i,f = divrem(seconds, 1) + return Duration{T}(convert(Int, i), T(f)) end -function Duration(sec::Int, frac::T) where {T<:Number} - return Duration{T}(sec, frac) -end +function Duration(seconds::T) where {T <: Number} + Duration{T}(seconds) +end +ftype(::Duration{T}) where T = T value(d::Duration{T}) where T = d.seconds + d.fraction -function Base.isless(d::Duration{T}, q::Number) where T - return value(d) < q -end +# --- +# Type Conversions and Promotions -function Base.isless(d::Duration{T1}, d2::Duration{T2}) where {T1, T2} - return value(d) < value(d2) +function Base.convert(::Type{Duration{T}}, d::Duration{S}) where {T,S} + return Duration(d.seconds, convert(T, d.fraction)) end -function fmasf(a, b, mul) - amulb = fma(mul, a, b) - i, f = divrem(amulb, 1) - return i, f +function Base.convert(::Type{T}, d::Duration{S}) where {T<:Number,S} + return Duration(d.seconds, convert(T, d.fraction)) end -function Base.:-(d1::Duration, d2::Duration) - s1, f1 = d1.seconds, d1.fraction - s2, f2 = d2.seconds, d2.fraction - ds, df = divrem(f1 - f2, 1) - sec = s1 - s2 + ds - if df < 0 - sec -= 1 - df += 1 - end - return Duration(convert(Int, sec), df) +function Base.promote_rule(::Type{Duration{T}}, ::Type{Duration{S}}) where {T,S} + return promote_rule(T, S) end -function Base.:+(d1::Duration, d2::Duration) - s1, f1 = d1.seconds, d1.fraction - s2, f2 = d2.seconds, d2.fraction - s, f = fmasf(f1, f2, 1) - return Duration(convert(Int, s1 + s2 + s), f) -end +# ---- +# Operations + +Base.isless(d::Duration, q::Number) = value(d) < q +Base.isless(q::Number, d::Duration) = q < value(d) +Base.isless(d1::Duration, d2::Duration) = value(d1) < value(d2) function Base.:+(d::Duration, x::Number) es, ef = d.seconds, d.fraction @@ -65,6 +55,13 @@ function Base.:+(d::Duration, x::Number) return Duration(convert(Int, es + xs + s), f) end +function Base.:+(d1::Duration, d2::Duration) + s1, f1 = d1.seconds, d1.fraction + s2, f2 = d2.seconds, d2.fraction + s, f = fmasf(f1, f2, 1) + return Duration(convert(Int, s1 + s2 + s), f) +end + function Base.:-(d::Duration, x::Number) es, ef = d.seconds, d.fraction xs, xf = divrem(x, 1) @@ -77,3 +74,21 @@ function Base.:-(d::Duration, x::Number) return Duration(convert(Int, sec), df) end +function Base.:-(d1::Duration, d2::Duration) + s1, f1 = d1.seconds, d1.fraction + s2, f2 = d2.seconds, d2.fraction + ds, df = divrem(f1 - f2, 1) + sec = s1 - s2 + ds + if df < 0 + sec -= 1 + df += 1 + end + return Duration(convert(Int, sec), df) +end + + +function fmasf(a, b, mul) + amulb = fma(mul, a, b) + i, f = divrem(amulb, 1) + return i, f +end diff --git a/src/epoch.jl b/src/epoch.jl index ecb9bab..0632970 100644 --- a/src/epoch.jl +++ b/src/epoch.jl @@ -70,8 +70,8 @@ struct Epoch{S,T} end function Epoch{S}(seconds::Number) where {S<:AbstractTimeScale} - sec, frac = divrem(seconds, 1) - return Epoch{S, typeof(frac)}(S(), Duration(Int(sec), frac)) + d = Duration(seconds) + return Epoch{S, ftype(d)}(S(), d) end Epoch(sec::Number, ::S) where {S<:AbstractTimeScale} = Epoch{S}(sec) @@ -83,7 +83,7 @@ Epoch(dt::DateTime, ::Type{S}) where {S<:AbstractTimeScale} = Epoch{S}(j2000s(dt Epoch(e::Epoch) = e Epoch{S,T}(e::Epoch{S,T}) where {S,T} = e -Epoch{S,T}(e::Epoch{S,N}) where {S, N, T} = Epoch{S}(T(j2000s(e))) +Epoch{S,T}(e::Epoch{S,N}) where {S, N, T} = Epoch{S,T}(e.scale, convert(T, e.dur)) # Construct an epoch from an ISO string and a scale function Epoch(s::AbstractString, scale::S) where {S <: AbstractTimeScale} @@ -91,7 +91,7 @@ function Epoch(s::AbstractString, scale::S) where {S <: AbstractTimeScale} # TODO: the precision of this could be improved _, jd2 = calhms2jd(y, m, d, H, M, sec + sf) - return Epoch(jd2 * 86400, scale) + return Epoch(jd2 * DAY2SEC, scale) end # Construct an epoch from an ISO string @@ -201,21 +201,8 @@ function Base.:-(::Epoch{S1}, ::Epoch{S2}) where {S1, S2} throw(ErrorException("only epochs defined in the same timescale can be subtracted.")) end -function Base.:+(e::Epoch{S, N}, x::Number) where {S, N} - return Epoch{S, N}(timescale(e), e.dur + x) -end - -function Base.:-(e::Epoch{S, N}, x::Number) where {S, N} - return Epoch{S, N}(timescale(e), e.dur - x) -end - -function Base.:+(e::Epoch{S, N}, d::Duration{<:Number}) where {S, N} - return Epoch{S, N}(timescale(e), e.dur + d) -end - -function Base.:-(e::Epoch{S, N}, d::Duration{<:Number}) where {S, N} - return Epoch{S, N}(timescale(e), e.dur - d) -end +Base.:+(e::Epoch, x::Number) = Epoch(timescale(e), e.dur + x) +Base.:-(e::Epoch, x::Number) = Epoch(timescale(e), e.dur - x) function (::Base.Colon)(start::Epoch, step::Number, stop::Epoch) step = start < stop ? step : -step @@ -227,6 +214,9 @@ function (::Base.Colon)(start::Epoch, step::Duration, stop::Epoch) return (:)(start, value(step), stop) end +(::Base.Colon)(start::Epoch, stop::Epoch) = (:)(start, 86400, stop) + + Base.isless(e1::Epoch{S}, e2::Epoch{S}) where {S} = e1.dur < e2.dur function Base.isapprox(e1::Epoch{S}, e2::Epoch{S}; kwargs...) where {S} diff --git a/test/Tempo/duration.jl b/test/Tempo/duration.jl index ebe622e..8eb88f1 100644 --- a/test/Tempo/duration.jl +++ b/test/Tempo/duration.jl @@ -1,6 +1,7 @@ # TestSet for Duration @testset "Duration" begin + # Test Duration constructor with a floating point number d1 = Duration(5.75) @test d1.seconds == 5 @@ -11,10 +12,25 @@ @test d2.seconds == 10 @test d2.fraction == 0.25 + # Check Constructor with a Big Float + db1 = Duration(BigFloat(2.5422)) + + @test db1.seconds == 2 + @test db1.fraction ≈ 0.5422 atol=1e-14 rtol=1e-14 + + # Test duration type + @test Tempo.ftype(db1) == BigFloat + # Test value function @test value(d1) == 5.75 @test value(d2) == 10.25 + # Test duration conversion + db2 = convert(BigFloat, d1) + @test Tempo.ftype(db2) == BigFloat + @test db2.seconds == 5 + @test db2.fraction ≈ 0.75 atol=1e-14 rtol=1e-14 + # Test isless with a number @test d1 < 6.0 @test d2 < 10.5 @@ -63,4 +79,5 @@ @test d10.fraction == d1.fraction @test d11.seconds == d1.seconds @test d11.fraction == d1.fraction + end diff --git a/test/Tempo/epoch.jl b/test/Tempo/epoch.jl index ad88d41..a68a010 100644 --- a/test/Tempo/epoch.jl +++ b/test/Tempo/epoch.jl @@ -68,8 +68,10 @@ @test_throws ErrorException e3-e1 ems = e1:86400:e2 + ems2 = e1:e2 for j = 2:lastindex(ems) @test ems[j] == e1 + 86400*(j-1) + @test ems2[j] == e1 + 86400*(j-1) end # Based on Vallado "Fundamental of astrodynamics" page 196