Repository: iamkun/dayjs

Problem statement:
CustomParseFormat doesn't parse ordinals correctly when AdvancedFormat is enabled
**Describe the bug**
When using `AdvancedFormat` plugin that allows ordinals, the `CustomParseFormat` plugin doesn't parse dates that have ordinals. So:

```javascript
dayjs('7th March 2019', 'Do MMMM YYYY')
```

returns 

```javascript
{ [Number: NaN]
  '$L': 'en',
  '$u': undefined,
  '$d': Invalid Date,
  '$y': NaN,
  '$M': NaN,
  '$D': NaN,
  '$W': NaN,
  '$H': NaN,
  '$m': NaN,
  '$s': NaN,
  '$ms': NaN }
```

**Expected behavior**
When both `AdvancedFormat` and `CustomParseFormat` plugins are enabled

```javascript
dayjs('7th March 2019', 'Do MMMM YYYY')
```

should return a dayjs object, either with or without the ordinal defined in it:

```javascript
{ [Number: 1551916800000]
  '$L': 'en',
  '$u': undefined,
  '$d': '2019-03-07T00:00:00.000Z',
  '$y': 2019,
  '$M': 2,
  '$D': 7,
  '$o': 'th', // maybe?
  '$W': 4,
  '$H': 0,
  '$m': 0,
  '$s': 0,
  '$ms': 0 }
```

**Information**
 - Day.js version 1.8.9
 - OS: Windows Subsystem Linux using Node v9.11.2



Hints:

Patch:
diff --git a/src/index.js b/src/index.js
--- a/src/index.js
+++ b/src/index.js
@@ -109,36 +109,41 @@ class Dayjs {
     return this.endOf(units) < dayjs(that)
   }
 
-  year() {
-    return this.$y
+  $g(input, get, set) {
+    if (Utils.u(input)) return this[get]
+    return this.set(set, input)
   }
 
-  month() {
-    return this.$M
+  year(input) {
+    return this.$g(input, '$y', C.Y)
   }
 
-  day() {
-    return this.$W
+  month(input) {
+    return this.$g(input, '$M', C.M)
   }
 
-  date() {
-    return this.$D
+  day(input) {
+    return this.$g(input, '$W', C.D)
   }
 
-  hour() {
-    return this.$H
+  date(input) {
+    return this.$g(input, '$D', C.DATE)
   }
 
-  minute() {
-    return this.$m
+  hour(input) {
+    return this.$g(input, '$H', C.H)
   }
 
-  second() {
-    return this.$s
+  minute(input) {
+    return this.$g(input, '$m', C.MIN)
   }
 
-  millisecond() {
-    return this.$ms
+  second(input) {
+    return this.$g(input, '$s', C.S)
+  }
+
+  millisecond(input) {
+    return this.$g(input, '$ms', C.MS)
   }
 
   unix() {
@@ -227,7 +232,8 @@ class Dayjs {
     number = Number(number) // eslint-disable-line no-param-reassign
     const unit = Utils.p(units)
     const instanceFactory = (u, n) => {
-      const date = this.set(C.DATE, 1).set(u, n + number)
+      // clone is for badMutable plugin
+      const date = this.clone().set(C.DATE, 1).set(u, n + number)
       return date.set(C.DATE, Math.min(this.$D, date.daysInMonth()))
     }
     const instanceFactorySet = (n) => {
@@ -346,6 +352,7 @@ class Dayjs {
   }
 
   locale(preset, object) {
+    if (!preset) return this.$L
     const that = this.clone()
     that.$L = parseLocale(preset, object, true)
     return that
@@ -359,18 +366,6 @@ class Dayjs {
     return new Date(this.$d)
   }
 
-  toArray() {
-    return [
-      this.$y,
-      this.$M,
-      this.$D,
-      this.$H,
-      this.$m,
-      this.$s,
-      this.$ms
-    ]
-  }
-
   toJSON() {
     return this.toISOString()
   }
@@ -382,18 +377,6 @@ class Dayjs {
     return this.$d.toISOString()
   }
 
-  toObject() {
-    return {
-      years: this.$y,
-      months: this.$M,
-      date: this.$D,
-      hours: this.$H,
-      minutes: this.$m,
-      seconds: this.$s,
-      milliseconds: this.$ms
-    }
-  }
-
   toString() {
     return this.$d.toUTCString()
   }
diff --git a/src/locale/ne.js b/src/locale/ne.js
new file mode 100644
--- /dev/null
+++ b/src/locale/ne.js
@@ -0,0 +1,27 @@
+import dayjs from 'dayjs'
+
+const locale = {
+  name: 'ne',
+  weekdays: 'आइतबार_सोमबार_मङ्गलबार_बुधबार_बिहिबार_शुक्रबार_शनिबार'.split('_'),
+  months: 'जनवरी_फेब्रुवरी_मार्च_अप्रिल_मे_जुन_जुलाई_अगष्ट_सेप्टेम्बर_अक्टोबर_नोभेम्बर_डिसेम्बर'.split('_'),
+  relativeTime: {
+    future: '%s पछि',
+    past: '%s अघि',
+    s: 'सेकेन्ड',
+    m: 'एक मिनेट',
+    mm: '%d मिनेट',
+    h: 'घन्टा',
+    hh: '%d घन्टा',
+    d: 'एक दिन',
+    dd: '%d दिन',
+    M: 'एक महिना',
+    MM: '%d महिना',
+    y: 'एक वर्ष',
+    yy: '%d वर्ष'
+  },
+  ordinal: n => `${n}`.replace(/\d/g, i => '०१२३४५६७८९'[i])
+}
+
+dayjs.locale(locale, null, true)
+
+export default locale
diff --git a/src/locale/zh-cn.js b/src/locale/zh-cn.js
--- a/src/locale/zh-cn.js
+++ b/src/locale/zh-cn.js
@@ -7,7 +7,14 @@ const locale = {
   weekdaysMin: '日_一_二_三_四_五_六'.split('_'),
   months: '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'),
   monthsShort: '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),
-  ordinal: n => `${n}日`,
+  ordinal: (number, period) => {
+    switch (period) {
+      case 'W':
+        return `${number}周`
+      default:
+        return `${number}日`
+    }
+  },
   weekStart: 1,
   formats: {
     LT: 'HH:mm',
diff --git a/src/plugin/advancedFormat/index.js b/src/plugin/advancedFormat/index.js
--- a/src/plugin/advancedFormat/index.js
+++ b/src/plugin/advancedFormat/index.js
@@ -13,13 +13,16 @@ export default (o, c, d) => { // locale needed later
     const locale = this.$locale()
     const utils = this.$utils()
     const str = formatStr || FORMAT_DEFAULT
-    const result = str.replace(/Q|Do|X|x|k{1,2}|S/g, (match) => {
+    const result = str.replace(/Q|wo|gggg|Do|X|x|k{1,2}|S/g, (match) => {
       switch (match) {
         case 'Q':
           return Math.ceil((this.$M + 1) / 3)
-        case 'Do': {
+        case 'Do':
           return locale.ordinal(this.$D)
-        }
+        case 'gggg':
+          return this.weekYear()
+        case 'wo':
+          return locale.ordinal(this.week(), 'W') // W for week
         case 'k':
         case 'kk':
           return utils.s(String(this.$H === 0 ? 24 : this.$H), match === 'k' ? 1 : 2, '0')
diff --git a/src/plugin/badMutable/index.js b/src/plugin/badMutable/index.js
new file mode 100644
--- /dev/null
+++ b/src/plugin/badMutable/index.js
@@ -0,0 +1,53 @@
+export default (o, c) => { // locale needed later
+  const proto = c.prototype
+  proto.$g = function (input, get, set) {
+    if (this.$utils().u(input)) return this[get]
+    return this.$set(set, input)
+  }
+
+  proto.set = function (string, int) {
+    return this.$set(string, int)
+  }
+
+  const oldStartOf = proto.startOf
+  proto.startOf = function (units, startOf) {
+    this.$d = oldStartOf.bind(this)(units, startOf).toDate()
+    this.init()
+    return this
+  }
+
+  const oldAdd = proto.add
+  proto.add = function (number, units) {
+    this.$d = oldAdd.bind(this)(number, units).toDate()
+    this.init()
+    return this
+  }
+
+  const oldLocale = proto.locale
+  proto.locale = function (preset, object) {
+    if (!preset) return this.$L
+    this.$L = oldLocale.bind(this)(preset, object).$L
+    return this
+  }
+
+  const oldDaysInMonth = proto.daysInMonth
+  proto.daysInMonth = function () {
+    return oldDaysInMonth.bind(this.clone())()
+  }
+
+  const oldIsSame = proto.isSame
+  proto.isSame = function (that, units) {
+    return oldIsSame.bind(this.clone())(that, units)
+  }
+
+  const oldIsBefore = proto.isBefore
+  proto.isBefore = function (that, units) {
+    return oldIsBefore.bind(this.clone())(that, units)
+  }
+
+  const oldIsAfter = proto.isAfter
+  proto.isAfter = function (that, units) {
+    return oldIsAfter.bind(this.clone())(that, units)
+  }
+}
+
diff --git a/src/plugin/customParseFormat/index.js b/src/plugin/customParseFormat/index.js
--- a/src/plugin/customParseFormat/index.js
+++ b/src/plugin/customParseFormat/index.js
@@ -1,4 +1,4 @@
-const formattingTokens = /(\[[^[]*\])|([-:/.()\s]+)|(A|a|YYYY|YY?|MM?M?M?|DD?|hh?|HH?|mm?|ss?|S{1,3}|z|ZZ?)/g
+const formattingTokens = /(\[[^[]*\])|([-:/.()\s]+)|(A|a|YYYY|YY?|MM?M?M?|Do|DD?|hh?|HH?|mm?|ss?|S{1,3}|z|ZZ?)/g
 
 const match1 = /\d/ // 0 - 9
 const match2 = /\d\d/ // 00 - 99
@@ -47,15 +47,25 @@ const expressions = {
     this.milliseconds = +input
   }],
   s: [match1to2, addInput('seconds')],
-  ss: [match2, addInput('seconds')],
+  ss: [match1to2, addInput('seconds')],
   m: [match1to2, addInput('minutes')],
-  mm: [match2, addInput('minutes')],
+  mm: [match1to2, addInput('minutes')],
   H: [match1to2, addInput('hours')],
   h: [match1to2, addInput('hours')],
-  HH: [match2, addInput('hours')],
-  hh: [match2, addInput('hours')],
+  HH: [match1to2, addInput('hours')],
+  hh: [match1to2, addInput('hours')],
   D: [match1to2, addInput('day')],
   DD: [match2, addInput('day')],
+  Do: [matchWord, function (input) {
+    const { ordinal } = locale;
+    [this.day] = input.match(/\d+/)
+    if (!ordinal) return
+    for (let i = 1; i <= 31; i += 1) {
+      if (ordinal(i).replace(/\[|\]/g, '') === input) {
+        this.day = i
+      }
+    }
+  }],
   M: [match1to2, addInput('month')],
   MM: [match2, addInput('month')],
   MMM: [matchWord, function (input) {
diff --git a/src/plugin/isMoment/index.js b/src/plugin/isMoment/index.js
new file mode 100644
--- /dev/null
+++ b/src/plugin/isMoment/index.js
@@ -0,0 +1,6 @@
+export default (o, c, f) => {
+  f.isMoment = function (input) {
+    return f.isDayjs(input)
+  }
+}
+
diff --git a/src/plugin/localeData/index.js b/src/plugin/localeData/index.js
new file mode 100644
--- /dev/null
+++ b/src/plugin/localeData/index.js
@@ -0,0 +1,16 @@
+export default (o, c) => { // locale needed later
+  const proto = c.prototype
+  const localeData = function () {
+    return {
+      months: instance => instance.format('MMMM'),
+      monthsShort: instance => instance.format('MMM'),
+      firstDayOfWeek: () => this.$locale().weekStart || 0,
+      weekdaysMin: instance => instance.format('dd'),
+      weekdaysShort: instance => instance.format('ddd')
+    }
+  }
+  proto.localeData = function () {
+    return localeData.bind(this)()
+  }
+}
+
diff --git a/src/plugin/toArray/index.js b/src/plugin/toArray/index.js
new file mode 100644
--- /dev/null
+++ b/src/plugin/toArray/index.js
@@ -0,0 +1,15 @@
+export default (o, c) => {
+  const proto = c.prototype
+  proto.toArray = function () {
+    return [
+      this.$y,
+      this.$M,
+      this.$D,
+      this.$H,
+      this.$m,
+      this.$s,
+      this.$ms
+    ]
+  }
+}
+
diff --git a/src/plugin/toObject/index.js b/src/plugin/toObject/index.js
new file mode 100644
--- /dev/null
+++ b/src/plugin/toObject/index.js
@@ -0,0 +1,15 @@
+export default (o, c) => {
+  const proto = c.prototype
+  proto.toObject = function () {
+    return {
+      years: this.$y,
+      months: this.$M,
+      date: this.$D,
+      hours: this.$H,
+      minutes: this.$m,
+      seconds: this.$s,
+      milliseconds: this.$ms
+    }
+  }
+}
+
diff --git a/src/plugin/weekOfYear/index.js b/src/plugin/weekOfYear/index.js
--- a/src/plugin/weekOfYear/index.js
+++ b/src/plugin/weekOfYear/index.js
@@ -6,11 +6,12 @@ export default (o, c, d) => {
     if (week !== null) {
       return this.add((week - this.week()) * 7, 'day')
     }
-    const endOfYear = this.endOf(Y)
+    // d(this) clone is for badMutable plugin
+    const endOfYear = d(this).endOf(Y)
     if (endOfYear.day() !== 6 && this.month() === 11 && (31 - this.date()) <= endOfYear.day()) {
       return 1
     }
-    const startOfYear = d(this.$d).startOf(Y)
+    const startOfYear = d(this).startOf(Y)
     const compareDay = startOfYear.subtract(startOfYear.day(), D).subtract(1, MS)
     const diffInWeek = this.diff(compareDay, W, true)
     return Math.ceil(diffInWeek)
diff --git a/src/plugin/weekYear/index.js b/src/plugin/weekYear/index.js
new file mode 100644
--- /dev/null
+++ b/src/plugin/weekYear/index.js
@@ -0,0 +1,12 @@
+export default (o, c) => {
+  const proto = c.prototype
+  proto.weekYear = function () {
+    const month = this.month()
+    const weekOfYear = this.week()
+    const year = this.year()
+    if (weekOfYear === 1 && month === 11) {
+      return year + 1
+    }
+    return year
+  }
+}
diff --git a/types/index.d.ts b/types/index.d.ts
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -12,16 +12,6 @@ declare namespace dayjs {
   type OpUnitTypeShort = 'w'
   export type OpUnitType = UnitType | "week" | OpUnitTypeShort;
 
-  interface DayjsObject {
-    years: number
-    months: number
-    date: number
-    hours: number
-    minutes: number
-    seconds: number
-    milliseconds: number
-  }
-
   class Dayjs {
     constructor (config?: ConfigType)
 
@@ -31,20 +21,36 @@ declare namespace dayjs {
 
     year(): number
 
+    year(value: number): Dayjs
+
     month(): number
 
+    month(value: number): Dayjs
+
     date(): number
 
+    date(value: number): Dayjs
+
     day(): number
 
+    day(value: number): Dayjs
+
     hour(): number
 
+    hour(value: number): Dayjs
+
     minute(): number
 
+    minute(value: number): Dayjs
+
     second(): number
 
+    second(value: number): Dayjs
+
     millisecond(): number
 
+    millisecond(value: number): Dayjs
+
     set(unit: UnitType, value: number): Dayjs
 
     add(value: number, unit: OpUnitType): Dayjs
@@ -67,14 +73,10 @@ declare namespace dayjs {
 
     toDate(): Date
 
-    toArray(): number[]
-
     toJSON(): string
 
     toISOString(): string
 
-    toObject(): DayjsObject
-
     toString(): string
 
     utcOffset(): number
@@ -85,6 +87,8 @@ declare namespace dayjs {
 
     isAfter(date: ConfigType, unit?: OpUnitType): boolean
 
+    locale(): string
+
     locale(preset: string | { name: string, [key: string]: any }, object?: { [key: string]: any }): Dayjs
   }
 
diff --git a/types/plugin/badMutable.d.ts b/types/plugin/badMutable.d.ts
new file mode 100644
--- /dev/null
+++ b/types/plugin/badMutable.d.ts
@@ -0,0 +1,4 @@
+import { PluginFunc } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
diff --git a/types/plugin/isMoment.d.ts b/types/plugin/isMoment.d.ts
new file mode 100644
--- /dev/null
+++ b/types/plugin/isMoment.d.ts
@@ -0,0 +1,10 @@
+import { PluginFunc } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
+
+declare module 'dayjs' {
+
+  export function isMoment(input: any): boolean
+
+}
diff --git a/types/plugin/localeData.d.ts b/types/plugin/localeData.d.ts
new file mode 100644
--- /dev/null
+++ b/types/plugin/localeData.d.ts
@@ -0,0 +1,10 @@
+import { PluginFunc } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
+
+declare module 'dayjs' {
+  interface Dayjs {
+    localeData(): any
+  }
+}
diff --git a/types/plugin/toArray.d.ts b/types/plugin/toArray.d.ts
new file mode 100644
--- /dev/null
+++ b/types/plugin/toArray.d.ts
@@ -0,0 +1,10 @@
+import { PluginFunc } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
+
+declare module 'dayjs' {
+  interface Dayjs {
+    toArray(): number[]
+  }
+}
diff --git a/types/plugin/toObject.d.ts b/types/plugin/toObject.d.ts
new file mode 100644
--- /dev/null
+++ b/types/plugin/toObject.d.ts
@@ -0,0 +1,20 @@
+import { PluginFunc } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
+
+interface DayjsObject {
+  years: number
+  months: number
+  date: number
+  hours: number
+  minutes: number
+  seconds: number
+  milliseconds: number
+}
+
+declare module 'dayjs' {
+  interface Dayjs {
+    toObject(): DayjsObject
+  }
+}
diff --git a/types/plugin/weekOfYear.d.ts b/types/plugin/weekOfYear.d.ts
--- a/types/plugin/weekOfYear.d.ts
+++ b/types/plugin/weekOfYear.d.ts
@@ -6,5 +6,7 @@ export = plugin
 declare module 'dayjs' {
   interface Dayjs {
     week(): number
+
+    week(value : number): Dayjs
   }
 }
diff --git a/types/plugin/weekYear.d.ts b/types/plugin/weekYear.d.ts
new file mode 100644
--- /dev/null
+++ b/types/plugin/weekYear.d.ts
@@ -0,0 +1,10 @@
+import { PluginFunc } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
+
+declare module 'dayjs' {
+  interface Dayjs {
+    weekYear(): number
+  }
+}


Test patch:
diff --git a/test/display.test.js b/test/display.test.js
--- a/test/display.test.js
+++ b/test/display.test.js
@@ -230,10 +230,6 @@ it('As Javascript Date -> toDate', () => {
   expect(jsDate.toUTCString()).not.toBe(base.toString())
 })
 
-it('As Array -> toArray', () => {
-  expect(dayjs().toArray()).toEqual(moment().toArray())
-})
-
 it('As JSON -> toJSON', () => {
   expect(dayjs().toJSON()).toBe(moment().toJSON())
 })
@@ -241,7 +237,3 @@ it('As JSON -> toJSON', () => {
 it('As ISO 8601 String -> toISOString e.g. 2013-02-04T22:44:30.652Z', () => {
   expect(dayjs().toISOString()).toBe(moment().toISOString())
 })
-
-it('As Object -> toObject', () => {
-  expect(dayjs().toObject()).toEqual(moment().toObject())
-})
diff --git a/test/get-set.test.js b/test/get-set.test.js
--- a/test/get-set.test.js
+++ b/test/get-set.test.js
@@ -12,34 +12,50 @@ afterEach(() => {
 
 it('Year', () => {
   expect(dayjs().year()).toBe(moment().year())
+  expect(dayjs().year(0).valueOf()).toBe(moment().year(0).valueOf())
+  expect(dayjs().year(2000).valueOf()).toBe(moment().year(2000).valueOf())
 })
 
 it('Month', () => {
   expect(dayjs().month()).toBe(moment().month())
+  expect(dayjs().month(0).valueOf()).toBe(moment().month(0).valueOf())
+  expect(dayjs().month(1).valueOf()).toBe(moment().month(1).valueOf())
 })
 
 it('Day of Week', () => {
   expect(dayjs().day()).toBe(moment().day())
+  expect(dayjs().day(0).format()).toBe(moment().day(0).format())
+  expect(dayjs().day(1).format()).toBe(moment().day(1).format())
 })
 
 it('Date', () => {
   expect(dayjs().date()).toBe(moment().date())
+  expect(dayjs().date(0).valueOf()).toBe(moment().date(0).valueOf())
+  expect(dayjs().date(1).valueOf()).toBe(moment().date(1).valueOf())
 })
 
 it('Hour', () => {
   expect(dayjs().hour()).toBe(moment().hour())
+  expect(dayjs().hour(0).valueOf()).toBe(moment().hour(0).valueOf())
+  expect(dayjs().hour(1).valueOf()).toBe(moment().hour(1).valueOf())
 })
 
 it('Minute', () => {
   expect(dayjs().minute()).toBe(moment().minute())
+  expect(dayjs().minute(0).valueOf()).toBe(moment().minute(0).valueOf())
+  expect(dayjs().minute(1).valueOf()).toBe(moment().minute(1).valueOf())
 })
 
 it('Second', () => {
   expect(dayjs().second()).toBe(moment().second())
+  expect(dayjs().second(0).valueOf()).toBe(moment().second(0).valueOf())
+  expect(dayjs().second(1).valueOf()).toBe(moment().second(1).valueOf())
 })
 
 it('Millisecond', () => {
   expect(dayjs().millisecond()).toBe(moment().millisecond())
+  expect(dayjs().millisecond(0).valueOf()).toBe(moment().millisecond(0).valueOf())
+  expect(dayjs().millisecond(1).valueOf()).toBe(moment().millisecond(1).valueOf())
 })
 
 it('Set Day', () => {
diff --git a/test/locale.test.js b/test/locale.test.js
--- a/test/locale.test.js
+++ b/test/locale.test.js
@@ -1,4 +1,5 @@
 import MockDate from 'mockdate'
+import moment from 'moment'
 import dayjs from '../src'
 import es from '../src/locale/es'
 
@@ -44,6 +45,17 @@ it('set global locale', () => {
     .toBe('Saturday 28, April')
 })
 
+it('get instance locale name', () => {
+  expect(dayjs().locale()).toBe('en')
+  expect(dayjs().locale()).toBe(moment().locale())
+  expect(dayjs().locale('es').locale()).toBe('es')
+  expect(dayjs().locale('es').locale()).toBe(moment().locale('es').locale())
+  dayjs.locale(es)
+  moment.locale('es')
+  expect(dayjs().locale()).toBe('es')
+  expect(dayjs().locale()).toBe(moment().locale())
+})
+
 it('immutable instance locale', () => {
   dayjs.locale('en')
   const origin = dayjs('2018-4-28')
diff --git a/test/plugin/advancedFormat.test.js b/test/plugin/advancedFormat.test.js
--- a/test/plugin/advancedFormat.test.js
+++ b/test/plugin/advancedFormat.test.js
@@ -2,7 +2,12 @@ import MockDate from 'mockdate'
 import moment from 'moment'
 import dayjs from '../../src'
 import advancedFormat from '../../src/plugin/advancedFormat'
+import weekOfYear from '../../src/plugin/weekOfYear'
+import weekYear from '../../src/plugin/weekYear'
+import '../../src/locale/zh-cn'
 
+dayjs.extend(weekYear)
+dayjs.extend(weekOfYear)
 dayjs.extend(advancedFormat)
 
 beforeEach(() => {
@@ -65,3 +70,15 @@ it('Format Hour k kk 24-hour 1 - 24', () => {
   expect(dayjs(d).format('kk')).toBe('23')
   expect(dayjs(d).format('kk')).toBe(moment(d).format('kk'))
 })
+
+it('Format Week of Year wo', () => {
+  const d = '2018-12-01'
+  expect(dayjs(d).format('wo')).toBe(moment(d).format('wo'))
+  expect(dayjs(d).locale('zh-cn').format('wo'))
+    .toBe(moment(d).locale('zh-cn').format('wo'))
+})
+
+it('Format Week Year gggg', () => {
+  const d = '2018-12-31'
+  expect(dayjs(d).format('gggg')).toBe(moment(d).format('gggg'))
+})
diff --git a/test/plugin/badMutable.test.js b/test/plugin/badMutable.test.js
new file mode 100644
--- /dev/null
+++ b/test/plugin/badMutable.test.js
@@ -0,0 +1,177 @@
+import MockDate from 'mockdate'
+import moment from 'moment'
+import dayjs from '../../src'
+import badMutable from '../../src/plugin/badMutable'
+import '../../src/locale/zh-cn'
+
+dayjs.extend(badMutable)
+
+beforeEach(() => {
+  MockDate.set(new Date())
+})
+
+afterEach(() => {
+  MockDate.reset()
+})
+
+describe('Set', () => {
+  it('Setters', () => {
+    const d = dayjs()
+    const m = moment()
+    expect(d.year()).toBe(m.year())
+    d.year(2000)
+    m.year(2000)
+    expect(d.format()).toBe(m.format())
+    d.month(1)
+    m.month(1)
+    expect(d.format()).toBe(m.format())
+    d.day(1)
+    m.day(1)
+    expect(d.format()).toBe(m.format())
+    d.date(1)
+    m.date(1)
+    expect(d.format()).toBe(m.format())
+    d.hour(1)
+    m.hour(1)
+    expect(d.format()).toBe(m.format())
+    d.minute(1)
+    m.minute(1)
+    expect(d.format()).toBe(m.format())
+    d.second(1)
+    m.second(1)
+    expect(d.format()).toBe(m.format())
+    d.millisecond(1)
+    m.millisecond(1)
+    expect(d.format()).toBe(m.format())
+  })
+
+  it('Set', () => {
+    const d = dayjs()
+    const m = moment()
+    d.set('year', 2000)
+    m.set('year', 2000)
+    expect(d.format()).toBe(m.format())
+    d.set('month', 12)
+    m.set('month', 12)
+    expect(d.format()).toBe(m.format())
+    d.set('day', 1)
+    m.set('day', 1)
+    expect(d.format()).toBe(m.format())
+    d.set('date', 1)
+    m.set('date', 1)
+    expect(d.format()).toBe(m.format())
+    d.set('hour', 1)
+    m.set('hour', 1)
+    expect(d.format()).toBe(m.format())
+    d.set('minute', 1)
+    m.set('minute', 1)
+    expect(d.format()).toBe(m.format())
+    d.set('second', 1)
+    m.set('second', 1)
+    expect(d.format()).toBe(m.format())
+    d.set('millisecond', 1)
+    m.set('millisecond', 1)
+    expect(d.format()).toBe(m.format())
+  })
+})
+
+describe('StartOf', () => {
+  it('StartOf', () => {
+    const d = dayjs()
+    const m = moment()
+    d.startOf('year')
+    m.startOf('year')
+    expect(d.format()).toBe(m.format())
+    d.startOf('month')
+    m.startOf('month')
+    expect(d.format()).toBe(m.format())
+    d.startOf('day')
+    m.startOf('day')
+    expect(d.format()).toBe(m.format())
+    d.startOf('date')
+    m.startOf('date')
+    expect(d.format()).toBe(m.format())
+    d.startOf('hour')
+    m.startOf('hour')
+    expect(d.format()).toBe(m.format())
+    d.startOf('minute')
+    m.startOf('minute')
+    expect(d.format()).toBe(m.format())
+    d.startOf('second')
+    m.startOf('second')
+    expect(d.format()).toBe(m.format())
+    d.startOf('millisecond')
+    m.startOf('millisecond')
+    expect(d.format()).toBe(m.format())
+    d.startOf('week')
+    m.startOf('week')
+    expect(d.format()).toBe(m.format())
+  })
+})
+
+describe('Add', () => {
+  it('Add', () => {
+    const d = dayjs()
+    const m = moment()
+    d.add(1, 'year')
+    m.add(1, 'year')
+    expect(d.format()).toBe(m.format())
+    d.add(12, 'month')
+    m.add(12, 'month')
+    expect(d.format()).toBe(m.format())
+    d.add(1, 'day')
+    m.add(1, 'day')
+    expect(d.format()).toBe(m.format())
+    d.add(1, 'date')
+    m.add(1, 'date')
+    expect(d.format()).toBe(m.format())
+    d.add(1, 'hour')
+    m.add(1, 'hour')
+    expect(d.format()).toBe(m.format())
+    d.add(1, 'minute')
+    m.add(1, 'minute')
+    expect(d.format()).toBe(m.format())
+    d.add(1, 'second')
+    m.add(1, 'second')
+    expect(d.format()).toBe(m.format())
+    d.add(1, 'millisecond')
+    m.add(1, 'millisecond')
+    expect(d.format()).toBe(m.format())
+    d.add(1, 'week')
+    m.add(1, 'week')
+    expect(d.format()).toBe(m.format())
+  })
+})
+
+it('daysInMonth', () => {
+  const d = dayjs()
+  const m = moment()
+  expect(d.daysInMonth()).toBe(m.daysInMonth())
+  expect(d.format()).toBe(m.format())
+})
+
+it('Locale', () => {
+  const d = dayjs()
+  const m = moment()
+  const format = 'MMMM'
+  expect(d.locale()).toBe(m.locale())
+  expect(d.format(format)).toBe(m.format(format))
+  d.locale('zh-cn')
+  m.locale('zh-cn')
+  expect(d.locale()).toBe(m.locale())
+  expect(d.format(format)).toBe(m.format(format))
+})
+
+it('isAfter isBefore isSame', () => {
+  const d = dayjs()
+  const format = dayjs().format()
+  d.isSame(dayjs, 'year')
+  expect(d.format()).toBe(format)
+  expect(d.isSame()).toBe(true)
+  d.isBefore(dayjs, 'hour')
+  expect(d.format()).toBe(format)
+  expect(d.isBefore()).toBe(false)
+  d.isAfter(dayjs, 'month')
+  expect(d.format()).toBe(format)
+  expect(d.isAfter()).toBe(false)
+})
diff --git a/test/plugin/customParseFormat.test.js b/test/plugin/customParseFormat.test.js
--- a/test/plugin/customParseFormat.test.js
+++ b/test/plugin/customParseFormat.test.js
@@ -3,6 +3,7 @@ import moment from 'moment'
 import dayjs from '../../src'
 import customParseFormat from '../../src/plugin/customParseFormat'
 import uk from '../../src/locale/uk'
+import '../../src/locale/zh-cn'
 
 dayjs.extend(customParseFormat)
 
@@ -73,12 +74,30 @@ it('timezone with no hour', () => {
   expect(dayjs(input, format).valueOf()).toBe(moment(input, format).valueOf())
 })
 
-it('parse just hh:mm)', () => {
+it('parse hh:mm', () => {
   const input = '12:00'
   const format = 'hh:mm'
   expect(dayjs(input, format).valueOf()).toBe(moment(input, format).valueOf())
 })
 
+it('parse HH:mm:ss', () => {
+  const input = '00:27:21'
+  const format = 'HH:mm:ss'
+  expect(dayjs(input, format).valueOf()).toBe(moment(input, format).valueOf())
+})
+
+it('parse HH:mm:ss but only one digit', () => {
+  const input = '0:0:1'
+  const format = 'HH:mm:ss'
+  expect(dayjs(input, format).valueOf()).toBe(moment(input, format).valueOf())
+})
+
+it('parse hh:mm:ss but only one digit', () => {
+  const input = '0:0:1'
+  const format = 'hh:mm:ss'
+  expect(dayjs(input, format).valueOf()).toBe(moment(input, format).valueOf())
+})
+
 it('fails with an invalid format', () => {
   const input = '2018-05-02 12:00 PM'
   const format = 'C'
@@ -151,3 +170,19 @@ it('correctly parse month from string after changing locale globally', () => {
     moment.locale(momentLocale)
   }
 })
+
+it('correctly parse ordinal', () => {
+  const input = '7th March 2019'
+  const input2 = '17th March 2019'
+  const inputFalse = '7st March 2019'
+  const inputZHCN = '7日 三月 2019'
+  const format = 'Do MMMM YYYY'
+  expect(dayjs(input, format).valueOf())
+    .toBe(moment(input, format).valueOf())
+  expect(dayjs(input2, format).valueOf())
+    .toBe(moment(input2, format).valueOf())
+  expect(dayjs(inputFalse, format).valueOf())
+    .toBe(moment(inputFalse, format).valueOf())
+  expect(dayjs(inputZHCN, format, 'zh-cn').valueOf())
+    .toBe(moment(inputZHCN, format, 'zh-cn').valueOf())
+})
diff --git a/test/plugin/isMoment.test.js b/test/plugin/isMoment.test.js
new file mode 100644
--- /dev/null
+++ b/test/plugin/isMoment.test.js
@@ -0,0 +1,18 @@
+import MockDate from 'mockdate'
+import dayjs from '../../src'
+import isMoment from '../../src/plugin/isMoment'
+
+dayjs.extend(isMoment)
+
+beforeEach(() => {
+  MockDate.set(new Date())
+})
+
+afterEach(() => {
+  MockDate.reset()
+})
+
+it('IsLeapYear', () => {
+  expect(dayjs.isMoment(dayjs())).toBe(true)
+  expect(dayjs.isMoment(new Date())).toBe(false)
+})
diff --git a/test/plugin/localeData.test.js b/test/plugin/localeData.test.js
new file mode 100644
--- /dev/null
+++ b/test/plugin/localeData.test.js
@@ -0,0 +1,26 @@
+import MockDate from 'mockdate'
+import moment from 'moment'
+import dayjs from '../../src'
+import localeData from '../../src/plugin/localeData'
+
+dayjs.extend(localeData)
+
+beforeEach(() => {
+  MockDate.set(new Date())
+})
+
+afterEach(() => {
+  MockDate.reset()
+})
+
+it('localeData', () => {
+  const d = dayjs()
+  const m = moment()
+  const dayjsLocaleData = dayjs().localeData()
+  const momentLocaleData = moment().localeData()
+  expect(dayjsLocaleData.firstDayOfWeek()).toBe(momentLocaleData.firstDayOfWeek())
+  expect(dayjsLocaleData.months(d)).toBe(momentLocaleData.months(m))
+  expect(dayjsLocaleData.monthsShort(d)).toBe(momentLocaleData.monthsShort(m))
+  expect(dayjsLocaleData.weekdaysMin(d)).toBe(momentLocaleData.weekdaysMin(m))
+  expect(dayjsLocaleData.weekdaysShort(d)).toBe(momentLocaleData.weekdaysShort(m))
+})
diff --git a/test/plugin/toArray.test.js b/test/plugin/toArray.test.js
new file mode 100644
--- /dev/null
+++ b/test/plugin/toArray.test.js
@@ -0,0 +1,18 @@
+import MockDate from 'mockdate'
+import moment from 'moment'
+import dayjs from '../../src'
+import toArray from '../../src/plugin/toArray'
+
+dayjs.extend(toArray)
+
+beforeEach(() => {
+  MockDate.set(new Date())
+})
+
+afterEach(() => {
+  MockDate.reset()
+})
+
+it('As Array -> toArray', () => {
+  expect(dayjs().toArray()).toEqual(moment().toArray())
+})
diff --git a/test/plugin/toObject.test.js b/test/plugin/toObject.test.js
new file mode 100644
--- /dev/null
+++ b/test/plugin/toObject.test.js
@@ -0,0 +1,18 @@
+import MockDate from 'mockdate'
+import moment from 'moment'
+import dayjs from '../../src'
+import toObject from '../../src/plugin/toObject'
+
+dayjs.extend(toObject)
+
+beforeEach(() => {
+  MockDate.set(new Date())
+})
+
+afterEach(() => {
+  MockDate.reset()
+})
+
+it('As Object -> toObject', () => {
+  expect(dayjs().toObject()).toEqual(moment().toObject())
+})
diff --git a/test/plugin/weekYear.test.js b/test/plugin/weekYear.test.js
new file mode 100644
--- /dev/null
+++ b/test/plugin/weekYear.test.js
@@ -0,0 +1,30 @@
+import moment from 'moment'
+import MockDate from 'mockdate'
+import dayjs from '../../src'
+import weekYear from '../../src/plugin/weekYear'
+import weekOfYear from '../../src/plugin/weekOfYear'
+
+dayjs.extend(weekYear)
+dayjs.extend(weekOfYear)
+
+beforeEach(() => {
+  MockDate.set(new Date())
+})
+
+afterEach(() => {
+  MockDate.reset()
+})
+
+it('Week Year', () => {
+  const daySet = [
+    ['2018-12-01', 2018],
+    ['2018-12-30', 2019],
+    ['2018-12-31', 2019],
+    ['2019-01-01', 2019]
+  ]
+  daySet.forEach((d) => {
+    const [day, result] = d
+    expect(dayjs(day).weekYear()).toBe(result)
+    expect(dayjs(day).weekYear()).toBe(moment(day).weekYear())
+  })
+})