Repository: iamkun/dayjs (JavaScript)

Issue:
ES6 Module Support?
I am trying to use this library in a polymer 3 typescript project.  Would it be possible to add support for es6 modules?

Code
```javascript
import dayjs from 'dayjs';
```

Result
```
my-app.ts:73 One or more components failed to load SyntaxError: The requested module '../node_modules/dayjs/dayjs.min.js' does not provide an export named 'default'
```

Workaround
```
<script src="node_modules/dayjs/dayjs.min.js" async></script>
```


Hints:
try this `import * as dayjs from 'dayjs';`
When i import like that i receive an error 

```
One or more components failed to load TypeError: Cannot set property 'dayjs' of undefined
```

Is there something i have not configured properly with typescript perhaps?
Have you ever tried the `--allowSyntheticDefaultImports` option for `tsc`? I think with that option you may use the ES6 syntax.
Script tag workaround helps in browser, but I can't test (jest) my ES6 modules which use dayjs.
I vote for ES6 modules support! Isn't it a "must-have" feature nowdays, when browsers get native modue imports support? Especially for such nice and usefull tool like dayjs :)
:tada: This is included in version 1.8.1 :tada:

The release is available on:
- [npm package (@latest dist-tag)](https://www.npmjs.com/package/dayjs)
- [GitHub release](https://github.com/iamkun/dayjs/releases/tag/v1.8.1)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
One issue with --allowSyntheticDefaultImports is that, as library developers we can't rely on this to be set. We really need way to use this library without this flag.
Why is this issue closed? I still get this error when using 

import dayjs from '/node_modules/dayjs/dayjs.min.js';
like this I could use it in ES6 directly (without minification)

import dayjs from '../../node_modules/dayjs/esm/index.js'
import LocalizedFormat from '../../node_modules/dayjs/esm/plugin/localizedFormat/index.js'

@iamkun `browser` config in your `package.json` should be pointing to `esm` bundle like in [firebase package.json](https://github.com/firebase/firebase-js-sdk/blob/master/packages/app/package.json#L7) to fix this issue. Please take a look at it when you have some time.
Should we use esm in this way?
```
import dayjs from 'dayjs/esm/index.js'
```
@iamkun at least when using Angular, that would cause "deep import" warnings. If you fixed `package.json` we could just use ES6 module without doing these kind of deep imports.
Detailed discussion about this on firebase repository: https://github.com/firebase/firebase-js-sdk/issues/3315#issuecomment-672750452
If we follow [NodeJS documentation](https://nodejs.org/api/packages.html#packages_dual_commonjs_es_module_packages) `module` config should point to ES6 module and `main` can point to commonJS module for backwards compatibility. I think it might be best if dayjs defined its modules the same way firebase does:
```json
  "main": "dayjs.min.js",
  "module": "esm/index.js",
  "browser": "esm/index.js"
```
@hakimio Thanks. You can find some related issue here https://github.com/iamkun/dayjs/issues/598
@iamkun if you can't change `module` because of #492 , maybe you could at least add `browser` config?
Cool, I'm not sure if `browser` has the same side effect or not, some more test is needed before adding. 
@iamkun [browser](https://docs.npmjs.com/cli/v6/configuring-npm/package-json#browser) config shouldn't have any effect on nodejs. It just might be important to test if it doesn't break TypeScript projects.
I try that:
```js
import * as dayjs from 'dayjs'
var someDate = dayjs(new Date()).add(28, 'day')
```
and I get `dayjs is not a function`
How come there is no sample of ES6 import in the docs?
@hiredgunhouse 
For some reason the way you import it changed: `import dayjs from 'dayjs';`:
https://stackblitz.com/edit/js-uccszw

Patch:
diff --git a/src/constant.js b/src/constant.js
--- a/src/constant.js
+++ b/src/constant.js
@@ -26,5 +26,5 @@ export const FORMAT_DEFAULT = 'YYYY-MM-DDTHH:mm:ssZ'
 export const INVALID_DATE_STRING = 'Invalid Date'
 
 // regex
-export const REGEX_PARSE = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[^0-9]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?.?(\d+)?$/
+export const REGEX_PARSE = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[^0-9]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?\.?(\d+)?$/
 export const REGEX_FORMAT = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g
diff --git a/src/locale/ar.js b/src/locale/ar.js
--- a/src/locale/ar.js
+++ b/src/locale/ar.js
@@ -2,6 +2,31 @@
 import dayjs from 'dayjs'
 
 const months = 'يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_')
+const symbolMap = {
+  1: '١',
+  2: '٢',
+  3: '٣',
+  4: '٤',
+  5: '٥',
+  6: '٦',
+  7: '٧',
+  8: '٨',
+  9: '٩',
+  0: '٠'
+}
+
+const numberMap = {
+  '١': '1',
+  '٢': '2',
+  '٣': '3',
+  '٤': '4',
+  '٥': '5',
+  '٦': '6',
+  '٧': '7',
+  '٨': '8',
+  '٩': '9',
+  '٠': '0'
+}
 
 const locale = {
   name: 'ar',
@@ -26,6 +51,19 @@ const locale = {
     y: 'عام واحد',
     yy: '%d أعوام'
   },
+  preparse(string) {
+    return string
+      .replace(
+        /[١٢٣٤٥٦٧٨٩٠]/g,
+        match => numberMap[match]
+      )
+      .replace(/،/g, ',')
+  },
+  postformat(string) {
+    return string
+      .replace(/\d/g, match => symbolMap[match])
+      .replace(/,/g, '،')
+  },
   ordinal: n => n,
   formats: {
     LT: 'HH:mm',
diff --git a/src/plugin/localeData/index.js b/src/plugin/localeData/index.js
--- a/src/plugin/localeData/index.js
+++ b/src/plugin/localeData/index.js
@@ -29,7 +29,8 @@ export default (o, c, dayjs) => { // locale needed later
       weekdaysShort: instance =>
         (instance ? instance.format('ddd') : getShort(this, 'weekdaysShort', 'weekdays', 3)),
       longDateFormat: format => getLongDateFormat(this.$locale(), format),
-      meridiem: this.$locale().meridiem
+      meridiem: this.$locale().meridiem,
+      ordinal: this.$locale().ordinal
     }
   }
   proto.localeData = function () {
@@ -46,7 +47,8 @@ export default (o, c, dayjs) => { // locale needed later
       months: () => dayjs.months(),
       monthsShort: () => dayjs.monthsShort(),
       longDateFormat: format => getLongDateFormat(localeObject, format),
-      meridiem: localeObject.meridiem
+      meridiem: localeObject.meridiem,
+      ordinal: localeObject.ordinal
     }
   }
 
diff --git a/src/plugin/preParsePostFormat/index.js b/src/plugin/preParsePostFormat/index.js
new file mode 100644
--- /dev/null
+++ b/src/plugin/preParsePostFormat/index.js
@@ -0,0 +1,47 @@
+// Plugin template from https://day.js.org/docs/en/plugin/plugin
+export default (option, dayjsClass) => {
+  const oldParse = dayjsClass.prototype.parse
+  dayjsClass.prototype.parse = function (cfg) {
+    if (typeof cfg.date === 'string') {
+      const locale = this.$locale()
+      cfg.date =
+        locale && locale.preparse ? locale.preparse(cfg.date) : cfg.date
+    }
+    // original parse result
+    return oldParse.bind(this)(cfg)
+  }
+
+  // // overriding existing API
+  // // e.g. extend dayjs().format()
+  const oldFormat = dayjsClass.prototype.format
+  dayjsClass.prototype.format = function (...args) {
+    // original format result
+    const result = oldFormat.call(this, ...args)
+    // return modified result
+    const locale = this.$locale()
+    return locale && locale.postformat ? locale.postformat(result) : result
+  }
+
+  const oldFromTo = dayjsClass.prototype.fromToBase
+
+  if (oldFromTo) {
+    dayjsClass.prototype.fromToBase = function (
+      input,
+      withoutSuffix,
+      instance,
+      isFrom
+    ) {
+      const locale = this.$locale() || instance.$locale()
+
+      // original format result
+      return oldFromTo.call(
+        this,
+        input,
+        withoutSuffix,
+        instance,
+        isFrom,
+        locale && locale.postformat
+      )
+    }
+  }
+}
diff --git a/src/plugin/relativeTime/index.js b/src/plugin/relativeTime/index.js
--- a/src/plugin/relativeTime/index.js
+++ b/src/plugin/relativeTime/index.js
@@ -19,7 +19,7 @@ export default (o, c, d) => {
     yy: '%d years'
   }
   d.en.relativeTime = relObj
-  const fromTo = (input, withoutSuffix, instance, isFrom) => {
+  proto.fromToBase = (input, withoutSuffix, instance, isFrom, postFormat) => {
     const loc = instance.$locale().relativeTime || relObj
     const T = o.thresholds || [
       { l: 's', r: 44, d: C.S },
@@ -46,11 +46,14 @@ export default (o, c, d) => {
           ? d(input).diff(instance, t.d, true)
           : instance.diff(input, t.d, true)
       }
-      const abs = (o.rounding || Math.round)(Math.abs(result))
+      let abs = (o.rounding || Math.round)(Math.abs(result))
       isFuture = result > 0
       if (abs <= t.r || !t.r) {
         if (abs <= 1 && i > 0) t = T[i - 1] // 1 minutes -> a minute, 0 seconds -> 0 second
         const format = loc[t.l]
+        if (postFormat) {
+          abs = postFormat(`${abs}`)
+        }
         if (typeof format === 'string') {
           out = format.replace('%d', abs)
         } else {
@@ -66,6 +69,11 @@ export default (o, c, d) => {
     }
     return pastOrFuture.replace('%s', out)
   }
+
+  function fromTo(input, withoutSuffix, instance, isFrom) {
+    return proto.fromToBase(input, withoutSuffix, instance, isFrom)
+  }
+
   proto.to = function (input, withoutSuffix) {
     return fromTo(input, withoutSuffix, this, true)
   }
diff --git a/types/index.d.ts b/types/index.d.ts
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -14,10 +14,14 @@ declare namespace dayjs {
   export type OptionType = { locale?: string, format?: string, utc?: boolean } | string | string[]
 
   type UnitTypeShort = 'd' | 'M' | 'y' | 'h' | 'm' | 's' | 'ms'
-  export type UnitType = 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'month' | 'year' | 'date' | UnitTypeShort;
 
-  export type OpUnitType = UnitType | "week" | 'w';
-  export type QUnitType = UnitType | "quarter" | 'Q';
+  type UnitTypeLong = 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'month' | 'year' | 'date'
+
+  type UnitTypeLongPlural = `${UnitTypeLong}s`
+  export type UnitType = UnitTypeLong | UnitTypeLongPlural | UnitTypeShort;
+
+  export type OpUnitType = UnitType | "week" | "weeks" | 'w';
+  export type QUnitType = UnitType | "quarter" | "quarters" | 'Q';
 
   class Dayjs {
     constructor (config?: ConfigType)
diff --git a/types/plugin/preParsePostFormat.d.ts b/types/plugin/preParsePostFormat.d.ts
new file mode 100644
--- /dev/null
+++ b/types/plugin/preParsePostFormat.d.ts
@@ -0,0 +1,4 @@
+import { PluginFunc } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin


Test patch:
diff --git a/test/locale/ar.test.js b/test/locale/ar.test.js
new file mode 100644
--- /dev/null
+++ b/test/locale/ar.test.js
@@ -0,0 +1,52 @@
+import moment from 'moment'
+import MockDate from 'mockdate'
+import dayjs from '../../src'
+import relativeTime from '../../src/plugin/relativeTime'
+import preParsePostFormat from '../../src/plugin/preParsePostFormat'
+import localeData from '../../src/plugin/localeData'
+import '../../src/locale/ar'
+
+dayjs.extend(localeData)
+dayjs.extend(relativeTime)
+dayjs.extend(preParsePostFormat)
+
+beforeEach(() => {
+  MockDate.set(new Date())
+})
+
+afterEach(() => {
+  MockDate.reset()
+})
+
+it('Format Month with locale function', () => {
+  for (let i = 0; i <= 7; i += 1) {
+    const dayjsAR = dayjs().locale('ar').add(i, 'day')
+    const momentAR = moment().locale('ar').add(i, 'day')
+    const testFormat1 = 'DD MMMM YYYY MMM'
+    const testFormat2 = 'MMMM'
+    const testFormat3 = 'MMM'
+    expect(dayjsAR.format(testFormat1)).toEqual(momentAR.format(testFormat1))
+    expect(dayjsAR.format(testFormat2)).toEqual(momentAR.format(testFormat2))
+    expect(dayjsAR.format(testFormat3)).toEqual(momentAR.format(testFormat3))
+  }
+})
+
+it('Preparse with locale function', () => {
+  for (let i = 0; i <= 7; i += 1) {
+    dayjs.locale('ar')
+    const momentAR = moment().locale('ar').add(i, 'day')
+    expect(dayjs(momentAR.format()).format()).toEqual(momentAR.format())
+  }
+})
+
+it('RelativeTime: Time from X gets formatted', () => {
+  const T = [
+    [44.4, 'second', 'منذ ثانية واحدة']
+  ]
+
+  T.forEach((t) => {
+    dayjs.locale('ar')
+    expect(dayjs().from(dayjs().add(t[0], t[1])))
+      .toBe(t[2])
+  })
+})
diff --git a/test/locale/ja.test.js b/test/locale/ja.test.js
new file mode 100644
--- /dev/null
+++ b/test/locale/ja.test.js
@@ -0,0 +1,45 @@
+import MockDate from 'mockdate'
+import moment from 'moment'
+import dayjs from '../../src'
+import relativeTime from '../../src/plugin/relativeTime'
+import '../../src/locale/ja'
+
+dayjs.extend(relativeTime)
+
+beforeEach(() => {
+  MockDate.set(new Date())
+})
+
+afterEach(() => {
+  MockDate.reset()
+})
+
+it('Finnish locale relative time in past and future', () => {
+  const cases = [
+    [1, 'd', '1日後'],
+    [-1, 'd', '1日前'],
+    [2, 'd', '2日後'],
+    [-2, 'd', '2日前'],
+    [10, 'd', '10日後'],
+    [-10, 'd', '10日前'],
+    [6, 'm', '6分後'],
+    [-6, 'm', '6分前'],
+    [5, 'h', '5時間後'],
+    [-5, 'h', '5時間前'],
+    [3, 'M', '3ヶ月後'],
+    [-3, 'M', '3ヶ月前'],
+    [4, 'y', '4年後'],
+    [-4, 'y', '4年前']
+  ]
+  cases.forEach((c) => {
+    expect(dayjs().add(c[0], c[1]).locale('ja').fromNow())
+      .toBe(c[2])
+    expect(dayjs().add(c[0], c[1]).locale('ja').fromNow())
+      .toBe(moment().add(c[0], c[1]).locale('ja').fromNow())
+  })
+  expect(dayjs().add(-10, 'd').locale('ja').fromNow(true))
+    .toBe('10日')
+  expect(dayjs().add(-10, 'd').locale('ja').fromNow(true))
+    .toBe(moment().add(-10, 'd').locale('ja').fromNow(true))
+})
+
diff --git a/test/parse.test.js b/test/parse.test.js
--- a/test/parse.test.js
+++ b/test/parse.test.js
@@ -148,4 +148,49 @@ describe('REGEX_PARSE', () => {
     expect(dayjs(date).valueOf()).toBe(moment(date).valueOf())
     expect(d.join('-')).toBe('2019-03-25T06:41:00.999999999-2019-03-25-06-41-00-999999999')
   })
+  it('20210102T012345', () => {
+    const date = '20210102T012345'
+    const d = date.match(REGEX_PARSE)
+    expect(dayjs(date).valueOf()).toBe(moment(date).valueOf())
+    expect(d.join('-')).toBe('20210102T012345-2021-01-02-01-23-45-')
+  })
+  it('2021-01-02T01:23', () => {
+    const date = '2021-01-02T01:23'
+    const d = date.match(REGEX_PARSE)
+    expect(dayjs(date).valueOf()).toBe(moment(date).valueOf())
+    expect(d.join('-')).toBe('2021-01-02T01:23-2021-01-02-01-23--')
+  })
+  it('2021-01-02T01:23:45', () => {
+    const date = '2021-01-02T01:23:45'
+    const d = date.match(REGEX_PARSE)
+    expect(dayjs(date).valueOf()).toBe(moment(date).valueOf())
+    expect(d.join('-')).toBe('2021-01-02T01:23:45-2021-01-02-01-23-45-')
+  })
+
+  it('2020-12-31T18:00:00.000-0500 (no regex match)', () => {
+    const date = '2020-12-31T18:00:00.000-0500'
+    const d = date.match(REGEX_PARSE)
+    expect(dayjs(date).valueOf()).toBe(moment(date).valueOf())
+    expect(d).toBe(null)
+  })
+
+  it('2020-12-31T18:00:00-05:00 (no regex match)', () => {
+    const date = '2020-12-31T18:00:00-05:00'
+    const d = date.match(REGEX_PARSE)
+    expect(dayjs(date).valueOf()).toBe(moment(date).valueOf())
+    expect(d).toBe(null)
+  })
+
+  it('2021-01-02T01:23:45-0500 (no regex match)', () => {
+    const date = '2021-01-02T01:23:45-0500'
+    const d = date.match(REGEX_PARSE)
+    expect(dayjs(date).valueOf()).toBe(moment(date).valueOf())
+    expect(d).toBe(null)
+  })
+  it('2021-01-02T01:23:45Z (no regex match)', () => {
+    const date = '2021-01-02T01:23:45Z'
+    const d = date.match(REGEX_PARSE)
+    expect(dayjs(date).valueOf()).toBe(moment(date).valueOf())
+    expect(d).toBe(null)
+  })
 })
diff --git a/test/plugin/localeData.test.js b/test/plugin/localeData.test.js
--- a/test/plugin/localeData.test.js
+++ b/test/plugin/localeData.test.js
@@ -110,3 +110,10 @@ it('meridiem', () => {
   expect(typeof dayjs().localeData().meridiem).toEqual('function')
   dayjs.locale('en')
 })
+
+it('ordinal', () => {
+  dayjs.locale('zh-cn')
+  expect(typeof dayjs.localeData().ordinal).toEqual('function')
+  expect(typeof dayjs().localeData().ordinal).toEqual('function')
+  dayjs.locale('en')
+})
diff --git a/test/plugin/preParsePostFormat.test.js b/test/plugin/preParsePostFormat.test.js
new file mode 100755
--- /dev/null
+++ b/test/plugin/preParsePostFormat.test.js
@@ -0,0 +1,169 @@
+import MockDate from 'mockdate'
+// import moment from 'moment'
+import dayjs from '../../src'
+import preParsePostFormat from '../../src/plugin/preParsePostFormat'
+import localeData from '../../src/plugin/localeData'
+import duration from '../../src/plugin/duration'
+import calendar from '../../src/plugin/calendar'
+import objectSupport from '../../src/plugin/objectSupport'
+import customParseFormat from '../../src/plugin/customParseFormat'
+import relativeTime from '../../src/plugin/relativeTime'
+import utc from '../../src/plugin/utc'
+import arraySupport from '../../src/plugin/arraySupport'
+import en from '../../src/locale/en'
+
+dayjs.extend(utc)
+dayjs.extend(localeData)
+dayjs.extend(customParseFormat)
+dayjs.extend(arraySupport)
+dayjs.extend(objectSupport)
+dayjs.extend(calendar)
+dayjs.extend(duration)
+dayjs.extend(relativeTime)
+dayjs.extend(preParsePostFormat)
+
+const symbolMap = {
+  1: '!',
+  2: '@',
+  3: '#',
+  4: '$',
+  5: '%',
+  6: '^',
+  7: '&',
+  8: '*',
+  9: '(',
+  0: ')'
+}
+const numberMap = {
+  '!': '1',
+  '@': '2',
+  '#': '3',
+  $: '4',
+  '%': '5',
+  '^': '6',
+  '&': '7',
+  '*': '8',
+  '(': '9',
+  ')': '0'
+}
+
+const localeCustomizations = {
+  ...en,
+  preparse(string) {
+    if (typeof string !== 'string') {
+      // console.error('preparse - Expected string, got', {
+      //   string
+      // })
+      throw new Error(`preparse - Expected string, got ${typeof string}`)
+    }
+    try {
+      const res = string.replace(/[!@#$%^&*()]/g, match => numberMap[match])
+      // console.log('Called custom preparse', { string, res })
+      return res
+    } catch (error) {
+      const errorMsg = `Unexpected error during preparse of '${string}' - ${error}`
+      // console.error(errorMsg)
+      throw new Error(errorMsg)
+    }
+  },
+  postformat(string) {
+    if (typeof string !== 'string') {
+      // console.error('postformat - Expected string, got', {
+      //   string
+      // })
+      throw new Error(`postformat - Expected string, got ${typeof string}`)
+    }
+    try {
+      const res = string.replace(/\d/g, match => symbolMap[match])
+      // console.log('Called custom postformat', { string, res })
+      return res
+    } catch (error) {
+      const errorMsg = `Unexpected error during postFormat of '${string}' - ${error}`
+      // console.error(errorMsg)
+      throw new Error(errorMsg)
+    }
+  }
+}
+
+beforeEach(() => {
+  MockDate.set(new Date())
+  dayjs.locale('symbol', localeCustomizations)
+})
+
+afterEach(() => {
+  MockDate.reset()
+  dayjs.locale('symbol', null)
+})
+
+describe('preparse and postformat', () => {
+  describe('transform', () => {
+    const TEST_DATE = '@)!@-)*-@&'
+    const TEST_NUM = 1346025600
+    it('preparse string + format', () =>
+      expect(dayjs.utc(TEST_DATE, 'YYYY-MM-DD').unix()).toBe(TEST_NUM))
+    it('preparse ISO8601 string', () =>
+      expect(dayjs.utc(TEST_DATE).unix()).toBe(TEST_NUM))
+    it('postformat', () =>
+      expect(dayjs
+        .unix(TEST_NUM)
+        .utc()
+        .format('YYYY-MM-DD'))
+        .toBe(TEST_DATE))
+  })
+
+  describe('transform from', () => {
+    dayjs.locale('symbol', localeCustomizations)
+    const start = dayjs([2007, 1, 28])
+
+    const t1 = dayjs([2007, 1, 28]).add({ s: 90 })
+    it('postformat should work on dayjs.fn.from', () =>
+      expect(start.from(t1, true)).toBe('@ minutes'))
+
+    const t2 = dayjs().add(6, 'd')
+    it('postformat should work on dayjs.fn.fromNow', () =>
+      expect(t2.fromNow(true)).toBe('^ days'))
+
+    it('postformat should work on dayjs.duration.fn.humanize', () =>
+      expect(dayjs.duration(10, 'h').humanize()).toBe('!) hours'))
+  })
+})
+
+describe('calendar day', () => {
+  const a = dayjs()
+    .hour(12)
+    .minute(0)
+    .second(0)
+
+  it('today at the same time', () =>
+    expect(dayjs(a).calendar()).toBe('Today at !@:)) PM'))
+
+  it('Now plus 25 min', () =>
+    expect(dayjs(a)
+      .add({ m: 25 })
+      .calendar())
+      .toBe('Today at !@:@% PM'))
+
+  it('Now plus 1 hour', () =>
+    expect(dayjs(a)
+      .add({ h: 1 })
+      .calendar())
+      .toBe('Today at !:)) PM'))
+
+  it('tomorrow at the same time', () =>
+    expect(dayjs(a)
+      .add({ d: 1 })
+      .calendar())
+      .toBe('Tomorrow at !@:)) PM'))
+
+  it('Now minus 1 hour', () =>
+    expect(dayjs(a)
+      .subtract({ h: 1 })
+      .calendar())
+      .toBe('Today at !!:)) AM'))
+
+  it('yesterday at the same time', () =>
+    expect(dayjs(a)
+      .subtract({ d: 1 })
+      .calendar())
+      .toBe('Yesterday at !@:)) PM'))
+})