tempo/date
Functions to use with the Date type in Tempo.
Example
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"
}
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
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.
Types
Values
pub fn add(date: tempo.Date, days days: Int) -> tempo.Date
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
date.literal("2024-06-12")
|> date.add(days: 1)
// -> date.literal("2024-06-13")
date.literal("2024-06-12")
|> date.add(days: 12)
// -> date.literal("2024-06-24")
pub fn as_period(
start start: tempo.Date,
end end: tempo.Date,
) -> tempo.Period
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
date.literal("2024-06-12")
|> date.as_period(end: date.literal("2024-06-23"))
|> period.as_days
// -> 11
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 compare(a: tempo.Date, to b: tempo.Date) -> order.Order
Compares two dates.
Examples
date.literal("2024-06-12")
|> date.compare(to: date.literal("2024-06-12"))
// -> order.Eq
date.literal("2024-05-12")
|> date.compare(to: date.literal("2024-06-13"))
// -> order.Lt
date.literal("2034-06-12")
|> date.compare(to: date.literal("2024-06-11"))
// -> order.Gt
pub fn current_local() -> tempo.Date
Gets the current local date of the host, as a proleptic Gregorian calendar date.
Examples
date.current_local()
|> date.to_string
// -> "2024-06-13"
pub fn current_utc() -> tempo.Date
Gets the current UTC date of the host, as a proleptic Gregorian calendar date.
Examples
date.current_utc()
|> date.to_string
// -> "2024-06-14"
pub fn day_of_week_to_long_string(
day_of_week: DayOfWeek,
) -> String
Returns the long string representation of a day of the week.
Examples
date|> tempo.date_get_day_of_week_to_long_string(date.Fri)
// -> "Friday"
pub fn day_of_week_to_short_string(
day_of_week: DayOfWeek,
) -> String
Returns the short string representation of a day of the week.
Examples
date|> tempo.date_get_day_of_week_to_short_string(date.Mon)
// -> "Mon"
pub fn describe_out_of_bounds_error(
error: error.DateOutOfBoundsError,
) -> String
Converts a date out of bounds error to a human readable error message.
Example
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_parse_error(
error: error.DateParseError,
) -> String
Converts a date parse error to a human readable error message.
Example
date.parse_any("01:32 PM")
|> snag.map_error(with: date.describe_parse_error)
// -> snag.error("Invalid date format: 01:32 PM")
pub fn difference(from a: tempo.Date, to b: tempo.Date) -> Int
Gets the difference between two dates.
Examples
date.difference(from: date.literal("2024-06-12"), to: date.literal("2024-06-23"))
// -> 11
date.difference(from: date.literal("2024-06-03"), to: date.literal("2024-06-11"))
// -> 8
pub fn first_of_month(for date: tempo.Date) -> tempo.Date
Gets the first date of the Gregorian calendar month a date occurs in, so always day 1 of that month.
Examples
date.literal("2024-06-21")
|> date.first_of_month
// -> date.literal("2024-06-01")
pub fn format(
date: tempo.Date,
in format: tempo.DateFormat,
) -> String
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
datetime.literal("2024-12-26T13:02:01-04:00")
|> datetime.format(tempo.ISO8601Date)
// -> "2024-12-26"
datetime.literal("2024-06-21T13:42:11.314-04:00")
|> datetime.format(tempo.CustomDate("ddd @ h:mm A (z)"))
// -> "Fri @ 1:42 PM (-04)"
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"
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"
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 from_calendar_date(
date: calendar.Date,
) -> Result(tempo.Date, error.DateOutOfBoundsError)
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
calendar.Date(2024, calendar.June, 13)
|> date.from_calendar_date
// -> Ok(date.literal("2024-06-13"))
pub fn from_dynamic_string(
dynamic_string: dynamic.Dynamic,
) -> Result(tempo.Date, List(decode.DecodeError))
Checks if a dynamic value is a valid date string, and returns the date if it is.
Examples
dynamic.string("2024-06-21")
|> date.from_dynamic_string
// -> Ok(date.literal("2024-06-21"))
dynamic.string("153")
|> datetime.from_dynamic_string
// -> Error([
// decode.DecodeError(
// expected: "tempo.Date",
// found: "Invalid format: 153",
// path: [],
// ),
// ])
pub fn from_rata_die(rata_die: Int) -> tempo.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
date.from_rata_die(739_050)
// -> date.literal("2024-06-13")
pub fn from_string(
date: String,
) -> Result(tempo.Date, error.DateParseError)
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
date.from_string("2024-06-13")
// -> Ok(date.literal("2024-06-13"))
date.from_string("20240613")
// -> Ok(date.literal("2024-06-13"))
date.from_string("2409")
// -> Error(tempo_error.DateInvalidFormat)
pub fn from_tuple(
date: #(Int, Int, Int),
) -> Result(tempo.Date, error.DateOutOfBoundsError)
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
date.from_tuple(#(2024, 6, 13))
// -> Ok(date.literal("2024-06-13"))
date.from_tuple(#(98, 6, 13))
// -> Error(tempo_error.DateOutOfBounds)
// Feb 29 exists in Gregorian leap years only
date.from_tuple(#(2023, 2, 29))
// -> Error(tempo_error.DateOutOfBounds)
pub fn get_day(date: tempo.Date) -> Int
Gets the day of the Gregorian calendar month of a date, from 1 to 31.
Examples
date.literal("2024-06-13")
|> date.get_day
// -> 13
pub fn get_month(date: tempo.Date) -> calendar.Month
Gets the Gregorian calendar month of a date.
Examples
date.literal("2024-06-13")
|> date.get_month
// -> tempo.Jun
pub fn get_month_year(date: tempo.Date) -> tempo.MonthYear
Gets the Gregorian calendar month and year of a date.
Examples
date.literal("2024-06-13")
|> date.get_month_year
// -> calendar.MonthYear(tempo.Jun, 2024)
pub fn get_year(date: tempo.Date) -> Int
Gets the proleptic Gregorian calendar year of a date.
Examples
date.literal("2024-06-13")
|> date.get_year
// -> 2024
pub fn is_earlier(a: tempo.Date, than b: tempo.Date) -> Bool
Checks of the first date is earlier than the second date.
Examples
date.literal("2024-06-12")
|> date.is_earlier(than: date.literal("2024-06-13"))
// -> True
date.literal("2024-06-12")
|> date.is_earlier(than: date.literal("2024-06-12"))
// -> False
pub fn is_earlier_or_equal(
a: tempo.Date,
to b: tempo.Date,
) -> Bool
Checks if the first date is earlier than or equal to the second date.
Examples
date.literal("2024-06-12")
|> date.is_earlier_or_equal(to: date.literal("2024-06-12"))
// -> True
date.literal("2024-06-12")
|> date.is_earlier_or_equal(to: date.literal("2024-06-11"))
// -> False
pub fn is_equal(a: tempo.Date, to b: tempo.Date) -> Bool
Checks if two dates are equal.
Example
date.literal("2024-06-12")
|> date.is_equal(to: date.literal("2024-06-12"))
// -> True
pub fn is_later(a: tempo.Date, than b: tempo.Date) -> Bool
Checks if the first date is later than the second date.
Examples
date.literal("2024-06-14")
|> date.is_later(than: date.literal("2024-06-13"))
// -> True
date.literal("2024-06-12")
|> date.is_later(than: date.literal("2024-06-12"))
// -> False
pub fn is_later_or_equal(a: tempo.Date, to b: tempo.Date) -> Bool
Checks if the first date is later than or equal to the second date.
Examples
date.literal("2024-06-12")
|> date.is_later_or_equal(to: date.literal("2024-06-12"))
// -> True
date.literal("2024-06-12")
|> date.is_later_or_equal(to: date.literal("2024-06-13"))
// -> False
pub fn is_weekend(date: tempo.Date) -> Bool
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
date.literal("2024-06-22")
|> date.is_weekend
// -> True
pub fn last_of_month(for date: tempo.Date) -> tempo.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
date.literal("2024-02-13")
|> date.last_of_month
// -> date.literal("2024-02-29")
pub fn literal(date: String) -> tempo.Date
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
date.literal("2024-06-13")
|> date.to_string
// -> "2024-06-13"
date.literal("20240613")
|> date.to_string
// -> "2024-06-13"
date.literal("2409")
// -> panic
pub fn new(
year year: Int,
month month: Int,
day day: Int,
) -> Result(tempo.Date, error.DateOutOfBoundsError)
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
date.new(2024, 6, 13)
// -> Ok(date.literal("2024-06-13"))
date.new(2024, 6, 31)
// -> Error(tempo_error.DateOutOfBounds)
// 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)
// Years outside 1000 to 9999 are rejected
date.new(999, 1, 1)
// -> Error(tempo_error.DateOutOfBounds)
pub fn next_day_of_week(
date date: tempo.Date,
day_of_week dow: DayOfWeek,
) -> tempo.Date
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
date.literal("2024-06-21")
|> date.next_day_of_week(date.Mon)
// -> date.literal("2024-06-24")
date.literal("2024-06-21")
|> date.next_day_of_week(date.Fri)
// -> date.literal("2024-06-28")
pub fn parse(
str: String,
in format: tempo.DateFormat,
) -> Result(tempo.Date, error.DateParseError)
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
date.parse("2024/06/08, 13:42:11", "YYYY/MM/DD")
// -> Ok(date.literal("2024-06-08"))
date.parse("January 13, 2024", "MMMM DD, YYYY")
// -> Ok(date.literal("2024-01-13"))
date.parse("Hi! 2024 11 13", "[Hi!] YYYY M D")
// -> Ok(date.literal("2024-11-13"))
pub fn parse_any(
str: String,
) -> Result(tempo.Date, error.DateParseError)
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
date.parse_any("2024.06.21 01:32 PM -04:00")
// -> Ok(date.literal("2024-06-21"))
date.parse_any("2024.06.21")
// -> Ok(date.literal("2024-06-21"))
pub fn prior_day_of_week(
date date: tempo.Date,
day_of_week dow: DayOfWeek,
) -> tempo.Date
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
date.literal("2024-06-21")
|> date.prior_day_of_week(date.Mon)
// -> date.literal("2024-06-17")
date.literal("2024-06-21")
|> date.prior_day_of_week(date.Fri)
// -> date.literal("2024-06-14")
pub fn subtract(date: tempo.Date, days days: Int) -> tempo.Date
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
date.literal("2024-06-12")
|> date.subtract(days: 1)
// -> date.literal("2024-06-11")
date.literal("2024-06-12")
|> date.subtract(days: 12)
// -> date.literal("2024-05-31")
pub fn to_calendar_date(date: tempo.Date) -> calendar.Date
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
date.literal("2024-06-13")
|> date.to_calendar_date
// -> calendar.Date(2024, calendar.June, 13)
pub fn to_day_of_week(date: tempo.Date) -> DayOfWeek
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
date.literal("2024-06-20")
|> date.to_day_of_week
// -> Thur
pub fn to_day_of_week_number(date: tempo.Date) -> Int
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.
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.
pub fn to_rata_die(date: tempo.Date) -> Int
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
date.literal("2024-06-13")
|> date.to_rata_die
// -> 739_050
date.literal("1970-01-01")
|> date.to_rata_die
// -> 719_163
pub fn to_string(date: tempo.Date) -> String
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
date.literal("2024-06-13")
|> date.to_string
// -> "2024-06-13"
// 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_tuple(date: tempo.Date) -> #(Int, Int, Int)
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
date.literal("2024-06-14")
|> date.to_tuple
// -> #(2024, 6, 14)