Skip to content
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

Improvements to the Time APIs #1288

Merged
merged 1 commit into from
Nov 16, 2015

Conversation

wycats
Copy link
Contributor

@wycats wycats commented Sep 21, 2015

@nrc nrc added the T-libs-api Relevant to the library API team, which will review and decide on the RFC. label Sep 21, 2015

It provides convenience methods for constructing Durations from larger units
of time (minutes, hours, days), but gives them names like
`Duration.from_standard_hour`. A standard hour is 3600 seconds, regardless of
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

regardless of…?

@wycats
Copy link
Contributor Author

wycats commented Sep 21, 2015

@nagisa thanks for the early feedback. I'll correct these typos ASAP!

the timeframe of the process it was created in.

> In this context, monotonic means that a timestamp created later in real-world
> time will always be larger than a timestamp created earlier in real-world
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pedantically speaking, "no smaller than", since you might pull the time twice within the same tick.

@SimonSapin
Copy link
Contributor

Where should Duration::span go? An associated function of ProcessTime? A free function? (Having it in a single expression with a closure is sometimes nicer than let start = ProcessTime::now(); /* do something */; start.elapsed()

By the way, should ProcessTime and SystemTime have now() constructors?

@retep998
Copy link
Member

I like having the distinction between monotonic and system time.

@aturon
Copy link
Member

aturon commented Sep 21, 2015

cc @lifthrasiir

@asb
Copy link

asb commented Sep 22, 2015

Might it be worth contrasting this API with that offered by e.g. Jodatime,
Haskell's time library (
https://www.reddit.com/r/haskell/comments/3j2o7a/finally_a_great_tutorial_for_the_time_library/),
Go's time library, ...?

On 22 September 2015 at 00:05, Aaron Turon notifications@github.com wrote:

cc @lifthrasiir https://github.com/lifthrasiir


Reply to this email directly or view it on GitHub
#1288 (comment).

@yazaddaruvala
Copy link

Hopefully not just JodaTime but also java.time
Which is a redesign of JodaTime, by its author, for Java 8

The article is clearly really old, but might help this design:
http://blog.joda.org/2009/11/why-jsr-310-isn-joda-time_4941.html

@yazaddaruvala
Copy link

@wycats from the wording in the current draft it seems like the biggest problem isn't actually monotonicity of time but the creation of negative durations. If there are no other issues with time lacking monotonicity, one alternative I haven't seen discussed but would like to mention is to instead split up Duration.

SystemTime, ProcessTime -> Instant
Duration -> uDuration, iDuration (aka UnsignedDuration, SignedDuration)

Apart from naming conventions, I kinda like the consistency it has with numbers.
APIs that don't deal with negative 64bit numbers only accept u64.
APIs that don't deal with negative durations only accept UnsignedDuration.

I don't think it even changes all that much:

impl SignedDuration {
  // Maybe use `as` for conversions?
  pub fn to_unsigned(&self) -> Result<UnsignedDuration, Error>;

  ...
}

impl Instant {
  pub fn duration_from(&self, other: Instant) -> SignedDuration;

  pub fn duration_from_now(&self) -> SignedDuration;

  /// Panics if `earlier` is later than &self.
  /// Because ProcessTime is monotonic, the only time that `earlier` should be
  /// a later time is a bug in your code.
  pub fn duration_from_earlier(&self, earlier: Instant) -> UnsignedDuration {
    self.duration_from(earlier).to_unsigned().expect('Sorry, that wasn't earlier..')
  }

  /// Panics if self is later than the current time
  pub fn elapsed(&self) -> Duration;
}

Pros:
Duration could be a trait, allowing generic extensions like impl formatting, and user defined Durations (i.e. BigIntDuration)
Allows for use of SignedDuration when appropriate
Given an understanding of Rust's numbers, SignedDuration vs UnsignedDuration might be more intuitive, while ProcessTime vs SystemTime would require a tiny explanation

Cons:
Leaving methods like duration_from_earlier and elapsed on Instant may cause unexpected prod bugs which we are trying to avoid. With some ergonomic sacrifice, only expose duration_from/_now and let users handle conversion.
How should a SignedDuration convert to an UnsignedDuration?

Let me know what downsides I'm not aware of or am currently missing. Thanks.

P.S. After writing this up, if it helps, I did find: https://internals.rust-lang.org/t/unsigned-version-of-duration/893

@Veedrac
Copy link

Veedrac commented Sep 23, 2015

I have nothing against a positive-first API, but one should not ignore negative durations either. That said, I can't really think of a case where you'd want signed durations and not want to branch off their direction.

So my suggestion is that the Result<Duration, SystemTimeError> should be changed to Result<Duration, Duration>, where the later Duration is the negative of the difference. This makes it clear that the only case SystemTimeError was accounting for is time travel, and makes it really obvious how to deal with negative cases. When you do want a SignedDuration, Result<Duration, Duration> is only a little bigger (and could reasonably be optimized to the same size in the future by re-using padding).

@arielb1
Copy link
Contributor

arielb1 commented Sep 24, 2015

I don't think libstd should contain a calendar - these contain lots of subjective edge cases. However, we probably want some form of absolute time for cross-system timestamps (we would need to choose between classic UNIX, UTS, or TAI). Leap-second-aware format conversions would be nice.

@eternaleye
Copy link

Personally, I strongly favor TAI for cross-system times, for the following reasons:

  • POSIX time is incoherent-as-specified, as it mandates both an 86,400-second day and that it is UTC, which due to leap seconds does not have an 86,400-second day
  • UTC, strictly speaking, only applies to the string-based format in which "XX:YY:60" can be represented
  • The duration between two UTC times can change at any point before the latter of them has passed, due to leap seconds being awful

@cristicbz
Copy link

Two minor suggestions:

    pub struct StdHours(pub u64);
    pub struct StdMinutes(pub u64);
    // ...
    impl From<StdHours> for Duration {
        // ...
    }
    // ...

Then conversions become as simple as: Duration::from(StdHours(3)) and it ensures that I can store strongly typed numbers of hours/minutes etc. without getting them mixed up.

What do you think?

Edit: One worry might be panicking From conversions. I don't have a problem with that (since I don't have a problem with n as u32 panicking for large values of n), but I know some people might.

@wycats
Copy link
Contributor Author

wycats commented Sep 25, 2015

Replying to @cristicbz (more later on other comments):

I don't love MonotonicTime since I expect this to be the primary API developers use for things like benchmarks, and an obscure name might put them off.

The point about being allowed to use BOOT_TIME is good, though. Can you think of a friendly name that communicates monotonicity? TimeThatObeysTheLawsOfPhysics :p

I don't object to conversions at first glance and agree that they read nicely, provide nice ergonomics for mixing, and make it easy to avoid mistakes.

I'll let others weigh in on the panicking issue before I form a strong opinion though :) @aturon?

@eternaleye
Copy link

I think that C++'s steady_time would be effectively perfect (as SteadyTime) for CLOCK_MONOTONIC_RAW, but for CLOCK_MONOTONIC there's some difficulty. I think SequentialTime is about as good as it can get:

  • To a first approximation, sequential : monotonic :: ordinal : cardinal
  • Unlike SteadyTime it does not imply the intervals are identical
  • Sequential is a much more common word than monotonic

For CLOCK_BOOTTIME, there's the complication that it behaves like CLOCK_MONOTONIC in every way, except that it also covers time suspended. Thus, both have equal claim to SequentialTime, but constructions like SequentialTimeWithoutSuspended are, to put it frankly, ugly as sin.

As far as use cases go, what should each be used for?

  • CLOCK_MONOTONIC_RAW - anything that needs to (computationally) compare intervals/durations for relative size within a single machine. Benchmarks are a big one -"X took 1.6x as long as Y" requires constant-length ticks.
  • CLOCK_BOOTTIME - Anything that wants to give relative times in human units that correspond to external events ("Your last sync was six hours ago")
  • CLOCK_MONOTONIC - Anything that wants to give relative times in human units that correspond to internal events ("This process has been hung for five minutes. Kill it?")

Notably not covered:

  • Intervals that can be both compared and converted to human units. The system APIs just don't exist.
  • Intervals that can be compared across machines. This is not only difficult in practice, it's always going to have insoluble limitations due to physics. CLOCK_MONOTONIC is probably as good as we can get since its seconds should be roughly one SI second, but it's still not very good at all.
  • Intervals whose endpoints lie on different machines. CLOCK_TAI is probably the best that can be done here, and only if we assume accurately-synced clocks on both. (At least TAI lets us ignore leap seconds once we've got the Instant in our hands, unlike UTC/CLOCK_REALTIME).

@nagisa
Copy link
Member

nagisa commented Nov 4, 2015

Is there any indication of a fixed upper-bound on the non-monotonicity, such that it could be avoided by masking low bits?

That’s not a valid solution, is it?

Regarding @vadimcn’s link, virtualised environments are a pretty interesting use case/test which might uncover a whole can of worms with every platform we support (and possibly various different processors that might or might not have “interesting” clocks, if any), since they might not allow virtualised kernel to access hardware directly and other small details.

@eternaleye
Copy link

@nagisa

I was hoping it was like the architectural constraints/guarantees for RDTIME on the RISC-V architecture, which permits 1 tick of inconsistency between cores.

@vadimcn
Copy link
Contributor

vadimcn commented Nov 4, 2015

@eternaleye: I don't recall seeing the 2-3s number anywhere. I would guess that non-monotonicity is on the order of as few ticks (as returned by QueryPerformanceFrequency(), but who knows... Obviously, Microsoft did not intend there to be any non-monotonicity, so it's a hardware/HAL bug.
I don't think we should try to paper it over, though I'd be in favor of providing APIs that help in dealing with such errors (for example deriving a non-negative timeout from two Instants).

@eternaleye
Copy link

@vadimcn

I don't recall seeing the 2-3s number anywhere.

It's a comment by the original reporter in response to a question about the magnitude.

What is the value of ElapsedMilliseconds when negative? – Michael Haren Jun 17 '09 at 17:04

On my VM it around -3000 to -2000 – Vadym Stetsiak Jun 17 '09 at 17:11

@vadimcn
Copy link
Contributor

vadimcn commented Nov 4, 2015

@eternaleye: he probably meant 'ticks', not 'milliseconds'.

@eternaleye
Copy link

@vadimcn

ElapsedMilliseconds is consistently used - both in the original post's code example and the comments.

@arielb1
Copy link
Contributor

arielb1 commented Nov 4, 2015

@eternaleye

Yeah - having a UNIX_EPOCH without specifying whether we're counting SI or UT1 is stupid. I suppose we should just say we are counting "unspecified-UT1" for this API.

@kryptan
Copy link

kryptan commented Nov 4, 2015

Seems that no one mentioned but according to wikipedia the desicion whether to keep or abolish leap seconds will be made at World Radiocommunication Conference which is scheduled for November 2–27 (i.e. it is happening now). Indeed, their page states:

WRC-15 will address a number of key issues, in particular:

  • ...
  • ... the feasibility of achieving a continuous reference time-scale (UTC) with the possible suppression of the “leap second”

Lets hope that they will do the right thing.

`Duration` when used correctly.

This design does not assume that negative `Duration`s are never useful, but
rather than the most common uses of `Duration` do not have a meaningful
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: s/rather than/rather that/

@alexcrichton
Copy link
Member

The libs team discussed this RFC during triage last week and after some final clarifications we've decided to merge. The last pieces were indicating that Instant is not always increasing, but rather never decreasing, and guaranteeing that SystemTime is UTC.

For now we've decided to take the conservative route of not trying to exhaustively specify all aspects of the two types here, but as usual there will be a period of #[unstable] for these implementations in the standard library where final tweaks and changes can be made (especially around documentation and such). The implementations are likely to follow what already exists in the time crate as well as similar C++ implementations.

Thanks again for all the discussion everyone!

@alexcrichton alexcrichton merged commit d8133de into rust-lang:master Nov 16, 2015
@alexcrichton
Copy link
Member

Tracking issue

@alexcrichton
Copy link
Member

I've now got an implementation of this RFC, so if you'd like to take a look over it please feel free to leave a comment there!

alexcrichton added a commit to alexcrichton/rust that referenced this pull request Nov 19, 2015
This commit is an implementation of [RFC 1288][rfc] which adds two new unstable
types to the `std::time` module. The `Instant` type is used to represent
measurements of a monotonically increasing clock suitable for measuring time
withing a process for operations such as benchmarks or just the elapsed time to
do something. An `Instant` favors panicking when bugs are found as the bugs are
programmer errors rather than typical errors that can be encountered.

[rfc]: rust-lang/rfcs#1288

The `SystemTime` type is used to represent a system timestamp and is not
monotonic. Very few guarantees are provided about this measurement of the system
clock, but a fixed point in time (`UNIX_EPOCH`) is provided to learn about the
relative distance from this point for any particular time stamp.

This PR takes the same implementation strategy as the `time` crate on crates.io,
namely:

|  Platform  |  Instant                 |  SystemTime              |
|------------|--------------------------|--------------------------|
| Windows    | QueryPerformanceCounter  | GetSystemTimeAsFileTime  |
| OSX        | mach_absolute_time       | gettimeofday             |
| Unix       | CLOCK_MONOTONIC          | CLOCK_REALTIME           |

These implementations can perhaps be refined over time, but they currently
satisfy the requirements of the `Instant` and `SystemTime` types while also
being portable across implementations and revisions of each platform.
bors added a commit to rust-lang/rust that referenced this pull request Nov 19, 2015
This commit is an implementation of [RFC 1288][rfc] which adds two new unstable
types to the `std::time` module. The `Instant` type is used to represent
measurements of a monotonically increasing clock suitable for measuring time
withing a process for operations such as benchmarks or just the elapsed time to
do something. An `Instant` favors panicking when bugs are found as the bugs are
programmer errors rather than typical errors that can be encountered.

[rfc]: rust-lang/rfcs#1288

The `SystemTime` type is used to represent a system timestamp and is not
monotonic. Very few guarantees are provided about this measurement of the system
clock, but a fixed point in time (`UNIX_EPOCH`) is provided to learn about the
relative distance from this point for any particular time stamp.

This PR takes the same implementation strategy as the `time` crate on crates.io,
namely:

|  Platform  |  Instant                 |  SystemTime              |
|------------|--------------------------|--------------------------|
| Windows    | QueryPerformanceCounter  | GetSystemTimeAsFileTime  |
| OSX        | mach_absolute_time       | gettimeofday             |
| Unix       | CLOCK_MONOTONIC          | CLOCK_REALTIME           |

These implementations can perhaps be refined over time, but they currently
satisfy the requirements of the `Instant` and `SystemTime` types while also
being portable across implementations and revisions of each platform.

cc #29866
@tioover
Copy link

tioover commented Jan 31, 2016

Why not implement Hash to Duration and Instant?

For example

use std::time::Instant;

#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
struct ID {
    random: u32,
    instant: Instant,
}

@sfackler
Copy link
Member

@tioover Duration now implements Hash: rust-lang/rust#30818. The lack of an implementation for Instant is just an oversight - feel free to make a PR adding the impl.

Since this RFC has been merged, rust-lang/rust#29866 is the new place for discussion, by the way.

@Centril Centril added A-time Proposals relating to time & dates. A-types-libstd Proposals & ideas introducing new types to the standard library. labels Nov 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-time Proposals relating to time & dates. A-types-libstd Proposals & ideas introducing new types to the standard library. final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. T-libs-api Relevant to the library API team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.