with import ; let # Date utilities date = rec { isLeapYear = year: if mod year 4 != 0 then false else if mod year 100 != 0 then true else if mod year 400 != 0 then false else true; daysForYear = year: if isLeapYear year then 366 else 365; daysForMonth = year: month: if month == 2 then if isLeapYear year then 29 else 28 else if elem month [4 6 9 11] then 30 else 31; }; in input: let parsed = builtins.match "([0-9]{4})-([0-9]{2})-([0-9]{2})" input; year = toInt (elemAt parsed 0); month = toInt (elemAt parsed 1); day = toInt (elemAt parsed 2); # To not have to recompute everything from 1970 to 2018, we just store the # precomputed epoch for 2018-01-01 fixedYear = { year = 2018; epoch = 1514764800; }; years = range fixedYear.year (year - 1); fullYearDays = foldl (acc: year: acc + date.daysForYear year) 0 years; months = range 1 (month - 1); fullMonthDays = foldl (acc: month: acc + date.daysForMonth year month) 0 months; days = fullYearDays + fullMonthDays + (day - 1); seconds = days * 24 * 60 * 60; result = fixedYear.epoch + seconds; error = if parsed == null then "${input} is not of the form YYYY-MM-DD" else if year < fixedYear.year then "Years below ${toString fixedYear.year} are not supported" else if month < 1 || month > 12 then "${toString month} is not a valid month" else if day < 1 || day > date.daysForMonth year month then "${toString day} is not a valid day, this month only has ${toString (daysForMonth year month)} days" else null; in if error == null then { success = true; value = result; } else { success = false; value = error; }