std.period
Periods: amounts of time a calendar counts — a year, a month, a
week, a day, an hour — not fixed lengths: a month after January 31 is
29 days on in 2024 and 28 in 2023, so a period has no length until it
is laid on a date, and is no std.duration. NodaTime’s Period is the
model: a period holds each unit apart, as it was given, and 1 month is
not 4 weeks nor 30 days, nor 60 minutes 1 hour, until it is laid on a
date or a time (addToDate, addToTime, addToDateTime). Two dates
are a period apart in years, months and days (between). A period is
a record, its fields in plain sight; ISO 8601 writes one,
P1Y2M3W4DT5H6M7.5S. Import it by a name,
import std.period as Period, as its names are short.
import std.localdate as LocalDateimport std.period as Period
-- | the day a quarter of a year after the date-- | >>> maybe "" (\d -> show (nextQuarter d)) (LocalDate.parse "2024-11-30")-- | "2025-02-28"nextQuarter : LocalDate.LocalDate -> LocalDate.LocalDatenextQuarter d = Period.addToDate d (Period.months 3)Period
type Period = { years : Int , months : Int , weeks : Int , days : Int , hours : Int , minutes : Int , seconds : Int , nanoseconds : Int }A period: how many of each unit, each apart, any of them less than none for a period back in time.
zero
zero : PeriodThe period of nothing: every unit 0.
years
years : Int -> Periodn years.
>>> years 2{ years = 2, months = 0, weeks = 0, days = 0, hours = 0, minutes = 0, seconds = 0, nanoseconds = 0 }months
months : Int -> Periodn months.
weeks
weeks : Int -> Periodn weeks.
days
days : Int -> Periodn days.
hours
hours : Int -> Periodn hours.
minutes
minutes : Int -> Periodn minutes.
seconds
seconds : Int -> Periodn seconds.
nanoseconds
nanoseconds : Int -> Periodn nanoseconds.
instanceEqualityPeriod
instance instanceEqualityPeriod : Equality PeriodEqual unit by unit: 1 hour is not 60 minutes, as NodaTime has it.
>>> hours 1 == hours 1 && hours 1 /= minutes 60TrueinstanceAdditionPeriod
instance instanceAdditionPeriod : Addition PeriodAdded unit by unit, each kept apart.
>>> years 1 + months 14 + days 3{ years = 1, months = 14, weeks = 0, days = 3, hours = 0, minutes = 0, seconds = 0, nanoseconds = 0 }instanceNegationPeriod
instance instanceNegationPeriod : Negation PeriodEvery unit negated.
>>> -(months 1 + days (-2)) == months (-1) + days 2TrueinstanceDebugPeriod
instance instanceDebugPeriod : Debug PeriodAs a record literal writes it, every field.
>>> debug (months (-1))"{ years = 0, months = -1, weeks = 0, days = 0, hours = 0, minutes = 0, seconds = 0, nanoseconds = 0 }"instanceShowPeriod
instance instanceShowPeriod : Show PeriodAs ISO 8601 writes it, as NodaTime does: P, each date unit that is
not 0 (1Y2M3W4D), then, if a time unit is not 0, T and each time
unit that is not 0 (5H6M7.5S) — the seconds and the nanoseconds
together as seconds and their fraction, without the 0s it ends
with. A unit less than none is written with its own -
(P-1M-1D), and the period of nothing P0D.
>>> show (years 1 + months 2 + weeks 3 + days 4 + hours 5 + minutes 6 + seconds 7 + nanoseconds 500000000)"P1Y2M3W4DT5H6M7.5S">>> List.map show [zero, days 1, hours 36, months (-1) + days (-1), seconds 1 + nanoseconds (-1500000000), nanoseconds 1, years 1 + minutes (-1)]["P0D", "P1D", "PT36H", "P-1M-1D", "PT-0.5S", "PT0.000000001S", "P1YT-1M"]addToDate
addToDate : LocalDate.LocalDate -> Period -> LocalDate.LocalDateThe date the period after it: its years first, then its months,
each as std.localdate’s plusYears and plusMonths lay them, the day
clamped to the month’s last (2024-01-31 and a month is 2024-02-29),
then its weeks and days. A period with any time unit not 0 is
abandoned, as NodaTime throws: a date has no time of day to move.
>>> List.map (\p -> maybe "" (\d -> show (addToDate d p)) (LocalDate.parse "2024-01-31")) [months 1, years 1 + months 1, weeks 1 + days (-1), months (-11) + days 1, years (-2025)]["2024-02-29", "2025-02-28", "2024-02-06", "2023-03-01", "-0001-01-31"]>>> maybe "" (\d -> show (addToDate d (years 1 + months 1))) (LocalDate.parse "2024-02-29")"2025-03-28"addToTime
addToTime : LocalTime.LocalTime -> Period -> LocalTime.LocalTimeThe time the period’s time units after it, round the clock: past midnight on into the next day, and before it back into the day before. A period with any date unit not 0 is abandoned, as NodaTime throws: a time of day has no date to move.
>>> List.map (\p -> maybe "" (\t -> show (addToTime t p)) (LocalTime.make 22 30 0)) [hours 1, hours 2, minutes (-1350), hours 24, seconds 1 + nanoseconds (-1), hours 48 + minutes 1]["23:30:00", "00:30:00", "00:00:00", "22:30:00", "22:30:00.999999999", "22:31:00"]addToDateTime
addToDateTime : LocalDateTime.LocalDateTime -> Period -> LocalDateTime.LocalDateTimeThe date-time the period after it: its date units laid on the date
as addToDate lays them, then its time units, as a length of time,
on the time, the days they go past carried into the date.
>>> List.map (\p -> maybe "" (\t -> show (addToDateTime t p)) (LocalDateTime.parse "2024-01-31T22:30:00")) [months 1 + hours 2, hours (-23), days 1 + minutes 90, hours 8784, nanoseconds (-1)]["2024-03-01T00:30:00", "2024-01-30T23:30:00", "2024-02-02T00:00:00", "2025-01-31T22:30:00", "2024-01-31T22:29:59.999999999"]between
between : LocalDate.LocalDate -> LocalDate.LocalDate -> PeriodThe period from the first date to the second, in years, months and
days (no weeks), as NodaTime’s Period.Between with its years,
months and days units: as many whole years as do not go past the
second, then as many whole months, each as plusYears and
plusMonths lay them, then the days left; all less than none if
the second comes first. addToDate a (between a b) is b.
>>> List.map (\p -> maybe "" show (maybe Nothing (\a -> maybe Nothing (\b -> Just (between a b)) (LocalDate.parse (snd p))) (LocalDate.parse (fst p)))) [("2024-01-31", "2024-03-01"), ("2024-03-01", "2024-01-31"), ("2020-02-29", "2024-02-28"), ("2024-02-29", "2025-02-28"), ("1969-12-31", "2024-02-29"), ("2024-05-05", "2024-05-05"), ("0000-03-01", "0002-02-28")]["P1M1D", "P-1M-1D", "P3Y12M", "P1Y", "P54Y2M", "P0D", "P1Y11M27D"]>>> maybe "" (\a -> show (between a (LocalDate.fromEpochDays 0)) ^ " " ^ show (between (LocalDate.fromEpochDays 0) a)) (LocalDate.make (-1) 3 1)"P1970Y10M P-1970Y-10M">>> List.all (\p -> maybe False (\a -> maybe False (\b -> addToDate a (between a b) == b) (LocalDate.parse (snd p))) (LocalDate.parse (fst p))) [("2024-01-31", "2024-03-01"), ("2024-03-01", "2024-01-31"), ("2024-03-31", "2023-02-28"), ("2023-02-28", "2024-03-31"), ("2000-02-29", "1999-02-28"), ("0000-01-01", "9999-12-31")]True