-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove problematic Dates conversions #19920
Conversation
@deprecate convert{R<:Real}(::Type{R},x::Dates.DateTime) R(Dates.datetime2rata(x)) | ||
@deprecate convert{R<:Real}(::Type{R},x::Dates.Date) R(Dates.datetime2rata(x)) | ||
@deprecate convert(::Type{Dates.DateTime}, x::Real) Dates.rata2datetime(x) | ||
@deprecate convert(::Type{Dates.Date}, x::Real) Dates.Date(Dates.rata2datetime(x)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the previous 4 lines that need particular scrutiny and deciding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these are probably fine. The first two could be Dates.value(x)
, but it's probably more consistent to just use the rata2datetime
and datetime2rata
conversions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the one intended to replace convert(Date, x)
gives the wrong answer, though. Isn't it something like milliseconds vs days or something? (I apologize for not knowing as much as I should about the Dates infrastructure, it's clearly carefully thought out and I feel a bit like a bull in a well-designed china shop...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bump @quinnj.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GitHub seems to have messed up the threading order here (EDIT: only on "changes" view. Weird.): the comments were made in the order me, @quinnj, and then my comment about convert(Date, x)
giving the wrong answer in response to @quinnj. In other words, the problem I highlighted is still a concern (it's why the tests fail for this PR).
What should the API actually look like? What functions should we export?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, thanks for the help folks! I used these as a guideline to rework the modified tests at the bottom of test/dates/conversions.jl
, here are the results:
julia> @test Dates.datetime2rata(a) == 63082368000000
Test Failed
Expression: Dates.datetime2rata(a) == 63082368000000
Evaluated: 730120 == 63082368000000
ERROR: There was an error during testing
julia> @test Dates.UTD(63082368000000) == a
Test Failed
Expression: Dates.UTD(63082368000000) == a
Evaluated: Base.Dates.UTInstant{Base.Dates.Day}(63082368000000 days) == 2000-01-01T00:00:00
ERROR: There was an error during testing
julia> @test Dates.UTD(63082368000000.0) == a
Test Failed
Expression: Dates.UTD(6.3082368e13) == a
Evaluated: Base.Dates.UTInstant{Base.Dates.Day}(63082368000000 days) == 2000-01-01T00:00:00
ERROR: There was an error during testing
julia> @test Dates.UTM(730120) == b
Test Failed
Expression: Dates.UTM(730120) == b
Evaluated: Base.Dates.UTInstant{Base.Dates.Millisecond}(730120 milliseconds) == 2000-01-01
ERROR: There was an error during testing
julia> @test Dates.UTM(730120.0) == b
Test Failed
Expression: Dates.UTM(730120.0) == b
Evaluated: Base.Dates.UTInstant{Base.Dates.Millisecond}(730120 milliseconds) == 2000-01-01
ERROR: There was an error during testing
julia> @test Dates.UTM(Int32(730120)) == b
Test Failed
Expression: Dates.UTM(Int32(730120)) == b
Evaluated: Base.Dates.UTInstant{Base.Dates.Millisecond}(730120 milliseconds) == 2000-01-01
ERROR: There was an error during testing
I may have screwed some of those up, but it appears we aren't there yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tracked down the issue here. We need to change the deprecated conversions to:
@deprecate convert{R<:Real}(::Type{R},x::Dates.DateTime) R(Dates.value(x))
@deprecate convert{R<:Real}(::Type{R},x::Dates.Date) R(Dates.value(x))
@deprecate convert(::Type{Dates.DateTime}, x::Real) Dates.DateTime(Dates.UTM(x))
@deprecate convert(::Type{Dates.Date}, x::Real) Dates.Date(Dates.UTD(x))
The conversion from DateTime to Real was incorrect as it was working in Rata Die days instead of milliseconds. The tests at the end of test/dates/conversions.jl
should look like:
# Conversions to/from numbers
a = Dates.DateTime(2000)
b = Dates.Date(2000)
@test Dates.value(b) == 730120
@test Dates.value(a) == 63082368000000
@test Dates.DateTime(Dates.UTM(63082368000000)) == a
@test Dates.DateTime(Dates.UTM(63082368000000.0)) == a
@test Dates.Date(Dates.UTD(730120)) == b
@test Dates.Date(Dates.UTD(730120.0)) == b
@test Dates.Date(Dates.UTD(Int32(730120))) == b
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest that we add the following methods:
Base.convert(::Type{DateTime}, x::Millisecond) = DateTime(Dates.UTInstant(x)) # Converts Rata Die milliseconds to a DateTime
Base.convert(::Type{Date}, x::Day) = Date(Dates.UTInstant(x)) # Converts Rata Die days to a Date
That way we can have a reasonable way of converting Reals into DateTimes and Dates:
julia> convert(Date, Day(730120))
2000-01-01
julia> convert(DateTime, Millisecond(63082368000000))
2000-01-01T00:00:00
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the reverse conversions also be defined?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think both conversions to and from Day/Millisecond
are great.
At a higher level, almost all of the actual complexity of date and time stuff should not be in Base Julia – just the basic type definitions, printing and a little basic arithmetic. Everything else should be in a |
This probably also fixes #10451. |
Bump, can we get this finished up before (imminent) feature freeze? |
It's waiting on feedback from someone who actually knows about how Date/Time works. The key question is what the API should look like for conversions between "unitful" and "unitless" measures of time (which I presume are important for interoperation with other systems/software). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks reasonable.
""" | ||
function default end | ||
|
||
default{T<:DatePeriod}(p::Union{T,Type{T}}) = one(p) | ||
default{T<:DatePeriod}(p::Union{T,Type{T}}) = T(1) | ||
default{T<:TimePeriod}(p::Union{T,Type{T}}) = zero(p) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be T(0)
? At the very least it doesn't match the comment above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think both give the same answer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They do give the same answer. Just appears inconsistent
guess(a::DateTime,b::DateTime,c) = floor(Int64,(Int128(b) - Int128(a))/toms(c)) | ||
guess(a::Date,b::Date,c) = Int64(div(Int64(b - a),days(c))) | ||
guess(a::DateTime,b::DateTime,c) = floor(Int64,(Int128(value(b)) - Int128(value(a)))/toms(c)) | ||
guess(a::Date,b::Date,c) = Int64(div(Int64(value(b - a)),days(c))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be safe to remove the inner Int64
call.
@@ -482,10 +482,10 @@ function testmonthranges2(n) | |||
end | |||
testmonthranges2(100000) | |||
|
|||
@test length(Dates.Year(1):Dates.Year(10)) == 10 | |||
@test length(Dates.Year(1):Dates.Year(1):Dates.Year(10)) == 10 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to specify the step here? Can't we just define the following to get the old behaviour?
Base.colon{T<:Period}(start::T, stop::T) = StepRange(start, T(1), stop)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's dangerous to define ranges with unitful quantities if you don't explicitly specify the step, because with unitful quantities we tend to think that there's an underlying physical reality independent of the units you choose to express the quantities in. Why should 1m:10m
give a range of length 10 whereas 1000mm:10000mm
gives you a range of length 9001? Both of those ranges are defined with the same endpoints.
In my mind the only reasonable approach is to force the user to specify the step or the desired length of the range.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with requiring the user to specify the step, but this change should be documented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In principle I agree that you should define step for unitful ranges. However since ranges involving periods currently require that the endpoints and the step are of the same unit I think this change is mostly just inconvenient. Overall though I'm happy with the change if it leads to less confusion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see the sense in supporting it when you're typing the endpoints explicitly: I think it's pretty obvious what the user wants with Minute(1):Minute(60)
. But I worry about code like this:
function foo(a::Period, b::Period)
ap, bp = promote(a, b)
ap:bp
end
where now knowing what you'll get relies on a fair amount of mental gymnastics.
Either way, I agree completely with the perspective that we need a transitional strategy. I suspect a deprecation would suffice?
Thanks for the review, @omus! If you can help with #19920 (comment), that's really the key issue. Do we need a new name for one of the conversions? What functions should we export? |
What's needed to move this forward? |
@StefanKarpinski I believe this is the only blocking change. Should also add some documentation regarding requiring the step for Period ranges. The rest are minor. |
My comment above still stands. I was attempting to resolve my previous suggestions. |
Updated. Thanks for the suggestions! |
Base.range(start::DateTime, len::Integer) = range(start, Day(1), len) | ||
Base.range(start::Date, len::Integer) = range(start, Day(1), len) | ||
|
||
(::Type{StepRange{T,R}}){T<:Dates.DatePeriod,R<:Real}(start, step, stop) = throw(ArgumentError("must specify step as a Period when constructing Dates ranges")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line wrap
@@ -74,8 +73,7 @@ let t = Dates.Period[Dates.Week(2), Dates.Day(14), Dates.Hour(14*24), Dates.Minu | |||
end | |||
end | |||
@test Dates.Year(3) == Dates.Month(36) | |||
@test Int(convert(Dates.Month, Dates.Year(3))) == 36 | |||
@test Int(convert(Dates.Year, Dates.Month(36))) == 3 | |||
@test_throws ErrorException Int(Dates.Month(36)) # eventually change to MethodError |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this only warns, not throws - strangely it doesn't fail unless you run the tests with JULIA_CPU_CORES=1
though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll probably comment this out in #20226, but I wonder whether there's a test-system bug here.
Fixes #19896. This doesn't quite pass tests; we should discuss what the names for conversions between reals and
Date
/DateTime
should be (see the deprecations). CC @quinnj.