Skip to content

Commit

Permalink
Document and test
Browse files Browse the repository at this point in the history
  • Loading branch information
lpil committed Jan 26, 2025
1 parent 1441736 commit 769c1de
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 56 deletions.
33 changes: 29 additions & 4 deletions src/gleam/time/timestamp.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,21 @@ fn pad_digit(digit: Int, to desired_length: Int) -> String {
int.to_string(digit) |> string.pad_start(desired_length, "0")
}

// TODO: test
// TODO: document
/// Convert a `Timestamp` to calendar time, suitable for presenting to a human
/// to read.
///
/// If you want a machine to use the time value then you should not use this
/// function and should instead keep it as a timestamp. See the documentation
/// for the `gleam/time/calendar` module for more information.
///
/// # Examples
///
/// ```gleam
/// timestamp.from_unix_seconds(0)
/// |> timestamp.to_calendar(calendar.utc_offset)
/// // -> #(Date(1970, January, 1), TimeOfDay(0, 0, 0, 0))
/// ```
///
pub fn to_calendar(
timestamp: Timestamp,
offset: Duration,
Expand Down Expand Up @@ -283,8 +296,20 @@ fn to_calendar_from_offset(
#(year, month, day, hours, minutes, seconds)
}

// TODO: test
// TODO: document
/// Create a `Timestamp` from a human-readable calendar time.
///
/// # Examples
///
/// ```gleam
/// timestamp.from_calendar(
/// date: calendar.Date(2024, calendar.December, 25),
/// time: calendar.TimeOfDay(12, 30, 50, 0),
/// offset: calendar.utc_offset,
/// )
/// |> timestamp.to_rfc3339(calendar.utc_offset)
/// // -> "2024-12-25T12:30:50Z"
/// ```
///
pub fn from_calendar(
date date: calendar.Date,
time time: calendar.TimeOfDay,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import qcheck
/// Generate timestamps representing instants in the range `0000-01-01T00:00:00Z`
/// to `9999-12-31T23:59:59.999999999Z`.
///
pub fn timestamp_generator() {
pub fn timestamp() {
// prng can only generate good integers in the range
// [-2_147_483_648, 2_147_483_647]
//
Expand Down Expand Up @@ -46,7 +46,7 @@ pub fn timestamp_generator() {
timestamp.from_unix_seconds_and_nanoseconds(total_seconds, nanosecond)
}

pub fn date_time_generator(
pub fn rfc3339(
with_leap_second with_leap_second: Bool,
second_fraction_spec second_fraction_spec: SecondFractionSpec,
avoid_erlang_errors avoid_erlang_errors: Bool,
Expand Down Expand Up @@ -93,21 +93,6 @@ pub fn date_time_generator(
}
}

/// Generate date-time strings with no fractional second component.
///
/// This is a temporary solution until the to_rfc3339 function handles second
/// fractions.
pub fn date_time_no_second_fraction_generator(
with_leap_second with_leap_second: Bool,
) -> qcheck.Generator(String) {
use full_date, t, full_time <- qcheck.map3(
g1: full_date_generator(),
g2: t_generator(),
g3: full_time_no_second_fraction_generator(with_leap_second),
)
full_date <> t <> full_time
}

fn full_date_generator() -> qcheck.Generator(String) {
use date_fullyear <- qcheck.bind(date_fullyear_generator())
use date_month <- qcheck.bind(date_month_generator())
Expand Down Expand Up @@ -169,16 +154,6 @@ fn full_time_generator(
partial_time <> time_offset
}

fn full_time_no_second_fraction_generator(
with_leap_second with_leap_second: Bool,
) -> qcheck.Generator(String) {
use partial_time, time_offset <- qcheck.map2(
g1: partial_time_no_second_fraction_generator(with_leap_second),
g2: time_offset_generator(),
)
partial_time <> time_offset
}

fn partial_time_generator(
with_leap_second with_leap_second: Bool,
second_fraction_spec second_fraction_spec: SecondFractionSpec,
Expand All @@ -203,20 +178,6 @@ fn partial_time_generator(
)
}

fn partial_time_no_second_fraction_generator(
with_leap_second with_leap_second: Bool,
) {
qcheck.return({
use time_hour <- qcheck.parameter
use time_minute <- qcheck.parameter
use time_second <- qcheck.parameter
time_hour <> ":" <> time_minute <> ":" <> time_second
})
|> qcheck.apply(time_hour_generator())
|> qcheck.apply(time_minute_generator())
|> qcheck.apply(time_second_generator(with_leap_second))
}

fn time_hour_generator() -> qcheck.Generator(String) {
zero_padded_digits_generator(length: 2, from: 0, to: 23)
}
Expand Down
40 changes: 29 additions & 11 deletions test/gleam/time/timestamp_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import gleam/list
import gleam/order
import gleam/result
import gleam/string
import gleam/time/calendar.{August, Date, December, TimeOfDay}
import gleam/time/calendar.{August, Date, December, January, TimeOfDay}
import gleam/time/duration
import gleam/time/rfc3339_generator
import gleam/time/generators
import gleam/time/timestamp
import gleeunit/should
import qcheck
Expand Down Expand Up @@ -329,20 +329,20 @@ pub fn parse_rfc3339_3_test() {
}

pub fn timestamp_rfc3339_string_timestamp_roundtrip_property_test() {
use timestamp <- qcheck.given(rfc3339_generator.timestamp_generator())
use timestamp <- qcheck.given(generators.timestamp())

let assert Ok(parsed_timestamp) =
timestamp
|> timestamp.to_rfc3339(calendar.utc_offset)
|> timestamp.parse_rfc3339

timestamp.compare(timestamp, parsed_timestamp) == order.Eq
timestamp == parsed_timestamp
}

pub fn rfc3339_string_timestamp_rfc3339_string_roundtrip_property_test() {
use date_time <- qcheck.given(rfc3339_generator.date_time_generator(
use date_time <- qcheck.given(generators.rfc3339(
with_leap_second: True,
second_fraction_spec: rfc3339_generator.Default,
second_fraction_spec: generators.Default,
avoid_erlang_errors: False,
))

Expand All @@ -353,7 +353,7 @@ pub fn rfc3339_string_timestamp_rfc3339_string_roundtrip_property_test() {
|> timestamp.to_rfc3339(calendar.utc_offset)
|> timestamp.parse_rfc3339

timestamp.compare(original_timestamp, roundtrip_timestamp) == order.Eq
original_timestamp == roundtrip_timestamp
}

// Check against OCaml Ptime reference implementation.
Expand Down Expand Up @@ -515,11 +515,11 @@ pub fn parse_rfc3339_matches_oracle_example_10_test() {
}

pub fn parse_rfc3339_matches_oracle_property_test() {
use date_time <- qcheck.given(rfc3339_generator.date_time_generator(
use date_time <- qcheck.given(generators.rfc3339(
// JavaScript oracle cannot handle leap-seconds.
with_leap_second: False,
// JavaScript oracle has max precision of milliseconds.
second_fraction_spec: rfc3339_generator.WithMaxLength(3),
second_fraction_spec: generators.WithMaxLength(3),
// Some valid timestamps cannot be parsed by the Erlang oracle.
avoid_erlang_errors: True,
))
Expand All @@ -528,9 +528,9 @@ pub fn parse_rfc3339_matches_oracle_property_test() {
}

pub fn parse_rfc3339_succeeds_for_valid_inputs_property_test() {
use date_time <- qcheck.given_result(rfc3339_generator.date_time_generator(
use date_time <- qcheck.given_result(generators.rfc3339(
with_leap_second: True,
second_fraction_spec: rfc3339_generator.Default,
second_fraction_spec: generators.Default,
avoid_erlang_errors: False,
))
timestamp.parse_rfc3339(date_time)
Expand Down Expand Up @@ -757,3 +757,21 @@ pub fn from_calendar_2_test() {
|> timestamp.to_rfc3339(duration.empty)
|> should.equal("2024-12-25T12:30:50.001Z")
}

pub fn to_calendar_0_test() {
timestamp.from_unix_seconds(0)
|> timestamp.to_calendar(calendar.utc_offset)
|> should.equal(#(
Date(year: 1970, month: January, day: 1),
TimeOfDay(hours: 0, minutes: 0, seconds: 0, nanoseconds: 0),
))
}

pub fn calendar_roundtrip_test() {
use timestamp1 <- qcheck.given(generators.timestamp())
let #(date, time_of_day) =
timestamp.to_calendar(timestamp1, calendar.utc_offset)
let timestamp2 =
timestamp.from_calendar(date, time_of_day, calendar.utc_offset)
timestamp1 == timestamp2
}

0 comments on commit 769c1de

Please sign in to comment.