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

A named day of the week.

pub type DayOfWeek {
  Sun
  Mon
  Tue
  Wed
  Thu
  Fri
  Sat
}

Constructors

  • Sun
  • Mon
  • Tue
  • Wed
  • Thu
  • Fri
  • Sat

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)
pub const unix_epoch: tempo.Date

Starting point of unix timestamps

Search Document