Initial commit

This commit is contained in:
Hugo Mårdbrink 2025-11-30 15:44:22 +01:00
commit a6272848f9
379 changed files with 74829 additions and 0 deletions

View file

@ -0,0 +1 @@
export * from "../prelude.mjs";

View file

@ -0,0 +1,391 @@
import * as $int from "../../../gleam_stdlib/gleam/int.mjs";
import * as $order from "../../../gleam_stdlib/gleam/order.mjs";
import { Ok, Error, CustomType as $CustomType } from "../../gleam.mjs";
import * as $duration from "../../gleam/time/duration.mjs";
import { local_time_offset_seconds } from "../../gleam_time_ffi.mjs";
export class Date extends $CustomType {
constructor(year, month, day) {
super();
this.year = year;
this.month = month;
this.day = day;
}
}
export const Date$Date = (year, month, day) => new Date(year, month, day);
export const Date$isDate = (value) => value instanceof Date;
export const Date$Date$year = (value) => value.year;
export const Date$Date$0 = (value) => value.year;
export const Date$Date$month = (value) => value.month;
export const Date$Date$1 = (value) => value.month;
export const Date$Date$day = (value) => value.day;
export const Date$Date$2 = (value) => value.day;
export class TimeOfDay extends $CustomType {
constructor(hours, minutes, seconds, nanoseconds) {
super();
this.hours = hours;
this.minutes = minutes;
this.seconds = seconds;
this.nanoseconds = nanoseconds;
}
}
export const TimeOfDay$TimeOfDay = (hours, minutes, seconds, nanoseconds) =>
new TimeOfDay(hours, minutes, seconds, nanoseconds);
export const TimeOfDay$isTimeOfDay = (value) => value instanceof TimeOfDay;
export const TimeOfDay$TimeOfDay$hours = (value) => value.hours;
export const TimeOfDay$TimeOfDay$0 = (value) => value.hours;
export const TimeOfDay$TimeOfDay$minutes = (value) => value.minutes;
export const TimeOfDay$TimeOfDay$1 = (value) => value.minutes;
export const TimeOfDay$TimeOfDay$seconds = (value) => value.seconds;
export const TimeOfDay$TimeOfDay$2 = (value) => value.seconds;
export const TimeOfDay$TimeOfDay$nanoseconds = (value) => value.nanoseconds;
export const TimeOfDay$TimeOfDay$3 = (value) => value.nanoseconds;
export class January extends $CustomType {}
export const Month$January = () => new January();
export const Month$isJanuary = (value) => value instanceof January;
export class February extends $CustomType {}
export const Month$February = () => new February();
export const Month$isFebruary = (value) => value instanceof February;
export class March extends $CustomType {}
export const Month$March = () => new March();
export const Month$isMarch = (value) => value instanceof March;
export class April extends $CustomType {}
export const Month$April = () => new April();
export const Month$isApril = (value) => value instanceof April;
export class May extends $CustomType {}
export const Month$May = () => new May();
export const Month$isMay = (value) => value instanceof May;
export class June extends $CustomType {}
export const Month$June = () => new June();
export const Month$isJune = (value) => value instanceof June;
export class July extends $CustomType {}
export const Month$July = () => new July();
export const Month$isJuly = (value) => value instanceof July;
export class August extends $CustomType {}
export const Month$August = () => new August();
export const Month$isAugust = (value) => value instanceof August;
export class September extends $CustomType {}
export const Month$September = () => new September();
export const Month$isSeptember = (value) => value instanceof September;
export class October extends $CustomType {}
export const Month$October = () => new October();
export const Month$isOctober = (value) => value instanceof October;
export class November extends $CustomType {}
export const Month$November = () => new November();
export const Month$isNovember = (value) => value instanceof November;
export class December extends $CustomType {}
export const Month$December = () => new December();
export const Month$isDecember = (value) => value instanceof December;
/**
* Get the offset for the computer's currently configured time zone.
*
* Note this may not be the time zone that is correct to use for your user.
* 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.
*/
export function local_offset() {
return $duration.seconds(local_time_offset_seconds());
}
/**
* Returns the English name for a month.
*
* # Examples
*
* ```gleam
* month_to_string(April)
* // -> "April"
* ```
*/
export function month_to_string(month) {
if (month instanceof January) {
return "January";
} else if (month instanceof February) {
return "February";
} else if (month instanceof March) {
return "March";
} else if (month instanceof April) {
return "April";
} else if (month instanceof May) {
return "May";
} else if (month instanceof June) {
return "June";
} else if (month instanceof July) {
return "July";
} else if (month instanceof August) {
return "August";
} else if (month instanceof September) {
return "September";
} else if (month instanceof October) {
return "October";
} else if (month instanceof November) {
return "November";
} else {
return "December";
}
}
/**
* Returns the number for the month, where January is 1 and December is 12.
*
* # Examples
*
* ```gleam
* month_to_int(January)
* // -> 1
* ```
*/
export function month_to_int(month) {
if (month instanceof January) {
return 1;
} else if (month instanceof February) {
return 2;
} else if (month instanceof March) {
return 3;
} else if (month instanceof April) {
return 4;
} else if (month instanceof May) {
return 5;
} else if (month instanceof June) {
return 6;
} else if (month instanceof July) {
return 7;
} else if (month instanceof August) {
return 8;
} else if (month instanceof September) {
return 9;
} else if (month instanceof October) {
return 10;
} else if (month instanceof November) {
return 11;
} else {
return 12;
}
}
/**
* Returns the month for a given number, where January is 1 and December is 12.
*
* # Examples
*
* ```gleam
* month_from_int(1)
* // -> Ok(January)
* ```
*/
export function month_from_int(month) {
if (month === 1) {
return new Ok(new January());
} else if (month === 2) {
return new Ok(new February());
} else if (month === 3) {
return new Ok(new March());
} else if (month === 4) {
return new Ok(new April());
} else if (month === 5) {
return new Ok(new May());
} else if (month === 6) {
return new Ok(new June());
} else if (month === 7) {
return new Ok(new July());
} else if (month === 8) {
return new Ok(new August());
} else if (month === 9) {
return new Ok(new September());
} else if (month === 10) {
return new Ok(new October());
} else if (month === 11) {
return new Ok(new November());
} else if (month === 12) {
return new Ok(new December());
} else {
return new Error(undefined);
}
}
/**
* Determines if a given year is a leap year.
*
* A leap year occurs every 4 years, except for years divisible by 100,
* unless they are also divisible by 400.
*
* # Examples
*
* ```gleam
* is_leap_year(2024)
* // -> True
* ```
*
* ```gleam
* is_leap_year(2023)
* // -> False
* ```
*/
export function is_leap_year(year) {
let $ = (year % 400) === 0;
if ($) {
return $;
} else {
let $1 = (year % 100) === 0;
if ($1) {
return false;
} else {
return (year % 4) === 0;
}
}
}
/**
* Checks if a given date is valid.
*
* This function properly accounts for leap years when validating February days.
* A leap year occurs every 4 years, except for years divisible by 100,
* unless they are also divisible by 400.
*
* # Examples
*
* ```gleam
* is_valid_date(Date(2023, April, 15))
* // -> True
* ```
*
* ```gleam
* is_valid_date(Date(2023, April, 31))
* // -> False
* ```
*
* ```gleam
* is_valid_date(Date(2024, February, 29))
* // -> True (2024 is a leap year)
* ```
*/
export function is_valid_date(date) {
let year;
let month;
let day;
year = date.year;
month = date.month;
day = date.day;
let $ = day < 1;
if ($) {
return false;
} else {
if (month instanceof January) {
return day <= 31;
} else if (month instanceof February) {
let _block;
let $1 = is_leap_year(year);
if ($1) {
_block = 29;
} else {
_block = 28;
}
let max_february_days = _block;
return day <= max_february_days;
} else if (month instanceof March) {
return day <= 31;
} else if (month instanceof April) {
return day <= 30;
} else if (month instanceof May) {
return day <= 31;
} else if (month instanceof June) {
return day <= 30;
} else if (month instanceof July) {
return day <= 31;
} else if (month instanceof August) {
return day <= 31;
} else if (month instanceof September) {
return day <= 30;
} else if (month instanceof October) {
return day <= 31;
} else if (month instanceof November) {
return day <= 30;
} else {
return day <= 31;
}
}
}
/**
* Checks if a time of day is valid.
*
* Validates that hours are 0-23, minutes are 0-59, seconds are 0-59,
* and nanoseconds are 0-999,999,999.
*
* # Examples
*
* ```gleam
* is_valid_time_of_day(TimeOfDay(12, 30, 45, 123456789))
* // -> True
* ```
*/
export function is_valid_time_of_day(time) {
let hours;
let minutes;
let seconds;
let nanoseconds;
hours = time.hours;
minutes = time.minutes;
seconds = time.seconds;
nanoseconds = time.nanoseconds;
return (((((((hours >= 0) && (hours <= 23)) && (minutes >= 0)) && (minutes <= 59)) && (seconds >= 0)) && (seconds <= 59)) && (nanoseconds >= 0)) && (nanoseconds <= 999_999_999);
}
/**
* Naively compares two dates without any time zone information, returning an
* order.
*
* ## Correctness
*
* This function compares dates without any time zone information, only using
* the rules for the gregorian calendar. This is typically sufficient, but be
* aware that in reality some time zones will change their calendar date
* occasionally. This can result in days being skipped, out of order, or
* happening multiple times.
*
* If you need real-world correct time ordering then use the
* `gleam/time/timestamp` module instead.
*/
export function naive_date_compare(one, other) {
let _pipe = $int.compare(one.year, other.year);
let _pipe$1 = $order.lazy_break_tie(
_pipe,
() => {
return $int.compare(month_to_int(one.month), month_to_int(other.month));
},
);
return $order.lazy_break_tie(
_pipe$1,
() => { return $int.compare(one.day, other.day); },
);
}
/**
* The offset for the [Coordinated Universal Time (UTC)](https://en.wikipedia.org/wiki/Coordinated_Universal_Time)
* time zone.
*
* The utc zone has no time adjustments, it is always zero. It never observes
* daylight-saving time and it never shifts around based on political
* restructuring.
*/
export const utc_offset = $duration.empty;

View file

@ -0,0 +1,382 @@
import * as $bool from "../../../gleam_stdlib/gleam/bool.mjs";
import * as $int from "../../../gleam_stdlib/gleam/int.mjs";
import * as $order from "../../../gleam_stdlib/gleam/order.mjs";
import * as $string from "../../../gleam_stdlib/gleam/string.mjs";
import { CustomType as $CustomType, remainderInt, divideInt, isEqual } from "../../gleam.mjs";
class Duration extends $CustomType {
constructor(seconds, nanoseconds) {
super();
this.seconds = seconds;
this.nanoseconds = nanoseconds;
}
}
export class Nanosecond extends $CustomType {}
export const Unit$Nanosecond = () => new Nanosecond();
export const Unit$isNanosecond = (value) => value instanceof Nanosecond;
export class Microsecond extends $CustomType {}
export const Unit$Microsecond = () => new Microsecond();
export const Unit$isMicrosecond = (value) => value instanceof Microsecond;
export class Millisecond extends $CustomType {}
export const Unit$Millisecond = () => new Millisecond();
export const Unit$isMillisecond = (value) => value instanceof Millisecond;
export class Second extends $CustomType {}
export const Unit$Second = () => new Second();
export const Unit$isSecond = (value) => value instanceof Second;
export class Minute extends $CustomType {}
export const Unit$Minute = () => new Minute();
export const Unit$isMinute = (value) => value instanceof Minute;
export class Hour extends $CustomType {}
export const Unit$Hour = () => new Hour();
export const Unit$isHour = (value) => value instanceof Hour;
export class Day extends $CustomType {}
export const Unit$Day = () => new Day();
export const Unit$isDay = (value) => value instanceof Day;
export class Week extends $CustomType {}
export const Unit$Week = () => new Week();
export const Unit$isWeek = (value) => value instanceof Week;
export class Month extends $CustomType {}
export const Unit$Month = () => new Month();
export const Unit$isMonth = (value) => value instanceof Month;
export class Year extends $CustomType {}
export const Unit$Year = () => new Year();
export const Unit$isYear = (value) => value instanceof Year;
/**
* 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.
*
* @ignore
*/
function normalise(duration) {
let multiplier = 1_000_000_000;
let nanoseconds$1 = remainderInt(duration.nanoseconds, multiplier);
let overflow = duration.nanoseconds - nanoseconds$1;
let seconds$1 = duration.seconds + (divideInt(overflow, multiplier));
let $ = nanoseconds$1 >= 0;
if ($) {
return new Duration(seconds$1, nanoseconds$1);
} else {
return new Duration(seconds$1 - 1, multiplier + nanoseconds$1);
}
}
/**
* 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 size 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)
* ```
*/
export function approximate(duration) {
let s;
let ns;
s = duration.seconds;
ns = duration.nanoseconds;
let minute = 60;
let hour = minute * 60;
let day = hour * 24;
let week = day * 7;
let year = day * 365 + hour * 6;
let month = globalThis.Math.trunc(year / 12);
let microsecond = 1000;
let millisecond = microsecond * 1000;
let $ = undefined;
if (s < 0) {
let _block;
let _pipe = new Duration(- s, - ns);
let _pipe$1 = normalise(_pipe);
_block = approximate(_pipe$1);
let $1 = _block;
let amount;
let unit;
amount = $1[0];
unit = $1[1];
return [- amount, unit];
} else if (s >= year) {
return [divideInt(s, year), new Year()];
} else if (s >= month) {
return [divideInt(s, month), new Month()];
} else if (s >= week) {
return [divideInt(s, week), new Week()];
} else if (s >= day) {
return [divideInt(s, day), new Day()];
} else if (s >= hour) {
return [divideInt(s, hour), new Hour()];
} else if (s >= minute) {
return [divideInt(s, minute), new Minute()];
} else if (s > 0) {
return [s, new Second()];
} else if (ns >= millisecond) {
return [divideInt(ns, millisecond), new Millisecond()];
} else if (ns >= microsecond) {
return [divideInt(ns, microsecond), new Microsecond()];
} else {
return [ns, new Nanosecond()];
}
}
/**
* Compare one duration to another, indicating whether the first spans a
* larger amount of time (and so is greater) or smaller amount of time (and so
* is lesser) than the second.
*
* # Examples
*
* ```gleam
* compare(seconds(1), seconds(2))
* // -> order.Lt
* ```
*
* Whether a duration is negative or positive doesn't matter for comparing
* them, only the amount of time spanned matters.
*
* ```gleam
* compare(seconds(-2), seconds(1))
* // -> order.Gt
* ```
*/
export function compare(left, right) {
let parts = (x) => {
let $ = x.seconds >= 0;
if ($) {
return [x.seconds, x.nanoseconds];
} else {
return [x.seconds * -1 - 1, 1_000_000_000 - x.nanoseconds];
}
};
let $ = parts(left);
let ls;
let lns;
ls = $[0];
lns = $[1];
let $1 = parts(right);
let rs;
let rns;
rs = $1[0];
rns = $1[1];
let _pipe = $int.compare(ls, rs);
return $order.break_tie(_pipe, $int.compare(lns, rns));
}
/**
* Calculate the difference between two durations.
*
* This is effectively substracting the first duration from the second.
*
* # Examples
*
* ```gleam
* difference(seconds(1), seconds(5))
* // -> seconds(4)
* ```
*/
export function difference(left, right) {
let _pipe = new Duration(
right.seconds - left.seconds,
right.nanoseconds - left.nanoseconds,
);
return normalise(_pipe);
}
/**
* Add two durations together.
*
* # Examples
*
* ```gleam
* add(seconds(1), seconds(5))
* // -> seconds(6)
* ```
*/
export function add(left, right) {
let _pipe = new Duration(
left.seconds + right.seconds,
left.nanoseconds + right.nanoseconds,
);
return normalise(_pipe);
}
function nanosecond_digits(loop$n, loop$position, loop$acc) {
while (true) {
let n = loop$n;
let position = loop$position;
let acc = loop$acc;
if (position === 9) {
return acc;
} else if ((acc === "") && ((remainderInt(n, 10)) === 0)) {
loop$n = globalThis.Math.trunc(n / 10);
loop$position = position + 1;
loop$acc = acc;
} else {
let acc$1 = $int.to_string(n % 10) + acc;
loop$n = globalThis.Math.trunc(n / 10);
loop$position = position + 1;
loop$acc = acc$1;
}
}
}
/**
* Create a duration of a number of seconds.
*/
export function seconds(amount) {
return new Duration(amount, 0);
}
/**
* Create a duration of a number of minutes.
*/
export function minutes(amount) {
return seconds(amount * 60);
}
/**
* Create a duration of a number of hours.
*/
export function hours(amount) {
return seconds(amount * 60 * 60);
}
/**
* Create a duration of a number of milliseconds.
*/
export function milliseconds(amount) {
let remainder = amount % 1000;
let overflow = amount - remainder;
let nanoseconds$1 = remainder * 1_000_000;
let seconds$1 = globalThis.Math.trunc(overflow / 1000);
let _pipe = new Duration(seconds$1, nanoseconds$1);
return normalise(_pipe);
}
/**
* Create a duration of a number of nanoseconds.
*
* # JavaScript int limitations
*
* Remember that JavaScript can only perfectly represent ints between positive
* and negative 9,007,199,254,740,991! If you use a single call to this
* function to create durations larger than that number of nanoseconds then
* you will likely not get exactly the value you expect. Use `seconds` and
* `milliseconds` as much as possible for large durations.
*/
export function nanoseconds(amount) {
let _pipe = new Duration(0, amount);
return normalise(_pipe);
}
/**
* 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.
*/
export function to_seconds(duration) {
let seconds$1 = $int.to_float(duration.seconds);
let nanoseconds$1 = $int.to_float(duration.nanoseconds);
return seconds$1 + (nanoseconds$1 / 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.
*/
export function to_seconds_and_nanoseconds(duration) {
return [duration.seconds, duration.nanoseconds];
}
export const empty = /* @__PURE__ */ new Duration(0, 0);
/**
* 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
*/
export function to_iso8601_string(duration) {
return $bool.guard(
isEqual(duration, empty),
"PT0S",
() => {
let split = (total, limit) => {
let amount = remainderInt(total, limit);
let remainder = divideInt((total - amount), limit);
return [amount, remainder];
};
let $ = split(duration.seconds, 60);
let seconds$1;
let rest;
seconds$1 = $[0];
rest = $[1];
let $1 = split(rest, 60);
let minutes$1;
let rest$1;
minutes$1 = $1[0];
rest$1 = $1[1];
let $2 = split(rest$1, 24);
let hours$1;
let rest$2;
hours$1 = $2[0];
rest$2 = $2[1];
let days = rest$2;
let add$1 = (out, value, unit) => {
if (value === 0) {
return out;
} else {
return (out + $int.to_string(value)) + unit;
}
};
let _block;
let _pipe = "P";
let _pipe$1 = add$1(_pipe, days, "D");
let _pipe$2 = $string.append(_pipe$1, "T");
let _pipe$3 = add$1(_pipe$2, hours$1, "H");
_block = add$1(_pipe$3, minutes$1, "M");
let output = _block;
let $3 = duration.nanoseconds;
if ($3 === 0) {
if (seconds$1 === 0) {
return output;
} else {
return (output + $int.to_string(seconds$1)) + "S";
}
} else {
let f = nanosecond_digits(duration.nanoseconds, 0, "");
return (((output + $int.to_string(seconds$1)) + ".") + f) + "S";
}
},
);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,468 @@
-module(gleam@time@calendar).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleam/time/calendar.gleam").
-export([local_offset/0, month_to_string/1, month_to_int/1, month_from_int/1, is_leap_year/1, is_valid_date/1, is_valid_time_of_day/1, naive_date_compare/2]).
-export_type([date/0, time_of_day/0, month/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" This module is for working with the Gregorian calendar, established by\n"
" Pope Gregory XIII in 1582!\n"
"\n"
" ## When should you use this module?\n"
"\n"
" > **tldr:** You probably want to use [`gleam/time/timestamp`](./timestamp.html)\n"
" > instead!\n"
"\n"
" Calendar time is difficult to work with programmatically, it is the source\n"
" of most time-related bugs in software. Compared to _epoch time_, which the\n"
" `gleam/time/timestamp` module uses, there are many disadvantages to\n"
" calendar time:\n"
"\n"
" - They are ambiguous if you don't know what time-zone is being used.\n"
"\n"
" - A time-zone database is required to understand calendar time even when\n"
" you have the time zone. These are large and your program has to\n"
" continously be updated as new versions of the database are published.\n"
"\n"
" - The type permits invalid states. e.g. `days` could be set to the number\n"
" 32, but this should not be possible!\n"
"\n"
" - There is not a single unique canonical value for each point in time,\n"
" thanks to time zones. Two different `Date` + `TimeOfDay` value pairs\n"
" could represent the same point in time. This means that you can't check\n"
" for time equality with `==` when using calendar types.\n"
"\n"
" - They are computationally complex, using a more memory to represent and\n"
" requiring a lot more CPU time to manipulate.\n"
"\n"
" There are also advantages to calendar time:\n"
"\n"
" - Calendar time is how human's talk about time, so if you want to show a\n"
" time or take a time from a human user then calendar time will make it\n"
" easier for them.\n"
"\n"
" - They can represent more abstract time periods such as \"New Year's Day\".\n"
" This may seem like an exact window of time at first, but really the\n"
" definition of \"New Year's Day\" is more fuzzy than that. When it starts\n"
" and ends will depend where in the world you are, so if you want to refer\n"
" to a day as a global concept instead of a fixed window of time for that\n"
" day in a specific location, then calendar time can represent that.\n"
"\n"
" So when should you use calendar time? These are our recommendations:\n"
"\n"
" - Default to `gleam/time/timestamp`, which is epoch time. It is\n"
" unambiguous, efficient, and significantly less likely to result in logic\n"
" bugs.\n"
"\n"
" - When writing time to a database or other data storage use epoch time,\n"
" using whatever epoch format it supports. For example, PostgreSQL\n"
" `timestamp` and `timestampz` are both epoch time, and `timestamp` is\n"
" preferred as it is more straightforward to use as your application is\n"
" also using epoch time.\n"
"\n"
" - When communicating with other computer systems continue to use epoch\n"
" time. For example, when sending times to another program you could\n"
" encode time as UNIX timestamps (seconds since 00:00:00 UTC on 1 January\n"
" 1970).\n"
"\n"
" - When communicating with humans use epoch time internally, and convert\n"
" to-and-from calendar time at the last moment, when iteracting with the\n"
" human user. It may also help the users to also show the time as a fuzzy\n"
" duration from the present time, such as \"about 4 days ago\".\n"
"\n"
" - When representing \"fuzzy\" human time concepts that don't exact periods\n"
" in time, such as \"one month\" (varies depending on which month, which\n"
" year, and in which time zone) and \"Christmas Day\" (varies depending on\n"
" which year and time zone) then use calendar time.\n"
"\n"
" Any time you do use calendar time you should be extra careful! It is very\n"
" easy to make mistake with. Avoid it where possible.\n"
"\n"
" ## Time zone offsets\n"
"\n"
" This package includes the `utc_offset` value and the `local_offset`\n"
" function, which are the offset for the UTC time zone and get the time\n"
" offset the computer running the program is configured to respectively.\n"
"\n"
" If you need to use other offsets in your program then you will need to get\n"
" them from somewhere else, such as from a package which loads the\n"
" [IANA Time Zone Database](https://www.iana.org/time-zones), or from the\n"
" website visitor's web browser, which your frontend can send for you.\n"
"\n"
" ## Use in APIs\n"
"\n"
" If you are making an API such as a HTTP JSON API you are encouraged to use\n"
" Unix timestamps instead of calendar times.\n"
).
-type date() :: {date, integer(), month(), integer()}.
-type time_of_day() :: {time_of_day, integer(), integer(), integer(), integer()}.
-type month() :: january |
february |
march |
april |
may |
june |
july |
august |
september |
october |
november |
december.
-file("src/gleam/time/calendar.gleam", 147).
?DOC(
" Get the offset for the computer's currently configured time zone.\n"
"\n"
" Note this may not be the time zone that is correct to use for your user.\n"
" For example, if you are making a web application that runs on a server you\n"
" want _their_ computer's time zone, not yours.\n"
"\n"
" This is the _current local_ offset, not the current local time zone. This\n"
" means that while it will result in the expected outcome for the current\n"
" time, it may result in unexpected output if used with other timestamps. For\n"
" example: a timestamp that would locally be during daylight savings time if\n"
" is it not currently daylight savings time when this function is called.\n"
).
-spec local_offset() -> gleam@time@duration:duration().
local_offset() ->
gleam@time@duration:seconds(gleam_time_ffi:local_time_offset_seconds()).
-file("src/gleam/time/calendar.gleam", 163).
?DOC(
" Returns the English name for a month.\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" month_to_string(April)\n"
" // -> \"April\"\n"
" ```\n"
).
-spec month_to_string(month()) -> binary().
month_to_string(Month) ->
case Month of
january ->
<<"January"/utf8>>;
february ->
<<"February"/utf8>>;
march ->
<<"March"/utf8>>;
april ->
<<"April"/utf8>>;
may ->
<<"May"/utf8>>;
june ->
<<"June"/utf8>>;
july ->
<<"July"/utf8>>;
august ->
<<"August"/utf8>>;
september ->
<<"September"/utf8>>;
october ->
<<"October"/utf8>>;
november ->
<<"November"/utf8>>;
december ->
<<"December"/utf8>>
end.
-file("src/gleam/time/calendar.gleam", 188).
?DOC(
" Returns the number for the month, where January is 1 and December is 12.\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" month_to_int(January)\n"
" // -> 1\n"
" ```\n"
).
-spec month_to_int(month()) -> integer().
month_to_int(Month) ->
case Month of
january ->
1;
february ->
2;
march ->
3;
april ->
4;
may ->
5;
june ->
6;
july ->
7;
august ->
8;
september ->
9;
october ->
10;
november ->
11;
december ->
12
end.
-file("src/gleam/time/calendar.gleam", 213).
?DOC(
" Returns the month for a given number, where January is 1 and December is 12.\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" month_from_int(1)\n"
" // -> Ok(January)\n"
" ```\n"
).
-spec month_from_int(integer()) -> {ok, month()} | {error, nil}.
month_from_int(Month) ->
case Month of
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}
end.
-file("src/gleam/time/calendar.gleam", 290).
?DOC(
" Determines if a given year is a leap year.\n"
"\n"
" A leap year occurs every 4 years, except for years divisible by 100,\n"
" unless they are also divisible by 400.\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" is_leap_year(2024)\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" is_leap_year(2023)\n"
" // -> False\n"
" ```\n"
).
-spec is_leap_year(integer()) -> boolean().
is_leap_year(Year) ->
case (Year rem 400) =:= 0 of
true ->
true;
false ->
case (Year rem 100) =:= 0 of
true ->
false;
false ->
(Year rem 4) =:= 0
end
end.
-file("src/gleam/time/calendar.gleam", 254).
?DOC(
" Checks if a given date is valid.\n"
"\n"
" This function properly accounts for leap years when validating February days.\n"
" A leap year occurs every 4 years, except for years divisible by 100,\n"
" unless they are also divisible by 400.\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" is_valid_date(Date(2023, April, 15))\n"
" // -> True\n"
" ```\n"
"\n"
" ```gleam\n"
" is_valid_date(Date(2023, April, 31))\n"
" // -> False\n"
" ```\n"
"\n"
" ```gleam\n"
" is_valid_date(Date(2024, February, 29))\n"
" // -> True (2024 is a leap year)\n"
" ```\n"
).
-spec is_valid_date(date()) -> boolean().
is_valid_date(Date) ->
{date, Year, Month, Day} = Date,
case Day < 1 of
true ->
false;
false ->
case Month of
january ->
Day =< 31;
march ->
Day =< 31;
may ->
Day =< 31;
july ->
Day =< 31;
august ->
Day =< 31;
october ->
Day =< 31;
december ->
Day =< 31;
april ->
Day =< 30;
june ->
Day =< 30;
september ->
Day =< 30;
november ->
Day =< 30;
february ->
Max_february_days = case is_leap_year(Year) of
true ->
29;
false ->
28
end,
Day =< Max_february_days
end
end.
-file("src/gleam/time/calendar.gleam", 313).
?DOC(
" Checks if a time of day is valid.\n"
"\n"
" Validates that hours are 0-23, minutes are 0-59, seconds are 0-59,\n"
" and nanoseconds are 0-999,999,999.\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" is_valid_time_of_day(TimeOfDay(12, 30, 45, 123456789))\n"
" // -> True\n"
" ```\n"
).
-spec is_valid_time_of_day(time_of_day()) -> boolean().
is_valid_time_of_day(Time) ->
{time_of_day, Hours, Minutes, Seconds, Nanoseconds} = Time,
(((((((Hours >= 0) andalso (Hours =< 23)) andalso (Minutes >= 0)) andalso (Minutes
=< 59))
andalso (Seconds >= 0))
andalso (Seconds =< 59))
andalso (Nanoseconds >= 0))
andalso (Nanoseconds =< 999999999).
-file("src/gleam/time/calendar.gleam", 340).
?DOC(
" Naively compares two dates without any time zone information, returning an\n"
" order.\n"
"\n"
" ## Correctness\n"
"\n"
" This function compares dates without any time zone information, only using\n"
" the rules for the gregorian calendar. This is typically sufficient, but be\n"
" aware that in reality some time zones will change their calendar date\n"
" occasionally. This can result in days being skipped, out of order, or\n"
" happening multiple times.\n"
"\n"
" If you need real-world correct time ordering then use the\n"
" `gleam/time/timestamp` module instead.\n"
).
-spec naive_date_compare(date(), date()) -> gleam@order:order().
naive_date_compare(One, Other) ->
_pipe = gleam@int:compare(erlang:element(2, One), erlang:element(2, Other)),
_pipe@1 = gleam@order:lazy_break_tie(
_pipe,
fun() ->
gleam@int:compare(
month_to_int(erlang:element(3, One)),
month_to_int(erlang:element(3, Other))
)
end
),
gleam@order:lazy_break_tie(
_pipe@1,
fun() ->
gleam@int:compare(erlang:element(4, One), erlang:element(4, Other))
end
).

View file

@ -0,0 +1,381 @@
-module(gleam@time@duration).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleam/time/duration.gleam").
-export([approximate/1, compare/2, difference/2, add/2, seconds/1, minutes/1, hours/1, milliseconds/1, nanoseconds/1, to_seconds/1, to_seconds_and_nanoseconds/1, to_iso8601_string/1]).
-export_type([duration/0, unit/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-opaque duration() :: {duration, integer(), integer()}.
-type unit() :: nanosecond |
microsecond |
millisecond |
second |
minute |
hour |
day |
week |
month |
year.
-file("src/gleam/time/duration.gleam", 110).
?DOC(
" Ensure the duration is represented with `nanoseconds` being positive and\n"
" less than 1 second.\n"
"\n"
" This function does not change the amount of time that the duratoin refers\n"
" to, it only adjusts the values used to represent the time.\n"
).
-spec normalise(duration()) -> duration().
normalise(Duration) ->
Multiplier = 1000000000,
Nanoseconds = case Multiplier of
0 -> 0;
Gleam@denominator -> erlang:element(3, Duration) rem Gleam@denominator
end,
Overflow = erlang:element(3, Duration) - Nanoseconds,
Seconds = erlang:element(2, Duration) + (case Multiplier of
0 -> 0;
Gleam@denominator@1 -> Overflow div Gleam@denominator@1
end),
case Nanoseconds >= 0 of
true ->
{duration, Seconds, Nanoseconds};
false ->
{duration, Seconds - 1, Multiplier + Nanoseconds}
end.
-file("src/gleam/time/duration.gleam", 76).
?DOC(
" Convert a duration to a number of the largest number of a unit, serving as\n"
" a rough description of the duration that a human can understand.\n"
"\n"
" The size used for each unit are described in the documentation for the\n"
" `Unit` type.\n"
"\n"
" ```gleam\n"
" seconds(125)\n"
" |> approximate\n"
" // -> #(2, Minute)\n"
" ```\n"
"\n"
" This function rounds _towards zero_. This means that if a duration is just\n"
" short of 2 days then it will approximate to 1 day.\n"
"\n"
" ```gleam\n"
" hours(47)\n"
" |> approximate\n"
" // -> #(1, Day)\n"
" ```\n"
).
-spec approximate(duration()) -> {integer(), unit()}.
approximate(Duration) ->
{duration, S, Ns} = Duration,
Minute = 60,
Hour = Minute * 60,
Day = Hour * 24,
Week = Day * 7,
Year = (Day * 365) + (Hour * 6),
Month = Year div 12,
Microsecond = 1000,
Millisecond = Microsecond * 1000,
case nil of
_ when S < 0 ->
{Amount, Unit} = begin
_pipe = {duration, - S, - Ns},
_pipe@1 = normalise(_pipe),
approximate(_pipe@1)
end,
{- Amount, Unit};
_ when S >= Year ->
{case Year of
0 -> 0;
Gleam@denominator -> S div Gleam@denominator
end, year};
_ when S >= Month ->
{case Month of
0 -> 0;
Gleam@denominator@1 -> S div Gleam@denominator@1
end, month};
_ when S >= Week ->
{case Week of
0 -> 0;
Gleam@denominator@2 -> S div Gleam@denominator@2
end, week};
_ when S >= Day ->
{case Day of
0 -> 0;
Gleam@denominator@3 -> S div Gleam@denominator@3
end, day};
_ when S >= Hour ->
{case Hour of
0 -> 0;
Gleam@denominator@4 -> S div Gleam@denominator@4
end, hour};
_ when S >= Minute ->
{case Minute of
0 -> 0;
Gleam@denominator@5 -> S div Gleam@denominator@5
end, minute};
_ when S > 0 ->
{S, second};
_ when Ns >= Millisecond ->
{case Millisecond of
0 -> 0;
Gleam@denominator@6 -> Ns div Gleam@denominator@6
end, millisecond};
_ when Ns >= Microsecond ->
{case Microsecond of
0 -> 0;
Gleam@denominator@7 -> Ns div Gleam@denominator@7
end, microsecond};
_ ->
{Ns, nanosecond}
end.
-file("src/gleam/time/duration.gleam", 140).
?DOC(
" Compare one duration to another, indicating whether the first spans a\n"
" larger amount of time (and so is greater) or smaller amount of time (and so\n"
" is lesser) than the second.\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" compare(seconds(1), seconds(2))\n"
" // -> order.Lt\n"
" ```\n"
"\n"
" Whether a duration is negative or positive doesn't matter for comparing\n"
" them, only the amount of time spanned matters.\n"
"\n"
" ```gleam\n"
" compare(seconds(-2), seconds(1))\n"
" // -> order.Gt\n"
" ```\n"
).
-spec compare(duration(), duration()) -> gleam@order:order().
compare(Left, Right) ->
Parts = fun(X) -> case erlang:element(2, X) >= 0 of
true ->
{erlang:element(2, X), erlang:element(3, X)};
false ->
{(erlang:element(2, X) * -1) - 1,
1000000000 - erlang:element(3, X)}
end end,
{Ls, Lns} = Parts(Left),
{Rs, Rns} = Parts(Right),
_pipe = gleam@int:compare(Ls, Rs),
gleam@order:break_tie(_pipe, gleam@int:compare(Lns, Rns)).
-file("src/gleam/time/duration.gleam", 164).
?DOC(
" Calculate the difference between two durations.\n"
"\n"
" This is effectively substracting the first duration from the second.\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" difference(seconds(1), seconds(5))\n"
" // -> seconds(4)\n"
" ```\n"
).
-spec difference(duration(), duration()) -> duration().
difference(Left, Right) ->
_pipe = {duration,
erlang:element(2, Right) - erlang:element(2, Left),
erlang:element(3, Right) - erlang:element(3, Left)},
normalise(_pipe).
-file("src/gleam/time/duration.gleam", 178).
?DOC(
" Add two durations together.\n"
"\n"
" # Examples\n"
"\n"
" ```gleam\n"
" add(seconds(1), seconds(5))\n"
" // -> seconds(6)\n"
" ```\n"
).
-spec add(duration(), duration()) -> duration().
add(Left, Right) ->
_pipe = {duration,
erlang:element(2, Left) + erlang:element(2, Right),
erlang:element(3, Left) + erlang:element(3, Right)},
normalise(_pipe).
-file("src/gleam/time/duration.gleam", 225).
-spec nanosecond_digits(integer(), integer(), binary()) -> binary().
nanosecond_digits(N, Position, Acc) ->
case Position of
9 ->
Acc;
_ when (Acc =:= <<""/utf8>>) andalso ((N rem 10) =:= 0) ->
nanosecond_digits(N div 10, Position + 1, Acc);
_ ->
Acc@1 = <<(erlang:integer_to_binary(N rem 10))/binary, Acc/binary>>,
nanosecond_digits(N div 10, Position + 1, Acc@1)
end.
-file("src/gleam/time/duration.gleam", 239).
?DOC(" Create a duration of a number of seconds.\n").
-spec seconds(integer()) -> duration().
seconds(Amount) ->
{duration, Amount, 0}.
-file("src/gleam/time/duration.gleam", 244).
?DOC(" Create a duration of a number of minutes.\n").
-spec minutes(integer()) -> duration().
minutes(Amount) ->
seconds(Amount * 60).
-file("src/gleam/time/duration.gleam", 249).
?DOC(" Create a duration of a number of hours.\n").
-spec hours(integer()) -> duration().
hours(Amount) ->
seconds((Amount * 60) * 60).
-file("src/gleam/time/duration.gleam", 254).
?DOC(" Create a duration of a number of milliseconds.\n").
-spec milliseconds(integer()) -> duration().
milliseconds(Amount) ->
Remainder = Amount rem 1000,
Overflow = Amount - Remainder,
Nanoseconds = Remainder * 1000000,
Seconds = Overflow div 1000,
_pipe = {duration, Seconds, Nanoseconds},
normalise(_pipe).
-file("src/gleam/time/duration.gleam", 273).
?DOC(
" Create a duration of a number of nanoseconds.\n"
"\n"
" # JavaScript int limitations\n"
"\n"
" Remember that JavaScript can only perfectly represent ints between positive\n"
" and negative 9,007,199,254,740,991! If you use a single call to this\n"
" function to create durations larger than that number of nanoseconds then\n"
" you will likely not get exactly the value you expect. Use `seconds` and\n"
" `milliseconds` as much as possible for large durations.\n"
).
-spec nanoseconds(integer()) -> duration().
nanoseconds(Amount) ->
_pipe = {duration, 0, Amount},
normalise(_pipe).
-file("src/gleam/time/duration.gleam", 283).
?DOC(
" Convert the duration to a number of seconds.\n"
"\n"
" There may be some small loss of precision due to `Duration` being\n"
" nanosecond accurate and `Float` not being able to represent this.\n"
).
-spec to_seconds(duration()) -> float().
to_seconds(Duration) ->
Seconds = erlang:float(erlang:element(2, Duration)),
Nanoseconds = erlang:float(erlang:element(3, Duration)),
Seconds + (Nanoseconds / 1000000000.0).
-file("src/gleam/time/duration.gleam", 292).
?DOC(
" Convert the duration to a number of seconds and nanoseconds. There is no\n"
" loss of precision with this conversion on any target.\n"
).
-spec to_seconds_and_nanoseconds(duration()) -> {integer(), integer()}.
to_seconds_and_nanoseconds(Duration) ->
{erlang:element(2, Duration), erlang:element(3, Duration)}.
-file("src/gleam/time/duration.gleam", 192).
?DOC(
" Convert the duration to an [ISO8601][1] formatted duration string.\n"
"\n"
" The ISO8601 duration format is ambiguous without context due to months and\n"
" years having different lengths, and because of leap seconds. This function\n"
" encodes the duration as days, hours, and seconds without any leap seconds.\n"
" Be sure to take this into account when using the duration strings.\n"
"\n"
" [1]: https://en.wikipedia.org/wiki/ISO_8601#Durations\n"
).
-spec to_iso8601_string(duration()) -> binary().
to_iso8601_string(Duration) ->
gleam@bool:guard(
Duration =:= {duration, 0, 0},
<<"PT0S"/utf8>>,
fun() ->
Split = fun(Total, Limit) ->
Amount = case Limit of
0 -> 0;
Gleam@denominator -> Total rem Gleam@denominator
end,
Remainder = case Limit of
0 -> 0;
Gleam@denominator@1 -> (Total - Amount) div Gleam@denominator@1
end,
{Amount, Remainder}
end,
{Seconds, Rest} = Split(erlang:element(2, Duration), 60),
{Minutes, Rest@1} = Split(Rest, 60),
{Hours, Rest@2} = Split(Rest@1, 24),
Days = Rest@2,
Add = fun(Out, Value, Unit) -> case Value of
0 ->
Out;
_ ->
<<<<Out/binary,
(erlang:integer_to_binary(Value))/binary>>/binary,
Unit/binary>>
end end,
Output = begin
_pipe = <<"P"/utf8>>,
_pipe@1 = Add(_pipe, Days, <<"D"/utf8>>),
_pipe@2 = gleam@string:append(_pipe@1, <<"T"/utf8>>),
_pipe@3 = Add(_pipe@2, Hours, <<"H"/utf8>>),
Add(_pipe@3, Minutes, <<"M"/utf8>>)
end,
case {Seconds, erlang:element(3, Duration)} of
{0, 0} ->
Output;
{_, 0} ->
<<<<Output/binary,
(erlang:integer_to_binary(Seconds))/binary>>/binary,
"S"/utf8>>;
{_, _} ->
F = nanosecond_digits(
erlang:element(3, Duration),
0,
<<""/utf8>>
),
<<<<<<<<Output/binary,
(erlang:integer_to_binary(Seconds))/binary>>/binary,
"."/utf8>>/binary,
F/binary>>/binary,
"S"/utf8>>
end
end
).

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,12 @@
-module(gleam_time_ffi).
-export([system_time/0, local_time_offset_seconds/0]).
system_time() ->
{0, erlang:system_time(nanosecond)}.
local_time_offset_seconds() ->
Utc = calendar:universal_time(),
Local = calendar:local_time(),
UtcSeconds = calendar:datetime_to_gregorian_seconds(Utc),
LocalSeconds = calendar:datetime_to_gregorian_seconds(Local),
LocalSeconds - UtcSeconds.

View file

@ -0,0 +1,11 @@
export function system_time() {
const now = Date.now();
const milliseconds = now % 1_000;
const nanoseconds = milliseconds * 1000_000;
const seconds = (now - milliseconds) / 1_000;
return [seconds, nanoseconds];
}
export function local_time_offset_seconds() {
return new Date().getTimezoneOffset() * -60;
}