Skip to content

Support Timestamp and Duration types #434

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

Merged
merged 9 commits into from
May 26, 2021
Merged

Support Timestamp and Duration types #434

merged 9 commits into from
May 26, 2021

Conversation

npars
Copy link
Contributor

@npars npars commented Apr 30, 2021

  • Implement Timestamp and Duration types (See Support for more common data types #348)
  • For Rust, map Timestamp to Rust SystemTime type and Duration to Rust
    Duration type
  • For Kotlin, map Timestamp to Java Instant type and Duration to Java
    Duration type
  • For Python, map Timestamp to naive Python datetime type and Duration
    to Python timedelta type
  • Implement bindings for Swift
  • Implement bindings for Gecko
  • Update documentation

@npars
Copy link
Contributor Author

npars commented Apr 30, 2021

This PR adds the common Timestamp and Duration types to Uniffi, before I proceed any further there were a few open questions I wanted to discuss:

  1. The current implementation for Python loses precision because Python's internal datetime type uses a floating point number to store the timestamp internally (or at least for doing some of it's math). This could be a gotcha for those expecting to have the exact nanoseconds as kept by Rust.

    I there think is a trade-off we need to decide on here:

    • A) Always use the internal types for each language, warning users that they may experience some loss of precision
    • B) Roll our own types for each language (like gRPC does for Timestamp) and force the user to make their own decisions on how to handle any conversions that may result in loss of precision
  2. The Duration type for Swift (TimeInterval) is just a typealias on Double. Since we already have bindings for Double in our templates this causes a namespace collision if I try to add them for TimeInterval. Any suggestions on the best way forward here would be appreciated. Should I try rewriting the Swift templates so that the function names are explicit? IE: liftDouble, liftTimeInterval, etc.

@mhammond
Copy link
Member

mhammond commented May 3, 2021

Thanks for this PR. This looks reasonable to me, and the fact that Python is going to lose nanoseconds doesn't seem that much of a big deal to me. However, for various reasons the people who are best suited to dig in to this aren't going to be fully available for a couple of weeks, so please don't see any delays here as representing a lack of interest!

@npars
Copy link
Contributor Author

npars commented May 13, 2021

@mhammond do you think it would be acceptable to leave the Gecko bindings unimplemented in this PR? It doesn't seem like we have standardized on testing in the examples like we have with Swift, Kotlin, and Python, which make me a bit wary about implementing.

@mhammond
Copy link
Member

@mhammond do you think it would be acceptable to leave the Gecko bindings unimplemented in this PR?

Yes, I think that would be fine.

@npars npars force-pushed the time branch 2 times, most recently from 5521811 to 3b9e5cf Compare May 15, 2021 23:47
@npars npars marked this pull request as ready for review May 15, 2021 23:47
Copy link
Collaborator

@rfk rfk left a comment

Choose a reason for hiding this comment

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

@npars thanks for diving in! This is a really well executed PR. I've left detailed comments below, but in summary, the "request" changes here is mainly for:

  • Please consider moving the test coverage from ./examples to ./fixtures.
  • I think at least the Kotlin code needs to be more careful around handling negative durations.

Also, a broader thought that I figured I'd share....some experimenting suggests that Rust is quite happy to represent SystemTime instances that are before the unix epoch. Given that all three of the target languages here require code to explicitly disallow pre-epoch times, I wonder if it's worth doing the opposite and explicitly supporting them, making the Rust code a bit more complicated but the code in each of the binding languages a bit simpler.

For example, we might serialize a timestamp as a signed number of seconds and unsigned nanoseconds, then in the rust code, check for positive or negative duration and calculate the resulting SystemTime via UNIX_EPOCH + duration or UNIX_EPOCH - duration as appropriate.

What do you think?

@npars
Copy link
Contributor Author

npars commented May 18, 2021

Also, a broader thought that I figured I'd share....some experimenting suggests that Rust is quite happy to represent SystemTime instances that are before the unix epoch. Given that all three of the target languages here require code to explicitly disallow pre-epoch times, I wonder if it's worth doing the opposite and explicitly supporting them, making the Rust code a bit more complicated but the code in each of the binding languages a bit simpler.

For example, we might serialize a timestamp as a signed number of seconds and unsigned nanoseconds, then in the rust code, check for positive or negative duration and calculate the resulting SystemTime via UNIX_EPOCH + duration or UNIX_EPOCH - duration as appropriate.

What do you think?

That's a good catch, I mistakenly made an assumption based on the Rust Duration type that SystemTime was stored internally as a u64 as well. After digging a bit it looks like SystemTime's implementation is actually specific to each target environment. In the playground you posted I verified that the max value for that env is:

std::time::UNIX_EPOCH + Duration::from_secs(i64::MAX as u64)

As a side note, it looks like there is an existing issue around this behaviour.

I think it makes sense to support pre-epoch times. That change will still leave us with positive only Duration, but that's perhaps a bit easier to swallow.

@rfk
Copy link
Collaborator

rfk commented May 19, 2021

That change will still leave us with positive only Duration, but that's perhaps a bit easier to swallow.

Agreed, I don't think there's much we can do about that for Duration, and also it seems reasonable to reflect the Rust code's semantics in that case.

@npars
Copy link
Contributor Author

npars commented May 24, 2021

@rfk I think I've addressed all of your comments. There's probably some room for optimization in the conversion code for each language, but I think it's ok for a first pass.

npars added 8 commits May 24, 2021 13:59
- Implement Timestamp and Duration types
- For Rust, map Timestamp to Rust SystemTime type and Duration to Rust
  Duration type
- For Kotlin, map Timestamp to Java Instant type and Duration to Java
  Duration type
- For Python, map Timestamp to naive Python datetime type and Duration
  to Python timedelta type
- Add Timestamp and Duration support to Swift bindings
- Add simple test for Timestamp and Duration Swift bindings
- Move time tests to fixtures directory
- Remove redundant uniffi.toml from time fixture
@npars npars requested a review from rfk May 24, 2021 23:04
@rfk
Copy link
Collaborator

rfk commented May 25, 2021

There's probably some room for optimization in the conversion code for each language

This could be said about many things in the current state of this repo, I don't think it will be a blocker for merge 👍🏻

Copy link
Collaborator

@rfk rfk left a comment

Choose a reason for hiding this comment

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

Looks great, thank you!

I left a couple of requests for more comments below, and one place where I think the name of a variable is misleading, but otherwise this is in really good shape. I'm going to tick "approve" here because I don't need to look at this again in any detail, please just adjust for those small comments and let me know when it's pushed, and I'll go ahead and merge.

- Fix outdated timestamp description in book
- Add documentation around timestamp calculation
- Simplify Swift timestamp calculation for pre-epoch times
}
}

fileprivate func write(into buf: Writer) {
var delta = self.timeIntervalSince1970
var sign: Int64 = 1
if delta < 0 {
// The nanoseconds portion of the epoch offset must always be
// positive, to simplify the calculation we will use the absolute
// value of the offset.
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍🏻

/// offset should be combined with the nanosecond portion. This is because
/// the sign of the seconds portion represents the direction of the offset
/// overall. The sign of the seconds portion can then be used to determine
/// if the total offset should be added to or subtracted from the unix epoch.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Perfect, thanks so much for adding these!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants