-
-
Notifications
You must be signed in to change notification settings - Fork 5
Basic time types #1
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
Changes from 14 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
f5309e7
Duration, timestamp
lpil 671b7d4
Date parse function
lpil 97de3c5
Duration constructors
lpil 9b3ce59
Make opaque
lpil d7abab4
Constructors and getters
lpil 2b07766
Implement some duration types
lpil d966072
duration.to_iso8601_string
lpil 1c4203c
Include nanoseconds in duration
lpil ce9f8e5
timestamp.duration
lpil 0dc9da1
Documentation
lpil 716c30f
Timestamp functions
lpil 14696d3
rfc3339
lpil 977ddf9
Run tests on JS also
lpil 719f847
Accept an offset in RFC function
lpil 01f0945
Test for Jak
lpil 92ed148
Fix duration normalise bug
lpil 9933ffc
Fix timestamp normalise bug
lpil c20b983
Compare properties
lpil 3bbdeb1
Better qcheck usage
lpil 490d46b
Adopt decided upon duration comparison semantics
lpil 6efb6e1
Work around qcheck bug
lpil File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
import gleam/int | ||
import gleam/order | ||
import gleam/string | ||
|
||
/// An amount of time, with up to nanosecond precision. | ||
/// | ||
/// This type does not represent calendar periods such as "1 month" or "2 | ||
/// days". Those periods will be different lengths of time depending on which | ||
/// month or day they apply to. For example, January is longer than February. | ||
/// A different type should be used for calendar periods. | ||
/// | ||
pub opaque type Duration { | ||
// When compiling to JavaScript ints have limited precision and size. This | ||
// means that if we were to store the the timestamp in a single int the | ||
// duration would not be able to represent very large or small durations. | ||
// Durations are instead represented as a number of seconds and a number of | ||
// nanoseconds. | ||
// | ||
// If you have manually adjusted the seconds and nanoseconds values the | ||
// `normalise` function can be used to ensure the time is represented the | ||
// intended way, with `nanoseconds` being positive and less than 1 second. | ||
Duration(seconds: Int, nanoseconds: Int) | ||
} | ||
|
||
/// Ensure the duration is represented with `nanoseconds` being positive and | ||
/// less than 1 second. | ||
/// | ||
/// This function does not change the amount of time that the duratoin refers | ||
/// to, it only adjusts the values used to represent the time. | ||
/// | ||
fn normalise(duration: Duration) -> Duration { | ||
let multiplier = 1_000_000_000 | ||
let nanoseconds = duration.nanoseconds % multiplier | ||
let overflow = duration.nanoseconds - nanoseconds | ||
let seconds = duration.seconds + overflow / multiplier | ||
Duration(seconds, nanoseconds) | ||
lpil marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/// Compare one duration to another, indicating whether the first is greater or | ||
/// smaller than the second. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ```gleam | ||
/// compare(seconds(1), seconds(2)) | ||
/// // -> order.Lt | ||
/// ``` | ||
/// | ||
pub fn compare(left: Duration, right: Duration) -> order.Order { | ||
order.break_tie( | ||
int.compare(left.seconds, right.seconds), | ||
int.compare(left.nanoseconds, right.nanoseconds), | ||
) | ||
} | ||
lpil marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/// Calculate the difference between two durations. | ||
/// | ||
/// This is effectively substracting the first duration from the second. | ||
lpil marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// | ||
/// # Examples | ||
/// | ||
/// ```gleam | ||
/// difference(seconds(1), seconds(5)) | ||
/// // -> seconds(4) | ||
/// ``` | ||
/// | ||
pub fn difference(left: Duration, right: Duration) -> Duration { | ||
Duration(right.seconds - left.seconds, right.nanoseconds - left.nanoseconds) | ||
|> normalise | ||
} | ||
|
||
/// Add two durations together. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ```gleam | ||
/// add(seconds(1), seconds(5)) | ||
/// // -> seconds(6) | ||
/// ``` | ||
/// | ||
pub fn add(left: Duration, right: Duration) -> Duration { | ||
Duration(left.seconds + right.seconds, left.nanoseconds + right.nanoseconds) | ||
|> normalise | ||
} | ||
|
||
/// Convert the duration to an [ISO8601][1] formatted duration string. | ||
/// | ||
/// The ISO8601 duration format is ambiguous without context due to months and | ||
/// years having different lengths, and because of leap seconds. This function | ||
/// encodes the duration as days, hours, and seconds without any leap seconds. | ||
/// Be sure to take this into account when using the duration strings. | ||
/// | ||
/// [1]: https://en.wikipedia.org/wiki/ISO_8601#Durations | ||
/// | ||
pub fn to_iso8601_string(duration: Duration) -> String { | ||
lpil marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let split = fn(total, limit) { | ||
let amount = total % limit | ||
let remainder = { total - amount } / limit | ||
#(amount, remainder) | ||
} | ||
let #(seconds, rest) = split(duration.seconds, 60) | ||
let #(minutes, rest) = split(rest, 60) | ||
let #(hours, rest) = split(rest, 24) | ||
let days = rest | ||
lpil marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let add = fn(out, value, unit) { | ||
case value { | ||
0 -> out | ||
_ -> out <> int.to_string(value) <> unit | ||
} | ||
} | ||
let output = | ||
"P" | ||
|> add(days, "D") | ||
lpil marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|> string.append("T") | ||
|> add(hours, "H") | ||
|> add(minutes, "M") | ||
case seconds, duration.nanoseconds { | ||
0, 0 -> output | ||
_, 0 -> output <> int.to_string(seconds) <> "S" | ||
_, _ -> { | ||
let f = nanosecond_digits(duration.nanoseconds, 0, "") | ||
output <> int.to_string(seconds) <> "." <> f <> "S" | ||
} | ||
} | ||
} | ||
|
||
fn nanosecond_digits(n: Int, position: Int, acc: String) -> String { | ||
case position { | ||
9 -> acc | ||
_ if acc == "" && n % 10 == 0 -> { | ||
nanosecond_digits(n / 10, position + 1, acc) | ||
} | ||
_ -> { | ||
let acc = int.to_string(n % 10) <> acc | ||
nanosecond_digits(n / 10, position + 1, acc) | ||
} | ||
} | ||
} | ||
|
||
/// Create a duration of a number of seconds. | ||
pub fn seconds(amount: Int) -> Duration { | ||
Duration(amount, 0) |> normalise | ||
lpil marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/// Create a duration of a number of milliseconds. | ||
pub fn milliseconds(amount: Int) -> Duration { | ||
let remainder = amount % 1000 | ||
let overflow = amount - remainder | ||
let nanoseconds = remainder * 1_000_000 | ||
let seconds = overflow / 1000 | ||
Duration(seconds, nanoseconds) | ||
} | ||
|
||
/// Create a duration of a number of nanoseconds. | ||
pub fn nanoseconds(amount: Int) -> Duration { | ||
Duration(0, amount) | ||
|> normalise | ||
} | ||
|
||
/// Convert the duration to a number of seconds. | ||
/// | ||
/// There may be some small loss of precision due to `Duration` being | ||
/// nanosecond accurate and `Float` not being able to represent this. | ||
/// | ||
pub fn to_seconds(duration: Duration) -> Float { | ||
let seconds = int.to_float(duration.seconds) | ||
let nanoseconds = int.to_float(duration.nanoseconds) | ||
seconds +. { nanoseconds /. 1_000_000_000.0 } | ||
} | ||
|
||
/// Convert the duration to a number of seconds and nanoseconds. There is no | ||
/// loss of precision with this conversion on any target. | ||
pub fn to_seconds_and_nanoseconds(duration: Duration) -> #(Int, Int) { | ||
#(duration.seconds, duration.nanoseconds) | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.