1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
|
/*
Calendrical conversions using a proleptic Gregorian calendar.
Implemented using formulas from: Calendrical Calculations Ultimate Edition,
Reingold & Dershowitz
*/
package datetime
import "base:intrinsics"
/*
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
}
/*
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, nil}, .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), {}, nil}, .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
delta = Delta{days=ord_a - ord_b}
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
validate(a.time) or_return
validate(b.time) or_return
seconds_a := i64(a.hour) * 3600 + i64(a.minute) * 60 + i64(a.second)
seconds_b := i64(b.hour) * 3600 + i64(b.minute) * 60 + i64(b.second)
delta = Delta{ord_a - ord_b, seconds_a - seconds_b, i64(a.nano) - i64(b.nano)}
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.
ord += delta.days
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
a_seconds := i64(a.hour) * 3600 + i64(a.minute) * 60 + i64(a.second)
a_delta := Delta{days=days, seconds=a_seconds, nanos=i64(a.nano)}
sum_delta := Delta{days=a_delta.days + delta.days, seconds=a_delta.seconds + delta.seconds, nanos=a_delta.nanos + delta.nanos}
sum_delta = normalize_delta(sum_delta) or_return
datetime.date = ordinal_to_date(sum_delta.days) or_return
hour, rem := divmod(sum_delta.seconds, 3600)
minute, second := divmod(rem, 60)
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
ord := unsafe_date_to_ordinal(date)
_, day_number = unsafe_ordinal_to_year(ord)
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
delta := sub(date, Date{date.year, 12, 31}) or_return
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.
validate(Date{year, month, 1}) or_return
month_days := MONTH_DAYS
day = month_days[month]
if month == 2 && is_leap_year(year) {
day += 1
}
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)
days := 366 if is_leap else 365
range = make([]Date, days, allocator)
month_days := MONTH_DAYS
if is_leap {
month_days[2] = 29
}
i := 0
for month in 1..=len(month_days) {
for day in 1..=month_days[month] {
range[i], _ = components_to_date(year, month, day)
i += 1
}
}
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)
// Add original seconds to rolled over seconds.
seconds += delta.seconds
days: i64
// Distribute seconds into number of days and remaining seconds.
days, seconds = divmod(seconds, 24 * 3600)
// Add original days
days += delta.days
if days <= MIN_ORD || days >= MAX_ORD {
return {}, .Invalid_Delta
}
return Delta{days, seconds, nanos}, .None
}
// 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
// Day before epoch
ordinal = EPOCH - 1
// Add non-leap days
ordinal += 365 * year_minus_one
// Add leap days
ordinal += floor_div(year_minus_one, 4) // Julian-rule leap days
ordinal -= floor_div(year_minus_one, 100) // Prior century years
ordinal += floor_div(year_minus_one, 400) // Prior 400-multiple years
ordinal += floor_div(367 * i64(date.month) - 362, 12) // Prior days this year
// Apply correction
if date.month <= 2 {
ordinal += 0
} else if is_leap_year(date.year) {
ordinal -= 1
} else {
ordinal -= 2
}
// Add days
ordinal += i64(date.day)
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
// Number of 400-year cycles and remainder
n400, d1 := divmod(d0, 146097)
// Number of 100-year cycles and remainder
n100, d2 := divmod(d1, 36524)
// Number of 4-year cycles and remainder
n4, d3 := divmod(d2, 1461)
// Number of remaining days
n1, d4 := divmod(d3, 365)
year = 400 * n400 + 100 * n100 + 4 * n4 + n1
if n1 != 4 && n100 != 4 {
day_ordinal = d4 + 1
} else {
day_ordinal = 366
}
if n100 == 4 || n1 == 4 {
return year, day_ordinal
}
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)
prior_days := ordinal - unsafe_date_to_ordinal(Date{year, 1, 1})
correction := Ordinal(2)
if ordinal < unsafe_date_to_ordinal(Date{year, 3, 1}) {
correction = 0
} else if is_leap_year(year) {
correction = 1
}
month := i8(floor_div((12 * (prior_days + correction) + 373), 367))
day := i8(ordinal - unsafe_date_to_ordinal(Date{year, month, 1}) + 1)
return {year, month, day}
}
|