teamdigitale/italia-ts-commons

View on GitHub
src/dates.ts

Summary

Maintainability
C
1 day
Test Coverage
import * as t from "io-ts";
import * as E from "fp-ts/lib/Either";
import { pipe } from "fp-ts/lib/function";
import { PatternString } from "./strings";

const isDate = (v: t.mixed): v is Date => v instanceof Date;

/**
 * Accepts short formats (ie. "2018-10-13")
 * with or without time and timezone parts.
 */
export const DateFromString = new t.Type<Date, string>(
  "DateFromString",
  isDate,
  (v, c) =>
    isDate(v)
      ? t.success(v)
      : pipe(
          t.string.validate(v, c),
          E.chain((s) => {
            const d = new Date(s);
            return isNaN(d.getTime()) ? t.failure(s, c) : t.success(d);
          })
        ),
  (a) => a.toISOString()
);

export type DateFromString = t.TypeOf<typeof DateFromString>;

export const patternDateFromString = (
  pattern: string,
  tagName: string
): t.Type<Date, string> =>
  new t.Type<Date, string>(
    tagName,
    isDate,
    (v, c) =>
      isDate(v)
        ? t.success(v)
        : pipe(
            PatternString(pattern).validate(v, c),
            E.chain((s) => {
              const d = new Date(s);
              return isNaN(d.getTime()) ? t.failure(s, c) : t.success(d);
            })
          ),
    (a) => a.toISOString()
  );

/**
 * ISO8601 format for dates.
 *
 */
/**
 * Date and time is separated with a capital T.
 * UTC time is defined with a capital letter Z.
 *
 */
const UTC_ISO8601_FULL_REGEX =
  "^\\d{4}-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d(\\.\\d+)?(Z)?$";
/**
 * Date and time is separated with a capital T.
 * Timezone is defined with +/-00:00 format.
 *
 */
const TIMEZONE_ISO8601_FULL_REGEX =
  "^\\d{4}-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d(\\.\\d+)?[+-](\\d{2})\\:(\\d{2})$";

/**
 * Accepts only full ISO8601 format with UTC (Z) timezone
 *
 * ie. "2018-10-13T00:00:00.000Z"
 */
export const UtcOnlyIsoDateFromString = patternDateFromString(
  UTC_ISO8601_FULL_REGEX,
  "UtcOnlyIsoDateFromString"
);
export type UtcOnlyIsoDateFromString = t.TypeOf<
  typeof UtcOnlyIsoDateFromString
>;

/**
 * Accepts only full ISO8601 format with +/-00:00 timezone
 *
 * ie. "2018-10-13T00:00:00.000+00:00"
 */
export const TimezoneOnlyIsoDateFromString = patternDateFromString(
  TIMEZONE_ISO8601_FULL_REGEX,
  "TimezoneOnlyIsoDateFromString"
);
export type TimezoneOnlyIsoDateFromString = t.TypeOf<
  typeof TimezoneOnlyIsoDateFromString
>;

export const IsoDateFromString = t.union(
  [UtcOnlyIsoDateFromString, TimezoneOnlyIsoDateFromString],
  "IsoDateFromString"
);
export type IsoDateFromString = t.TypeOf<typeof IsoDateFromString>;

/**
 * @deprecated use IsoDateFromString instead.
 */
export const UTCISODateFromString = IsoDateFromString;
/**
 * @deprecated use IsoDateFromString instead.
 */
export type UTCISODateFromString = t.TypeOf<typeof UTCISODateFromString>;

/**
 * Accepts only a valid timestamp format (number)
 *
 * ie. 1577836800000
 */
export const DateFromTimestamp = new t.Type<Date, number>(
  "DateFromTimestamp",
  isDate,
  (v, c) =>
    isDate(v)
      ? t.success(v)
      : pipe(
          t.number.validate(v, c),
          E.chain((s) => {
            const d = new Date(s);
            return isNaN(d.getTime()) ? t.failure(s, c) : t.success(d);
          })
        ),
  (a) => a.valueOf()
);

export type DateFromTimestamp = t.TypeOf<typeof DateFromTimestamp>;