View raw

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