From 1ca1816f5d2a0a89e2e6b9bffeed570821b563e5 Mon Sep 17 00:00:00 2001 From: Louis Pilfold Date: Fri, 4 Apr 2025 16:48:05 +0100 Subject: [PATCH 1/7] Minutes, hours --- CHANGELOG.md | 4 ++++ src/gleam/time/duration.gleam | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d52c8e1..7b00c6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleasd + +- The `duration` module gains the `minutes` and `hours` functions. + ## v1.1.0 - 2025-03-29 - The `calendar` module gains the `month_to_string` function. diff --git a/src/gleam/time/duration.gleam b/src/gleam/time/duration.gleam index 0263381..b6590e7 100644 --- a/src/gleam/time/duration.gleam +++ b/src/gleam/time/duration.gleam @@ -162,6 +162,16 @@ pub fn seconds(amount: Int) -> Duration { Duration(amount, 0) } +/// Create a duration of a number of minutes. +pub fn minutes(amount: Int) -> Duration { + seconds(amount * 60) +} + +/// Create a duration of a number of hours. +pub fn hours(amount: Int) -> Duration { + seconds(amount * 60 * 60) +} + /// Create a duration of a number of milliseconds. pub fn milliseconds(amount: Int) -> Duration { let remainder = amount % 1000 From 5f61a0a1784d2ed001fbb6f5c85b7304e8c9351d Mon Sep 17 00:00:00 2001 From: Louis Pilfold Date: Fri, 4 Apr 2025 16:58:03 +0100 Subject: [PATCH 2/7] Unit type --- CHANGELOG.md | 3 ++- src/gleam/time/duration.gleam | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b00c6a..b095fa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ ## Unreleasd -- The `duration` module gains the `minutes` and `hours` functions. +- The `duration` module gains the `Unit` type and the `minutes` and `hours` + functions. ## v1.1.0 - 2025-03-29 diff --git a/src/gleam/time/duration.gleam b/src/gleam/time/duration.gleam index b6590e7..edb4f34 100644 --- a/src/gleam/time/duration.gleam +++ b/src/gleam/time/duration.gleam @@ -24,6 +24,33 @@ pub opaque type Duration { Duration(seconds: Int, nanoseconds: Int) } +/// A division of time. +/// +/// Note that not all months and years are the same length, so a reasonable +/// average length is used by this module. +/// +pub type Unit { + Nanosecond + /// 1000 nanoseconds. + Microsecond + /// 1000 microseconds. + Millisecond + /// 1000 microseconds. + Second + /// 60 seconds. + Minute + /// 60 minutes. + Hour + /// 24 hours. + Day + /// 7 days. + Week + /// About 30 days. Real calendar months vary in length. + Month + /// About 365.25 days. Real calendar years vary in length. + Year +} + /// Ensure the duration is represented with `nanoseconds` being positive and /// less than 1 second. /// From 0ed301879d932b45d9fabb396d538647427451aa Mon Sep 17 00:00:00 2001 From: Louis Pilfold Date: Fri, 4 Apr 2025 17:12:01 +0100 Subject: [PATCH 3/7] month int functions --- CHANGELOG.md | 2 ++ src/gleam/time/calendar.gleam | 51 +++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b095fa8..3feb6ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - The `duration` module gains the `Unit` type and the `minutes` and `hours` functions. +- The `calendar` module gains the `month_to_int` and `month_from_int` + functions. ## v1.1.0 - 2025-03-29 diff --git a/src/gleam/time/calendar.gleam b/src/gleam/time/calendar.gleam index 3af2a25..2d3e0b9 100644 --- a/src/gleam/time/calendar.gleam +++ b/src/gleam/time/calendar.gleam @@ -115,3 +115,54 @@ pub fn month_to_string(month: Month) -> String { December -> "December" } } + +/// Returns the number for the month, where January is 1 and December is 12. +/// +/// # Examples +/// +/// ```gleam +/// month_to_int(January) +/// // -> 1 +/// ``` +pub fn month_to_int(month: Month) -> Int { + case month { + January -> 1 + February -> 2 + March -> 3 + April -> 4 + May -> 5 + June -> 6 + July -> 7 + August -> 8 + September -> 9 + October -> 10 + November -> 11 + December -> 12 + } +} + +/// Returns the month for a given number, where January is 1 and December is 12. +/// +/// # Examples +/// +/// ```gleam +/// month_from_int(1) +/// // -> Ok(January) +/// ``` +pub fn month_from_int(month: Int) -> Result(Month, Nil) { + case month { + 1 -> Ok(January) + 2 -> Ok(February) + 3 -> Ok(March) + 4 -> Ok(April) + 5 -> Ok(May) + 6 -> Ok(June) + 7 -> Ok(July) + 8 -> Ok(August) + 9 -> Ok(September) + 10 -> Ok(October) + 11 -> Ok(November) + 12 -> Ok(December) + _ -> Error(Nil) + } +} From 867040013a7a1c79a778a503bc705fb225a46e73 Mon Sep 17 00:00:00 2001 From: Louis Pilfold Date: Sun, 20 Apr 2025 16:46:02 +0100 Subject: [PATCH 4/7] approximate --- CHANGELOG.md | 6 +- src/gleam/time/duration.gleam | 52 +++++++++- test/gleam/time/duration_test.gleam | 144 ++++++++++++++++++++++++++++ 3 files changed, 199 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3feb6ad..e2704f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,10 @@ ## Unreleasd -- The `duration` module gains the `Unit` type and the `minutes` and `hours` - functions. +- Fixed a bug where the `milliseconds` function could return an incorrect value + for negative numbers. +- The `duration` module gains the `Unit` type and the `approximate`, `minutes` + and `hours` functions. - The `calendar` module gains the `month_to_int` and `month_from_int` functions. diff --git a/src/gleam/time/duration.gleam b/src/gleam/time/duration.gleam index edb4f34..ba192e3 100644 --- a/src/gleam/time/duration.gleam +++ b/src/gleam/time/duration.gleam @@ -45,12 +45,61 @@ pub type Unit { Day /// 7 days. Week - /// About 30 days. Real calendar months vary in length. + /// About 30.4375 days. Real calendar months vary in length. Month /// About 365.25 days. Real calendar years vary in length. Year } +/// Convert a duration to a number of the largest number of a unit, serving as +/// a rough description of the duration that a human can understand. +/// +/// The sized used for each unit are described in the documentation for the +/// `Unit` type. +/// +/// ```gleam +/// seconds(125) +/// |> approximate +/// // -> #(2, Minute) +/// ``` +/// +/// This function rounds _towards zero_. This means that if a duration is just +/// short of 2 days then it will approximate to 1 day. +/// +/// ```gleam +/// hours(47) +/// |> approximate +/// // -> #(1, Day) +/// ``` +/// +pub fn approximate(duration: Duration) -> #(Int, Unit) { + let Duration(seconds: s, nanoseconds: ns) = duration + let minute = 60 + let hour = minute * 60 + let day = hour * 24 + let week = day * 7 + let year = day * 365 + hour * 6 + let month = year / 12 + let microsecond = 1000 + let millisecond = microsecond * 1000 + case Nil { + _ if s < 0 -> { + let #(amount, unit) = Duration(-s, -ns) |> normalise |> approximate + #(-amount, unit) + } + _ if s >= year -> #(s / year, Year) + _ if s >= month -> #(s / month, Month) + _ if s >= week -> #(s / week, Week) + _ if s >= day -> #(s / day, Day) + _ if s >= hour -> #(s / hour, Hour) + _ if s >= minute -> #(s / minute, Minute) + _ if s > 0 -> #(s, Second) + _ if ns >= millisecond -> #(ns / millisecond, Millisecond) + _ if ns >= microsecond -> #(ns / microsecond, Microsecond) + _ -> #(ns, Nanosecond) + } +} + /// Ensure the duration is represented with `nanoseconds` being positive and /// less than 1 second. /// @@ -206,6 +255,7 @@ pub fn milliseconds(amount: Int) -> Duration { let nanoseconds = remainder * 1_000_000 let seconds = overflow / 1000 Duration(seconds, nanoseconds) + |> normalise } /// Create a duration of a number of nanoseconds. diff --git a/test/gleam/time/duration_test.gleam b/test/gleam/time/duration_test.gleam index 641094f..ea17a21 100644 --- a/test/gleam/time/duration_test.gleam +++ b/test/gleam/time/duration_test.gleam @@ -361,3 +361,147 @@ pub fn difference_2_test() { duration.difference(duration.seconds(2), duration.milliseconds(3500)) |> should.equal(duration.milliseconds(1500)) } + +pub fn approximate_0_test() { + duration.minutes(10) + |> duration.approximate + |> should.equal(#(10, duration.Minute)) +} + +pub fn approximate_1_test() { + duration.seconds(30) + |> duration.approximate + |> should.equal(#(30, duration.Second)) +} + +pub fn approximate_2_test() { + duration.hours(23) + |> duration.approximate + |> should.equal(#(23, duration.Hour)) +} + +pub fn approximate_3_test() { + duration.hours(24) + |> duration.approximate + |> should.equal(#(1, duration.Day)) +} + +pub fn approximate_4_test() { + duration.hours(48) + |> duration.approximate + |> should.equal(#(2, duration.Day)) +} + +pub fn approximate_5_test() { + duration.hours(47) + |> duration.approximate + |> should.equal(#(1, duration.Day)) +} + +pub fn approximate_6_test() { + duration.hours(24 * 7) + |> duration.approximate + |> should.equal(#(1, duration.Week)) +} + +pub fn approximate_7_test() { + duration.hours(24 * 30) + |> duration.approximate + |> should.equal(#(4, duration.Week)) +} + +pub fn approximate_8_test() { + duration.hours(24 * 31) + |> duration.approximate + |> should.equal(#(1, duration.Month)) +} + +pub fn approximate_9_test() { + duration.hours(24 * 66) + |> duration.approximate + |> should.equal(#(2, duration.Month)) +} + +pub fn approximate_10_test() { + duration.hours(24 * 365) + |> duration.approximate + |> should.equal(#(11, duration.Month)) +} + +pub fn approximate_11_test() { + duration.hours(24 * 365 + 5) + |> duration.approximate + |> should.equal(#(11, duration.Month)) +} + +pub fn approximate_12_test() { + duration.hours(24 * 365 + 6) + |> duration.approximate + |> should.equal(#(1, duration.Year)) +} + +pub fn approximate_13_test() { + duration.hours(5 * 24 * 365 + 6) + |> duration.approximate + |> should.equal(#(4, duration.Year)) +} + +pub fn approximate_14_test() { + duration.hours(-5 * 24 * 365 + 6) + |> duration.approximate + |> should.equal(#(-4, duration.Year)) +} + +pub fn approximate_15_test() { + duration.milliseconds(1) + |> duration.approximate + |> should.equal(#(1, duration.Millisecond)) +} + +pub fn approximate_16_test() { + duration.milliseconds(-1) + |> duration.approximate + |> should.equal(#(-1, duration.Millisecond)) +} + +pub fn approximate_17_test() { + duration.milliseconds(999) + |> duration.approximate + |> should.equal(#(999, duration.Millisecond)) +} + +pub fn approximate_18_test() { + duration.nanoseconds(1000) + |> duration.approximate + |> should.equal(#(1, duration.Microsecond)) +} + +pub fn approximate_19_test() { + duration.nanoseconds(-1000) + |> duration.approximate + |> should.equal(#(-1, duration.Microsecond)) +} + +pub fn approximate_20_test() { + duration.nanoseconds(23_000) + |> duration.approximate + |> should.equal(#(23, duration.Microsecond)) +} + +pub fn approximate_21_test() { + duration.nanoseconds(999) + |> duration.approximate + |> should.equal(#(999, duration.Nanosecond)) +} + +pub fn approximate_22_test() { + duration.nanoseconds(-999) + |> duration.approximate + |> should.equal(#(-999, duration.Nanosecond)) +} + +pub fn approximate_23_test() { + duration.nanoseconds(0) + |> duration.approximate + |> should.equal(#(0, duration.Nanosecond)) +} From 3d132db5fb8a45af1a894fc8dac9539c6aa26456 Mon Sep 17 00:00:00 2001 From: Louis Pilfold Date: Sun, 20 Apr 2025 16:49:12 +0100 Subject: [PATCH 5/7] Typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2704f3..9a197d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## Unreleasd +## Unreleased - Fixed a bug where the `milliseconds` function could return an incorrect value for negative numbers. From 49e1ef735108ef0bbd6ca840f6da3f82f167640f Mon Sep 17 00:00:00 2001 From: Louis Pilfold Date: Sun, 20 Apr 2025 19:35:34 +0100 Subject: [PATCH 6/7] Document local offset vs zone --- src/gleam/time/calendar.gleam | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gleam/time/calendar.gleam b/src/gleam/time/calendar.gleam index 2d3e0b9..22d29c3 100644 --- a/src/gleam/time/calendar.gleam +++ b/src/gleam/time/calendar.gleam @@ -83,6 +83,12 @@ pub const utc_offset = duration.empty /// For example, if you are making a web application that runs on a server you /// want _their_ computer's time zone, not yours. /// +/// This is the _current local_ offset, not the current local time zone. This +/// means that while it will result in the expected outcome for the current +/// time, it may result in unexpected output if used with other timestamps. For +/// example: a timestamp that would locally be during daylight savings time if +/// is it not currently daylight savings time when this function is called. +/// pub fn local_offset() -> duration.Duration { duration.seconds(local_time_offset_seconds()) } From 72e5a475fc5f14d50bf63285670fcc835b50e18a Mon Sep 17 00:00:00 2001 From: Louis Pilfold Date: Sun, 20 Apr 2025 19:39:05 +0100 Subject: [PATCH 7/7] Typo Co-authored-by: Giacomo Cavalieri --- src/gleam/time/duration.gleam | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gleam/time/duration.gleam b/src/gleam/time/duration.gleam index ba192e3..5aae5ed 100644 --- a/src/gleam/time/duration.gleam +++ b/src/gleam/time/duration.gleam @@ -54,7 +54,7 @@ pub type Unit { /// Convert a duration to a number of the largest number of a unit, serving as /// a rough description of the duration that a human can understand. /// -/// The sized used for each unit are described in the documentation for the +/// The size used for each unit are described in the documentation for the /// `Unit` type. /// /// ```gleam