All files / app/utils date.ts

89.58% Statements 43/48
95.83% Branches 23/24
87.5% Functions 7/8
89.13% Lines 41/46

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 128175x   175x   3239x           16x 16x       775x     1525x 1525x       745x 1471x       726x 726x     24x       24x     175x 175x               1x 794x 5x         794x 5x         794x 5x         794x 17x         794x             793x 793x   793x     793x   772x 772x   772x         772x 751x         772x 752x         772x 727x           772x     793x                  
import { Duration as DurationPb } from "google-protobuf/google/protobuf/duration_pb";
import { Timestamp } from "google-protobuf/google/protobuf/timestamp_pb";
import { Temporal } from "temporal-polyfill";
 
export const UTC_TIMEZONE = "Etc/UTC";
 
/**
 * Computes the number of days/nights between two dates.
 * E.g. there's one day/night between 2020-01-01 and 2020-01-02.
 */
export function daysBetween(date1: Temporal.PlainDate, date2: Temporal.PlainDate): number {
  return date1.until(date2, { largestUnit: "days" }).days;
}
 
/** Converts a protobuf Timestamp to a Temporal.Instant value (timezone-agnostic). */
export function timestampToInstant(timestamp: Timestamp.AsObject): Temporal.Instant {
  // By protobuf, seconds and nanos should be integers.
  // Just in case, drop decimals otherwise BigInt will blow up.
  const nanos = BigInt(Math.floor(timestamp.nanos)) + BigInt(Math.floor(timestamp.seconds)) * 1_000_000_000n;
  return new Temporal.Instant(nanos);
}
 
/** Converts a Temporal Instant to a PlainDateTime in the browser's timezone. */
export function instantToPlainDateTime(instant: Temporal.Instant, timezone?: string): Temporal.PlainDateTime {
  return instant.toZonedDateTimeISO(timezone ?? Temporal.Now.timeZoneId()).toPlainDateTime();
}
 
/** Converts a protobuf Timestamp to a PlainDateTime in the browser's timezone. */
export function timestampToPlainDateTime(timestamp: Timestamp.AsObject, timezone?: string): Temporal.PlainDateTime {
  return instantToPlainDateTime(timestampToInstant(timestamp), timezone);
}
 
export function timestampToZonedDateTime(
  timestamp: Timestamp.AsObject,
  timezone: string | undefined,
): Temporal.ZonedDateTime {
  return timestampToInstant(timestamp).toZonedDateTimeISO(timezone ?? Temporal.Now.timeZoneId());
}
 
const APPROX_DAYS_PER_YEAR = 365;
const APPROX_DAYS_PER_MONTH = 30;
 
/**
 * Converts a duration which might have date units,
 * to an approximate equivalent which only has time units.
 * E.g. if the user asks to snooze something for 1 month (non-specific duration),
 * we need to convert that to an amount of time by approximating hours/month.
 */
export function approxTimeDuration(duration: Temporal.Duration): Temporal.Duration {
  if (duration.years != 0) {
    duration = duration.with({
      years: 0,
      days: duration.days + duration.years * APPROX_DAYS_PER_YEAR,
    });
  }
  if (duration.months != 0) {
    duration = duration.with({
      months: 0,
      days: duration.days + duration.months * APPROX_DAYS_PER_MONTH,
    });
  }
  if (duration.weeks != 0) {
    duration = duration.with({
      weeks: 0,
      days: duration.days + duration.weeks * 7,
    });
  }
  if (duration.days != 0) {
    duration = duration.with({
      days: 0,
      hours: duration.hours + duration.days * 24,
    });
  }
  return duration;
}
 
/**
 * Converts a duration which might have time and date units,
 * to an approximate equivalent that uses date units.
 */
export function approxDateDuration(duration: Temporal.Duration): Temporal.Duration {
  duration = approxTimeDuration(duration); // Remove date units
  // Spreads 90 seconds -> 1 minute + 30 seconds, etc. for time units
  duration = duration.round({ largestUnit: "hours" });
 
  // Approximate date units above 24 hours.
  if (duration.hours >= 24 || duration.hours <= -24) {
    // Manipulate the absolute value for modulo operations
    const isPositive = duration.sign >= 0;
    duration = duration.abs();
 
    duration = duration.with({
      days: Math.floor(duration.hours / 24),
      hours: duration.hours % 24,
    });
 
    if (duration.days >= APPROX_DAYS_PER_YEAR) {
      duration = duration.with({
        years: Math.floor(duration.days / APPROX_DAYS_PER_YEAR),
        days: duration.days % APPROX_DAYS_PER_YEAR,
      });
    }
    if (duration.days >= APPROX_DAYS_PER_MONTH) {
      duration = duration.with({
        months: Math.floor(duration.days / APPROX_DAYS_PER_MONTH),
        days: duration.days % APPROX_DAYS_PER_MONTH,
      });
    }
    if (duration.days >= 7) {
      duration = duration.with({
        weeks: Math.floor(duration.days / 7),
        days: duration.days % 7,
      });
    }
 
    if (!isPositive) duration = duration.negated();
  }
 
  return duration;
}
 
export function durationToProtobuf(duration: Temporal.Duration): DurationPb {
  const pb = new DurationPb();
  pb.setSeconds(Math.floor(duration.total("seconds")));
  pb.setNanos(duration.milliseconds * 1000 + duration.nanoseconds);
  return pb;
}