My personal data management layer
at main 96 lines 3.1 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6(** Common types and Jsont codecs for Bushel *) 7 8(** {1 Date Types} *) 9 10type date = Ptime.date 11(** A calendar date (year, month, day). *) 12 13(** {1 Jsont Codecs} *) 14 15let ptime_date_jsont : Ptime.date Jsont.t = 16 let dec s = 17 try 18 match String.split_on_char '-' s with 19 | [y; m; d] -> 20 let year = int_of_string y in 21 let month = int_of_string m in 22 let day = int_of_string d in 23 Ok (year, month, day) 24 | _ -> 25 Error (Printf.sprintf "Invalid date format: %s (expected YYYY-MM-DD)" s) 26 with _ -> 27 Error (Printf.sprintf "Invalid date: %s" s) 28 in 29 let enc (y, m, d) = Printf.sprintf "%04d-%02d-%02d" y m d in 30 Jsont.of_of_string ~kind:"Ptime.date" dec ~enc 31 32let ptime_jsont : Ptime.t Jsont.t = 33 let dec s = 34 (* Try RFC3339 first *) 35 match Ptime.of_rfc3339 s with 36 | Ok (t, _, _) -> Ok t 37 | Error _ -> 38 (* Try date-only format *) 39 try 40 match String.split_on_char '-' s with 41 | [y; m; d] -> 42 let year = int_of_string y in 43 let month = int_of_string m in 44 let day = int_of_string d in 45 (match Ptime.of_date (year, month, day) with 46 | Some t -> Ok t 47 | None -> Error (Printf.sprintf "Invalid date: %s" s)) 48 | _ -> 49 Error (Printf.sprintf "Invalid timestamp: %s" s) 50 with _ -> 51 Error (Printf.sprintf "Invalid timestamp: %s" s) 52 in 53 let enc t = 54 let (y, m, d), ((hh, mm, ss), _) = Ptime.to_date_time t in 55 if hh = 0 && mm = 0 && ss = 0 then 56 Printf.sprintf "%04d-%02d-%02d" y m d 57 else 58 Printf.sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ" y m d hh mm ss 59 in 60 Jsont.of_of_string ~kind:"Ptime.t" dec ~enc 61 62let ptime_option_jsont : Ptime.t option Jsont.t = 63 let null = Jsont.null None in 64 let some = Jsont.map ~dec:(fun t -> Some t) ~enc:(function Some t -> t | None -> assert false) ptime_jsont in 65 Jsont.any ~dec_null:null ~dec_string:some ~enc:(function None -> null | Some _ -> some) () 66 67let string_option_jsont : string option Jsont.t = 68 Jsont.option Jsont.string 69 70(** {1 Helper Functions} *) 71 72let ptime_of_date_exn date = 73 match Ptime.of_date date with 74 | Some t -> t 75 | None -> 76 let (y, m, d) = date in 77 failwith (Printf.sprintf "Invalid date: %04d-%02d-%02d" y m d) 78 79let date_of_ptime t = Ptime.to_date t 80 81let compare_dates (d1 : date) (d2 : date) = 82 let t1 = ptime_of_date_exn d1 in 83 let t2 = ptime_of_date_exn d2 in 84 Ptime.compare t1 t2 85 86let format_date (y, m, d) = 87 Printf.sprintf "%04d-%02d-%02d" y m d 88 89let month_name = function 90 | 1 -> "January" | 2 -> "February" | 3 -> "March" | 4 -> "April" 91 | 5 -> "May" | 6 -> "June" | 7 -> "July" | 8 -> "August" 92 | 9 -> "September" | 10 -> "October" | 11 -> "November" | 12 -> "December" 93 | _ -> "Unknown" 94 95let format_date_human (y, m, _d) = 96 Printf.sprintf "%s %d" (month_name m) y