with import ; let 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; secondsPerMinute = 60; secondsPerHour = secondsPerMinute * 60; secondsPerDay = 24 * secondsPerHour; in rec { dateTimeToEpoch = dateTime: let # To not have to recompute everything from 1970 to 2018, we just store the # precomputed epoch for 2018-01-01 fixedYear = { year = 1970; epoch = 0; }; yearDays = let go = year: days: if year >= dateTime.year then days else go (year + 1) (days + daysForYear year); in go 1970 0; monthDays = let go = month: days: if month >= dateTime.month then days else go (month + 1) (days + daysForMonth dateTime.year month); in go 1 yearDays; days = monthDays + (dateTime.day - 1); epoch = dateTime.second + 60 * (dateTime.minute + 60 * (dateTime.hour + 24 * days)); in epoch; epochToDateTime = epoch: let matched = builtins.match "([0-9]*)(\\.[0-9]*)?" (toString epoch); seconds = toInt (elemAt matched 0); ms = if elemAt matched 1 == null then 0 else builtins.fromJSON (elemAt matched 1); date = seconds / secondsPerDay; findYear = let go = year: days: let yearDays = daysForYear year; in if days < yearDays then { inherit year days; } else go (year + 1) (days - yearDays); in go 1970 date; findMonth = let go = month: days: let monthDays = daysForMonth findYear.year month; in if days < monthDays then { inherit month days; } else go (month + 1) (days - monthDays); in go 1 findYear.days; time = seconds - secondsPerDay * date; second = mod time 60; minute = mod (time / 60) 60; hour = time / 60 / 60; in { year = findYear.year; month = findMonth.month; day = findMonth.days + 1; inherit hour minute second; inherit ms; }; diffDateTime = a: b: let # move first date to XXXX-YY-01 00:00:00, read days, hours, minutes, seconds from second date a' = { year = a.year; month = a.month; day = 1; hour = 0; minute = 0; second = 0; }; shift = dateTimeToEpoch a - dateTimeToEpoch a'; b' = epochToDateTime (dateTimeToEpoch b - shift); months = (b'.year * 12 + b'.month) - (a'.year * 12 + a'.month); in { years = months / 12; months = mod months 12; days = b'.day - 1; hours = b'.hour; minutes = b'.minute; seconds = b'.second; }; prettyDuration = duration: let names = filter (name: duration."${name}s" > 0) [ "year" "month" "day" "hour" "minute" "second" ]; showName = name: let value = duration."${name}s"; in "${toString value} ${name}${optionalString (value != 1) "s"}"; in if names == [] then "0s" else concatMapStringsSep ", " showName names; parseDateTime = str: let matched = builtins.match "([0-9]+)-([0-9]+)-([0-9]+)( ([0-9]+):([0-9]+):(.+))?" str; hasTime = elemAt matched 3 != null; in { year = toInt (elemAt matched 0); month = toInt (elemAt matched 1); day = toInt (elemAt matched 2); hour = if hasTime then toInt (elemAt matched 4) else 0; minute = if hasTime then toInt (elemAt matched 5) else 0; second = if hasTime then builtins.fromJSON (elemAt matched 6) else 0; }; prettyDateTime = dateTime: let twoDigits = value: optionalString (value < 10) "0" + toString value; date = "${toString dateTime.year}-${twoDigits dateTime.month}-${twoDigits dateTime.day}"; time = "${twoDigits dateTime.hour}:${twoDigits dateTime.minute}:${twoDigits dateTime.second}"; ms = optionalString (dateTime.ms > 0) ".${toString dateTime.ms}"; in "${date} ${time}${ms} UTC"; }