All files / app/utils date.ts

92.72% Statements 51/55
100% Branches 23/23
90% Functions 9/10
94% Lines 47/50

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 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148      27x 27x   27x             236x 27x                                           64x       123x 45x   123x 123x       59x               59x                   236x         236x 236x   236x 236x   236x 236x       27x               359x 1747x   359x 359x   29x 29x 29x             29x 29x 24x 24x 24x 20x   24x 9x     29x 23x 23x 23x 1x     29x 16x   29x       1231x       113x                 113x     1231x  
// format a date
import { Timestamp } from "google-protobuf/google/protobuf/timestamp_pb";
 
import daysjs, { Dayjs } from "./dayjs";
import { dayMillis } from "./timeAgo";
 
const numNights = (date1: string, date2: string) => {
  const diffTime = Date.parse(date1) - Date.parse(date2);
  const diffDays = Math.ceil(diffTime / dayMillis);
  return diffDays;
};
 
/// Allow call sites to explicitly reference the browser's timezone (clearer than "undefined").
export const BROWSER_TIMEZONE: unique symbol = Symbol("browser-timezone");
export const UTC_TIMEZONE: string = "Etc/UTC";
 
interface LocalizeDateTimeParams {
  /// The locale to localize in.
  locale: string;
  /// The timezone to be used to figure the date components (defaults to the browser's).
  timezone?: string | typeof BROWSER_TIMEZONE;
  /// Whether to include the date (defaults to true).
  includeDate?: boolean;
  /// If including the date, whether to include the day (defaults to true).
  includeDay?: boolean;
  /// If including the date, whether to include the day of week (defaults to false).
  includeDayOfWeek?: boolean;
  /// Whether to include the time (defaults to true).
  includeTime?: boolean;
  /// If including the time, whether to include seconds (defaults to false).
  includeSeconds?: boolean;
  /// Whether to abbreviate days of the week and month names (defaults to false).
  abbreviate?: boolean;
}
 
/// Localizes a date and time, optionally with the day of the week.
export function localizeDateTime(
  date: Date | Dayjs,
  args: LocalizeDateTimeParams,
): string {
  if (daysjs.isDayjs(date)) {
    date = date.toDate();
  }
  const format = getIntlDateTimeFormat(args);
  return format.format(date);
}
 
/// Localizes only the year and month of a date.
export function localizeYearMonth(
  date: Date | Dayjs,
  args: {
    timezone?: string | typeof BROWSER_TIMEZONE;
    locale: string;
    abbreviate?: boolean;
  },
): string {
  return localizeDateTime(date, {
    timezone: args.timezone,
    locale: args.locale,
    abbreviate: args.abbreviate,
    includeDay: false,
    includeTime: false,
  });
}
 
/// Localizes a range of date and times as a string.
export function localizeDateTimeRange(
  start: Date | Dayjs,
  end: Date | Dayjs,
  args: LocalizeDateTimeParams,
): string {
  if (daysjs.isDayjs(start)) {
    start = start.toDate();
  }
  if (daysjs.isDayjs(end)) {
    end = end.toDate();
  }
  const format = getIntlDateTimeFormat(args);
  return format.formatRange(start, end);
}
 
// Creating Intl.DateTimeFormat every time is 40x slower.
const intlDateTimeFormatCache = new Map<string, Intl.DateTimeFormat>();
 
/// Gets an Intl.DateTimeFormat based on params.
function getIntlDateTimeFormat(
  args: LocalizeDateTimeParams,
): Intl.DateTimeFormat {
  // We can't use args as the Map key as it uses reference equality.
  // Convert it to a json string. The Symbol type requires special handling.
  const cacheKey = JSON.stringify(args, (_, v) =>
    typeof v === "symbol" ? v.toString() : v,
  );
  const cached = intlDateTimeFormatCache.get(cacheKey);
  if (cached) return cached;
 
  const format = createIntlDateTimeFormat(args);
  intlDateTimeFormatCache.set(cacheKey, format);
  return format;
}
 
/// Creates a new Intl.DateTimeFormat object based on params.
function createIntlDateTimeFormat(
  args: LocalizeDateTimeParams,
): Intl.DateTimeFormat {
  const options: Intl.DateTimeFormatOptions = {};
  if (args.includeDate !== false) {
    options.year = "numeric";
    options.month = args.abbreviate ? "short" : "long";
    if (args.includeDay !== false) {
      options.day = "numeric";
    }
    if (args.includeDayOfWeek) {
      options.weekday = args.abbreviate ? "short" : "long";
    }
  }
  if (args.includeTime !== false) {
    options.hour = "numeric";
    options.minute = "numeric";
    if (args.includeSeconds) {
      options.second = "numeric";
    }
  }
  if (args.timezone && args.timezone !== BROWSER_TIMEZONE) {
    options.timeZone = args.timezone;
  }
  return Intl.DateTimeFormat(args.locale, options);
}
 
function timestamp2Date(timestamp: Timestamp.AsObject): Date {
  return new Date(Math.floor(timestamp.seconds * 1e3 + timestamp.nanos / 1e6));
}
 
function isSameDate(date1: Dayjs, date2: Dayjs): boolean {
  return (
    date1.month() === date2.month() &&
    date1.year() === date2.year() &&
    date1.date() === date2.date()
  );
}
 
/** Compares whether date1 is equal to or in the future of date2 */
function isSameOrFutureDate(date1: Dayjs, date2: Dayjs): boolean {
  return isSameDate(date1, date2) || date1.isAfter(date2);
}
 
export { isSameOrFutureDate, numNights, timestamp2Date };