datetime.odin 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. /*
  2. Calendrical conversions using a proleptic Gregorian calendar.
  3. Implemented using formulas from: Calendrical Calculations Ultimate Edition,
  4. Reingold & Dershowitz
  5. */
  6. package datetime
  7. import "base:intrinsics"
  8. /*
  9. Obtain an ordinal from a date.
  10. This procedure converts the specified date into an ordinal. If the specified
  11. date is not a valid date, an error is returned.
  12. */
  13. date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal, err: Error) {
  14. validate(date) or_return
  15. return unsafe_date_to_ordinal(date), .None
  16. }
  17. /*
  18. Obtain an ordinal from date components.
  19. This procedure converts the specified date, provided by its individual
  20. components, into an ordinal. If the specified date is not a valid date, an error
  21. is returned.
  22. */
  23. components_to_ordinal :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (ordinal: Ordinal, err: Error) {
  24. validate(year, month, day) or_return
  25. return unsafe_date_to_ordinal({year, i8(month), i8(day)}), .None
  26. }
  27. /*
  28. Obtain date using an Ordinal.
  29. This provedure converts the specified ordinal into a date. If the ordinal is not
  30. a valid ordinal, an error is returned.
  31. */
  32. ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date, err: Error) {
  33. validate(ordinal) or_return
  34. return unsafe_ordinal_to_date(ordinal), .None
  35. }
  36. /*
  37. Obtain a date from date components.
  38. This procedure converts date components, specified by a year, a month and a day,
  39. into a date object. If the provided date components don't represent a valid
  40. date, an error is returned.
  41. */
  42. components_to_date :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (date: Date, err: Error) {
  43. validate(year, month, day) or_return
  44. return Date{i64(year), i8(month), i8(day)}, .None
  45. }
  46. /*
  47. Obtain time from time components.
  48. This procedure converts time components, specified by an hour, a minute, a second
  49. and nanoseconds, into a time object. If the provided time components don't
  50. represent a valid time, an error is returned.
  51. */
  52. components_to_time :: proc "contextless" (#any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (time: Time, err: Error) {
  53. validate(hour, minute, second, nanos) or_return
  54. return Time{i8(hour), i8(minute), i8(second), i32(nanos)}, .None
  55. }
  56. /*
  57. Obtain datetime from components.
  58. This procedure converts date components and time components into a datetime object.
  59. If the provided date components or time components don't represent a valid
  60. datetime, an error is returned.
  61. */
  62. 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) {
  63. date := components_to_date(year, month, day) or_return
  64. time := components_to_time(hour, minute, second, nanos) or_return
  65. return {date, time, nil}, .None
  66. }
  67. /*
  68. Obtain an datetime from an ordinal.
  69. This procedure converts the value of an ordinal into a datetime. Since the
  70. ordinal only has the amount of days, the resulting time in the datetime
  71. object will always have the time equal to `00:00:00.000`.
  72. */
  73. ordinal_to_datetime :: proc "contextless" (ordinal: Ordinal) -> (datetime: DateTime, err: Error) {
  74. d := ordinal_to_date(ordinal) or_return
  75. return {Date(d), {}, nil}, .None
  76. }
  77. /*
  78. Calculate the weekday from an ordinal.
  79. This procedure takes the value of an ordinal and returns the day of week for
  80. that ordinal.
  81. */
  82. day_of_week :: proc "contextless" (ordinal: Ordinal) -> (day: Weekday) {
  83. return Weekday(ordinal %% 7)
  84. }
  85. /*
  86. Calculate the difference between two dates.
  87. This procedure calculates the difference between two dates `a - b`, and returns
  88. a delta between the two dates in `days`. If either `a` or `b` is not a valid
  89. date, an error is returned.
  90. */
  91. subtract_dates :: proc "contextless" (a, b: Date) -> (delta: Delta, err: Error) {
  92. ord_a := date_to_ordinal(a) or_return
  93. ord_b := date_to_ordinal(b) or_return
  94. delta = Delta{days=ord_a - ord_b}
  95. return
  96. }
  97. /*
  98. Calculate the difference between two datetimes.
  99. This procedure calculates the difference between two datetimes, `a - b`, and
  100. returns a delta between the two dates. The difference is returned in all three
  101. fields of the `Delta` struct: the difference in days, the difference in seconds
  102. and the difference in nanoseconds.
  103. If either `a` or `b` is not a valid datetime, an error is returned.
  104. */
  105. subtract_datetimes :: proc "contextless" (a, b: DateTime) -> (delta: Delta, err: Error) {
  106. ord_a := date_to_ordinal(a) or_return
  107. ord_b := date_to_ordinal(b) or_return
  108. validate(a.time) or_return
  109. validate(b.time) or_return
  110. seconds_a := i64(a.hour) * 3600 + i64(a.minute) * 60 + i64(a.second)
  111. seconds_b := i64(b.hour) * 3600 + i64(b.minute) * 60 + i64(b.second)
  112. delta = Delta{ord_a - ord_b, seconds_a - seconds_b, i64(a.nano) - i64(b.nano)}
  113. return
  114. }
  115. /*
  116. Calculate a difference between two deltas.
  117. */
  118. subtract_deltas :: proc "contextless" (a, b: Delta) -> (delta: Delta, err: Error) {
  119. delta = Delta{a.days - b.days, a.seconds - b.seconds, a.nanos - b.nanos}
  120. delta = normalize_delta(delta) or_return
  121. return
  122. }
  123. /*
  124. Calculate a difference between two datetimes, dates or deltas.
  125. */
  126. sub :: proc{subtract_datetimes, subtract_dates, subtract_deltas}
  127. /*
  128. Add certain amount of days to a date.
  129. This procedure adds the specified amount of days to a date and returns a new
  130. date. The new date would have happened the specified amount of days after the
  131. specified date.
  132. */
  133. add_days_to_date :: proc "contextless" (a: Date, days: i64) -> (date: Date, err: Error) {
  134. ord := date_to_ordinal(a) or_return
  135. ord += days
  136. return ordinal_to_date(ord)
  137. }
  138. /*
  139. Add delta to a date.
  140. This procedure adds a delta to a date, and returns a new date. The new date
  141. would have happened the time specified by `delta` after the specified date.
  142. **Note**: The delta is assumed to be normalized. That is, if it contains seconds
  143. or milliseconds, regardless of the amount only the days will be added.
  144. */
  145. add_delta_to_date :: proc "contextless" (a: Date, delta: Delta) -> (date: Date, err: Error) {
  146. ord := date_to_ordinal(a) or_return
  147. // Because the input is a Date, we add only the days from the Delta.
  148. ord += delta.days
  149. return ordinal_to_date(ord)
  150. }
  151. /*
  152. Add delta to datetime.
  153. This procedure adds a delta to a datetime, and returns a new datetime. The new
  154. datetime would have happened the time specified by `delta` after the specified
  155. datetime.
  156. */
  157. add_delta_to_datetime :: proc "contextless" (a: DateTime, delta: Delta) -> (datetime: DateTime, err: Error) {
  158. days := date_to_ordinal(a) or_return
  159. a_seconds := i64(a.hour) * 3600 + i64(a.minute) * 60 + i64(a.second)
  160. a_delta := Delta{days=days, seconds=a_seconds, nanos=i64(a.nano)}
  161. sum_delta := Delta{days=a_delta.days + delta.days, seconds=a_delta.seconds + delta.seconds, nanos=a_delta.nanos + delta.nanos}
  162. sum_delta = normalize_delta(sum_delta) or_return
  163. datetime.date = ordinal_to_date(sum_delta.days) or_return
  164. hour, rem := divmod(sum_delta.seconds, 3600)
  165. minute, second := divmod(rem, 60)
  166. datetime.time = components_to_time(hour, minute, second, sum_delta.nanos) or_return
  167. return
  168. }
  169. /*
  170. Add days to a date, delta to a date or delta to datetime.
  171. */
  172. add :: proc{add_days_to_date, add_delta_to_date, add_delta_to_datetime}
  173. /*
  174. Obtain the day number in a year
  175. This procedure returns the number of the day in a year, starting from 1. If
  176. the date is not a valid date, an error is returned.
  177. */
  178. day_number :: proc "contextless" (date: Date) -> (day_number: i64, err: Error) {
  179. validate(date) or_return
  180. ord := unsafe_date_to_ordinal(date)
  181. _, day_number = unsafe_ordinal_to_year(ord)
  182. return
  183. }
  184. /*
  185. Obtain the remaining number of days in a year.
  186. This procedure returns the number of days between the specified date and
  187. December 31 of the same year. If the date is not a valid date, an error is
  188. returned.
  189. */
  190. days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err: Error) {
  191. // Alternative formulation `day_number` subtracted from 365 or 366 depending on leap year
  192. validate(date) or_return
  193. delta := sub(date, Date{date.year, 12, 31}) or_return
  194. return delta.days, .None
  195. }
  196. /*
  197. Obtain the last day of a given month on a given year.
  198. This procedure returns the amount of days in a specified month on a specified
  199. date. If the specified year or month is not valid, an error is returned.
  200. */
  201. last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8) -> (day: i8, err: Error) {
  202. // Not using formula 2.27 from the book. This is far simpler and gives the same answer.
  203. validate(Date{year, month, 1}) or_return
  204. month_days := MONTH_DAYS
  205. day = month_days[month]
  206. if month == 2 && is_leap_year(year) {
  207. day += 1
  208. }
  209. return
  210. }
  211. /*
  212. Obtain the new year date of a given year.
  213. This procedure returns the January 1st date of the specified year. If the year
  214. is not valid, an error is returned.
  215. */
  216. new_year :: proc "contextless" (#any_int year: i64) -> (new_year: Date, err: Error) {
  217. validate(year, 1, 1) or_return
  218. return {year, 1, 1}, .None
  219. }
  220. /*
  221. Obtain the end year of a given date.
  222. This procedure returns the December 31st date of the specified year. If the year
  223. is not valid, an error is returned.
  224. */
  225. year_end :: proc "contextless" (#any_int year: i64) -> (year_end: Date, err: Error) {
  226. validate(year, 12, 31) or_return
  227. return {year, 12, 31}, .None
  228. }
  229. /*
  230. Obtain the range of dates for a given year.
  231. This procedure returns dates, for every day of a given year in a slice.
  232. */
  233. year_range :: proc (#any_int year: i64, allocator := context.allocator) -> (range: []Date) {
  234. is_leap := is_leap_year(year)
  235. days := 366 if is_leap else 365
  236. range = make([]Date, days, allocator)
  237. month_days := MONTH_DAYS
  238. if is_leap {
  239. month_days[2] = 29
  240. }
  241. i := 0
  242. for month in 1..=len(month_days) {
  243. for day in 1..=month_days[month] {
  244. range[i], _ = components_to_date(year, month, day)
  245. i += 1
  246. }
  247. }
  248. return
  249. }
  250. /*
  251. Normalize the delta.
  252. This procedure normalizes the delta in such a way that the number of seconds
  253. is between 0 and the number of seconds in the day and nanoseconds is between
  254. 0 and 10^9.
  255. If the value for `days` overflows during this operation, an error is returned.
  256. */
  257. normalize_delta :: proc "contextless" (delta: Delta) -> (normalized: Delta, err: Error) {
  258. // Distribute nanos into seconds and remainder
  259. seconds, nanos := divmod(delta.nanos, 1e9)
  260. // Add original seconds to rolled over seconds.
  261. seconds += delta.seconds
  262. days: i64
  263. // Distribute seconds into number of days and remaining seconds.
  264. days, seconds = divmod(seconds, 24 * 3600)
  265. // Add original days
  266. days += delta.days
  267. if days <= MIN_ORD || days >= MAX_ORD {
  268. return {}, .Invalid_Delta
  269. }
  270. return Delta{days, seconds, nanos}, .None
  271. }
  272. // The following procedures don't check whether their inputs are in a valid range.
  273. // They're still exported for those who know their inputs have been validated.
  274. /*
  275. Obtain an ordinal from a date.
  276. This procedure converts a date into an ordinal. If the date is not a valid date,
  277. the result is unspecified.
  278. */
  279. unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal) {
  280. year_minus_one := date.year - 1
  281. ordinal = 0
  282. // Add non-leap days
  283. ordinal += 365 * year_minus_one
  284. // Add leap days
  285. ordinal += floor_div(year_minus_one, 4) // Julian-rule leap days
  286. ordinal -= floor_div(year_minus_one, 100) // Prior century years
  287. ordinal += floor_div(year_minus_one, 400) // Prior 400-multiple years
  288. ordinal += floor_div(367 * i64(date.month) - 362, 12) // Prior days this year
  289. // Apply correction
  290. if date.month <= 2 {
  291. ordinal += 0
  292. } else if is_leap_year(date.year) {
  293. ordinal -= 1
  294. } else {
  295. ordinal -= 2
  296. }
  297. // Add days
  298. ordinal += i64(date.day)
  299. return
  300. }
  301. /*
  302. Obtain a year and a day of the year from an ordinal.
  303. This procedure returns the year and the day of the year of a given ordinal.
  304. Of the ordinal is outside of its valid range, the result is unspecified.
  305. */
  306. unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, day_ordinal: i64) {
  307. // Correct for leap year cycle starting at day 1.
  308. d0 := ordinal - 1
  309. // Number of 400-year cycles and remainder
  310. n400, d1 := divmod(d0, 365*400 + 100 - 3)
  311. // Number of 100-year cycles and remainder
  312. n100, d2 := divmod(d1, 365*100 + 25 - 1)
  313. // Number of 4-year cycles and remainder
  314. n4, d3 := divmod(d2, 365*4 + 1)
  315. // Number of remaining days
  316. n1, d4 := divmod(d3, 365)
  317. year = 400 * n400 + 100 * n100 + 4 * n4 + n1
  318. if n1 != 4 && n100 != 4 {
  319. day_ordinal = d4 + 1
  320. } else {
  321. day_ordinal = 366
  322. }
  323. if n100 == 4 || n1 == 4 {
  324. return year, day_ordinal
  325. }
  326. return year + 1, day_ordinal
  327. }
  328. /*
  329. Obtain a date from an ordinal.
  330. This procedure converts an ordinal into a date. If the ordinal is outside of
  331. its valid range, the result is unspecified.
  332. */
  333. unsafe_ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date) {
  334. year, _ := unsafe_ordinal_to_year(ordinal)
  335. prior_days := ordinal - unsafe_date_to_ordinal(Date{year, 1, 1})
  336. correction := Ordinal(2)
  337. if ordinal < unsafe_date_to_ordinal(Date{year, 3, 1}) {
  338. correction = 0
  339. } else if is_leap_year(year) {
  340. correction = 1
  341. }
  342. month := i8(floor_div((12 * (prior_days + correction) + 373), 367))
  343. day := i8(ordinal - unsafe_date_to_ordinal(Date{year, month, 1}) + 1)
  344. return {year, month, day}
  345. }