[OCaml] High Intensity Training Online
1
type error = Malformed of string
2
3
let pp_error ppf = function
4
| Malformed detail -> Format.fprintf ppf "malformed timestamp: %s" detail
5
6
let encode timestamp =
7
let time =
8
Unix.gmtime (float_of_int (Recovery.timestamp_to_unix_seconds timestamp))
9
in
10
Printf.sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ" (time.Unix.tm_year + 1900)
11
(time.Unix.tm_mon + 1) time.Unix.tm_mday time.Unix.tm_hour time.Unix.tm_min
12
time.Unix.tm_sec
13
14
let digit text index =
15
let value = Char.code text.[index] - Char.code '0' in
16
if value >= 0 && value <= 9 then Some value else None
17
18
let number text start length =
19
let rec loop index value =
20
if index = start + length then Some value
21
else
22
match digit text index with
23
| None -> None
24
| Some value' -> loop (index + 1) ((value * 10) + value')
25
in
26
loop start 0
27
28
(* Return the number of days from 1970-01-01 for a Gregorian date. *)
29
let days_from_civil year month day =
30
let year = year - if month <= 2 then 1 else 0 in
31
let era = if year >= 0 then year / 400 else (year - 399) / 400 in
32
let year_of_era = year - (era * 400) in
33
let month = month + if month > 2 then -3 else 9 in
34
let day_of_year = (((153 * month) + 2) / 5) + day - 1 in
35
let day_of_era =
36
(year_of_era * 365) + (year_of_era / 4) - (year_of_era / 100) + day_of_year
37
in
38
(era * 146097) + day_of_era - 719468
39
40
let decode text =
41
let malformed detail = Error (Malformed detail) in
42
if String.length text <> 20 then malformed "expected YYYY-MM-DDTHH:MM:SSZ"
43
else if
44
text.[4] <> '-'
45
|| text.[7] <> '-'
46
|| text.[10] <> 'T'
47
|| text.[13] <> ':'
48
|| text.[16] <> ':'
49
|| text.[19] <> 'Z'
50
then malformed "expected YYYY-MM-DDTHH:MM:SSZ"
51
else
52
match
53
( number text 0 4,
54
number text 5 2,
55
number text 8 2,
56
number text 11 2,
57
number text 14 2,
58
number text 17 2 )
59
with
60
| Some year, Some month, Some day, Some hour, Some minute, Some second ->
61
if
62
month < 1 || month > 12 || day < 1 || day > 31 || hour > 23
63
|| minute > 59 || second > 59
64
then malformed "timestamp component out of range"
65
else
66
let seconds =
67
(days_from_civil year month day * 86_400)
68
+ (hour * 3_600) + (minute * 60) + second
69
in
70
let timestamp = Recovery.timestamp_of_unix_seconds seconds in
71
if String.equal (encode timestamp) text then Ok timestamp
72
else malformed "invalid calendar date"
73
| _ -> malformed "timestamp contains a non-digit"
74