Skip to content

Tracking Issue for duration_constructors #120301

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

Open
1 of 3 tasks
djc opened this issue Jan 24, 2024 · 53 comments
Open
1 of 3 tasks

Tracking Issue for duration_constructors #120301

djc opened this issue Jan 24, 2024 · 53 comments
Labels
C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@djc
Copy link
Contributor

djc commented Jan 24, 2024

Feature gate: #![feature(duration_constructors)]

This is a tracking issue for supporting larger unit sizes for Duration constructors.

Public API

Add the following constructors to Duration:

  • Duration::from_weeks()
  • Duration::from_days()
  • Duration::from_hours()
  • Duration::from_mins()

Steps / History

@djc djc added C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. labels Jan 24, 2024
@the8472
Copy link
Member

the8472 commented Jan 24, 2024

Similar things were previously proposed in #47097, #52556
They were intentionally omitted in RFC 1040 because leap seconds complicate things to make time-calculations non-trivial.

Imo it requires some significant discussion to add these. A PR and a tracking issue without context are insufficient.

@djc
Copy link
Contributor Author

djc commented Jan 24, 2024

Thanks for adding this context. I was fairly certain that this would have been discussed before, but searching the issue tracker (for open issues) didn't turn up any existing discussion. I think it's okay to have this discussion here/now?

From RFC 1040:

This proposal does not, at this time, include mechanisms for instantiating a Duration from weeks, days, hours or minutes, because there are caveats to each of those units. In particular, the existence of leap seconds means that it is only possible to properly understand them relative to a particular starting point.

It goes on to quote JodaTime documentation; I'll quote the current version of this:

A period in Joda-Time represents a period of time defined in terms of fields, for example, 3 years 5 months 2 days and 7 hours. This differs from a duration in that it is inexact in terms of milliseconds. A period can only be resolved to an exact number of milliseconds by specifying the instant (including chronology and time zone) it is relative to.

Note that the RFC is from 2015 and the latest discussion in #52556 is from 2018 so I feel like revisiting this decision isn't all that crazy.

Personally I feel this distinction is well-established in the Rust ecosystem at this point, and there's a clear interplay between SystemTime (grounded in real-world time) and Duration (which presents a pure duration). As such, I personally don't feel like leap seconds are a good reason to avoid adding these. I would also argue that both chrono and time are very popular libraries in the ecosystem, and both have made these available seemingly without many ill effects.

IMO each of these constructors have a clear unambiguous meaning in the context of a Duration. If we want, we could of course add some documentation that leap seconds are not considered.

Note: I happen to be the current chrono maintainer, so I have some background in these things.

@Mark-Simulacrum
Copy link
Member

We may want to consider as part of stabilization whether these could "just" (or in addition should be) associated constants. e.g., Duration::WEEK * 5 == Duration::from_weeks(5). We have paired constants for all existing constructor functions: millisecond, second, and nanosecond.

matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Feb 11, 2024
…Simulacrum

core: add Duration constructors

Add more `Duration` constructors.

Tracking issue: rust-lang#120301.

These match similar convenience constructors available on both `chrono::Duration` and `time::Duration`.

What's the best ordering for these with respect to the existing constructors?
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Feb 11, 2024
Rollup merge of rust-lang#120307 - djc:duration-constructors, r=Mark-Simulacrum

core: add Duration constructors

Add more `Duration` constructors.

Tracking issue: rust-lang#120301.

These match similar convenience constructors available on both `chrono::Duration` and `time::Duration`.

What's the best ordering for these with respect to the existing constructors?
@djc
Copy link
Contributor Author

djc commented Feb 12, 2024

We may want to consider as part of stabilization whether these could "just" (or in addition should be) associated constants. e.g., Duration::WEEK * 5 == Duration::from_weeks(5). We have paired constants for all existing constructor functions: millisecond, second, and nanosecond.

I'm happy to add them, but I'd be disappointed to remove the new constructors in favor of these.

@Victor-N-Suadicani
Copy link
Contributor

Just thought about this and found this issue :)

I prefer the constructors to associated constants.

Have you considered making the constructors take u32 instead of u64, so they don't need to worry about overflow and hence panicking? You lose the option of making a duration longer than u32::MAX * 60 seconds using the from_mins constructor, but that's like 8171 years, so maybe it's okay? 😅

(PS: I think the panic message in from_weeks mistakenly says from_days)

@neumann-paulmann
Copy link

neumann-paulmann commented May 14, 2024

(PS: I think the panic message in from_weeks mistakenly says from_days)

Yup. That looks like a classic case of copy-paste error.

Also

Duration::from_secs(weeks * MINS_PER_HOUR * SECS_PER_MINUTE * HOURS_PER_DAY * DAYS_PER_WEEK)

is rather confusing.
Why not order the constants accordingly to make it simpler to reason about this like of code? E.g.:

Duration::from_secs(SECS_PER_MINUTE * MINS_PER_HOUR * HOURS_PER_DAY * DAYS_PER_WEEK * weeks)

@edwardwc
Copy link

@Mark-Simulacrum @djc I have attempted to add these associated constants in an idiomatic manner: #127700

I would love your feedback!

@eggyal
Copy link
Contributor

eggyal commented Jul 14, 2024

I notice that it isn't currently proposed to add Duration::from_months or Duration::from_years constructors. Why not? Presumably because their durations are variable. Of course, we could document that such constructors are based on a month being exactly 30 days and a year being exactly 365 days, each of exactly 24 hours each of exactly 60 minutes each of exactly 60 seconds... but wouldn't that just be a footgun that users would often misuse and then puzzle over buggy results? Is not the same true of leap seconds and the proposed constructors?

@ChrisDenton
Copy link
Member

We also don't have from_centuries or from_millenia. But these are convenience functions so they're not essential. There's no reason to add them just to round out the API.

It's notable that neither chrono nor time equivalents of Duration are interested in years, which suggests there's no great demand for the API.

@liigo
Copy link
Contributor

liigo commented Dec 26, 2024

In particular, the existence of leap seconds means that it is only possible to properly understand them relative to a particular starting point.

1 minute = 60 seconds. I don't think the leap second should be involved in (if then, 1 second != 1 second, Duration::from_secs shouldn't exist too).

@tbu-
Copy link
Contributor

tbu- commented Jan 16, 2025

Is there motivation for these constructors beyond the following?

These match similar convenience constructors available on both chrono::Duration and time::Duration.

If there's no motivation except for this, I think we should rather omit them from the standard library and let users be explicit that they want to sleep for 300 seconds and not 5 minutes (the latter being ambiguous wrt. leap seconds).

@youknowone
Copy link
Contributor

youknowone commented Jan 17, 2025

Leap second is a concept about absolute time - more specifically, about calendar. Duration is about the amount of time. Even though leap seconds affect the time related API design, it will be related to Instant, SystemTime or something else, not to Duration.

23:59:45 + 60 seconds could be one of 24:00:44, 24:00:45 or 24:00:46 by how to design the absolute time. But the duration 60 seconds could mean a physical quantity 60 seconds. If this still can be ambiguous, this can be documented enough on Duration.

@TimNN
Copy link
Contributor

TimNN commented Apr 16, 2025

Some prior art from other languages / libraries:

  • Abseil Time Durations:
    • Has up to absl::Hours(...)
  • java.time.Duration
    • Has up to Duration::ofDays(...)
    • The first sentence of the doc comment is very precise:
      • "[...] representing a number of standard 24 hour days"
      • "[...] representing a number of standard hours"
      • "[...] representing a number of standard minutes"
      • Smaller units lack the "standard"

Ohter languages and libraries that I checked1 did not have a comparable "Duration" concept. If you know of any, let me know and I'll be happy to add them.

Personal thoughts:

  • "weeks" feel like a property of Civil Time
    • Maybe there are even calendar systems that define them differently?
  • I like how explicit the Java documentation is
    • Especially that it mentions "24 hours" for the "days" factory.

Footnotes

  1. C++'s chrono is heavily template-based, Python's timedelta stores days as a separate component, JavaScript's luxon stores all units as separate components

@BurntSushi
Copy link
Member

BurntSushi commented Apr 16, 2025

I'm fine with adding hours/minutes constructors. I think those are pretty uncontroversial in what their semantics ought to be. Only leap seconds impact the length of an hour/minute, but we should just do what everyone else does and what SystemTime does: ignore leap seconds. Especially just for the constructors. That is, I'm not aware of real world use cases where someone wants to write Duration::from_mins(1) and expect that to be anything other than 60 seconds. AFAIK, not even hifitime makes that distinction, and its main selling point is that it doesn't ignore leap seconds.

But I'm strongly opposed to adding a constructor based on units of days (or anything bigger). I think that's a major mistake because it is common for days to not be 24 hours and for this to actually matter to humans. The new Javascript Temporal API, for example, generally does not let you treat days as "always 24 hours" unless you're explicitly working with civil time. When you're working with zoned datetimes, then 1 day might be more or less than 24 hours.

@Dietr1ch
Copy link
Contributor

Dietr1ch commented Apr 18, 2025

I'm not aware of real world use cases where someone wants to write Duration::from_mins(1) and expect that to be anything other than 60 seconds. AFAIK, not even hifitime makes that distinction, and its main selling point is that it doesn't ignore leap seconds.

I don't think the ("absolute") time keeping subtleties like leap second smearing, timezone differences or daylight saving time changes would trip people.

A Duration measures length of the passing of time, and time keeping subtleties around "absolute time" have nothing to do with this length.

Maybe English isn't the best language to describe this as "time" is used for both, "absolute time" and relative time, but I think it's better to admit this ambiguity that often appears on spoken language and stick to well defined Duration and Time concepts that many other libraries already use and seem to be well understood already.


I'm not aware of real-world confusion, but only some cautious pushback around "standard" minutes/hours/days/weeks. (Yeah, I guess months being 30 days or years 365 days would be harder to justify, but still somewhat reasonable picks if we ever want to go there).

@BurntSushi
Copy link
Member

I don't think the ("absolute") time keeping subtleties like leap second smearing, timezone differences or daylight saving time changes would trip people.

They can and do. Compare adding 1 day to 2025-03-08T17:00-05 in New York and adding 24 hours.

(Yeah, I guess months being 30 days or years 365 days would be harder to justify, but still somewhat reasonable picks if we ever want to go there).

Those might be reasonable in specific contexts where the error is deemed acceptable, but I think it is a terrible choice to make for a general purpose library.

@Dietr1ch
Copy link
Contributor

Dietr1ch commented Apr 18, 2025

They can and do. Compare adding 1 day to 2025-03-08T17:00-05 in New York and adding 24 hours.

Doing that where? This to me seems to be an issue with defining the + operator to deal with a Time + Duration sum.

When trying to define the length of Duration::from_days(1) to be the same as Duration::from_hours(24) we haven't even mentioned "absolute time" (Time) yet.

Maybe what we need to avoid possible footguns is to properly define + for Time+Duration and its subtlety-aware evil twin as fundamentally different functions.

@Dietr1ch
Copy link
Contributor

Dietr1ch commented Apr 18, 2025

Also, like BurntSushi suggested, since the controversial bits seem to be mostly around Duration::from_weeks and Duration::from_days, is it possible to either split this, or partially deliver this to get Duration::from_hours and Duration::from_mins out sooner?

I'm mostly interested on not forcing people to download a new nightly build every day just because of these 2 missing functions.

@BurntSushi
Copy link
Member

I would question that "one day in elapsed actual time" has an unambiguous meaning. But I have no problem asking users to write Duration::from_hours(24) for cases where they really want "days" to mean "always 24 hours." What I'm objecting to here is a universal claim that days are always 24 hours absent any other context.

@the8472
Copy link
Member

the8472 commented Apr 18, 2025

Then how would you normally provide this context? Should we make duration generic over a clock source? I don't think this is possible retroactively because durations can be transplanted between Instant and SystemTime mentioned above.

@BurntSushi
Copy link
Member

BurntSushi commented Apr 18, 2025

Then how would you normally provide this context?

In std? I don't think I would. Or at least, we'd need to back all the way up and re-examine what problem we're trying to solve. In this context, we're looking to add a few convenience constructors whose utility is presumably self-evident. I'm opposed to convenience constructors for std::time::Duration based on calendar units because it isn't always appropriate and has real world footguns. But if we want to seriously consider alternative designs for appropriately contextualizing the concept of "days," then we've left the neighborhood of self-evidently useful utility constructors. The framing of the discussion would need to totally change.

@pravic
Copy link
Contributor

pravic commented Apr 18, 2025

I gave an example. Please respond with one. If Duration::from_days exists, how are you going to prevent the footgun I demonstrated?

@BurntSushi I am no expert in time libraries, but Chrono seems do the job: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=f1cd329588b14f9580cddc59965481a5.

@Dietr1ch
Copy link
Contributor

I gave an example. Please respond with one. If Duration::from_days exists, how are you going to prevent the footgun I demonstrated?

@BurntSushi I am no expert in time libraries, but Chrono seems do the job: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=f1cd329588b14f9580cddc59965481a5.

Note that on the example on 2025-03-09 NY is no longer at UTC-05:00, but at UTC-04:00.

@BurntSushi
Copy link
Member

BurntSushi commented Apr 18, 2025

I gave an example. Please respond with one. If Duration::from_days exists, how are you going to prevent the footgun I demonstrated?

@BurntSushi I am no expert in time libraries, but Chrono seems do the job: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=f1cd329588b14f9580cddc59965481a5.

It does not. 2025-03-09 13:00:00 -05:00 isn't valid for New York. By that point, it's in DST and should have an offset of -04.

Case in point, with your setup, Chrono will let you generate a time that will literally never appear on the clocks in New York: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=8c06a26675f11c2f1e17c5f572e3b40b

You need to be using chrono-tz to get the correct behavior here.

(This is one of the many reasons I went out and built Jiff.)

@the8472
Copy link
Member

the8472 commented Apr 18, 2025

I'm opposed to convenience constructors for std::time::Duration based on calendar units because it isn't always appropriate and has real world footguns.

Well, they're also physical units because almost nobody uses megaseconds.
For example NIST recognizes days as a time unit equivalent to 86400s, the IAU even uses the julian year as a fixed unit, e.g. for the lightyear definition.

This is what I was aiming for with from_tai_days, I was trying to provide the context that they're physical units of time, not calendar time.

@tyilo
Copy link
Contributor

tyilo commented Apr 18, 2025

Isn't the problem with days and DST the same for minutes and leap seconds?

@BurntSushi
Copy link
Member

Sure. The problem is that "day" is an overload term. It is difficult to establish the right context while still using the word "day." Like it just doesn't seem worth it to be given how easy the footgun is personally.

@Dietr1ch
Copy link
Contributor

Dietr1ch commented Apr 18, 2025

It seems to me we just need a CivicTimeOffset struct to signify the offset humans think of when you say "1 day later"

//                                                                                       |  |
let appointment = SystemTime::UNIX_EPOCH + Duration::from_secs(1741456800);  // 2025-03-08T13:00:00-05:00[America/New_York]
let poorly_rescheduled  = appointment + Duration::from_days(1);              // 2025-03-09T14:00:00-04:00[America/New_York]
let civicly_rescheduled = appointment + CivicTimeOffset::days(1);            // 2025-03-09T13:00:00-04:00[America/New_York]

We should probably delay Duration::from_days and larger multipliers until we have something to signify "tomorrow, same time" also coming to the standard library (which might be never due to how hairy absolute time can get).

(had to move this because it got lost amidst a discussion)

@BurntSushi
Copy link
Member

Isn't the problem with days and DST the same for minutes and leap seconds?

Already addressed above: #120301 (comment)

@BurntSushi
Copy link
Member

Adding an entirely different duration type seems like a major escalation to me. It's also a totally different discussion from my perspective, and starts walking down the path of "let's add datetime support to std." Doing it half-assed would be a disaster. If you really want to have that discussion, I'd suggest opening a new issue so that we don't get derailed from considering the much more narrow proposal of convenience constructors on Duration.

@Dietr1ch
Copy link
Contributor

Adding an entirely different duration type seems like a major escalation to me. It's also a totally different discussion from my perspective, and starts walking down the path of "let's add datetime support to std." Doing it half-assed would be a disaster.

I'm aware of this. The progression we all seem to foresee is,

  1. Adding Duration::from_mins and Duration::from_hours
  2. Properly modeling DateTime and its subtleties in std
  3. Adding Duration::from_days and friends as now we warn people that they might not be doing what they meant and finally give them a working alternative.

And of course 2 is a huge task, but that shouldn't stop everyone from getting 1. soon.

@BurntSushi
Copy link
Member

If from_days and from_weeks are removed, I'd be happy to start an FCP here.

@youknowone
Copy link
Contributor

youknowone commented Apr 18, 2025

I am glad we seem overcoming the leap second issue.

Unfortunately, the definition of day is too much ambiguous. It even doesn't have a single definition in science.

I'd like to remind a thing. Convenience constructors are provided for convenience. They don't provide a new real feature.

If the name matters, I hope the next discussion to be not to have or not the specifically named function from_days, but whether we need the concept of from_days or not. If not, we don't need to worry about the name. Otherwise, we can avoid the name from_days and discuss a better name of it.

@youknowone
Copy link
Contributor

While I have no idea how much from_weeks is useful, I had experience (not the name of but) the concept of from_days is useful to help not to do * 86400 everywhere. Its name may can be from_24hours, which is not very attractive though.

At the same time from_days is still less useful than from_mins or from_hours. I upvote for from_mins and from_hours regardless how others go.

@Dietr1ch
Copy link
Contributor

So, to play the devil's advocate against from_mins and from_hours we have the same issue BurntSushi presented scaled down to leap seconds as the ambiguity lies in misinterpreting Duration for a human offset that corrects for leap seconds.


This is way more unlikely to be a problem. Even if we could end up just saying the programmer was wrong and had a fundamental misunderstanding of what Duration stands for, it wouldn't inconvenience people depending on the buggy program too much. Failing to account for the extra second at 23:59:60 or the missing 23:59:59 wastes less time than reading this comment.

Also, we may not even have more leap seconds in the future due to a decrease on the Earth's rotation cancelling it1.

Footnotes

  1. https://en.wikipedia.org/wiki/Leap_second#Procedure

@ChrisDenton
Copy link
Member

I don't think there's any need to play devil's advocate nor a reason to continue blocking stabilizing from_mins and from_hours, which seems to be acceptable to everyone so far.

@Dietr1ch
Copy link
Contributor

Dietr1ch commented Apr 18, 2025

@djc are you open to gut this to get from_mins and from_hours out?

I truly believe we should be free to get all of it, but I'd rather see some progress than waiting on a DateTime implementation getting to std.

@tkr-sh
Copy link
Contributor

tkr-sh commented May 7, 2025

I don't know at which point is this possible, but can't this be splitted into 2 issues ?

  1. from_mins & from_hours which seems to be consensual, and that will remove them from the feature gating of duration_constructors
  2. from_days & from_weeks which needs more time

This would stabilize some useful API, and will minify the content of each PR so that the comments on from_days aren't in a PR that also concerns from_mins.

@djc
Copy link
Contributor Author

djc commented May 7, 2025

I'm fine with someone splitting the feature, although I am not particularly motivated to do so.

But I'm strongly opposed to adding a constructor based on units of days (or anything bigger). I think that's a major mistake because it is common for days to not be 24 hours and for this to actually matter to humans. The new Javascript Temporal API, for example, generally does not let you treat days as "always 24 hours" unless you're explicitly working with civil time. When you're working with zoned datetimes, then 1 day might be more or less than 24 hours.

I think this is coming in pretty strong; did you read my early comment where I argued that in the context of a type named Duration these all seem pretty unambiguous? A duration of two days is the same as 48 hours, and I think this is commonly understood. At the usage site, the callee will just get a Duration which also is a pretty clear concept. In that sense, I think your code sample misrepresents usage because it creates a from_days() abstraction that omits usage of the Duration type name.

Maybe we can add a word to the name instead? Like from_common_days()?

@liigo
Copy link
Contributor

liigo commented May 7, 2025

common days should be named commonly, from_days(). from_uncommon_days() may be used to days that is less than 24 hours. (the later is very hard to define, because no one knowns the precise number of hours in a day, if the number is not 24.)


other than that, i suggest adding Duration::from_days(value: f64), the double-precision floating-point value hints that we don't care about the very precision here.

@youknowone
Copy link
Contributor

i suggest adding Duration::from_days(value: f64), the double-precision floating-point value hints that we don't care about the very precision here.

This is not the point. "1.00000 day" still doesnt define any strict physical duration. This is matter of unit, not the counts

@BurntSushi
Copy link
Member

But I'm strongly opposed to adding a constructor based on units of days (or anything bigger). I think that's a major mistake because it is common for days to not be 24 hours and for this to actually matter to humans. The new Javascript Temporal API, for example, generally does not let you treat days as "always 24 hours" unless you're explicitly working with civil time. When you're working with zoned datetimes, then 1 day might be more or less than 24 hours.

I think this is coming in pretty strong

Yeah I feel strongly about this.

did you read my early comment where I argued that in the context of a type named Duration these all seem pretty unambiguous?

Yes. I don't understand why you're suggesting that the name Duration makes anything better here. Say more?

A duration of two days is the same as 48 hours, and I think this is commonly understood.

I don't agree. This is what I tried to show with my example above.

At the usage site, the callee will just get a Duration which also is a pretty clear concept. In that sense, I think your code sample misrepresents usage because it creates a from_days() abstraction that omits usage of the Duration type name.

I renamed from_days to duration_from_days in the example above. At least for me, it doesn't change anything about the example. So I don't understand the misrepresentation that you're pointing out. Say more?

Maybe we can add a word to the name instead? Like from_common_days()?

I don't really think it's worth it to be honest, and it's not clear that this name will do much if anything to avoid the footguns I've pointed out.

@Dietr1ch
Copy link
Contributor

Dietr1ch commented May 10, 2025

I'm fine with someone splitting the feature, although I am not particularly motivated to do so.

I forked this feature into duration_constructors_lite and it's implementation into #140882.

@Dietr1ch
Copy link
Contributor

Little update, the watered down version of this feature made it into unstable, so please comment on duration_constructors_lite if you have any reservations or other comments around Duration::from_secs and Duration::from_mins implemented originally as part of the "full" feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests