Day.js is a lightweight JavaScript library that handles times and dates, keeping exactly the same API design as moment. js. If you’ve ever used moment.js, do you already know how to use day.js

This is the official description of Day.js. It is a small and elegant source code library with a size of only 2KB, which is ideal for beginners to read.

Before reading this article, please read the official documentation for a basic understanding of the library’s usage

The document address

This article focuses on parsing the main API source for Day.js. Readers who are interested in other parts of the API can read it on their own or communicate with me.

The factory function

dayjs('2018-08-08')
/ * {' $L ':' en ', '$d' : the 2018-08-07 T16:00:00, 000 z, '$x: {},' $y: 2018, '$M: 7,' $d ': 8,' $W: 3, '$H: 0,' $M: 0, '$s': 0, '$ms': 0 } */

dayjs() // Return the date of the day
/ * {' $L ':' en ', '$d' : the 2021-06-15 T07:24:27. 814 z, '$x: {},' $y: 2021, '$M: 5,' $d ': 15,' $W: 2, '$H: 15,' $M: 24, '$s': 27, '$ms': 814 } */
Copy the code

Dayjs creates instances through factory functions, rather than directly creating an instance, which is convenient for users to construct instances and for DayJS to extend. We know from the documentation that the DayJS function can pass in different types of data, and that plug-ins can be used to expand the types of data allowed to pass in.

And through the plug-in mechanism, DayJS can greatly reduce its code volume, only store the main function code, the rest of the plug-in extension.

Now look at the dayJS factory function:

const dayjs = function (date, c) {
  if (isDayjs(date)) {
    return date.clone()
  }
  // eslint-disable-next-line no-nested-ternary
  const cfg = typeof c === 'object' ? c : {}
  cfg.date = date
  cfg.args = arguments// eslint-disable-line prefer-rest-params
  return new Dayjs(cfg) // eslint-disable-line no-use-before-define
}

const isDayjs = d= > d instanceof Dayjs // eslint-disable-line no-use-before-define
Copy the code

Date is the data passed in by the user, while C is a custom setting. If date itself is a Dayjs instance, make a copy, otherwise a setup object is generated and passed to Dayjs to create a new instance.

clone() {
  return Utils.w(this.$d, this)}// Create a new date object
const wrapper = (date, instance) = >
  dayjs(date, {
    locale: instance.$L,
    utc: instance.$u,
    x: instance.$x,
    $offset: instance.$offset // todo: refactor; do not use this.$offset in you code
  })

const Utils = U // for plugin use
Utils.w = wrapper
Copy the code

The copy method is as simple as passing the Dayjs time attribute $d to the Dayjs factory function to construct a new object.

If we look at the documentation, in the parse section we see:

dayjs.extend(arraySupport)
dayjs([2010.1.14.15.25.50.125]); // February 14th, 3:25:50.125 PM
Copy the code

Take a look at the relevant source code:

dayjs.extend = (plugin, option) = > {
  if(! plugin.$i) {// install plugin only once
    plugin(option, Dayjs, dayjs)
    plugin.$i = true
  }
  return dayjs
}
Copy the code

The so-called extension is to see if the plug-in is installed, if not, call the plug-in, pass in the Dayjs class and Dayjs function.

Dayjs constructor

class Dayjs {
  constructor(cfg) {
    this.$L = parseLocale(cfg.locale, null.true)
    this.parse(cfg) // for plugin
  }

  parse(cfg) {
    this.$d = parseDate(cfg)
    this.$x = cfg.x || {}
    this.init()
  }

  init() {
    const { $d } = this
    this.$y = $d.getFullYear()
    this.$M = $d.getMonth()
    this.$D = $d.getDate()
    this.$W = $d.getDay()
    this.$H = $d.getHours()
    this.$m = $d.getMinutes()
    this.$s = $d.getSeconds()
    this.$ms = $d.getMilliseconds()
  }
}
Copy the code

Finally look at the Dayjs instance, first get the localization properties, then parse the passed parameters, the focus here is parseData, it will return a Date object, yes, the Dayjs library is the Date object encapsulation.

const parseDate = (cfg) = > {
  const { date, utc } = cfg
  if (date === null) return new Date(NaN) // null is invalid
  if (Utils.u(date)) return new Date(a)// today
  if (date instanceof Date) return new Date(date)
  if (typeof date === 'string'&&!/Z$/i.test(date)) {
    const d = date.match(C.REGEX_PARSE)
    if (d) {
      const m = d[2] - 1 || 0
      const ms = (d[7] | |'0').substring(0.3)
      if (utc) {
        return new Date(Date.UTC(d[1], m, d[3] | |1, d[4] | |0, d[5] | |0, d[6] | |0, ms))
      }
      return new Date(d[1], m, d[3] | |1, d[4] | |0, d[5] | |0, d[6] | |0, ms)
    }
  }

  return new Date(date) // everything else
}

// utils.js

const isUndefined = s= > s === undefined

export default {
  s: padStart,
  z: padZoneStr,
  m: monthDiff,
  a: absFloor,
  p: prettyUnit,
  u: isUndefined
}
Copy the code

According to the incoming different data types for processing, mainly depends on the situation of the incoming string, with regular expression to extract the string information, and finally use them to create a new Date object. So we have to look at the regular expression:

// constant.js

export const REGEX_PARSE = /^(\d{4})[-/]? (\ d {1, 2})? [-] /? (\ d {0, 2}) [^ 0-9] * (\ d {1, 2})? :? (\ d {1, 2})? :? (\ d {1, 2})? [. :]? (\d+)? $/
Copy the code

This regular expression is not too difficult. If you find it hard to read, you can check out the following resources for more debugging:

Front end advanced high salary must see – regular article

format

dayjs().format('{YYYY} MM-DDTHH:mm:ss SSS [Z] A') / / display

/*
{2021} 06-15T17:03:45 468 Z PM
*/
Copy the code

Formatting is another important DAYJS API, which is not too difficult to implement. It simply matches the incoming format with a regular expression, and if any keywords are found, replaces them with the relevant attributes of the DayJS object.

  format(formatStr) {
    const locale = this.$locale()

    if (!this.isValid()) return locale.invalidDate || C.INVALID_DATE_STRING

    const str = formatStr || C.FORMAT_DEFAULT // 'YYYY-MM-DDTHH:mm:ssZ'
    const zoneStr = Utils.z(this)
    const { $H, $m, $M } = this
    const {
      weekdays, months, meridiem
    } = locale
    const getShort = (arr, index, full, length) = > (
      (arr && (arr[index] || arr(this, str))) || full[index].substr(0, length)
    )
    const get$H = num= > (
      Utils.s($H % 12 || 12, num, '0'))const meridiemFunc = meridiem || ((hour, minute, isLowercase) = > {
      const m = (hour < 12 ? 'AM' : 'PM')
      return isLowercase ? m.toLowerCase() : m
    })

    const matches = {
      YY: String(this.$y).slice(-2),
      YYYY: this.$y,
      M: $M + 1.MM: Utils.s($M + 1.2.'0'),
      MMM: getShort(locale.monthsShort, $M, months, 3),
      MMMM: getShort(months, $M),
      D: this.$D,
      DD: Utils.s(this.$D, 2.'0'),
      d: String(this.$W),
      dd: getShort(locale.weekdaysMin, this.$W, weekdays, 2),
      ddd: getShort(locale.weekdaysShort, this.$W, weekdays, 3),
      dddd: weekdays[this.$W],
      H: String($H),
      HH: Utils.s($H, 2.'0'),
      h: get$H(1),
      hh: get$H(2),
      a: meridiemFunc($H, $m, true),
      A: meridiemFunc($H, $m, false),
      m: String($m),
      mm: Utils.s($m, 2.'0'),
      s: String(this.$s),
      ss: Utils.s(this.$s, 2.'0'),
      SSS: Utils.s(this.$ms, 3.'0'),
      Z: zoneStr // 'ZZ' logic below
    }

    // Replace with the date value or the original value
    return str.replace(C.REGEX_FORMAT, (match, $1) = > $1 || matches[match] || zoneStr.replace(':'.' ')) // 'ZZ'
  }
Copy the code

It looks like a lot of stuff, basically processing the Dayjs attributes so that they can replace the keyword of the formatStr string.

str.replace(C.REGEX_FORMAT, (match, $1) = > $1 || matches[match] || zoneStr.replace(':'.' ')) // 'ZZ'

// constant.js
export const REGEX_FORMAT = / \ [(+) [^ \]]] {1, 4} | | Y M {1, 2} {1, 4} | D | D | H {1, 4} {1, 2} {1, 2} | a | | H | M a {1, 2} {1, 2} | s | Z | SSS/g {1, 2}
Copy the code

The first argument to replace is a regular expression, the second is a function, the first argument is the matching string, and the second is the string captured by parentheses. So it’s not a normal string, it’s not a keyword, it’s a related attribute, it’s not a time region substitution.

getter/setter

Dayjs getters and setters are clever and worth a look:

dayjs().second(30).valueOf() // => new Date().setSeconds(30)
dayjs().second() // => new Date().getSeconds()
Copy the code

Passing an argument is a setter, not passing an argument is a getter. How does dayJS reload js?

import * as C from './constant'.constproto = Dayjs.prototype dayjs.prototype = proto; [['$ms', C.MS],
  ['$s', C.S],
  ['$m', C.MIN],
  ['$H', C.H],
  ['$W', C.D],
  ['$M', C.M],
  ['$y', C.Y],
  ['$D', C.DATE]
].forEach((g) = > {
  proto[g[1]] = function (input) {
    return this.$g(input, g[0], g[1])
  }
})

$g(input, get, set) {
  if (Utils.u(input)) return this[get]
  return this.set(set, input)
}

// constant.js
export const MS = 'millisecond'
export const S = 'second'
export const MIN = 'minute'
export const H = 'hour'
export const D = 'day'
export const W = 'week'
export const M = 'month'
export const Q = 'quarter'
export const Y = 'year'
export const DATE = 'date'
Copy the code

Dayjs is overloaded by corritization, encapsulating $g with a function that is a getter if an input is passed, a setter otherwise.

Take a look at the implementation of the set method:

set(string, int) {
  return this.clone().$set(string, int)
}
Copy the code

$set is a private method:

  $set(units, int) { // private set
    const unit = Utils.p(units) // First transform the property to be changed
    const utcPad = `setThe ${this.$u ? 'UTC' : ' '}`
    const name = {
      [C.D]: `${utcPad}Date`,
      [C.DATE]: `${utcPad}Date`,
      [C.M]: `${utcPad}Month`,
      [C.Y]: `${utcPad}FullYear`,
      [C.H]: `${utcPad}Hours`,
      [C.MIN]: `${utcPad}Minutes`,
      [C.S]: `${utcPad}Seconds`,
      [C.MS]: `${utcPad}Milliseconds`
    }[unit]
    // Process the date
    const arg = unit === C.D ? this.$D + (int - this.$W) : int

    if (unit === C.M || unit === C.Y) {
      // clone is for badMutable plugin
      const date = this.clone().set(C.DATE, 1)
      date.$d[name](arg)
      date.init()
      this.$d = date.set(C.DATE, Math.min(this.$D, date.daysInMonth())).$d
    } else if (name) this.$d[name](arg)

    this.init()
    return this
  }
Copy the code

The simple answer is to first process the parameters passed in, and then modify the $D attribute of the Dayjs instance, which is the Date instance.