All files / shared/utils timeFormatter.ts

94.44% Statements 34/36
100% Branches 6/6
100% Functions 16/16
94.44% Lines 34/36

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103                                                      672x 53684x   672x         53533x   672x 3582x   672x 23942x   672x 642x   672x 1099x   672x 23990x   672x 6x   672x 254x         672x     672x         672x 9x       672x 6x   672x 1295x 1295x   1295x 1295x   1295x     672x     9x     672x         151x        
import { tz } from "@date-fns/tz";
import { format, formatDistance } from "date-fns";
import { Locale } from "shared/types/locale";
import { getCurrentLocale, localeFor } from "shared/utils/setLocale";
import { TIMEZONE } from "shared/utils/timezone";
 
// date-fns formatting is only called here to make sure all client times use the
// correct timezone and the active locale
 
// These throw on a time they cannot parse, deliberately. Program item times are
// validated where they enter the system - as an ISO datetime on ingest, and as a
// Date on the way out of the database, which rejects an invalid one - so a throw
// here means the data is already wrong. Rendering a placeholder instead would
// show an attendee a blank where a sign-up time belongs and let the bug that
// produced it go unnoticed, which is the silent-wrong-answer failure this module
// exists to avoid.
 
// No number: a bare number is a valid argument to date-fns format but is almost
// always a duration rather than an instant, and renders as a plausible wrong
// clock time (120 becomes "02:00") instead of failing
export type Time = Date | string;
 
// Every formatter takes the language to render in. The client never passes it
// by hand: a hook binds these to the active language, because it otherwise lives
// in module state that React cannot see, and a memoized weekday would keep
// rendering in the language it was first formatted in. Omitting it falls back to
// that module state, which is what non-rendering callers want.
const resolveLocale = (locale?: Locale): ReturnType<typeof getCurrentLocale> =>
  locale === undefined ? getCurrentLocale() : localeFor(locale);
 
const formatInEventTimezone = (
  time: Time,
  pattern: string,
  locale?: Locale,
): string =>
  format(time, pattern, { in: tz(TIMEZONE), locale: resolveLocale(locale) });
 
export const getWeekdayAndTime = (time: Time, locale?: Locale): string =>
  formatInEventTimezone(time, "cccc HH:mm", locale);
 
export const getDate = (time: Time, locale?: Locale): string =>
  formatInEventTimezone(time, "d.M.yyyy", locale);
 
export const getShortDate = (time: Time, locale?: Locale): string =>
  formatInEventTimezone(time, "ccc d.M.", locale);
 
export const getTime = (time: Time, locale?: Locale): string =>
  formatInEventTimezone(time, "HH:mm", locale);
 
export const getShortWeekdayAndTime = (time: Time, locale?: Locale): string =>
  formatInEventTimezone(time, "ccc HH:mm", locale);
 
export const getTimezone = (time: Time, locale?: Locale): string =>
  formatInEventTimezone(time, "zzz", locale);
 
export const getDateAndTime = (time: Time, locale?: Locale): string =>
  formatInEventTimezone(time, "ccc d.M.yyyy HH:mm", locale);
 
// Deliberately the viewer's own timezone rather than the event's: these answer
// "what time is it where you are", next to the Finnish time in the same text.
// Only the timezone is local - the weekday still follows the UI language.
export const getLocalDateAndTime = (time: Time, locale?: Locale): string =>
  format(time, "ccc d.M.yyyy HH:mm", { locale: resolveLocale(locale) });
 
export const getLocalTimezone = (time: Time, locale?: Locale): string =>
  format(time, "zzz", { locale: resolveLocale(locale) });
 
// For the generated statistics documents, which group rows by the event's wall
// clock. Numeric, so the active language never reaches the output.
export const getIsoDate = (time: Time): string =>
  formatInEventTimezone(time, "yyyy-MM-dd");
 
// Those documents are written in English whatever the UI language is, so this
// names the locale instead of taking the active one
export const getShortWeekdayInEnglish = (time: Time): string =>
  format(time, "ccc", { in: tz(TIMEZONE), locale: localeFor(Locale.EN) });
 
export const formatProgramItemDuration = (mins: number): string => {
  const hours = Math.floor(mins / 60);
  const minutes = mins % 60;
 
  const hoursStr = hours === 0 ? "" : `${hours} h`;
  const minutesStr = minutes === 0 ? "" : `${minutes} min`;
 
  return `${hoursStr} ${minutesStr}`;
};
 
export const formattedCurrentTime = (
  currentTime: Date,
  locale?: Locale,
): string => formatInEventTimezone(currentTime, "HH:mm:ss", locale);
 
// Describes `to` relative to `from`, e.g. "2 minutes ago" or "in an hour"
export const formatRelativeTime = (
  from: Date,
  to: Date,
  locale?: Locale,
): string =>
  formatDistance(to, from, {
    addSuffix: true,
    locale: resolveLocale(locale),
  });