date.ts
  1  import dayjs, { UnitType } from "dayjs"
  2  
  3  import dayjsDurationPlugin from "dayjs/plugin/duration"
  4  import dayjsAdvancedFormatPlugin from "dayjs/plugin/advancedFormat"
  5  import dayjsIsoWeekPlugin from "dayjs/plugin/isoWeek"
  6  import dayjsWeekYearPlugin from "dayjs/plugin/weekYear"
  7  import dayjsWeekOfYearPlugin from "dayjs/plugin/weekOfYear"
  8  import dayjsRelativeTimePlugin from "dayjs/plugin/relativeTime"
  9  import dayjsUtcPlugin from "dayjs/plugin/utc"
 10  import dayjsTimezonePlugin from "dayjs/plugin/timezone"
 11  
 12  dayjs.extend(dayjsDurationPlugin)
 13  dayjs.extend(dayjsAdvancedFormatPlugin)
 14  dayjs.extend(dayjsIsoWeekPlugin)
 15  dayjs.extend(dayjsWeekYearPlugin)
 16  dayjs.extend(dayjsWeekOfYearPlugin)
 17  dayjs.extend(dayjsRelativeTimePlugin)
 18  dayjs.extend(dayjsUtcPlugin)
 19  dayjs.extend(dayjsTimezonePlugin)
 20  
 21  /**
 22   * This file was largely taken from the helper-date package - we did this for two reasons:
 23   * 1. It made use of both moment of date.js - this caused some weird bugs with some relatively simple
 24   * syntax and didn't offer much in return.
 25   * 2. Replacing moment with dayjs helps massively reduce bundle size.
 26   * The original package can be found here:
 27   * https://github.com/helpers/helper-date
 28   */
 29  
 30  function isOptions(val: any) {
 31    return typeof val === "object" && typeof val.hash === "object"
 32  }
 33  
 34  function isApp(thisArg: any) {
 35    return (
 36      typeof thisArg === "object" &&
 37      typeof thisArg.options === "object" &&
 38      typeof thisArg.app === "object"
 39    )
 40  }
 41  
 42  function getContext(thisArg: any, locals: any, options: any) {
 43    if (isOptions(thisArg)) {
 44      return getContext({}, locals, thisArg)
 45    }
 46    // ensure args are in the correct order
 47    if (isOptions(locals)) {
 48      return getContext(thisArg, options, locals)
 49    }
 50    const appContext = isApp(thisArg) ? thisArg.context : {}
 51    options = options || {}
 52  
 53    // if "options" is not handlebars options, merge it onto locals
 54    if (!isOptions(options)) {
 55      locals = Object.assign({}, locals, options)
 56    }
 57    // merge handlebars root data onto locals if specified on the hash
 58    if (isOptions(options) && options.hash.root === true) {
 59      locals = Object.assign({}, options.data.root, locals)
 60    }
 61    let context = Object.assign({}, appContext, locals, options.hash)
 62    if (!isApp(thisArg)) {
 63      context = Object.assign({}, thisArg, context)
 64    }
 65    if (isApp(thisArg) && thisArg.view && thisArg.view.data) {
 66      context = Object.assign({}, context, thisArg.view.data)
 67    }
 68    return context
 69  }
 70  
 71  function initialConfig(str: any, pattern: any, options?: any) {
 72    if (isOptions(pattern)) {
 73      options = pattern
 74      pattern = DEFAULT_FORMAT
 75    }
 76  
 77    if (isOptions(str)) {
 78      options = str
 79      pattern = null
 80      str = null
 81    }
 82    return { str, pattern, options }
 83  }
 84  
 85  function setLocale(this: any, str: any, pattern: any, options?: any) {
 86    // if options is null then it'll get updated here
 87    const config = initialConfig(str, pattern, options)
 88    const defaults = { lang: "en", date: new Date(config.str) }
 89    // for now don't allow this to be configurable, don't pass in options
 90    const opts = getContext(this, defaults, {})
 91  
 92    // set the language to use
 93    dayjs.locale(opts.lang || opts.language)
 94  }
 95  
 96  const DEFAULT_FORMAT = "MMMM DD, YYYY"
 97  
 98  export const date = (str: any, pattern: any, options: any) => {
 99    const config = initialConfig(str, pattern, options)
100  
101    // if no args are passed, return a formatted date
102    if (config.str == null && config.pattern == null) {
103      dayjs.locale("en")
104      return dayjs().format(DEFAULT_FORMAT)
105    }
106  
107    setLocale(config.str, config.pattern, config.options)
108  
109    let date = dayjs(new Date(config.str))
110    if (typeof config.options === "string") {
111      date =
112        config.options.toLowerCase() === "utc"
113          ? date.utc()
114          : date.tz(config.options)
115    } else {
116      date = date.tz(dayjs.tz.guess())
117    }
118    if (config.pattern === "") {
119      return date.toISOString()
120    }
121    return date.format(config.pattern)
122  }
123  
124  export const duration = (str: any, pattern: any, format?: any) => {
125    const config = initialConfig(str, pattern)
126  
127    setLocale(config.str, config.pattern)
128  
129    const duration = dayjs.duration(config.str, config.pattern)
130    if (format && !isOptions(format)) {
131      return duration.format(format)
132    } else {
133      return duration.humanize()
134    }
135  }
136  
137  export const difference = (from: string, to: string, units?: UnitType) => {
138    const result = dayjs(new Date(from)).diff(dayjs(new Date(to)), units)
139    return result
140  }
141  
142  export const durationFromNow = (from: string) => {
143    const diff = difference(from, new Date().toISOString(), "ms")
144    return duration(diff, "ms")
145  }