[time/datetime]: Document package datetime

This commit is contained in:
flysand7
2024-07-20 12:49:40 +11:00
parent 7237f9c9f8
commit 0c78cab336
4 changed files with 273 additions and 10 deletions
+168 -4
View File
@@ -1,56 +1,113 @@
/*
Calendrical conversions using a proleptic Gregorian calendar.
Calendrical conversions using a proleptic Gregorian calendar.
Implemented using formulas from: Calendrical Calculations Ultimate Edition, Reingold & Dershowitz
Implemented using formulas from: Calendrical Calculations Ultimate Edition,
Reingold & Dershowitz
*/
package datetime
import "base:intrinsics"
// Procedures that return an Ordinal
/*
Obtain an ordinal from a date.
This procedure converts the specified date into an ordinal. If the specified
date is not a valid date, an error is returned.
*/
date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal, err: Error) {
validate(date) or_return
return unsafe_date_to_ordinal(date), .None
}
/*
Obtain an ordinal from date components.
This procedure converts the specified date, provided by its individual
components, into an ordinal. If the specified date is not a valid date, an error
is returned.
*/
components_to_ordinal :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (ordinal: Ordinal, err: Error) {
validate(year, month, day) or_return
return unsafe_date_to_ordinal({year, i8(month), i8(day)}), .None
}
// Procedures that return a Date
/*
Obtain date using an Ordinal.
This provedure converts the specified ordinal into a date. If the ordinal is not
a valid ordinal, an error is returned.
*/
ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date, err: Error) {
validate(ordinal) or_return
return unsafe_ordinal_to_date(ordinal), .None
}
/*
Obtain a date from date components.
This procedure converts date components, specified by a year, a month and a day,
into a date object. If the provided date components don't represent a valid
date, an error is returned.
*/
components_to_date :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (date: Date, err: Error) {
validate(year, month, day) or_return
return Date{i64(year), i8(month), i8(day)}, .None
}
/*
Obtain time from time components.
This procedure converts time components, specified by an hour, a minute, a second
and nanoseconds, into a time object. If the provided time components don't
represent a valid time, an error is returned.
*/
components_to_time :: proc "contextless" (#any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (time: Time, err: Error) {
validate(hour, minute, second, nanos) or_return
return Time{i8(hour), i8(minute), i8(second), i32(nanos)}, .None
}
/*
Obtain datetime from components.
This procedure converts date components and time components into a datetime object.
If the provided date components or time components don't represent a valid
datetime, an error is returned.
*/
components_to_datetime :: proc "contextless" (#any_int year, #any_int month, #any_int day, #any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (datetime: DateTime, err: Error) {
date := components_to_date(year, month, day) or_return
time := components_to_time(hour, minute, second, nanos) or_return
return {date, time}, .None
}
/*
Obtain an datetime from an ordinal.
This procedure converts the value of an ordinal into a datetime. Since the
ordinal only has the amount of days, the resulting time in the datetime
object will always have the time equal to `00:00:00.000`.
*/
ordinal_to_datetime :: proc "contextless" (ordinal: Ordinal) -> (datetime: DateTime, err: Error) {
d := ordinal_to_date(ordinal) or_return
return {Date(d), {}}, .None
}
/*
Calculate the weekday from an ordinal.
This procedure takes the value of an ordinal and returns the day of week for
that ordinal.
*/
day_of_week :: proc "contextless" (ordinal: Ordinal) -> (day: Weekday) {
return Weekday((ordinal - EPOCH + 1) %% 7)
}
/*
Calculate the difference between two dates.
This procedure calculates the difference between two dates `a - b`, and returns
a delta between the two dates in `days`. If either `a` or `b` is not a valid
date, an error is returned.
*/
subtract_dates :: proc "contextless" (a, b: Date) -> (delta: Delta, err: Error) {
ord_a := date_to_ordinal(a) or_return
ord_b := date_to_ordinal(b) or_return
@@ -59,6 +116,16 @@ subtract_dates :: proc "contextless" (a, b: Date) -> (delta: Delta, err: Error)
return
}
/*
Calculate the difference between two datetimes.
This procedure calculates the difference between two datetimes, `a - b`, and
returns a delta between the two dates. The difference is returned in all three
fields of the `Delta` struct: the difference in days, the difference in seconds
and the difference in nanoseconds.
If either `a` or `b` is not a valid datetime, an error is returned.
*/
subtract_datetimes :: proc "contextless" (a, b: DateTime) -> (delta: Delta, err: Error) {
ord_a := date_to_ordinal(a) or_return
ord_b := date_to_ordinal(b) or_return
@@ -73,19 +140,42 @@ subtract_datetimes :: proc "contextless" (a, b: DateTime) -> (delta: Delta, err:
return
}
/*
Calculate a difference between two deltas.
*/
subtract_deltas :: proc "contextless" (a, b: Delta) -> (delta: Delta, err: Error) {
delta = Delta{a.days - b.days, a.seconds - b.seconds, a.nanos - b.nanos}
delta = normalize_delta(delta) or_return
return
}
/*
Calculate a difference between two datetimes, dates or deltas.
*/
sub :: proc{subtract_datetimes, subtract_dates, subtract_deltas}
/*
Add certain amount of days to a date.
This procedure adds the specified amount of days to a date and returns a new
date. The new date would have happened the specified amount of days after the
specified date.
*/
add_days_to_date :: proc "contextless" (a: Date, days: i64) -> (date: Date, err: Error) {
ord := date_to_ordinal(a) or_return
ord += days
return ordinal_to_date(ord)
}
/*
Add delta to a date.
This procedure adds a delta to a date, and returns a new date. The new date
would have happened the time specified by `delta` after the specified date.
**Note**: The delta is assumed to be normalized. That is, if it contains seconds
or milliseconds, regardless of the amount only the days will be added.
*/
add_delta_to_date :: proc "contextless" (a: Date, delta: Delta) -> (date: Date, err: Error) {
ord := date_to_ordinal(a) or_return
// Because the input is a Date, we add only the days from the Delta.
@@ -93,6 +183,13 @@ add_delta_to_date :: proc "contextless" (a: Date, delta: Delta) -> (date: Date,
return ordinal_to_date(ord)
}
/*
Add delta to datetime.
This procedure adds a delta to a datetime, and returns a new datetime. The new
datetime would have happened the time specified by `delta` after the specified
datetime.
*/
add_delta_to_datetime :: proc "contextless" (a: DateTime, delta: Delta) -> (datetime: DateTime, err: Error) {
days := date_to_ordinal(a) or_return
@@ -110,8 +207,18 @@ add_delta_to_datetime :: proc "contextless" (a: DateTime, delta: Delta) -> (date
datetime.time = components_to_time(hour, minute, second, sum_delta.nanos) or_return
return
}
/*
Add days to a date, delta to a date or delta to datetime.
*/
add :: proc{add_days_to_date, add_delta_to_date, add_delta_to_datetime}
/*
Obtain the day number in a year
This procedure returns the number of the day in a year, starting from 1. If
the date is not a valid date, an error is returned.
*/
day_number :: proc "contextless" (date: Date) -> (day_number: i64, err: Error) {
validate(date) or_return
@@ -120,6 +227,13 @@ day_number :: proc "contextless" (date: Date) -> (day_number: i64, err: Error) {
return
}
/*
Obtain the remaining number of days in a year.
This procedure returns the number of days between the specified date and
December 31 of the same year. If the date is not a valid date, an error is
returned.
*/
days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err: Error) {
// Alternative formulation `day_number` subtracted from 365 or 366 depending on leap year
validate(date) or_return
@@ -127,6 +241,12 @@ days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err:
return delta.days, .None
}
/*
Obtain the last day of a given month on a given year.
This procedure returns the amount of days in a specified month on a specified
date. If the specified year or month is not valid, an error is returned.
*/
last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8) -> (day: i8, err: Error) {
// Not using formula 2.27 from the book. This is far simpler and gives the same answer.
@@ -140,16 +260,33 @@ last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8)
return
}
/*
Obtain the new year date of a given year.
This procedure returns the January 1st date of the specified year. If the year
is not valid, an error is returned.
*/
new_year :: proc "contextless" (#any_int year: i64) -> (new_year: Date, err: Error) {
validate(year, 1, 1) or_return
return {year, 1, 1}, .None
}
/*
Obtain the end year of a given date.
This procedure returns the December 31st date of the specified year. If the year
is not valid, an error is returned.
*/
year_end :: proc "contextless" (#any_int year: i64) -> (year_end: Date, err: Error) {
validate(year, 12, 31) or_return
return {year, 12, 31}, .None
}
/*
Obtain the range of dates for a given year.
This procedure returns dates, for every day of a given year in a slice.
*/
year_range :: proc (#any_int year: i64, allocator := context.allocator) -> (range: []Date) {
is_leap := is_leap_year(year)
@@ -171,6 +308,15 @@ year_range :: proc (#any_int year: i64, allocator := context.allocator) -> (rang
return
}
/*
Normalize the delta.
This procedure normalizes the delta in such a way that the number of seconds
is between 0 and the number of seconds in the day and nanoseconds is between
0 and 10^9.
If the value for `days` overflows during this operation, an error is returned.
*/
normalize_delta :: proc "contextless" (delta: Delta) -> (normalized: Delta, err: Error) {
// Distribute nanos into seconds and remainder
seconds, nanos := divmod(delta.nanos, 1e9)
@@ -194,6 +340,12 @@ normalize_delta :: proc "contextless" (delta: Delta) -> (normalized: Delta, err:
// The following procedures don't check whether their inputs are in a valid range.
// They're still exported for those who know their inputs have been validated.
/*
Obtain an ordinal from a date.
This procedure converts a date into an ordinal. If the date is not a valid date,
the result is unspecified.
*/
unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal) {
year_minus_one := date.year - 1
@@ -223,6 +375,12 @@ unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal)
return
}
/*
Obtain a year and a day of the year from an ordinal.
This procedure returns the year and the day of the year of a given ordinal.
Of the ordinal is outside of its valid range, the result is unspecified.
*/
unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, day_ordinal: i64) {
// Days after epoch
d0 := ordinal - EPOCH
@@ -253,6 +411,12 @@ unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, d
return year + 1, day_ordinal
}
/*
Obtain a date from an ordinal.
This procedure converts an ordinal into a date. If the ordinal is outside of
its valid range, the result is unspecified.
*/
unsafe_ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date) {
year, _ := unsafe_ordinal_to_year(ordinal)