#!/usr/bin/env mf # usage: git log --format='%aI' | mf average-commit-time.mf # # test: printf '2026-01-01T09:30:45+00:00\n2026-01-02T13:20:30-04:00\n' | mf average-commit-time.mf # test: printf '2026-01-01T09:01:30+00:00\n2026-01-02T10:01:30+00:00\n' | mf average-commit-time.mf let datetimes = input().trim().split("\n"); #println($"input datetimes: {datetimes}"); # Transforms an ISO 8601 date and time with offset: # # YYYY-MM-DDTHH:MM:SS+XH:YM # # into a normalized UTC time with hours, minutes, and seconds in the form: # # {.h = HH - XH, .m = MM - YM, .s = SS} let iso8601_into_hms = function(s) { # YYYY-MM-DDTHH:MM:SS+XH:YM -> HH+XH MM+YM SS if s =~ r`\d{4}-\d{2}-\d{2}T(\d{2}):(\d{2}):(\d{2})([+-])(\d{2}):(\d{2})` { return { .h = number::init($1) - number::init($"{$4}{$5}"), .m = number::init($2) - number::init($"{$4}{$6}"), .s = number::init($3), }; } error $`expected ISO 8601 date and time with offset, received {s}`; }; let datetimes_hms = datetimes.map(iso8601_into_hms); #println($"datetimes (UTC in HMS): {datetimes_hms}"); let SECONDS_PER_MINUTE = 60; let SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE; let SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR; # Transforms a UTC time specified in hours, minutes, and seconds in the form: # # {.h = HH, .m = MM, .s = SS} # # into the equivalent time of day as the number of seconds since midnight UTC. let hms_into_seconds = function(hms) { return hms.h * SECONDS_PER_HOUR + hms.m * SECONDS_PER_MINUTE + hms.s; }; let datetimes_sec = datetimes_hms.map(hms_into_seconds); #println($"datetimes (seconds since UTC midnight): {datetimes_sec}"); # Transforms a time of day specified by the number of seconds since midnight # into radians where 0 and 2π radians are equivalent to midnight. let seconds_into_radians = function(sec) { # y radians = 2π radians 1 day x seconds # ---------- * ----------------------- * # 1 day SECONDS_PER_DAY seconds return (2 * math::pi) / SECONDS_PER_DAY * sec; }; let datetimes_rad = datetimes_sec.map(seconds_into_radians); #println($"datetimes (radians): {datetimes_rad}"); # Transforms an angle in radians into x and y Cartesian coordinates. let radians_into_cartesian = function(rad) { return { .x = math::cos(rad), .y = math::sin(rad), }; }; let datetimes_xy = datetimes_rad.map(radians_into_cartesian); #println($"datetimes (Cartesian coordinates): {datetimes_xy}"); let sum = {.x = 0, .y = 0}; for point in datetimes_xy { sum.x = sum.x + point.x; sum.y = sum.y + point.y; } let mean_rad = math::atan2(sum.y, sum.x); #println($"mean datetime (radians): {mean_rad}"); # Transforms a time of day specified in radians with 0 and 2π radians denoting # midnight into the number of seconds since midnight. let radians_into_seconds = function(rad) { # y seconds = SECONDS_PER_DAY seconds 1 day x radians # ----------------------- * ---------- * # 1 day 2π radians let raw = SECONDS_PER_DAY / (2 * math::pi) * rad; return ((raw % SECONDS_PER_DAY) + SECONDS_PER_DAY) % SECONDS_PER_DAY; }; let mean_sec = radians_into_seconds(mean_rad); #println($"mean datetime (seconds): {mean_sec}"); # Transforms the time of day as the number of seconds since midnight UTC into a # UTC time specified in hours, minutes, and seconds in the form: # # {.h = HH, .m = MM, .s = SS} let seconds_into_hms = function(sec) { let h = math::floor(sec / SECONDS_PER_HOUR); let m = math::floor((sec % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE); let s = math::floor(sec % SECONDS_PER_MINUTE); return {.h = h, .m = m, .s = s}; }; let mean_hms = seconds_into_hms(mean_sec); #println($"mean datetime (HMS): {mean_hms}"); println($"{mean_hms.h.format("02d")}:{mean_hms.m.format("02d")}:{mean_hms.s.format("02d")} UTC");