Source: services/date.js

/**
 * Commonly-used functions for rendering badges containing a date
 *
 * @module
 */

import dayjs from 'dayjs'
import calendar from 'dayjs/plugin/calendar.js'
import customParseFormat from 'dayjs/plugin/customParseFormat.js'
import { colorScale } from './color-formatters.js'
import { InvalidResponse } from './index.js'

dayjs.extend(calendar)
dayjs.extend(customParseFormat)

/**
 * Parse and validate a string date into a dayjs object. Use this helper
 * in preference to invoking dayjs directly when parsing a date from string.
 *
 * @param {...any} args - Variadic: Arguments to pass through to dayjs
 * @returns {dayjs} - Parsed object
 * @throws {InvalidResponse} - Error if validation fails
 * @see https://day.js.org/docs/en/parse/string
 * @see https://day.js.org/docs/en/parse/string-format
 * @see https://day.js.org/docs/en/parse/is-valid
 * @example
 * parseDate('2024-01-01')
 * parseDate('31/01/2024', 'DD/MM/YYYY')
 * parseDate('2018 Enero 15', 'YYYY MMMM DD', 'es')
 */
function parseDate(...args) {
  let date
  if (args.length >= 2) {
    // always use strict mode if format arg is supplied
    date = dayjs(...args, true)
  } else {
    date = dayjs(...args)
  }
  if (!date.isValid()) {
    throw new InvalidResponse({ prettyMessage: 'invalid date' })
  }
  return date
}

/**
 * Returns a formatted date string without the year based on the value of input date param d.
 *
 * @param {Date | string | number | dayjs } d JS Date object, string, unix timestamp or dayjs object
 * @returns {string} Formatted date string
 */
function formatDate(d) {
  const date = parseDate(d)
  const dateString = date.calendar(null, {
    lastDay: '[yesterday]',
    sameDay: '[today]',
    lastWeek: '[last] dddd',
    sameElse: 'MMMM YYYY',
  })
  // Trim current year from date string
  return dateString.replace(` ${dayjs().year()}`, '').toLowerCase()
}

/**
 * Determines the color used for a badge according to the age.
 * Age is calculated as days elapsed till current date.
 * The color varies from bright green to red as the age increases
 * or the other way around if `reverse` is given `true`.
 *
 * @param {Date | string | number | dayjs } date JS Date object, string, unix timestamp or dayjs object
 * @param {boolean} reversed Reverse the color scale (the older, the better)
 * @returns {string} Badge color
 */
function age(date, reversed = false) {
  const colorByAge = colorScale([7, 30, 180, 365, 730], undefined, !reversed)
  const daysElapsed = dayjs().diff(parseDate(date), 'days')
  return colorByAge(daysElapsed)
}

/**
 * Creates a badge object that displays a date
 *
 * @param {Date | string | number | dayjs } date JS Date object, string, unix timestamp or dayjs object
 * @param {boolean} reversed Reverse the color scale (the older, the better)
 * @returns {object} A badge object that has two properties: message, and color
 */
function renderDateBadge(date, reversed = false) {
  const d = parseDate(date)
  const color = age(d, reversed)
  const message = formatDate(d)
  return { message, color }
}

/**
 * Returns a relative date from the input timestamp.
 * For example, day after tomorrow's timestamp will return 'in 2 days'.
 *
 * @param {number | string} timestamp - Unix timestamp
 * @returns {string} Relative date from the unix timestamp
 */
function formatRelativeDate(timestamp) {
  const parsedDate = dayjs.unix(parseInt(timestamp, 10))
  if (!parsedDate.isValid()) {
    return 'invalid date'
  }
  return dayjs().to(parsedDate).toLowerCase()
}

export { parseDate, renderDateBadge, formatDate, formatRelativeDate, age }