import * as $bit_array from "../../../gleam_stdlib/gleam/bit_array.mjs"; import * as $float from "../../../gleam_stdlib/gleam/float.mjs"; import * as $int from "../../../gleam_stdlib/gleam/int.mjs"; import * as $list from "../../../gleam_stdlib/gleam/list.mjs"; import * as $order from "../../../gleam_stdlib/gleam/order.mjs"; import * as $result from "../../../gleam_stdlib/gleam/result.mjs"; import * as $string from "../../../gleam_stdlib/gleam/string.mjs"; import { Ok, Error, toList, Empty as $Empty, prepend as listPrepend, CustomType as $CustomType, remainderInt, divideFloat, divideInt, toBitArray, bitArraySlice, } from "../../gleam.mjs"; import * as $calendar from "../../gleam/time/calendar.mjs"; import * as $duration from "../../gleam/time/duration.mjs"; import { system_time as get_system_time } from "../../gleam_time_ffi.mjs"; class Timestamp extends $CustomType { constructor(seconds, nanoseconds) { super(); this.seconds = seconds; this.nanoseconds = nanoseconds; } } /** * Ensure the time is represented with `nanoseconds` being positive and less * than 1 second. * * This function does not change the time that the timestamp refers to, it * only adjusts the values used to represent the time. * * @ignore */ function normalise(timestamp) { let multiplier = 1_000_000_000; let nanoseconds = remainderInt(timestamp.nanoseconds, multiplier); let overflow = timestamp.nanoseconds - nanoseconds; let seconds = timestamp.seconds + (divideInt(overflow, multiplier)); let $ = nanoseconds >= 0; if ($) { return new Timestamp(seconds, nanoseconds); } else { return new Timestamp(seconds - 1, multiplier + nanoseconds); } } /** * Compare one timestamp to another, indicating whether the first is further * into the future (greater) or further into the past (lesser) than the * second. * * # Examples * * ```gleam * compare(from_unix_seconds(1), from_unix_seconds(2)) * // -> order.Lt * ``` */ export function compare(left, right) { return $order.break_tie( $int.compare(left.seconds, right.seconds), $int.compare(left.nanoseconds, right.nanoseconds), ); } /** * Get the current system time. * * Note this time is not unique or monotonic, it could change at any time or * even go backwards! The exact behaviour will depend on the runtime used. See * the module documentation for more information. * * On Erlang this uses [`erlang:system_time/1`][1]. On JavaScript this uses * [`Date.now`][2]. * * [1]: https://www.erlang.org/doc/apps/erts/erlang#system_time/1 * [2]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now */ export function system_time() { let $ = get_system_time(); let seconds; let nanoseconds; seconds = $[0]; nanoseconds = $[1]; return normalise(new Timestamp(seconds, nanoseconds)); } /** * Calculate the difference between two timestamps. * * This is effectively substracting the first timestamp from the second. * * # Examples * * ```gleam * difference(from_unix_seconds(1), from_unix_seconds(5)) * // -> duration.seconds(4) * ``` */ export function difference(left, right) { let seconds = $duration.seconds(right.seconds - left.seconds); let nanoseconds = $duration.nanoseconds(right.nanoseconds - left.nanoseconds); return $duration.add(seconds, nanoseconds); } /** * Add a duration to a timestamp. * * # Examples * * ```gleam * add(from_unix_seconds(1000), duration.seconds(5)) * // -> from_unix_seconds(1005) * ``` */ export function add(timestamp, duration) { let $ = $duration.to_seconds_and_nanoseconds(duration); let seconds; let nanoseconds; seconds = $[0]; nanoseconds = $[1]; let _pipe = new Timestamp( timestamp.seconds + seconds, timestamp.nanoseconds + nanoseconds, ); return normalise(_pipe); } function pad_digit(digit, desired_length) { let _pipe = $int.to_string(digit); return $string.pad_start(_pipe, desired_length, "0"); } function duration_to_minutes(duration) { return $float.round($duration.to_seconds(duration) / 60.0); } function modulo(n, m) { let $ = $int.modulo(n, m); if ($ instanceof Ok) { let n$1 = $[0]; return n$1; } else { return 0; } } function floored_div(numerator, denominator) { let n = divideFloat($int.to_float(numerator), denominator); return $float.round($float.floor(n)); } function to_civil(minutes) { let raw_day = floored_div(minutes, (60.0 * 24.0)) + 719_468; let _block; let $ = raw_day >= 0; if ($) { _block = globalThis.Math.trunc(raw_day / 146_097); } else { _block = globalThis.Math.trunc((raw_day - 146_096) / 146_097); } let era = _block; let day_of_era = raw_day - era * 146_097; let year_of_era = globalThis.Math.trunc( (((day_of_era - (globalThis.Math.trunc(day_of_era / 1460))) + (globalThis.Math.trunc( day_of_era / 36_524 ))) - (globalThis.Math.trunc(day_of_era / 146_096))) / 365 ); let year = year_of_era + era * 400; let day_of_year = day_of_era - ((365 * year_of_era + (globalThis.Math.trunc( year_of_era / 4 ))) - (globalThis.Math.trunc(year_of_era / 100))); let mp = globalThis.Math.trunc((5 * day_of_year + 2) / 153); let _block$1; let $1 = mp < 10; if ($1) { _block$1 = mp + 3; } else { _block$1 = mp - 9; } let month = _block$1; let day = (day_of_year - (globalThis.Math.trunc((153 * mp + 2) / 5))) + 1; let _block$2; let $2 = month <= 2; if ($2) { _block$2 = year + 1; } else { _block$2 = year; } let year$1 = _block$2; return [year$1, month, day]; } function to_calendar_from_offset(timestamp, offset) { let total = timestamp.seconds + offset * 60; let seconds = modulo(total, 60); let total_minutes = floored_div(total, 60.0); let minutes = globalThis.Math.trunc(modulo(total, 60 * 60) / 60); let hours = divideInt(modulo(total, 24 * 60 * 60), 60 * 60); let $ = to_civil(total_minutes); let year; let month; let day; year = $[0]; month = $[1]; day = $[2]; return [year, month, day, hours, minutes, seconds]; } /** * 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)) * ``` */ export function to_calendar(timestamp, offset) { let offset$1 = duration_to_minutes(offset); let $ = to_calendar_from_offset(timestamp, offset$1); let year; let month; let day; let hours; let minutes; let seconds; year = $[0]; month = $[1]; day = $[2]; hours = $[3]; minutes = $[4]; seconds = $[5]; let _block; if (month === 1) { _block = new $calendar.January(); } else if (month === 2) { _block = new $calendar.February(); } else if (month === 3) { _block = new $calendar.March(); } else if (month === 4) { _block = new $calendar.April(); } else if (month === 5) { _block = new $calendar.May(); } else if (month === 6) { _block = new $calendar.June(); } else if (month === 7) { _block = new $calendar.July(); } else if (month === 8) { _block = new $calendar.August(); } else if (month === 9) { _block = new $calendar.September(); } else if (month === 10) { _block = new $calendar.October(); } else if (month === 11) { _block = new $calendar.November(); } else { _block = new $calendar.December(); } let month$1 = _block; let nanoseconds = timestamp.nanoseconds; let date = new $calendar.Date(year, month$1, day); let time = new $calendar.TimeOfDay(hours, minutes, seconds, nanoseconds); return [date, time]; } function do_remove_trailing_zeros(loop$reversed_digits) { while (true) { let reversed_digits = loop$reversed_digits; if (reversed_digits instanceof $Empty) { return reversed_digits; } else { let digit = reversed_digits.head; if (digit === 0) { let digits = reversed_digits.tail; loop$reversed_digits = digits; } else { let reversed_digits$1 = reversed_digits; return $list.reverse(reversed_digits$1); } } } } /** * Given a list of digits, return new list with any trailing zeros removed. * * @ignore */ function remove_trailing_zeros(digits) { let reversed_digits = $list.reverse(digits); return do_remove_trailing_zeros(reversed_digits); } function do_get_zero_padded_digits(loop$number, loop$digits, loop$count) { while (true) { let number = loop$number; let digits = loop$digits; let count = loop$count; let number$1 = number; if ((number$1 <= 0) && (count >= 9)) { return digits; } else { let number$2 = number; if (number$2 <= 0) { loop$number = number$2; loop$digits = listPrepend(0, digits); loop$count = count + 1; } else { let number$3 = number; let digit = number$3 % 10; let number$4 = floored_div(number$3, 10.0); loop$number = number$4; loop$digits = listPrepend(digit, digits); loop$count = count + 1; } } } } /** * Returns the list of digits of `number`. If the number of digits is less * than 9, the result is zero-padded at the front. * * @ignore */ function get_zero_padded_digits(number) { return do_get_zero_padded_digits(number, toList([]), 0); } /** * Converts nanoseconds into a `String` representation of fractional seconds. * * Assumes that `nanoseconds < 1_000_000_000`, which will be true for any * normalised timestamp. * * @ignore */ function show_second_fraction(nanoseconds) { let $ = $int.compare(nanoseconds, 0); if ($ instanceof $order.Lt) { return ""; } else if ($ instanceof $order.Eq) { return ""; } else { let _block; let _pipe = nanoseconds; let _pipe$1 = get_zero_padded_digits(_pipe); let _pipe$2 = remove_trailing_zeros(_pipe$1); let _pipe$3 = $list.map(_pipe$2, $int.to_string); _block = $string.join(_pipe$3, ""); let second_fraction_part = _block; return "." + second_fraction_part; } } /** * Convert a timestamp to a RFC 3339 formatted time string, with an offset * supplied as an additional argument. * * The output of this function is also ISO 8601 compatible so long as the * offset not negative. Offsets have at-most minute precision, so an offset * with higher precision will be rounded to the nearest minute. * * If you are making an API such as a HTTP JSON API you are encouraged to use * Unix timestamps instead of this format or ISO 8601. Unix timestamps are a * better choice as they don't contain offset information. Consider: * * - UTC offsets are not time zones. This does not and cannot tell us the time * zone in which the date was recorded. So what are we supposed to do with * this information? * - Users typically want dates formatted according to their local time zone. * What if the provided UTC offset is different from the current user's time * zone? What are we supposed to do with it then? * - Despite it being useless (or worse, a source of bugs), the UTC offset * creates a larger payload to transfer. * * They also uses more memory than a unix timestamp. The way they are better * than Unix timestamp is that it is easier for a human to read them, but * this is a hinderance that tooling can remedy, and APIs are not primarily * for humans. * * # Examples * * ```gleam * timestamp.from_unix_seconds_and_nanoseconds(1000, 123_000_000) * |> to_rfc3339(calendar.utc_offset) * // -> "1970-01-01T00:16:40.123Z" * ``` * * ```gleam * timestamp.from_unix_seconds(1000) * |> to_rfc3339(duration.seconds(3600)) * // -> "1970-01-01T01:16:40+01:00" * ``` */ export function to_rfc3339(timestamp, offset) { let offset$1 = duration_to_minutes(offset); let $ = to_calendar_from_offset(timestamp, offset$1); let years; let months; let days; let hours; let minutes; let seconds; years = $[0]; months = $[1]; days = $[2]; hours = $[3]; minutes = $[4]; seconds = $[5]; let offset_minutes = modulo(offset$1, 60); let offset_hours = $int.absolute_value(floored_div(offset$1, 60.0)); let n2 = (_capture) => { return pad_digit(_capture, 2); }; let n4 = (_capture) => { return pad_digit(_capture, 4); }; let out = ""; let out$1 = ((((out + n4(years)) + "-") + n2(months)) + "-") + n2(days); let out$2 = out$1 + "T"; let out$3 = ((((out$2 + n2(hours)) + ":") + n2(minutes)) + ":") + n2(seconds); let out$4 = out$3 + show_second_fraction(timestamp.nanoseconds); let $1 = $int.compare(offset$1, 0); if ($1 instanceof $order.Lt) { return (((out$4 + "-") + n2(offset_hours)) + ":") + n2(offset_minutes); } else if ($1 instanceof $order.Eq) { return out$4 + "Z"; } else { return (((out$4 + "+") + n2(offset_hours)) + ":") + n2(offset_minutes); } } function is_leap_year(year) { return ((year % 4) === 0) && (((year % 100) !== 0) || ((year % 400) === 0)); } function parse_sign(bytes) { if (bytes.bitSize >= 8) { if (bytes.byteAt(0) === 43) { if ((bytes.bitSize - 8) % 8 === 0) { let remaining_bytes = bitArraySlice(bytes, 8); return new Ok(["+", remaining_bytes]); } else { return new Error(undefined); } } else if (bytes.byteAt(0) === 45 && (bytes.bitSize - 8) % 8 === 0) { let remaining_bytes = bitArraySlice(bytes, 8); return new Ok(["-", remaining_bytes]); } else { return new Error(undefined); } } else { return new Error(undefined); } } /** * Accept the given value from `bytes` and move past it if found. * * @ignore */ function accept_byte(bytes, value) { if (bytes.bitSize >= 8 && (bytes.bitSize - 8) % 8 === 0) { let byte = bytes.byteAt(0); if (byte === value) { let remaining_bytes = bitArraySlice(bytes, 8); return new Ok(remaining_bytes); } else { return new Error(undefined); } } else { return new Error(undefined); } } function accept_empty(bytes) { if (bytes.bitSize === 0) { return new Ok(undefined); } else { return new Error(undefined); } } /** * Note: It is the callers responsibility to ensure the inputs are valid. * * See https://www.tondering.dk/claus/cal/julperiod.php#formula * * @ignore */ function julian_day_from_ymd(year, month, day) { let adjustment = globalThis.Math.trunc((14 - month) / 12); let adjusted_year = (year + 4800) - adjustment; let adjusted_month = (month + 12 * adjustment) - 3; return (((((day + (globalThis.Math.trunc((153 * adjusted_month + 2) / 5))) + 365 * adjusted_year) + (globalThis.Math.trunc( adjusted_year / 4 ))) - (globalThis.Math.trunc(adjusted_year / 100))) + (globalThis.Math.trunc( adjusted_year / 400 ))) - 32_045; } /** * Create a timestamp from a number of seconds since 00:00:00 UTC on 1 January * 1970. */ export function from_unix_seconds(seconds) { return new Timestamp(seconds, 0); } /** * Create a timestamp from a number of seconds and nanoseconds since 00:00:00 * UTC on 1 January 1970. * * # JavaScript int limitations * * Remember that JavaScript can only perfectly represent ints between positive * and negative 9,007,199,254,740,991! If you only use the nanosecond field * then you will almost certainly not get the date value you want due to this * loss of precision. Always use seconds primarily and then use nanoseconds * for the final sub-second adjustment. */ export function from_unix_seconds_and_nanoseconds(seconds, nanoseconds) { let _pipe = new Timestamp(seconds, nanoseconds); return normalise(_pipe); } /** * Convert the timestamp to a number of seconds since 00:00:00 UTC on 1 * January 1970. * * There may be some small loss of precision due to `Timestamp` being * nanosecond accurate and `Float` not being able to represent this. */ export function to_unix_seconds(timestamp) { let seconds = $int.to_float(timestamp.seconds); let nanoseconds = $int.to_float(timestamp.nanoseconds); return seconds + (nanoseconds / 1_000_000_000.0); } /** * Convert the timestamp to a number of seconds and nanoseconds since 00:00:00 * UTC on 1 January 1970. There is no loss of precision with this conversion * on any target. */ export function to_unix_seconds_and_nanoseconds(timestamp) { return [timestamp.seconds, timestamp.nanoseconds]; } const seconds_per_day = 86_400; const seconds_per_hour = 3600; const seconds_per_minute = 60; function offset_to_seconds(sign, hours, minutes) { let abs_seconds = hours * seconds_per_hour + minutes * seconds_per_minute; if (sign === "-") { return - abs_seconds; } else { return abs_seconds; } } /** * `julian_seconds_from_parts(year, month, day, hours, minutes, seconds)` * returns the number of Julian * seconds represented by the given arguments. * * Note: It is the callers responsibility to ensure the inputs are valid. * * See https://www.tondering.dk/claus/cal/julperiod.php#formula * * @ignore */ function julian_seconds_from_parts(year, month, day, hours, minutes, seconds) { let julian_day_seconds = julian_day_from_ymd(year, month, day) * seconds_per_day; return ((julian_day_seconds + hours * seconds_per_hour) + minutes * seconds_per_minute) + seconds; } const nanoseconds_per_second = 1_000_000_000; /** * The `:` character as a byte * * @ignore */ const byte_colon = 0x3A; /** * The `-` character as a byte * * @ignore */ const byte_minus = 0x2D; /** * The `0` character as a byte * * @ignore */ const byte_zero = 0x30; /** * The `9` character as a byte * * @ignore */ const byte_nine = 0x39; function do_parse_second_fraction_as_nanoseconds( loop$bytes, loop$acc, loop$power ) { while (true) { let bytes = loop$bytes; let acc = loop$acc; let power = loop$power; let power$1 = globalThis.Math.trunc(power / 10); if (bytes.bitSize >= 8 && (bytes.bitSize - 8) % 8 === 0) { let byte = bytes.byteAt(0); if (((0x30 <= byte) && (byte <= 0x39)) && (power$1 < 1)) { let remaining_bytes = bitArraySlice(bytes, 8); loop$bytes = remaining_bytes; loop$acc = acc; loop$power = power$1; } else { let byte$1 = bytes.byteAt(0); if ((0x30 <= byte$1) && (byte$1 <= 0x39)) { let remaining_bytes = bitArraySlice(bytes, 8); let digit = byte$1 - 0x30; loop$bytes = remaining_bytes; loop$acc = acc + digit * power$1; loop$power = power$1; } else { return new Ok([acc, bytes]); } } } else { return new Ok([acc, bytes]); } } } function parse_second_fraction_as_nanoseconds(bytes) { if (bytes.bitSize >= 8 && bytes.byteAt(0) === 46) { if (bytes.bitSize >= 16 && (bytes.bitSize - 16) % 8 === 0) { let byte = bytes.byteAt(1); if ((0x30 <= byte) && (byte <= 0x39)) { let remaining_bytes = bitArraySlice(bytes, 16); return do_parse_second_fraction_as_nanoseconds( toBitArray([byte, remaining_bytes]), 0, nanoseconds_per_second, ); } else if ((bytes.bitSize - 8) % 8 === 0) { return new Error(undefined); } else { return new Ok([0, bytes]); } } else if ((bytes.bitSize - 8) % 8 === 0) { return new Error(undefined); } else { return new Ok([0, bytes]); } } else { return new Ok([0, bytes]); } } function do_parse_digits(loop$bytes, loop$count, loop$acc, loop$k) { while (true) { let bytes = loop$bytes; let count = loop$count; let acc = loop$acc; let k = loop$k; if (k >= count) { return new Ok([acc, bytes]); } else if (bytes.bitSize >= 8 && (bytes.bitSize - 8) % 8 === 0) { let byte = bytes.byteAt(0); if ((0x30 <= byte) && (byte <= 0x39)) { let remaining_bytes = bitArraySlice(bytes, 8); loop$bytes = remaining_bytes; loop$count = count; loop$acc = acc * 10 + (byte - 0x30); loop$k = k + 1; } else { return new Error(undefined); } } else { return new Error(undefined); } } } /** * Parse and return the given number of digits from the given bytes. * * @ignore */ function parse_digits(bytes, count) { return do_parse_digits(bytes, count, 0, 0); } function parse_year(bytes) { return parse_digits(bytes, 4); } function parse_month(bytes) { return $result.try$( parse_digits(bytes, 2), (_use0) => { let month; let bytes$1; month = _use0[0]; bytes$1 = _use0[1]; let $ = (1 <= month) && (month <= 12); if ($) { return new Ok([month, bytes$1]); } else { return new Error(undefined); } }, ); } function parse_day(bytes, year, month) { return $result.try$( parse_digits(bytes, 2), (_use0) => { let day; let bytes$1; day = _use0[0]; bytes$1 = _use0[1]; return $result.try$( (() => { if (month === 1) { return new Ok(31); } else if (month === 3) { return new Ok(31); } else if (month === 5) { return new Ok(31); } else if (month === 7) { return new Ok(31); } else if (month === 8) { return new Ok(31); } else if (month === 10) { return new Ok(31); } else if (month === 12) { return new Ok(31); } else if (month === 4) { return new Ok(30); } else if (month === 6) { return new Ok(30); } else if (month === 9) { return new Ok(30); } else if (month === 11) { return new Ok(30); } else if (month === 2) { let $ = is_leap_year(year); if ($) { return new Ok(29); } else { return new Ok(28); } } else { return new Error(undefined); } })(), (max_day) => { let $ = (1 <= day) && (day <= max_day); if ($) { return new Ok([day, bytes$1]); } else { return new Error(undefined); } }, ); }, ); } function parse_hours(bytes) { return $result.try$( parse_digits(bytes, 2), (_use0) => { let hours; let bytes$1; hours = _use0[0]; bytes$1 = _use0[1]; let $ = (0 <= hours) && (hours <= 23); if ($) { return new Ok([hours, bytes$1]); } else { return new Error(undefined); } }, ); } function parse_minutes(bytes) { return $result.try$( parse_digits(bytes, 2), (_use0) => { let minutes; let bytes$1; minutes = _use0[0]; bytes$1 = _use0[1]; let $ = (0 <= minutes) && (minutes <= 59); if ($) { return new Ok([minutes, bytes$1]); } else { return new Error(undefined); } }, ); } function parse_seconds(bytes) { return $result.try$( parse_digits(bytes, 2), (_use0) => { let seconds; let bytes$1; seconds = _use0[0]; bytes$1 = _use0[1]; let $ = (0 <= seconds) && (seconds <= 60); if ($) { return new Ok([seconds, bytes$1]); } else { return new Error(undefined); } }, ); } function parse_numeric_offset(bytes) { return $result.try$( parse_sign(bytes), (_use0) => { let sign; let bytes$1; sign = _use0[0]; bytes$1 = _use0[1]; return $result.try$( parse_hours(bytes$1), (_use0) => { let hours; let bytes$2; hours = _use0[0]; bytes$2 = _use0[1]; return $result.try$( accept_byte(bytes$2, byte_colon), (bytes) => { return $result.try$( parse_minutes(bytes), (_use0) => { let minutes; let bytes$1; minutes = _use0[0]; bytes$1 = _use0[1]; let offset_seconds = offset_to_seconds(sign, hours, minutes); return new Ok([offset_seconds, bytes$1]); }, ); }, ); }, ); }, ); } function parse_offset(bytes) { if (bytes.bitSize >= 8) { if (bytes.byteAt(0) === 90) { if ((bytes.bitSize - 8) % 8 === 0) { let remaining_bytes = bitArraySlice(bytes, 8); return new Ok([0, remaining_bytes]); } else { return parse_numeric_offset(bytes); } } else if (bytes.byteAt(0) === 122 && (bytes.bitSize - 8) % 8 === 0) { let remaining_bytes = bitArraySlice(bytes, 8); return new Ok([0, remaining_bytes]); } else { return parse_numeric_offset(bytes); } } else { return parse_numeric_offset(bytes); } } /** * The `t` character as a byte * * @ignore */ const byte_t_lowercase = 0x74; /** * The `T` character as a byte * * @ignore */ const byte_t_uppercase = 0x54; /** * The `T` character as a byte * * @ignore */ const byte_space = 0x20; function accept_date_time_separator(bytes) { if (bytes.bitSize >= 8 && (bytes.bitSize - 8) % 8 === 0) { let byte = bytes.byteAt(0); if (((byte === 0x54) || (byte === 0x74)) || (byte === 0x20)) { let remaining_bytes = bitArraySlice(bytes, 8); return new Ok(remaining_bytes); } else { return new Error(undefined); } } else { return new Error(undefined); } } /** * The Julian seconds of the UNIX epoch (Julian day is 2_440_588) * * @ignore */ const julian_seconds_unix_epoch = 210_866_803_200; /** * Note: The caller of this function must ensure that all inputs are valid. * * @ignore */ function from_date_time( year, month, day, hours, minutes, seconds, second_fraction_as_nanoseconds, offset_seconds ) { let julian_seconds = julian_seconds_from_parts( year, month, day, hours, minutes, seconds, ); let julian_seconds_since_epoch = julian_seconds - julian_seconds_unix_epoch; let _pipe = new Timestamp( julian_seconds_since_epoch - offset_seconds, second_fraction_as_nanoseconds, ); return normalise(_pipe); } /** * 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" * ``` */ export function from_calendar(date, time, offset) { let _block; let $ = date.month; if ($ instanceof $calendar.January) { _block = 1; } else if ($ instanceof $calendar.February) { _block = 2; } else if ($ instanceof $calendar.March) { _block = 3; } else if ($ instanceof $calendar.April) { _block = 4; } else if ($ instanceof $calendar.May) { _block = 5; } else if ($ instanceof $calendar.June) { _block = 6; } else if ($ instanceof $calendar.July) { _block = 7; } else if ($ instanceof $calendar.August) { _block = 8; } else if ($ instanceof $calendar.September) { _block = 9; } else if ($ instanceof $calendar.October) { _block = 10; } else if ($ instanceof $calendar.November) { _block = 11; } else { _block = 12; } let month = _block; return from_date_time( date.year, month, date.day, time.hours, time.minutes, time.seconds, time.nanoseconds, $float.round($duration.to_seconds(offset)), ); } /** * Parses an [RFC 3339 formatted time string][spec] into a `Timestamp`. * * [spec]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6 * * # Examples * * ```gleam * let assert Ok(ts) = timestamp.parse_rfc3339("1970-01-01T00:00:01Z") * timestamp.to_unix_seconds_and_nanoseconds(ts) * // -> #(1, 0) * ``` * * Parsing an invalid timestamp returns an error. * * ```gleam * let assert Error(Nil) = timestamp.parse_rfc3339("1995-10-31") * ``` * * ## Time zones * * It may at first seem that the RFC 3339 format includes timezone * information, as it can specify an offset such as `Z` or `+3`, so why does * this function not return calendar time with a time zone? There are multiple * reasons: * * - RFC 3339's timestamp format is based on calendar time, but it is * unambigous, so it can be converted into epoch time when being parsed. It * is always better to internally use epoch time to represent unambiguous * points in time, so we perform that conversion as a convenience and to * ensure that programmers with less time experience don't accidentally use * a less suitable time representation. * * - RFC 3339's contains _calendar time offset_ information, not time zone * information. This is enough to convert it to an unambiguous timestamp, * but it is not enough information to reliably work with calendar time. * Without the time zone and the time zone database it's not possible to * know what time period that offset is valid for, so it cannot be used * without risk of bugs. * * ## Behaviour details * * - Follows the grammar specified in section 5.6 Internet Date/Time Format of * RFC 3339 . * - The `T` and `Z` characters may alternatively be lower case `t` or `z`, * respectively. * - Full dates and full times must be separated by `T` or `t`. A space is also * permitted. * - Leap seconds rules are not considered. That is, any timestamp may * specify digts `00` - `60` for the seconds. * - Any part of a fractional second that cannot be represented in the * nanosecond precision is tructated. That is, for the time string, * `"1970-01-01T00:00:00.1234567899Z"`, the fractional second `.1234567899` * will be represented as `123_456_789` in the `Timestamp`. */ export function parse_rfc3339(input) { let bytes = $bit_array.from_string(input); return $result.try$( parse_year(bytes), (_use0) => { let year; let bytes$1; year = _use0[0]; bytes$1 = _use0[1]; return $result.try$( accept_byte(bytes$1, byte_minus), (bytes) => { return $result.try$( parse_month(bytes), (_use0) => { let month; let bytes$1; month = _use0[0]; bytes$1 = _use0[1]; return $result.try$( accept_byte(bytes$1, byte_minus), (bytes) => { return $result.try$( parse_day(bytes, year, month), (_use0) => { let day; let bytes$1; day = _use0[0]; bytes$1 = _use0[1]; return $result.try$( accept_date_time_separator(bytes$1), (bytes) => { return $result.try$( parse_hours(bytes), (_use0) => { let hours; let bytes$1; hours = _use0[0]; bytes$1 = _use0[1]; return $result.try$( accept_byte(bytes$1, byte_colon), (bytes) => { return $result.try$( parse_minutes(bytes), (_use0) => { let minutes; let bytes$1; minutes = _use0[0]; bytes$1 = _use0[1]; return $result.try$( accept_byte(bytes$1, byte_colon), (bytes) => { return $result.try$( parse_seconds(bytes), (_use0) => { let seconds; let bytes$1; seconds = _use0[0]; bytes$1 = _use0[1]; return $result.try$( parse_second_fraction_as_nanoseconds( bytes$1, ), (_use0) => { let second_fraction_as_nanoseconds; let bytes$2; second_fraction_as_nanoseconds = _use0[0]; bytes$2 = _use0[1]; return $result.try$( parse_offset(bytes$2), (_use0) => { let offset_seconds; let bytes$3; offset_seconds = _use0[0]; bytes$3 = _use0[1]; return $result.try$( accept_empty(bytes$3), (_use0) => { return new Ok( from_date_time( year, month, day, hours, minutes, seconds, second_fraction_as_nanoseconds, offset_seconds, ), ); }, ); }, ); }, ); }, ); }, ); }, ); }, ); }, ); }, ); }, ); }, ); }, ); }, ); }, ); } /** * The epoch of Unix time, which is 00:00:00 UTC on 1 January 1970. */ export const unix_epoch = /* @__PURE__ */ new Timestamp(0, 0);