-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Change Time constructor to have nanosecond
& kind
as named argument
#5072
Conversation
src/time.cr
Outdated
raise ArgumentError.new "Invalid time" | ||
end | ||
|
||
new(year, month, day, hour, minute, second, nanosecond: millisecond * 1_000, kind: kind) |
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.
You should use the NANOSECONDS_PER_MILLISECOND
constant. It even has the right number.
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.
Done
ac0dca7
to
4bcb859
Compare
def test(*, foo = 0)
puts "foo"
end
def test(*, bar = 0)
puts "bar"
end
test(bar: 5)
test(foo: 5)
Surely this is a bug, and the second method shouldn't overload the first, because their arguments are named differently and the calls are entirely unambiguous. EDIT: removing the |
CI failed. Please make sure to run the specs locally (I think running |
Oh, a bug. So maybe just remove the |
It's actually not a bug, because if you invoke it without arguments then there's no way to choose one or the other. So the default value can't be present in both overloads. Let's either remove the |
@asterite, as there is a constructor taking no arguments, doesn't that resolve the ambiguity? The compiler sees a call to The only possible match is The issue only arises if you'd have Or am I overlooking something here? |
A lot of PRs related to Time. I think that we should forget about milliseconds in constructor, just use parser with %L specifier. |
Or maybe instead of having: We should have:
So that when one specify the constructor without precision, it'll work, and when using the (after re-reading your comment @asterite, that's your second option) |
I personally don't care about breaking code because I'm pretty sure no one uses that long Time constructor. So just having I'm a bit tired of these discussions right now. I think #5069 and #5070 should be merged, and this must be closed. |
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 agree with having strict named arguments for all second subdivisions overloads.
And default overload that would be up to second precision.
src/time.cr
Outdated
@@ -140,7 +140,15 @@ struct Time | |||
new(seconds: seconds + offset, nanoseconds: nanoseconds, kind: Kind::Local) | |||
end | |||
|
|||
def self.new(year, month, day, hour = 0, minute = 0, second = 0, nanosecond = 0, kind = Kind::Unspecified) | |||
def self.new(year, month, day, hour = 0, minute = 0, second = 0, *, millisecond = 0, kind = Kind::Unspecified) | |||
unless 0 <= millisecond <= 999_999 |
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 milliseconds should be between 0 and 999.
Otherwise we are speaking of microseconds. Which, could be other overload if needed.
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.
Oh right, I knew I was missing something here... thanks!
Perhaps it doesn't break anything, but I like this design more anyway. If the milliseconds overload is non-obvious, which I think it is, it's much better to name it something for moire readable code. I don't mind not having the |
Idea with named arguments will be better with |
I left the |
We simply have to remove |
src/time.cr
Outdated
@@ -140,7 +140,15 @@ struct Time | |||
new(seconds: seconds + offset, nanoseconds: nanoseconds, kind: Kind::Local) | |||
end | |||
|
|||
def self.new(year, month, day, hour = 0, minute = 0, second = 0, nanosecond = 0, kind = Kind::Unspecified) | |||
def self.new(year, month, day, hour = 0, minute = 0, second = 0, *, millisecond = 0, kind = Kind::Unspecified) |
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 constructor should never be called if millisecond
isn't specified (no conversion required) so simply remove the default value and specs should pass.
But why do we want to keep that constructor? In any case, I doubt that |
@bcardiff please review the numbers in my last commit 😄 (specs are passing for me, I removed the default value for the |
d6dafea
to
c7cd6af
Compare
Another option is to remove the nanoseconds or milliseconds argument entirely, and simply use |
Please rebase this on master. |
spec/std/time/time_spec.cr
Outdated
|
||
it "initialize milliseconds 1_000_000" do | ||
Time.expect_invalid do | ||
Time.new(9999, 12, 31, 23, 59, 59, millisecond: 1_000_000) |
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 spec should be with millisecond: 1_000
@RX14 although I defend |
2d30867
to
29cf460
Compare
Rebased & squashed! |
spec/std/time/time_spec.cr
Outdated
end | ||
|
||
it "initialize max with milliseconds" do | ||
time = Time.new(9999, 12, 31, 23, 59, 59, millisecond: 999) |
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.
Is this correct? or should we use 999.999_999
so that it expands to 999_999_999
nanoseconds?
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.
999.999_999
won't pass the 0 <= millisecond <= 999
I think the issue is the name of the spec.
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.
we could then use 0 <= millisecond < 1_000
?
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 they are restricted to be integer since the initialize(*, @seconds : Int64, @nanoseconds : Int32, @kind : Kind)
is called at the end of the day
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.
We could add .to_i
just before calling the Int32
restricted constructor. I think that would work OK.
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 prefer them integer actually. All of them. But that's just my opinion 🤷♂️
I support @asterite, there is no need for many constructors and forcing kwargs on this. Why reintroduce a millisecond constructor?
|
I agree that perhaps having both constructors is perhaps too much, but I think we should keep the kwargs requirement even if we loose the milliseconds constructor (which appears to be the consensus). |
I don't think there should be a constructor accepting milliseconds. It's totally fine to just use nanoseconds, makes everything simpler to use just one grade of precision. |
src/yaml/schema/core/time_parser.cr
Outdated
@@ -109,7 +109,7 @@ struct YAML::Schema::Core::TimeParser | |||
|
|||
return nil if @reader.has_next? | |||
|
|||
time = new_time(year, month, day, hour, minute, second, millisecond: millisecond) | |||
time = new_time(year, month, day, hour, minute, second, nanosecond: millisecond * 1_000_000) |
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 doesn't feel to be the good way to get the nanoseconds out of milliseconds.
Should we allow a Time::Span
to be passed, so we would write: (..., nanosecond: millisecond.nanoseconds)
?
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 should this not be a good way? Multiplying by 1_000_000
is the proper transformation from milli do nano.
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.
probably, maybe I just don't like to see some numeric constants written in code like that, but it's totally fine actually.
@bcardiff @asterite @ysbaddaden another review? |
nanosecond
& kind
as named argument
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.
A bit more of time is needed at some places. 999999ns actually. 🙃
src/time.cr
Outdated
@@ -500,7 +500,7 @@ struct Time | |||
end | |||
end | |||
|
|||
def_at(end_of_year) { Time.new(year, 12, 31, 23, 59, 59, 999, kind: kind) } | |||
def_at(end_of_year) { Time.new(year, 12, 31, 23, 59, 59, nanosecond: 999, kind: kind) } |
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.
these at_end_of_*
are not returning the nearest representable time before the next interval.
nanosecond: 999_999_999
should probably be used instead of the old 999 milliseconds value.
I am not sure how this is playing when printing number with millisecond precision. It should truncate and not round to.
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.
You're totally right, thank a lot for your in-depth review! I fixed it 😄
src/time.cr
Outdated
@@ -509,7 +509,7 @@ struct Time | |||
else | |||
month, day = 12, 31 | |||
end | |||
Time.new(year, month, day, 23, 59, 59, 999, kind: kind) | |||
Time.new(year, month, day, 23, 59, 59, nanosecond: 999, kind: kind) |
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.
ditto
src/time.cr
Outdated
@@ -523,10 +523,10 @@ struct Time | |||
else | |||
month, day = 12, 31 | |||
end | |||
Time.new(year, month, day, 23, 59, 59, 999, kind: kind) | |||
Time.new(year, month, day, 23, 59, 59, nanosecond: 999, kind: kind) |
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.
ditto
src/time.cr
Outdated
end | ||
|
||
def_at(end_of_month) { Time.new(year, month, Time.days_in_month(year, month), 23, 59, 59, 999, kind: kind) } | ||
def_at(end_of_month) { Time.new(year, month, Time.days_in_month(year, month), 23, 59, 59, nanosecond: 999, kind: kind) } |
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.
ditto
src/time.cr
Outdated
def_at(midday) { Time.new(year, month, day, 12, 0, 0, 0, kind: kind) } | ||
def_at(end_of_day) { Time.new(year, month, day, 23, 59, 59, nanosecond: 999, kind: kind) } | ||
def_at(end_of_hour) { Time.new(year, month, day, hour, 59, 59, nanosecond: 999, kind: kind) } | ||
def_at(end_of_minute) { Time.new(year, month, day, hour, minute, 59, nanosecond: 999, kind: kind) } |
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.
ditto*3
spec/std/time/time_spec.cr
Outdated
@@ -34,15 +34,15 @@ describe Time do | |||
time.nanosecond.should eq(999_999_999) | |||
end | |||
|
|||
it "initialize millisecond negative" do | |||
it "initialize nanosecond negative" do |
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 this description should be fail initialize with negative nanosecond
WDYT?
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.
Sure. There are many specs in std with very crude labels... ;)
spec/std/time/time_spec.cr
Outdated
Time.expect_invalid do | ||
Time.new(9999, 12, 31, 23, 59, 59, -1) | ||
Time.new(9999, 12, 31, 23, 59, 59, nanosecond: -1) | ||
end | ||
end | ||
|
||
it "initialize nanoseconds 1_000_000_000" do |
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.
ditto, with fail initialize when nanosecond is a second
or something like that
This looks like a serious yet interesting issue, though I believe it is to much for this PR... |
@bcardiff I fixed the missing times in 999999ns! I'll squash the changes with proper commit message on your approval |
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.
LGTM. Sorry @bew for the many small reviews. Thanks for all the fixes!
901b249
to
1198566
Compare
No problem @bcardiff, it keeps the PR alive 😄 (and fix things!!) I've squashed the commits & rebased, so it should be ready to merge! |
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 not fond of forcing kwargs; it looks nice with literals (and it's encouraged) such nanosecond: 123
, but leads to noisy repetitions with variables, such as nanosecond: nanosecond, kind: kind
, as if the constructor wasn't large enough already.
I wish we'd only force kwargs for a couple releases, to help catch potential bugs (most likely none), then remove it and break nothing, because crystal is so cool :)
@ysbaddaden in this case the kwarg make it clearer since the subdivision is nos obvious from the argument. Plus due to the change of time precision it would help catch potential silent bugs. I like also the artifact of doing slight breaking changes across versions to allow safe migration from one to another. Similar to what I propose for IO refactoring into classes. But in this case I would stick with the kwarg, otherwise is to ambiguos IMO. |
@bew I just noticed the Time::Span use nanoseconds as argument name (in plural), but Time use nanosecond (in singular). As a non-native english speaker, is there a problem if both are unified in the plural form? That would match both types and also de ivar of Time. |
I believe the difference is that one tells the nanosecond of a time, whereas in the other this is the number of nanoseconds in a time span. |
@bcardiff When first reading your comment, I agreed that it would be coherent to have In the def self.new(year, month, day, hour, minute, second, *, nanosecond, kind) In the same way, (almost) all Should those getters be plural? I don't think so. |
@bew see my comment above. Time getters are singular because it's a snapshot of a time (singular), it's not elapsed seconds (plural). Time constructors must follow otherwise it would be inconsistent, and confusing, since it could mean "give me time at years + months + ... + seconds + nanoseconds". |
I think this should address #5069 (comment)
Somehow I can't get the spec to pass, I always get:
As if it doesn't find the other constructor with
millisecond
, and I don't see what I'm missing..In addition to have
millisecond
&nanosecond
, there is alsokind
as required named argument, I think it is a safe breaking change.