Current section

Files

Jump to
gtempo src tempo date.gleam
Raw

src/tempo/date.gleam

//// Functions to use with the `Date` type in Tempo.
////
//// ## Example
////
//// ```gleam
//// import tempo/date
////
//// pub fn main() {
//// date.literal("2024-06-21")
//// |> date.to_string
//// // -> "2024-06-21"
////
//// date.parse("06/21/2024", tempo.CustomDate("MM/DD/YYYY"))
//// |> date.to_string
//// // -> "2024-06-21"
////
//// date.current_local()
//// |> date.to_string
//// // -> "2024-10-09"
//// }
//// ```
////
//// ```gleam
//// import tempo/date
////
//// pub fn is_older_than_a_week(date_str: String) {
//// let date = date.from_string(date_str)
////
//// date
//// |> date.is_earlier(
//// than: date |> date.subtract(days: 7)
//// )
//// }
//// ```
////
//// ## Calendar
////
//// Dates in this module are
//// [proleptic Gregorian calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar)
//// dates. Every year-month-day these functions accept or produce is a
//// Gregorian one, with Gregorian month lengths and the Gregorian leap year
//// rule (divisible by 4, except century years, which must be divisible by
//// 400). "Proleptic" means those rules are applied to dates before the
//// calendar was adopted in 1582 as well, so a date such as `1500-01-01` is not
//// the day that was recorded as such at the time, when the Julian calendar was
//// in use. No other calendar system is supported.
////
//// ## Range of valid dates
////
//// Constructing a date from calendar parts is limited to **years 1000 through
//// 9999**, so that a year is always four digits. `new`, `from_tuple`,
//// `from_calendar_date`, `from_string`, `parse`, `parse_any` and `literal` all
//// reject years outside that range.
////
//// Date arithmetic is not range checked. `add`, `subtract` and `from_rata_die`
//// will happily produce dates outside 1000 to 9999, including year zero and
//// negative years. Such dates still compare and do arithmetic correctly, but
//// `to_string` and `format` will render their years without four-digit zero
//// padding, which is not valid ISO 8601. Keep results within 1000 to 9999 if
//// you intend to format them.
import gleam/bit_array
import gleam/dynamic
import gleam/dynamic/decode
import gleam/list
import gleam/option.{None, Some}
import gleam/order
import gleam/result
import gleam/string
import gleam/time/calendar
import gtempo/internal
import tempo
import tempo/error as tempo_error
import tempo/month
/// Starting point of unix timestamps
pub const unix_epoch = tempo.unix_epoch_date
/// A named day of the week.
pub type DayOfWeek {
Sun
Mon
Tue
Wed
Thu
Fri
Sat
}
/// Creates a new proleptic Gregorian calendar date and validates it.
///
/// The year must be between 1000 and 9999, the month between 1 and 12, and the
/// day between 1 and the number of days that month has in that Gregorian year.
/// Anything else returns an error.
///
/// ## Examples
///
/// ```gleam
/// date.new(2024, 6, 13)
/// // -> Ok(date.literal("2024-06-13"))
/// ```
///
/// ```gleam
/// date.new(2024, 6, 31)
/// // -> Error(tempo_error.DateOutOfBounds)
/// ```
///
/// ```gleam
/// // 2024 is a Gregorian leap year, 2023 is not
/// date.new(2024, 2, 29)
/// // -> Ok(date.literal("2024-02-29"))
/// date.new(2023, 2, 29)
/// // -> Error(tempo_error.DateOutOfBounds)
/// ```
///
/// ```gleam
/// // Years outside 1000 to 9999 are rejected
/// date.new(999, 1, 1)
/// // -> Error(tempo_error.DateOutOfBounds)
/// ```
pub fn new(
year year: Int,
month month: Int,
day day: Int,
) -> Result(tempo.Date, tempo_error.DateOutOfBoundsError) {
tempo.new_date(year, month, day)
}
/// Creates a new date value from a string literal, but will panic if
/// the string is invalid. Accepted formats are `YYYY-MM-DD`, `YYYY-M-D`,
/// `YYYY/MM/DD`, `YYYY/M/D`, `YYYY.MM.DD`, `YYYY.M.D`, `YYYY_MM_DD`,
/// `YYYY_M_D`, `YYYY MM DD`, `YYYY M D`, or `YYYYMMDD`.
///
/// Useful for declaring date literals that you know are valid within your
/// program.
///
/// The string is read as a proleptic Gregorian calendar date, and must have a
/// year between 1000 and 9999. A year outside that range panics, as does a day
/// that does not exist in that Gregorian month, such as `"2023-02-29"`.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-13")
/// |> date.to_string
/// // -> "2024-06-13"
/// ```
///
/// ```gleam
/// date.literal("20240613")
/// |> date.to_string
/// // -> "2024-06-13"
/// ```
///
/// ```gleam
/// date.literal("2409")
/// // -> panic
/// ```
pub fn literal(date: String) -> tempo.Date {
case from_string(date) {
Ok(date) -> date
Error(tempo_error.DateInvalidFormat(_)) ->
panic as "Invalid date literal format"
Error(tempo_error.DateOutOfBounds(_, tempo_error.DateDayOutOfBounds(..))) ->
panic as "Invalid date literal day value"
Error(tempo_error.DateOutOfBounds(_, tempo_error.DateMonthOutOfBounds(..))) ->
panic as "Invalid date literal month value"
Error(tempo_error.DateOutOfBounds(_, tempo_error.DateYearOutOfBounds(..))) ->
panic as "Invalid date literal year value"
}
}
/// Gets the current local date of the host, as a proleptic Gregorian calendar
/// date.
///
/// ## Examples
///
/// ```gleam
/// date.current_local()
/// |> date.to_string
/// // -> "2024-06-13"
/// ```
pub fn current_local() {
{ tempo.now_utc_ffi() + tempo.offset_local_micro() } |> from_unix_micro
}
/// Gets the current UTC date of the host, as a proleptic Gregorian calendar
/// date.
///
/// ## Examples
///
/// ```gleam
/// date.current_utc()
/// |> date.to_string
/// // -> "2024-06-14"
/// ```
pub fn current_utc() {
tempo.now_utc_ffi() |> from_unix_micro
}
/// Gets the proleptic Gregorian calendar year of a date.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-13")
/// |> date.get_year
/// // -> 2024
/// ```
pub fn get_year(date: tempo.Date) -> Int {
date |> tempo.date_get_year
}
/// Gets the Gregorian calendar month of a date.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-13")
/// |> date.get_month
/// // -> tempo.Jun
/// ```
pub fn get_month(date: tempo.Date) -> calendar.Month {
date |> tempo.date_get_month
}
/// Gets the day of the Gregorian calendar month of a date, from 1 to 31.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-13")
/// |> date.get_day
/// // -> 13
/// ```
pub fn get_day(date: tempo.Date) -> Int {
date |> tempo.date_get_day
}
/// Gets the Gregorian calendar month and year of a date.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-13")
/// |> date.get_month_year
/// // -> calendar.MonthYear(tempo.Jun, 2024)
/// ```
pub fn get_month_year(date: tempo.Date) -> tempo.MonthYear {
date |> tempo.date_get_month_year
}
/// Parses a date string in the format `YYYY-MM-DD`, `YYYY-M-D`, `YYYY/MM/DD`,
/// `YYYY/M/D`, `YYYY.MM.DD`, `YYYY.M.D`, `YYYY_MM_DD`, `YYYY_M_D`, `YYYY MM DD`,
/// `YYYY M D`, or `YYYYMMDD`.
///
/// The string is read as a proleptic Gregorian calendar date. The year must be
/// between 1000 and 9999, and the day must exist in that Gregorian month, so
/// `"2023-02-29"` is an error while `"2024-02-29"` is not.
///
/// ## Examples
///
/// ```gleam
/// date.from_string("2024-06-13")
/// // -> Ok(date.literal("2024-06-13"))
/// ```
///
/// ```gleam
/// date.from_string("20240613")
/// // -> Ok(date.literal("2024-06-13"))
/// ```
///
/// ```gleam
/// date.from_string("2409")
/// // -> Error(tempo_error.DateInvalidFormat)
/// ```
pub fn from_string(
date: String,
) -> Result(tempo.Date, tempo_error.DateParseError) {
use parts <- result.try(
parse_date_parts(bit_array.from_string(date))
|> result.replace_error(tempo_error.DateInvalidFormat(date)),
)
from_tuple(parts)
|> result.map_error(tempo_error.DateOutOfBounds(date, _))
}
fn parse_date_parts(input: BitArray) -> Result(#(Int, Int, Int), Nil) {
use #(parts, input) <- result.try(internal.take_date_parts(input))
use Nil <- result.map(internal.accept_empty(input))
parts
}
/// Returns a string representation of a proleptic Gregorian calendar date in
/// the format `YYYY-MM-DD`.
///
/// Dates built by the constructors always have a year between 1000 and 9999
/// and so always render as four digits. Date arithmetic is not range checked
/// though, so a date pushed outside that range by `add`, `subtract` or
/// `from_rata_die` renders its year without four-digit zero padding, which is
/// not valid ISO 8601.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-13")
/// |> date.to_string
/// // -> "2024-06-13"
/// ```
///
/// ```gleam
/// // Outside the 1000 to 9999 range the year is not padded
/// date.literal("1000-01-01")
/// |> date.subtract(days: 1)
/// |> date.to_string
/// // -> "999-12-31"
/// ```
pub fn to_string(date: tempo.Date) -> String {
tempo.date_to_string(date)
}
/// Parses a date string in the provided format. Always prefer using
/// this over `parse_any`. All parsed formats must have all parts of a date.
///
/// Values can be escaped by putting brackets around them, like "[Hello!] YYYY".
///
/// Available directives: YY (two-digit year), YYYY (four-digit year), M (month),
/// MM (two-digit month), MMM (short month name), MMMM (full month name),
/// D (day of the month), DD (two-digit day of the month),
///
/// The result is a proleptic Gregorian calendar date, and the month names the
/// directives accept are the English Gregorian ones. The parsed year must land
/// between 1000 and 9999, and the day must exist in that Gregorian month, or an
/// error is returned. A `YY` two-digit year is resolved into the hundred years
/// leading up to the current date, so `"99"` parses as 1999 rather than 2099.
///
/// ## Example
///
/// ```gleam
/// date.parse("2024/06/08, 13:42:11", "YYYY/MM/DD")
/// // -> Ok(date.literal("2024-06-08"))
/// ```
///
/// ```gleam
/// date.parse("January 13, 2024", "MMMM DD, YYYY")
/// // -> Ok(date.literal("2024-01-13"))
/// ```
///
/// ```gleam
/// date.parse("Hi! 2024 11 13", "[Hi!] YYYY M D")
/// // -> Ok(date.literal("2024-11-13"))
/// ```
pub fn parse(
str: String,
in format: tempo.DateFormat,
) -> Result(tempo.Date, tempo_error.DateParseError) {
let format_str = tempo.get_date_format_str(format)
use parts <- result.try(
tempo.consume_format(str, in: format_str)
|> result.map_error(tempo_error.DateInvalidFormat),
)
tempo.find_date(in: parts)
}
/// Tries to parse a given date string without a known format. It will not
/// parse two digit years and will assume the month always comes before the
/// day in a date. Will leave out any time or offset values present.
///
/// The result is a proleptic Gregorian calendar date with a year between 1000
/// and 9999. Since two digit years are not parsed, the year in the string must
/// already be four digits.
///
/// ## Example
///
/// ```gleam
/// date.parse_any("2024.06.21 01:32 PM -04:00")
/// // -> Ok(date.literal("2024-06-21"))
/// ```
///
/// ```gleam
/// date.parse_any("2024.06.21")
/// // -> Ok(date.literal("2024-06-21"))
/// ```
pub fn parse_any(
str: String,
) -> Result(tempo.Date, tempo_error.DateParseError) {
case tempo.parse_any(str) {
#(Some(date), _, _) -> Ok(date)
#(None, _, _) ->
Error(tempo_error.DateInvalidFormat("Unable to find date in " <> str))
}
}
/// Converts a date parse error to a human readable error message.
///
/// ## Example
///
/// ```gleam
/// date.parse_any("01:32 PM")
/// |> snag.map_error(with: date.describe_parse_error)
/// // -> snag.error("Invalid date format: 01:32 PM")
/// ```
pub fn describe_parse_error(error: tempo_error.DateParseError) -> String {
tempo_error.describe_date_parse_error(error)
}
/// Formats a date value into a string using the provided date format.
///
/// All calendar directives render proleptic Gregorian calendar values, and the
/// month and day names are the English Gregorian ones. Dates built by the
/// constructors always have a four-digit year, but a date pushed outside 1000
/// to 9999 by `add`, `subtract` or `from_rata_die` renders its year without
/// four-digit zero padding.
///
/// ## Example
///
/// ```gleam
/// datetime.literal("2024-12-26T13:02:01-04:00")
/// |> datetime.format(tempo.ISO8601Date)
/// // -> "2024-12-26"
/// ```
///
/// ```gleam
/// datetime.literal("2024-06-21T13:42:11.314-04:00")
/// |> datetime.format(tempo.CustomDate("ddd @ h:mm A (z)"))
/// // -> "Fri @ 1:42 PM (-04)"
/// ```
///
/// ```gleam
/// datetime.literal("2024-06-03T09:02:01-04:00")
/// |> datetime.format(tempo.CustomDate("YY YYYY M MM MMM MMMM D DD d dd ddd"))
/// // -------------------------------> "24 2024 6 06 Jun June 3 03 1 Mo Mon"
/// ```
///
/// ```gleam
/// datetime.literal("2024-06-03T09:02:01.014920202-00:00")
/// |> datetime.format(tempo.CustomDate("dddd SSS SSSS SSSSS Z ZZ z"))
/// // -> "Monday 014 014920 014920202 -00:00 -0000 Z"
/// ```
///
/// ```gleam
/// datetime.literal("2024-06-03T13:02:01-04:00")
/// |> datetime.format(tempo.CustomDate(("H HH h hh m mm s ss a A [An ant]"))
/// // -------------------------------> "13 13 1 01 2 02 1 01 pm PM An ant"
/// ```
pub fn format(date: tempo.Date, in format: tempo.DateFormat) -> String {
tempo.get_date_format_str(format)
|> tempo.tokenise_format
|> list.map(fn(token) {
case token {
tempo.Directive(directive) -> tempo.date_replace_format(directive, date)
tempo.Escaped(literal) -> literal
}
})
|> string.concat
}
/// Returns a date value from a tuple of ints if the values represent the
/// year, month, and day of a valid proleptic Gregorian calendar date. The year
/// must be between 1000 and 9999, the month between 1 and 12, and the day
/// between 1 and the number of days that month has in that Gregorian year.
///
/// Years less than 1000 are technically valid years, but are not common
/// and usually indicate that either a non-year value was passed as the year
/// or a two digit year was passed (which are too abiguous to be confidently
/// accepted). Years above 9999 are rejected so that a year is always four
/// digits.
///
/// ## Examples
///
/// ```gleam
/// date.from_tuple(#(2024, 6, 13))
/// // -> Ok(date.literal("2024-06-13"))
/// ```
///
/// ```gleam
/// date.from_tuple(#(98, 6, 13))
/// // -> Error(tempo_error.DateOutOfBounds)
/// ```
///
/// ```gleam
/// // Feb 29 exists in Gregorian leap years only
/// date.from_tuple(#(2023, 2, 29))
/// // -> Error(tempo_error.DateOutOfBounds)
/// ```
pub fn from_tuple(
date: #(Int, Int, Int),
) -> Result(tempo.Date, tempo_error.DateOutOfBoundsError) {
tempo.date_from_tuple(date)
}
/// Converts a date out of bounds error to a human readable error message.
///
/// ## Example
///
/// ```gleam
/// date.from_tuple(#(98, 6, 13))
/// |> snag.map_error(with: date.describe_out_of_bounds_error)
/// // -> snag.error("Year out of bounds in date: 98")
/// ```
pub fn describe_out_of_bounds_error(error: tempo_error.DateOutOfBoundsError) {
tempo_error.describe_date_out_of_bounds_error(error)
}
/// Returns a tuple of ints from a date value that represent the year, month,
/// and day of the date on the proleptic Gregorian calendar, where the month is
/// 1 for January through 12 for December.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-14")
/// |> date.to_tuple
/// // -> #(2024, 6, 14)
/// ```
pub fn to_tuple(date: tempo.Date) -> #(Int, Int, Int) {
#(
date |> tempo.date_get_year,
month.to_int(date |> tempo.date_get_month),
date |> tempo.date_get_day,
)
}
/// Converts a tempo date to a `gleam_time` calendar date. Both represent the
/// same proleptic Gregorian calendar year, month, and day, so no calendar
/// conversion takes place.
///
/// ## Example
///
/// ```gleam
/// date.literal("2024-06-13")
/// |> date.to_calendar_date
/// // -> calendar.Date(2024, calendar.June, 13)
/// ```
pub fn to_calendar_date(date: tempo.Date) -> calendar.Date {
tempo.date_to_calendar_date(date)
}
/// Converts a `gleam_time` calendar date to a tempo date. Both represent the
/// same proleptic Gregorian calendar year, month, and day, so no calendar
/// conversion takes place, but the year must be between 1000 and 9999 or an
/// error is returned.
///
/// ## Example
///
/// ```gleam
/// calendar.Date(2024, calendar.June, 13)
/// |> date.from_calendar_date
/// // -> Ok(date.literal("2024-06-13"))
/// ```
pub fn from_calendar_date(
date: calendar.Date,
) -> Result(tempo.Date, tempo_error.DateOutOfBoundsError) {
let calendar.Date(year, month, day) = date
from_tuple(#(year, month |> month.to_int, day))
}
/// Checks if a dynamic value is a valid date string, and returns the
/// date if it is.
///
/// ## Examples
///
/// ```gleam
/// dynamic.string("2024-06-21")
/// |> date.from_dynamic_string
/// // -> Ok(date.literal("2024-06-21"))
/// ```
///
/// ```gleam
/// dynamic.string("153")
/// |> datetime.from_dynamic_string
/// // -> Error([
/// // decode.DecodeError(
/// // expected: "tempo.Date",
/// // found: "Invalid format: 153",
/// // path: [],
/// // ),
/// // ])
/// ```
pub fn from_dynamic_string(
dynamic_string: dynamic.Dynamic,
) -> Result(tempo.Date, List(decode.DecodeError)) {
use date: String <- result.try(
// Uses the decode.string function but maintains the decode.DecodeError
// return type to maintain API compatibility.
decode.run(dynamic_string, decode.string)
|> result.map_error(fn(errs) {
list.map(errs, fn(err) {
decode.DecodeError(err.expected, err.found, err.path)
})
}),
)
case from_string(date) {
Ok(date) -> Ok(date)
Error(tempo_error) ->
Error([
decode.DecodeError(
expected: "tempo.Date",
found: case tempo_error {
tempo_error.DateInvalidFormat(msg) -> msg
tempo_error.DateOutOfBounds(msg, _) -> msg
},
path: [],
),
])
}
}
/// Returns the date of a unix timestamp.
///
/// From https://howardhinnant.github.io/date_algorithms.html#civil_from_days
///
/// ## Examples
///
/// ```gleam
/// date.from_unix_seconds(267_840_000)
/// // -> date.literal("1978-06-28")
/// ```
///
/// I am making this internal because it is created but I am not sure if it
/// should be part of the public API. I think it is too easy to use incorrectly.
/// Users should probably use the 'datetime' module's 'from_unix_seconds' function
/// instead and get the date from there if they need it.
@internal
pub fn from_unix_seconds(unix_ts: Int) -> tempo.Date {
tempo.date_from_unix_seconds(unix_ts)
}
/// Returns the UTC unix timestamp of a date, assuming the time on that date
/// is 00:00:00.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-12")
/// |> date.to_unix_seconds
/// // -> 1_718_150_400
/// ```
///
/// I am making this internal because it is created but I am not sure if it
/// should be part of the public API. I think it is too easy to use incorrectly.
/// Users should probably use the 'datetime' module's 'from_unix_seconds' function
/// instead and get the date from there if they need it.
@internal
pub fn to_unix_seconds(date: tempo.Date) -> Int {
tempo.date_to_unix_seconds(date)
}
/// Returns the UTC date of a unix timestamp in milliseconds.
///
/// From https://howardhinnant.github.io/date_algorithms.html#civil_from_days
///
/// ## Examples
///
/// ```gleam
/// date.from_unix_milli(267_840_000)
/// // -> date.literal("1978-06-28")
/// ```
///
/// I am making this internal because it is created but I am not sure if it
/// should be part of the public API. I think it is too easy to use incorrectly.
/// Users should probably use the 'datetime' module's 'from_unix_seconds' function
/// instead and get the date from there if they need it.
@internal
pub fn from_unix_milli(unix_ts: Int) -> tempo.Date {
// Not `from_unix_seconds(unix_ts / 1000)`: that truncates toward zero, which
// for a pre-epoch timestamp rounds up into the following day.
tempo.date_from_unix_milli(unix_ts)
}
/// Returns the UTC unix timestamp in milliseconds of a date, assuming the
/// time on that date is 00:00:00.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-12")
/// |> date.to_unix_milli
/// // -> 1_718_150_400_000
/// ```
///
/// I am making this internal because it is created but I am not sure if it
/// should be part of the public API. I think it is too easy to use incorrectly.
/// Users should probably use the 'datetime' module's 'from_unix_seconds' function
/// instead and get the date from there if they need it.
@internal
pub fn to_unix_milli(date: tempo.Date) -> Int {
to_unix_seconds(date) * 1000
}
/// Returns the UTC date of a unix timestamp in microseconds.
///
/// From https://howardhinnant.github.io/date_algorithms.html#civil_from_days
///
/// ## Examples
///
/// ```gleam
/// date.from_unix_milli(267_840_000_000)
/// // -> date.literal("1978-06-28")
/// ```
///
/// I am making this internal because it is created but I am not sure if it
/// should be part of the public API. I think it is too easy to use incorrectly.
/// Users should probably use the 'datetime' module's 'from_unix_seconds' function
/// instead and get the date from there if they need it.
@internal
pub fn from_unix_micro(unix_ts: Int) -> tempo.Date {
tempo.date_from_unix_micro(unix_ts)
}
/// Returns the UTC unix timestamp in microseconds of a date, assuming the
/// time on that date is 00:00:00.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-12")
/// |> date.to_unix_micro
/// // -> 1_718_150_400_000_000
/// ```
///
/// I am making this internal because it is created but I am not sure if it
/// should be part of the public API. I think it is too easy to use incorrectly.
/// Users should probably use the 'datetime' module's 'from_unix_seconds' function
/// instead and get the date from there if they need it.
@internal
pub fn to_unix_micro(date: tempo.Date) -> Int {
tempo.date_to_unix_micro(date)
}
/// Creates a date from a Rata Die value, the number of days since the day
/// before `0001-01-01` on the proleptic Gregorian calendar, so Rata Die 1 is
/// `0001-01-01` and `1970-01-01` is 719_163.
///
/// Adapted from the very nice Rada Gleam library!
///
/// This is not range checked, so unlike the calendar-part constructors it will
/// return dates outside the 1000 to 9999 year range, including year zero and
/// negative years for values of 0 and below. Those dates compare and do
/// arithmetic correctly but do not format as valid ISO 8601.
///
/// ## Example
///
/// ```gleam
/// date.from_rata_die(739_050)
/// // -> date.literal("2024-06-13")
/// ```
pub fn from_rata_die(rata_die: Int) -> tempo.Date {
tempo.date_from_rata_die(rata_die)
}
/// Returns the Rata Die value of the date as an Int, the number of days since
/// the day before `0001-01-01` on the proleptic Gregorian calendar, so
/// `0001-01-01` is 1 and `1970-01-01` is 719_163.
///
/// Adapted from the very nice Rada Gleam library!
///
/// ## Example
///
/// ```gleam
/// date.literal("2024-06-13")
/// |> date.to_rata_die
/// // -> 739_050
/// ```
///
/// ```gleam
/// date.literal("1970-01-01")
/// |> date.to_rata_die
/// // -> 719_163
/// ```
pub fn to_rata_die(date: tempo.Date) -> Int {
tempo.date_to_rata_die(date)
}
/// Compares two dates.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-12")
/// |> date.compare(to: date.literal("2024-06-12"))
/// // -> order.Eq
/// ```
///
/// ```gleam
/// date.literal("2024-05-12")
/// |> date.compare(to: date.literal("2024-06-13"))
/// // -> order.Lt
/// ```
///
/// ```gleam
/// date.literal("2034-06-12")
/// |> date.compare(to: date.literal("2024-06-11"))
/// // -> order.Gt
/// ```
pub fn compare(a: tempo.Date, to b: tempo.Date) -> order.Order {
tempo.date_compare(a, b)
}
/// Checks of the first date is earlier than the second date.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-12")
/// |> date.is_earlier(than: date.literal("2024-06-13"))
/// // -> True
/// ```
///
/// ```gleam
/// date.literal("2024-06-12")
/// |> date.is_earlier(than: date.literal("2024-06-12"))
/// // -> False
/// ```
pub fn is_earlier(a: tempo.Date, than b: tempo.Date) -> Bool {
tempo.date_is_earlier(a, than: b)
}
/// Checks if the first date is earlier than or equal to the second date.
///
/// ## Examples
/// ```gleam
/// date.literal("2024-06-12")
/// |> date.is_earlier_or_equal(to: date.literal("2024-06-12"))
/// // -> True
/// ```
///
/// ```gleam
/// date.literal("2024-06-12")
/// |> date.is_earlier_or_equal(to: date.literal("2024-06-11"))
/// // -> False
/// ```
pub fn is_earlier_or_equal(a: tempo.Date, to b: tempo.Date) -> Bool {
tempo.date_is_earlier_or_equal(a, b)
}
/// Checks if two dates are equal.
///
/// ## Example
///
/// ```gleam
/// date.literal("2024-06-12")
/// |> date.is_equal(to: date.literal("2024-06-12"))
/// // -> True
/// ```
pub fn is_equal(a: tempo.Date, to b: tempo.Date) -> Bool {
tempo.date_is_equal(a, to: b)
}
/// Checks if the first date is later than the second date.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-14")
/// |> date.is_later(than: date.literal("2024-06-13"))
/// // -> True
/// ```
///
/// ```gleam
/// date.literal("2024-06-12")
/// |> date.is_later(than: date.literal("2024-06-12"))
/// // -> False
/// ```
pub fn is_later(a: tempo.Date, than b: tempo.Date) -> Bool {
tempo.date_is_later(a, than: b)
}
/// Checks if the first date is later than or equal to the second date.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-12")
/// |> date.is_later_or_equal(to: date.literal("2024-06-12"))
/// // -> True
/// ```
///
/// ```gleam
/// date.literal("2024-06-12")
/// |> date.is_later_or_equal(to: date.literal("2024-06-13"))
/// // -> False
/// ```
pub fn is_later_or_equal(a: tempo.Date, to b: tempo.Date) -> Bool {
tempo.date_is_later_or_equal(a, to: b)
}
/// Gets the difference between two dates.
///
/// ## Examples
///
/// ```gleam
/// date.difference(from: date.literal("2024-06-12"), to: date.literal("2024-06-23"))
/// // -> 11
/// ```
///
/// ```gleam
/// date.difference(from: date.literal("2024-06-03"), to: date.literal("2024-06-11"))
/// // -> 8
/// ```
pub fn difference(from a: tempo.Date, to b: tempo.Date) -> Int {
tempo.date_days_apart(from: a, to: b)
}
/// Creates a period between the first date at 00:00:00 and the second date at
/// 24:00:00. Periods only represent positive datetime differences.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-12")
/// |> date.as_period(end: date.literal("2024-06-23"))
/// |> period.as_days
/// // -> 11
/// ```
///
/// ```gleam
/// date.literal("2024-06-12")
/// |> date.as_period(start: date.literal("2024-06-09"))
/// |> period.comprising_dates
/// // -> ["2024-06-09", "2024-06-10", "2024-06-11", "2024-06-12"]
/// ```
pub fn as_period(start start: tempo.Date, end end: tempo.Date) -> tempo.Period {
tempo.period_new_date(start:, end:)
}
/// Adds a number of days to a date, rolling over month and year boundaries
/// according to proleptic Gregorian month lengths and leap years.
///
/// This is not range checked. Adding enough days will push the date past year
/// 9999, and adding a negative number can push it below year 1000, down through
/// year zero into negative years. Such dates compare and do arithmetic
/// correctly but do not format as valid ISO 8601.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-12")
/// |> date.add(days: 1)
/// // -> date.literal("2024-06-13")
/// ```
///
/// ```gleam
/// date.literal("2024-06-12")
/// |> date.add(days: 12)
/// // -> date.literal("2024-06-24")
/// ```
pub fn add(date: tempo.Date, days days: Int) -> tempo.Date {
tempo.date_add(date, days: days)
}
/// Subtracts a number of days from a date, rolling back over month and year
/// boundaries according to proleptic Gregorian month lengths and leap years.
///
/// This is not range checked. Subtracting enough days will push the date below
/// year 1000, down through year zero into negative years, and subtracting a
/// negative number can push it past year 9999. Such dates compare and do
/// arithmetic correctly but do not format as valid ISO 8601.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-12")
/// |> date.subtract(days: 1)
/// // -> date.literal("2024-06-11")
/// ```
///
/// ```gleam
/// date.literal("2024-06-12")
/// |> date.subtract(days: 12)
/// // -> date.literal("2024-05-31")
/// ```
pub fn subtract(date: tempo.Date, days days: Int) -> tempo.Date {
tempo.date_subtract(date, days: days)
}
/// Returns the number of the day of week a date falls on, using the US
/// convention of counting Sunday as `0` through Saturday as `6`.
///
/// This does *not* follow the ISO 8601 day-of-week numbering (Monday as `1`
/// through Sunday as `7`).
///
/// The result is the day of the week on the proleptic Gregorian calendar, and
/// is exact for every date, with no restricted range. Note though that for
/// dates before a region adopted the Gregorian calendar (1582 in Catholic
/// Europe, as late as 1923 elsewhere), the result will not match the day of the
/// week recorded historically, because those dates were written on the Julian
/// calendar and this library reads every date as Gregorian.
@deprecated("Use `to_day_of_week` for a non-ambiguous named day of week value. If you need a corresponding number, prefer using a case statement to derive one in your application so that it is explicit, as different regions assume different conventions.")
pub fn to_day_of_week_number(date: tempo.Date) -> Int {
tempo.date_to_day_of_week_number_us_convention(date)
}
/// Returns the day of week a date falls on, on the proleptic Gregorian
/// calendar.
///
/// The result is exact for every date, with no restricted range. Note though
/// that for dates before a region adopted the Gregorian calendar (1582 in
/// Catholic Europe, as late as 1923 elsewhere), the result will not match the
/// day of the week recorded historically, because those dates were written on
/// the Julian calendar and this library reads every date as Gregorian.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-20")
/// |> date.to_day_of_week
/// // -> Thur
/// ```
pub fn to_day_of_week(date: tempo.Date) -> DayOfWeek {
case tempo.date_to_day_of_week_number_us_convention(date) {
0 -> Sun
1 -> Mon
2 -> Tue
3 -> Wed
4 -> Thu
5 -> Fri
6 -> Sat
_ -> panic as "Invalid day of week found after modulo by 7"
}
}
/// Returns the short string representation of a day of the week.
///
/// ## Examples
///
/// ```gleam
/// date|> tempo.date_get_day_of_week_to_short_string(date.Mon)
/// // -> "Mon"
/// ```
pub fn day_of_week_to_short_string(day_of_week: DayOfWeek) -> String {
case day_of_week {
Sun -> "Sun"
Mon -> "Mon"
Tue -> "Tue"
Wed -> "Wed"
Thu -> "Thu"
Fri -> "Fri"
Sat -> "Sat"
}
}
/// Returns the long string representation of a day of the week.
///
/// ## Examples
///
/// ```gleam
/// date|> tempo.date_get_day_of_week_to_long_string(date.Fri)
/// // -> "Friday"
/// ```
pub fn day_of_week_to_long_string(day_of_week: DayOfWeek) -> String {
case day_of_week {
Sun -> "Sunday"
Mon -> "Monday"
Tue -> "Tuesday"
Wed -> "Wednesday"
Thu -> "Thursday"
Fri -> "Friday"
Sat -> "Saturday"
}
}
/// Gets the date of the next specified day of the week, exclusive of
/// the passed date. The result is always 1 to 7 days after the given date.
///
/// Days of the week are those of the proleptic Gregorian calendar, as
/// described in `to_day_of_week`.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-21")
/// |> date.next_day_of_week(date.Mon)
/// // -> date.literal("2024-06-24")
/// ```
///
/// ```gleam
/// date.literal("2024-06-21")
/// |> date.next_day_of_week(date.Fri)
/// // -> date.literal("2024-06-28")
/// ```
pub fn next_day_of_week(
date date: tempo.Date,
day_of_week dow: DayOfWeek,
) -> tempo.Date {
let next = date |> add(days: 1)
case next |> to_day_of_week == dow {
True -> next
False -> next_day_of_week(next, dow)
}
}
/// Gets the date of the prior specified day of the week, exclusive of
/// the passed date. The result is always 1 to 7 days before the given date.
///
/// Days of the week are those of the proleptic Gregorian calendar, as
/// described in `to_day_of_week`.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-21")
/// |> date.prior_day_of_week(date.Mon)
/// // -> date.literal("2024-06-17")
/// ```
///
/// ```gleam
/// date.literal("2024-06-21")
/// |> date.prior_day_of_week(date.Fri)
/// // -> date.literal("2024-06-14")
/// ```
pub fn prior_day_of_week(
date date: tempo.Date,
day_of_week dow: DayOfWeek,
) -> tempo.Date {
let prior = date |> subtract(days: 1)
case prior |> to_day_of_week == dow {
True -> prior
False -> prior_day_of_week(prior, dow)
}
}
/// Checks if a date falls in a weekend, taking the weekend to be Saturday and
/// Sunday of the proleptic Gregorian calendar week. Regions that treat other
/// days as the weekend are not accounted for.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-22")
/// |> date.is_weekend
/// // -> True
/// ```
pub fn is_weekend(date: tempo.Date) -> Bool {
case to_day_of_week(date) {
Sat | Sun -> True
_ -> False
}
}
/// Gets the first date of the Gregorian calendar month a date occurs in, so
/// always day 1 of that month.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-06-21")
/// |> date.first_of_month
/// // -> date.literal("2024-06-01")
/// ```
pub fn first_of_month(for date: tempo.Date) -> tempo.Date {
let calendar_date = tempo.date_to_calendar_date(date)
calendar.Date(calendar_date.year, calendar_date.month, 1)
|> tempo.date_from_calendar_date
}
/// Gets the last date of the Gregorian calendar month a date occurs in, so day
/// 28, 29, 30 or 31 depending on the month and whether it is a Gregorian leap
/// year.
///
/// ## Examples
///
/// ```gleam
/// date.literal("2024-02-13")
/// |> date.last_of_month
/// // -> date.literal("2024-02-29")
/// ```
pub fn last_of_month(for date: tempo.Date) -> tempo.Date {
let calendar_date = tempo.date_to_calendar_date(date)
calendar.Date(
calendar_date.year,
calendar_date.month,
month.days(of: calendar_date.month, in: calendar_date.year),
)
|> tempo.date_from_calendar_date
}