aboutsummaryrefslogtreecommitdiff
path: root/lib/kernel/date.ml
blob: 525b35b031a7fd5d2507c816980f0939db087196 (plain) (blame)
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
type weekday = Sun | Mon | Tue | Wed | Thu | Fri | Sat

(* Internal representation: rata die = days since 1970-01-01 (proleptic
   Gregorian). Howard Hinnant's civil<->days algorithm; OCaml's `/` truncates
   toward zero, which the negative-branch adjustments account for. *)
type t = int

let days_from_civil y m d =
  let y = if m <= 2 then y - 1 else y in
  let era = (if y >= 0 then y else y - 399) / 400 in
  let yoe = y - (era * 400) in
  let doy = ((153 * (if m > 2 then m - 3 else m + 9)) + 2) / 5 + (d - 1) in
  let doe = (yoe * 365) + (yoe / 4) - (yoe / 100) + doy in
  (era * 146097) + doe - 719468

let civil_from_days z =
  let z = z + 719468 in
  let era = (if z >= 0 then z else z - 146096) / 146097 in
  let doe = z - (era * 146097) in
  let yoe = (doe - (doe / 1460) + (doe / 36524) - (doe / 146096)) / 365 in
  let y = yoe + (era * 400) in
  let doy = doe - ((365 * yoe) + (yoe / 4) - (yoe / 100)) in
  let mp = ((5 * doy) + 2) / 153 in
  let d = doy - (((153 * mp) + 2) / 5) + 1 in
  let m = if mp < 10 then mp + 3 else mp - 9 in
  ((if m <= 2 then y + 1 else y), m, d)

let year t = let y, _, _ = civil_from_days t in y
let month t = let _, m, _ = civil_from_days t in m
let day t = let _, _, d = civil_from_days t in d

let is_leap y = (y mod 4 = 0 && y mod 100 <> 0) || y mod 400 = 0

let days_in_month y m =
  match m with
  | 1 | 3 | 5 | 7 | 8 | 10 | 12 -> 31
  | 4 | 6 | 9 | 11 -> 30
  | 2 -> if is_leap y then 29 else 28
  | _ -> 0

let make ~year ~month ~day =
  if year < 1583 || year > 9999 then
    Error (Printf.sprintf "year %d out of range 1583..9999" year)
  else if month < 1 || month > 12 then
    Error (Printf.sprintf "month %d out of range 1..12" month)
  else if day < 1 || day > days_in_month year month then
    Error (Printf.sprintf "day %d out of range for %04d-%02d" day year month)
  else Ok (days_from_civil year month day)

let to_rata t = t
let of_rata z = z
let add_days t n = t + n
let compare = Int.compare

let weekday t =
  (* rata 0 = 1970-01-01 = Thursday (index 4 with Sun=0). *)
  let w = (((t + 4) mod 7) + 7) mod 7 in
  [| Sun; Mon; Tue; Wed; Thu; Fri; Sat |].(w)