let is_leap year = (year mod 4 = 0 && year mod 100 <> 0) || year mod 400 = 0 let days_in_month ~year ~month = match month with | 1 | 3 | 5 | 7 | 8 | 10 | 12 -> 31 | 4 | 6 | 9 | 11 -> 30 | 2 -> if is_leap year then 29 else 28 | _ -> invalid_arg "Calendar.days_in_month: month" let validate_date ~year ~month ~day = if month < 1 || month > 12 then invalid_arg "Calendar.date: month"; if day < 1 || day > days_in_month ~year ~month then invalid_arg "Calendar.date: day" let days_since_epoch ~year ~month ~day = validate_date ~year ~month ~day; let year = if month <= 2 then year - 1 else year in let era = (if year >= 0 then year else year - 399) / 400 in let year_of_era = year - (era * 400) in let day_of_year = (((153 * if month > 2 then month - 3 else month + 9) + 2) / 5) + day - 1 in let day_of_era = (year_of_era * 365) + (year_of_era / 4) - (year_of_era / 100) + day_of_year in (era * 146097) + day_of_era - 719468 (* The weekday of a given UTC date, 0 = Monday .. 6 = Sunday. This uses Howard Hinnant's day-count from the epoch. The count needs no timezone-aware libc call, because [timegm] is not in the stdlib. *) let monday_first_weekday ~year ~month ~day = let days = days_since_epoch ~year ~month ~day in (* 1970-01-01 is a Thursday = 3 in Monday-first indexing. *) ((((days mod 7) + 3) mod 7) + 7) mod 7 let unix_seconds_of_date ~year ~month ~day = days_since_epoch ~year ~month ~day * 86_400