diff --git a/src/MyPrayerJournal/Handlers.fs b/src/MyPrayerJournal/Handlers.fs index 7672e25..691ec5b 100644 --- a/src/MyPrayerJournal/Handlers.fs +++ b/src/MyPrayerJournal/Handlers.fs @@ -526,45 +526,45 @@ let routes = [ GET_HEAD [ route "/" Home.home ] subRoute "/components/" [ GET_HEAD [ - route "journal-items" Components.journalItems + route "journal-items" Components.journalItems // done routef "request/%s/add-notes" Components.addNotes routef "request/%s/item" Components.requestItem routef "request/%s/notes" Components.notes routef "request/%s/snooze" Components.snooze ] ] - GET_HEAD [ route "/docs" Home.docs ] - GET_HEAD [ route "/journal" Journal.journal ] + GET_HEAD [ route "/docs" Home.docs ] // done + GET_HEAD [ route "/journal" Journal.journal ] // done subRoute "/legal/" [ GET_HEAD [ - route "privacy-policy" Legal.privacyPolicy - route "terms-of-service" Legal.termsOfService + route "privacy-policy" Legal.privacyPolicy // done + route "terms-of-service" Legal.termsOfService // done ] ] subRoute "/request" [ GET_HEAD [ - routef "/%s/edit" Request.edit - routef "/%s/full" Request.getFull - route "s/active" Request.active - route "s/answered" Request.answered + routef "/%s/edit" Request.edit // done + routef "/%s/full" Request.getFull // done + route "s/active" Request.active // done + route "s/answered" Request.answered // done route "s/snoozed" Request.snoozed ] PATCH [ - route "" Request.update + route "" Request.update // done routef "/%s/cancel-snooze" Request.cancelSnooze - routef "/%s/prayed" Request.prayed + routef "/%s/prayed" Request.prayed // done routef "/%s/show" Request.show routef "/%s/snooze" Request.snooze ] POST [ - route "" Request.add + route "" Request.add // done routef "/%s/note" Request.addNote ] ] subRoute "/user/" [ GET_HEAD [ - route "log-off" User.logOff - route "log-on" User.logOn + route "log-off" User.logOff // done + route "log-on" User.logOn // done ] ] ] diff --git a/src/lib/Request.php b/src/lib/Request.php index 61c03e6..4ab7839 100644 --- a/src/lib/Request.php +++ b/src/lib/Request.php @@ -2,7 +2,6 @@ namespace MyPrayerJournal; -use DateTimeImmutable; use BitBadger\PDODocument\{Custom, DocumentException, DocumentList, Find, Mapper\DocumentMapper}; use Exception; use JsonSerializable; diff --git a/src/lib/UI.php b/src/lib/UI.php index 45df9c3..c8745a3 100644 --- a/src/lib/UI.php +++ b/src/lib/UI.php @@ -6,6 +6,7 @@ use BitBadger\PDODocument\DocumentException; use BitBadger\PDODocument\DocumentList; use DateTimeImmutable; use DateTimeZone; +use Exception; /** * User interface building blocks @@ -103,33 +104,69 @@ class UI foreach ($attrs as $key => $value) echo " $key=\"" . htmlspecialchars($value) . "\""; ?>>=$text?>getTimestamp(); - foreach (self::$periods as $period) { - if ($diff > $period[0]) continue; - $diff = floor($diff / $period[1]); - $value = $diff > 1 ? sprintf($period[2], $diff) : $period[3]; - $inZone = $parsed->setTimezone(new DateTimeZone($_SERVER['HTTP_X_TIME_ZONE'] ?? 'Etc/UTC')); - echo '' . $value . ''; - break; - } + $inZone = $parsed->setTimezone(new DateTimeZone($_SERVER['HTTP_X_TIME_ZONE'] ?? 'Etc/UTC')); + echo '' + . self::formatDistance('now', $parsed) . ''; + } + + // Many thanks to date-fns (https://date-fns.org) for this logic + /** + * Format the distance between two dates + * + * @param string|DateTimeImmutable $from The starting date/time + * @param string|DateTimeImmutable $to The ending date/time + * @return string The distance between two dates + * @throws Exception If date/time objects cannot be created + */ + public static function formatDistance(string|DateTimeImmutable $from, string|DateTimeImmutable $to): string + { + $aDay = 1_440.0; + $almost2Days = 2_520.0; + $aMonth = 43_200.0; + $twoMonths = 86_400.0; + + $dtFrom = is_string($from) ? new DateTimeImmutable($from) : $from; + $dtTo = is_string($to) ? new DateTimeImmutable($to) : $to; + $minutes = abs($dtFrom->getTimestamp() - $dtTo->getTimestamp()) / 60; + $months = round($minutes / $aMonth); + $years = round($months / 12); + + $typeAndNumber = match (true) { + $minutes < 1.0 => [FormatDistanceToken::LessThanXMinutes, 1], + $minutes < 45.0 => [FormatDistanceToken::XMinutes, round($minutes)], + $minutes < 90.0 => [FormatDistanceToken::AboutXHours, 1], + $minutes < $aDay => [FormatDistanceToken::AboutXHours, round($minutes / 60)], + $minutes < $almost2Days => [FormatDistanceToken::XDays, 1], + $minutes < $aMonth => [FormatDistanceToken::XDays, round($minutes / $aDay)], + $minutes < $twoMonths => [FormatDistanceToken::AboutXMonths, round($minutes / $aMonth)], + $months < 12 => [FormatDistanceToken::XMonths, round($minutes / $aMonth)], + $months % 12 < 3 => [FormatDistanceToken::AboutXYears, $years], + $months % 12 < 9 => [FormatDistanceToken::OverXYears, $years], + default => [FormatDistanceToken::AlmostXYears, $years] + }; + $format = match ($typeAndNumber[0]) { + FormatDistanceToken::LessThanXMinutes => ['less than a minute', 'less than %d minutes'], + FormatDistanceToken::XMinutes => ['a minute', '%d minutes'], + FormatDistanceToken::AboutXHours => ['about an hour', 'about %d hours'], + FormatDistanceToken::XHours => ['an hour', '%d hours'], + FormatDistanceToken::XDays => ['a day', '%d days'], + FormatDistanceToken::AboutXWeeks => ['about a week', 'about %d weeks'], + FormatDistanceToken::XWeeks => ['a week', '%d weeks'], + FormatDistanceToken::AboutXMonths => ['about a month', 'about %d months'], + FormatDistanceToken::XMonths => ['a month', '%d months'], + FormatDistanceToken::AboutXYears => ['about a year', 'about %d years'], + FormatDistanceToken::XYears => ['a year', '%d years'], + FormatDistanceToken::OverXYears => ['over a year', 'over %d years'], + FormatDistanceToken::AlmostXYears => ['almost a year', 'almost %d years'] + }; + $value = $typeAndNumber[1] == 1 ? $format[0] : sprintf($format[1], $typeAndNumber[1]); + return $dtFrom > $dtTo ? "$value ago" : "in $value"; } public static function requestList(DocumentList $reqs): void @@ -171,3 +208,20 @@ class UI