Repository: iamkun/dayjs (JavaScript)

Issue:
Cannot read property 'relativeTime' of undefined
Getting the following error:

```
Uncaught TypeError: Cannot read property 'relativeTime' of undefined
    at fromTo (index.js:21)
    at Dayjs.push../node_modules/dayjs/esm/plugin/relativeTime/index.js.__webpack_exports__.default.proto.from (index.js:89)
    at Module../src/app/core/utils/dayjs.ts (dayjs.ts:9)
```

While trying to run the code:

```
import _dayjs from 'dayjs/esm';
import relativeTime from 'dayjs/esm/plugin/relativeTime';
import 'dayjs/locale/en';
import 'dayjs/locale/he';

_dayjs.locale('he');
_dayjs.extend(relativeTime);

console.log(_dayjs().from(_dayjs('1990'))); // <-- Fails
```

Running `"dayjs": "^1.8.6"`


Hints:

Patch:
diff --git a/.babelrc b/babel.config.js
similarity index 87%
rename from .babelrc
rename to babel.config.js
--- a/.babelrc
+++ b/babel.config.js
@@ -1,20 +1,20 @@
-{
-  "env": {
-    "test": {
-      "presets": [
-        "@babel/preset-env"
-      ]
-    },
-    "build": {
-      "presets": [
-        [
-          "@babel/preset-env",
-          {
-            "modules": false,
-            "loose": true
-          }
-        ]
-      ]
-    }
-  }
-}
+module.exports = {
+  "env": {
+    "test": {
+      "presets": [
+        "@babel/preset-env"
+      ]
+    },
+    "build": {
+      "presets": [
+        [
+          "@babel/preset-env",
+          {
+            "modules": false,
+            "loose": true
+          }
+        ]
+      ]
+    }
+  }
+};
\ No newline at end of file
diff --git a/build/esm.js b/build/esm.js
new file mode 100644
--- /dev/null
+++ b/build/esm.js
@@ -0,0 +1,21 @@
+const fs = require('fs')
+const path = require('path')
+const util = require('util')
+
+const { promisify } = util
+
+const localeDir = path.join(process.env.PWD, 'esm/locale');
+
+(async () => {
+  try {
+    const readLocaleDir = await promisify(fs.readdir)(localeDir)
+    readLocaleDir.forEach(async (l) => {
+      const filePath = path.join(localeDir, l)
+      const readFile = await promisify(fs.readFile)(filePath, 'utf8')
+      const result = readFile.replace("'dayjs'", "'../index'")
+      await promisify(fs.writeFile)(filePath, result, 'utf8')
+    })
+  } catch (e) {
+    console.error(e) // eslint-disable-line no-console
+  }
+})()
diff --git a/build/index.js b/build/index.js
--- a/build/index.js
+++ b/build/index.js
@@ -3,6 +3,7 @@ const configFactory = require('./rollup.config')
 const fs = require('fs')
 const util = require('util')
 const path = require('path')
+const { ncp } = require('ncp')
 
 const { promisify } = util
 
@@ -39,6 +40,10 @@ async function build(option) {
       input: './src/index.js',
       fileName: './dayjs.min.js'
     }))
+
+    ncp('./types/', './', (err) => {
+      if (err) { throw err }
+    })
   } catch (e) {
     console.error(e) // eslint-disable-line no-console
   }
diff --git a/src/index.js b/src/index.js
--- a/src/index.js
+++ b/src/index.js
@@ -28,12 +28,12 @@ const parseLocale = (preset, object, isLocal) => {
   return l
 }
 
-const dayjs = (date, c) => {
+const dayjs = (date, c, pl) => {
   if (isDayjs(date)) {
     return date.clone()
   }
   // eslint-disable-next-line no-nested-ternary
-  const cfg = c ? (typeof c === 'string' ? { format: c } : c) : {}
+  const cfg = c ? (typeof c === 'string' ? { format: c, pl } : c) : {}
   cfg.date = date
   return new Dayjs(cfg) // eslint-disable-line no-use-before-define
 }
@@ -46,34 +46,31 @@ Utils.isDayjs = isDayjs
 Utils.wrapper = wrapper
 
 const parseDate = (date) => {
-  let reg
-  if (date === null) return new Date(NaN) // Treat null as an invalid date
-  if (Utils.isUndefined(date)) return new Date()
+  if (date === null) return new Date(NaN) // null is invalid
+  if (Utils.isUndefined(date)) return new Date() // today
   if (date instanceof Date) return date
-  // eslint-disable-next-line no-cond-assign
-  if ((typeof date === 'string')
-    && (/.*[^Z]$/i.test(date)) // looking for a better way
-    && (reg = date.match(C.REGEX_PARSE))) {
-    // 2018-08-08 or 20180808
-    return new Date(
-      reg[1], reg[2] - 1, reg[3] || 1,
-      reg[4] || 0, reg[5] || 0, reg[6] || 0, reg[7] || 0
-    )
+  if (typeof date === 'string' && !/Z$/i.test(date)) {
+    const d = date.match(C.REGEX_PARSE)
+    if (d) {
+      return new Date(d[1], d[2] - 1, d[3] || 1, d[4] || 0, d[5] || 0, d[6] || 0, d[7] || 0)
+    }
   }
-  return new Date(date) // timestamp
+
+  return new Date(date) // everything else
 }
 
 class Dayjs {
   constructor(cfg) {
+    this.$L = this.$L || parseLocale(cfg.locale, null, true) || L
     this.parse(cfg) // for plugin
   }
 
   parse(cfg) {
     this.$d = parseDate(cfg.date)
-    this.init(cfg)
+    this.init()
   }
 
-  init(cfg) {
+  init() {
     const { $d } = this
     this.$y = $d.getFullYear()
     this.$M = $d.getMonth()
@@ -83,7 +80,6 @@ class Dayjs {
     this.$m = $d.getMinutes()
     this.$s = $d.getSeconds()
     this.$ms = $d.getMilliseconds()
-    this.$L = this.$L || parseLocale(cfg.locale, null, true) || L
   }
 
   // eslint-disable-next-line class-methods-use-this
@@ -412,5 +408,6 @@ dayjs.unix = timestamp => (
 )
 
 dayjs.en = Ls[L]
+dayjs.Ls = Ls
 
 export default dayjs
diff --git a/src/locale/fa.js b/src/locale/fa.js
--- a/src/locale/fa.js
+++ b/src/locale/fa.js
@@ -14,19 +14,19 @@ const locale = {
     LLLL: 'dddd, D MMMM YYYY HH:mm'
   },
   relativeTime: {
-    future: '%s در',
-    past: 'پیش %s',
+    future: 'در %s',
+    past: '%s پیش',
     s: 'چند ثانیه',
     m: 'یک دقیقه',
-    mm: 'دقیقه %d',
+    mm: '%d دقیقه',
     h: 'یک ساعت',
-    hh: 'ساعت %d',
+    hh: '%d ساعت',
     d: 'یک روز',
-    dd: 'روز %d',
+    dd: '%d روز',
     M: 'یک ماه',
-    MM: 'ماه %d',
+    MM: '%d ماه',
     y: 'یک سال',
-    yy: 'سال %d'
+    yy: '%d سال'
   }
 }
 
diff --git a/src/locale/sw.js b/src/locale/sw.js
new file mode 100644
--- /dev/null
+++ b/src/locale/sw.js
@@ -0,0 +1,39 @@
+import dayjs from 'dayjs'
+
+const locale = {
+  name: 'sw',
+  weekdays: 'Jumapili_Jumatatu_Jumanne_Jumatano_Alhamisi_Ijumaa_Jumamosi'.split('_'),
+  weekdaysShort: 'Jpl_Jtat_Jnne_Jtan_Alh_Ijm_Jmos'.split('_'),
+  weekdaysMin: 'J2_J3_J4_J5_Al_Ij_J1'.split('_'),
+  months: 'Januari_Februari_Machi_Aprili_Mei_Juni_Julai_Agosti_Septemba_Oktoba_Novemba_Desemba'.split('_'),
+  monthsShort: 'Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ago_Sep_Okt_Nov_Des'.split('_'),
+  weekStart: 1,
+  ordinal: n => n,
+  relativeTime: {
+    future: '%s baadaye',
+    past: 'tokea %s',
+    s: 'hivi punde',
+    m: 'dakika moja',
+    mm: 'dakika %d',
+    h: 'saa limoja',
+    hh: 'masaa %d',
+    d: 'siku moja',
+    dd: 'masiku %d',
+    M: 'mwezi mmoja',
+    MM: 'miezi %d',
+    y: 'mwaka mmoja',
+    yy: 'miaka %d'
+  },
+  formats: {
+    LT: 'HH:mm',
+    LTS: 'HH:mm:ss',
+    L: 'DD.MM.YYYY',
+    LL: 'D MMMM YYYY',
+    LLL: 'D MMMM YYYY HH:mm',
+    LLLL: 'dddd, D MMMM YYYY HH:mm'
+  }
+}
+
+dayjs.locale(locale, null, true)
+
+export default locale
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?|DD?|hh?|HH?|mm?|ss?|S{1,3}|z|ZZ?)/g
+const formattingTokens = /(\[[^[]*\])|([-:/.()\s]+)|(A|a|YYYY|YY?|MM?M?M?|DD?|hh?|HH?|mm?|ss?|S{1,3}|z|ZZ?)/g
 
 const match1 = /\d/ // 0 - 9
 const match2 = /\d\d/ // 00 - 99
@@ -9,6 +9,9 @@ const matchUpperCaseAMPM = /[AP]M/
 const matchLowerCaseAMPM = /[ap]m/
 const matchSigned = /[+-]?\d+/ // -inf - inf
 const matchOffset = /[+-]\d\d:?\d\d/ // +00:00 -00:00 +0000 or -0000
+const matchWord = /\d*[^\s\d]+/ // Word
+
+let locale
 
 function offsetFromString(string) {
   const parts = string.match(/([+-]|\d\d)/g)
@@ -55,6 +58,24 @@ const expressions = {
   DD: [match2, addInput('day')],
   M: [match1to2, addInput('month')],
   MM: [match2, addInput('month')],
+  MMM: [matchWord, function (input) {
+    const { months, monthsShort } = locale
+    const matchIndex = monthsShort
+      ? monthsShort.findIndex(month => month === input)
+      : months.findIndex(month => month.substr(0, 3) === input)
+    if (matchIndex < 0) {
+      throw new Error()
+    }
+    this.month = matchIndex + 1
+  }],
+  MMMM: [matchWord, function (input) {
+    const { months } = locale
+    const matchIndex = months.indexOf(input)
+    if (matchIndex < 0) {
+      throw new Error()
+    }
+    this.month = matchIndex + 1
+  }],
   Y: [matchSigned, addInput('year')],
   YY: [match2, function (input) {
     input = +input
@@ -138,12 +159,13 @@ const parseFormattedInput = (input, format) => {
 }
 
 
-export default (o, C) => {
+export default (o, C, d) => {
   const proto = C.prototype
   const oldParse = proto.parse
   proto.parse = function (cfg) {
-    const { date: input, format } = cfg
+    const { date: input, format, pl } = cfg
     if (format) {
+      locale = pl ? d.Ls[pl] : this.$locale()
       this.$d = parseFormattedInput(input, format)
       this.init(cfg)
     } else {
diff --git a/index.d.ts b/types/index.d.ts
similarity index 63%
rename from index.d.ts
rename to types/index.d.ts
--- a/index.d.ts
+++ b/types/index.d.ts
@@ -1,10 +1,10 @@
 export = dayjs;
-declare function dayjs (config?: dayjs.ConfigType, option?: dayjs.OptionType): dayjs.Dayjs
+declare function dayjs (date?: dayjs.ConfigType, option?: dayjs.OptionType, locale?: string): dayjs.Dayjs
 
 declare namespace dayjs {
   export type ConfigType = string | number | Date | Dayjs
 
-  export type OptionType = { locale: string }
+  export type OptionType = { locale?: string, format?: string } | string
 
   type UnitTypeShort = 'd' | 'M' | 'y' | 'h' | 'm' | 's' | 'ms'
   export type UnitType = 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'month' | 'quarter' | 'year' | 'date' | UnitTypeShort;
@@ -57,7 +57,7 @@ declare namespace dayjs {
 
     format(template?: string): string
 
-    diff(dayjs: Dayjs, unit: OpUnitType, float?: boolean): number
+    diff(date: ConfigType, unit: OpUnitType, float?: boolean): number
 
     valueOf(): number
 
@@ -77,24 +77,22 @@ declare namespace dayjs {
 
     toString(): string
 
-    isBefore(dayjs: Dayjs, unit?: OpUnitType): boolean
+    isBefore(date: ConfigType, unit?: OpUnitType): boolean
 
-    isSame(dayjs: Dayjs, unit?: OpUnitType): boolean
+    isSame(date: ConfigType, unit?: OpUnitType): boolean
 
-    isAfter(dayjs: Dayjs, unit?: OpUnitType): boolean
+    isAfter(date: ConfigType, unit?: OpUnitType): boolean
 
-    isLeapYear(): boolean
-
-    locale(arg1: any, arg2?: any): Dayjs
+    locale(preset: string | { name: string, [key: string]: any }, object?: { [key: string]: any }): Dayjs
   }
 
-  export type PluginFunc = (option: ConfigType, d1: Dayjs, d2: Dayjs) => void
+  export type PluginFunc = (option: any, c: typeof Dayjs, d: typeof dayjs) => void
 
-  export function extend(plugin: PluginFunc, option?: ConfigType): Dayjs
+  export function extend(plugin: PluginFunc, option?: any): Dayjs
 
-  export function locale(arg1: any, arg2?: any): string
+  export function locale(preset: string | { name: string, [key: string]: any }, object?: { [key: string]: any }, isLocal?: boolean): string
 
   export function isDayjs(d: any): d is Dayjs
-  
+
   export function unix(t: number): Dayjs
 }
diff --git a/types/plugin/advancedFormat.d.ts b/types/plugin/advancedFormat.d.ts
new file mode 100644
--- /dev/null
+++ b/types/plugin/advancedFormat.d.ts
@@ -0,0 +1,4 @@
+import { PluginFunc } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
diff --git a/types/plugin/buddhistEra.d.ts b/types/plugin/buddhistEra.d.ts
new file mode 100644
--- /dev/null
+++ b/types/plugin/buddhistEra.d.ts
@@ -0,0 +1,4 @@
+import { PluginFunc } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
diff --git a/types/plugin/customParseFormat.d.ts b/types/plugin/customParseFormat.d.ts
new file mode 100644
--- /dev/null
+++ b/types/plugin/customParseFormat.d.ts
@@ -0,0 +1,4 @@
+import { PluginFunc } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
diff --git a/types/plugin/dayOfYear.d.ts b/types/plugin/dayOfYear.d.ts
new file mode 100644
--- /dev/null
+++ b/types/plugin/dayOfYear.d.ts
@@ -0,0 +1,10 @@
+import { PluginFunc } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
+
+declare module 'dayjs' {
+  interface Dayjs {
+    dayOfYear(): number
+  }
+}
diff --git a/types/plugin/isBetween.d.ts b/types/plugin/isBetween.d.ts
new file mode 100644
--- /dev/null
+++ b/types/plugin/isBetween.d.ts
@@ -0,0 +1,10 @@
+import { PluginFunc, ConfigType, OpUnitType } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
+
+declare module 'dayjs' {
+  interface Dayjs {
+    isBetween(a: ConfigType, b: ConfigType, c?: OpUnitType | null, d?: string): boolean
+  }
+}
diff --git a/types/plugin/isLeapYear.d.ts b/types/plugin/isLeapYear.d.ts
new file mode 100644
--- /dev/null
+++ b/types/plugin/isLeapYear.d.ts
@@ -0,0 +1,10 @@
+import { PluginFunc } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
+
+declare module 'dayjs' {
+  interface Dayjs {
+    isLeapYear(): boolean
+  }
+}
diff --git a/types/plugin/isSameOrAfter.d.ts b/types/plugin/isSameOrAfter.d.ts
new file mode 100644
--- /dev/null
+++ b/types/plugin/isSameOrAfter.d.ts
@@ -0,0 +1,10 @@
+import { PluginFunc, ConfigType, OpUnitType } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
+
+declare module 'dayjs' {
+  interface Dayjs {
+    isSameOrAfter(date: ConfigType, unit?: OpUnitType): boolean
+  }
+}
diff --git a/types/plugin/isSameOrBefore.d.ts b/types/plugin/isSameOrBefore.d.ts
new file mode 100644
--- /dev/null
+++ b/types/plugin/isSameOrBefore.d.ts
@@ -0,0 +1,10 @@
+import { PluginFunc, ConfigType, OpUnitType } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
+
+declare module 'dayjs' {
+  interface Dayjs {
+    isSameOrBefore(date: ConfigType, unit?: OpUnitType): boolean
+  }
+}
diff --git a/types/plugin/localizedFormat.d.ts b/types/plugin/localizedFormat.d.ts
new file mode 100644
--- /dev/null
+++ b/types/plugin/localizedFormat.d.ts
@@ -0,0 +1,4 @@
+import { PluginFunc } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
diff --git a/types/plugin/quarterOfYear.d.ts b/types/plugin/quarterOfYear.d.ts
new file mode 100644
--- /dev/null
+++ b/types/plugin/quarterOfYear.d.ts
@@ -0,0 +1,10 @@
+import { PluginFunc } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
+
+declare module 'dayjs' {
+  interface Dayjs {
+    quarter(): number
+  }
+}
diff --git a/types/plugin/relativeTime.d.ts b/types/plugin/relativeTime.d.ts
new file mode 100644
--- /dev/null
+++ b/types/plugin/relativeTime.d.ts
@@ -0,0 +1,13 @@
+import { PluginFunc, DateType } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
+
+declare module 'dayjs' {
+  interface Dayjs {
+    fromNow(withoutSuffix?: boolean): string
+    from(compared: DateType, withoutSuffix?: boolean): string
+    toNow(withoutSuffix?: boolean): string
+    to(compared: DateType, withoutSuffix?: boolean): string
+  }
+}
diff --git a/types/plugin/weekOfYear.d.ts b/types/plugin/weekOfYear.d.ts
new file mode 100644
--- /dev/null
+++ b/types/plugin/weekOfYear.d.ts
@@ -0,0 +1,10 @@
+import { PluginFunc } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
+
+declare module 'dayjs' {
+  interface Dayjs {
+    week(): number
+  }
+}


Test patch:
diff --git a/test/index.d.test.ts b/test/index.d.test.ts
deleted file mode 100644
--- a/test/index.d.test.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import dayjs from '../src'
-
-dayjs()
-
-dayjs('1993-03-1')
-
-dayjs(730944000000)
-
-dayjs(new Date(1993, 3, 1))
-
-dayjs().clone()
-
-dayjs().isValid()
-
-dayjs().year()
-
-dayjs().month()
-
-dayjs().date()
-
-dayjs().day()
-
-dayjs().hour()
-
-dayjs().minute()
-
-dayjs().second()
-
-dayjs().millisecond()
-
-dayjs().set('month', 3)
-dayjs().set('second', 30)
-
-dayjs().add(7, 'day')
-
-dayjs().subtract(7, 'year')
-
-dayjs().startOf('year')
-
-dayjs().endOf('month')
-
-dayjs().startOf('month').add(1, 'day').subtract(1, 'year')
-
-dayjs().format()
-dayjs().format('[YYYY] MM-DDTHH:mm:ssZ')
-
-dayjs().diff(dayjs(), 'year')
-
-dayjs().valueOf()
-
-dayjs().unix()
-
-dayjs().daysInMonth()
-
-dayjs().toDate()
-
-dayjs().toArray()
-
-dayjs().toJSON()
-
-dayjs().toISOString()
-
-dayjs().toObject()
-
-dayjs().toString()
-
-dayjs().isBefore(dayjs())
-
-dayjs().isSame(dayjs())
-
-dayjs().isAfter(dayjs())
-
-dayjs().isBefore(dayjs(), 'minutes')
-
-dayjs().isSame(dayjs(), 'hours')
-
-dayjs().isAfter(dayjs(), 'year')
-
-dayjs('2000-01-01').isLeapYear()
diff --git a/test/manipulate.test.js b/test/manipulate.test.js
--- a/test/manipulate.test.js
+++ b/test/manipulate.test.js
@@ -22,10 +22,6 @@ describe('StartOf EndOf', () => {
     })
   })
 
-  it('StartOf EndOf Week with week start setting', () => {
-    expect(dayjs().locale({ name: 'test', weekStart: 1 }).startOf('week').valueOf()).toBe(moment().startOf('week').add(1, 'day').valueOf())
-  })
-
   it('StartOf EndOf Other -> no change', () => {
     expect(dayjs().startOf('otherString').valueOf()).toBe(moment().startOf('otherString').valueOf())
     expect(dayjs().endOf('otherString').valueOf()).toBe(moment().endOf('otherString').valueOf())
diff --git a/test/parse.test.js b/test/parse.test.js
--- a/test/parse.test.js
+++ b/test/parse.test.js
@@ -15,7 +15,7 @@ describe('Parse', () => {
     expect(dayjs().valueOf()).toBe(moment().valueOf())
   })
 
-  it('String 20130208', () => {
+  it('moment-js like formatted dates', () => {
     global.console.warn = jest.genMockFunction()// moment.js '2018-4-1 1:1:1:22' will throw warn
     let d = '20130108'
     expect(dayjs(d).valueOf()).toBe(moment(d).valueOf())
@@ -46,6 +46,40 @@ describe('Parse', () => {
     expect(dayjs(time).valueOf()).toBe(moment(time).valueOf())
   })
 
+  it('String RFC 2822, time and zone', () => {
+    const time = 'Mon, 11 Feb 2019 09:46:50 GMT+1'
+    const expected = '2019-02-11T08:46:50.000Z'
+    const d = dayjs(time)
+    expect(d.toISOString()).toEqual(expected)
+    expect(d.valueOf()).toBe(moment(time).valueOf())
+  })
+
+  it('String ECMAScript, time and zone', () => {
+    // should parse dates formatted in ECMA script format
+    // see https://www.ecma-international.org/ecma-262/9.0/index.html#sec-date.prototype.tostring
+    const time = 'Mon Feb 11 2019 11:01:37 GMT+0100 (Mitteleuropäische Normalzeit)'
+    const expected = '2019-02-11T10:01:37.000Z'
+    const d = dayjs(time)
+    expect(d.toISOString()).toEqual(expected)
+    expect(d.valueOf()).toBe(moment(time).valueOf())
+  })
+
+  it('rejects invalid values', () => {
+    expect(dayjs({}).isValid()).toBe(false)
+    expect(dayjs(() => '2018-01-01').isValid()).toBe(false)
+    expect(dayjs(Infinity).isValid()).toBe(false)
+    expect(dayjs(NaN).isValid()).toBe(false)
+    expect(dayjs([2018, 5, 1, 13, 52, 44]).isValid()).toBe(false) // Arrays with time part
+  })
+
+  it('parses Arrays with date part', () => {
+    const dateParts = [2018, 5, 1]
+    const expected = '2018-05-01T00:00:00.000Z'
+    const d = dayjs(dateParts)
+    const normalized = d.add(d.utcOffset(), 'minutes') // make test run in every timezone
+    expect(normalized.toISOString()).toEqual(expected)
+  })
+
   it('String Other, Null and isValid', () => {
     global.console.warn = jest.genMockFunction()// moment.js otherString will throw warn
     expect(dayjs('otherString').toString().toLowerCase()).toBe(moment('otherString').toString().toLowerCase())
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
@@ -2,6 +2,7 @@ import MockDate from 'mockdate'
 import moment from 'moment'
 import dayjs from '../../src'
 import customParseFormat from '../../src/plugin/customParseFormat'
+import uk from '../../src/locale/uk'
 
 dayjs.extend(customParseFormat)
 
@@ -84,3 +85,69 @@ it('fails with an invalid format', () => {
   expect(dayjs(input, format).format().toLowerCase())
     .toBe(moment(input, format).format().toLowerCase())
 })
+
+it('parse month from string', () => {
+  const input = '2018 February 03'
+  const format = 'YYYY MMMM DD'
+  expect(dayjs(input, format).valueOf()).toBe(moment(input, format).valueOf())
+})
+
+it('parse month from short string', () => {
+  const input = '2018 Feb 03'
+  const format = 'YYYY MMM DD'
+  expect(dayjs(input, format).valueOf()).toBe(moment(input, format).valueOf())
+})
+
+it('parse month from string with locale in config', () => {
+  const input = '2018 лютий 03'
+  const format = 'YYYY MMMM DD'
+
+  expect(dayjs(input, { format, locale: uk }).valueOf()).toBe(moment(input, format, 'uk').valueOf())
+})
+
+it('parse month from short string with locale in config', () => {
+  const input = '2018 трав 03'
+  const format = 'YYYY MMM DD'
+  expect(dayjs(input, { format, locale: uk }).valueOf()).toBe(moment(input, format, 'uk').valueOf())
+})
+
+it('parse month from short string with locale in argument', () => {
+  const input = '2018 трав 03'
+  const format = 'YYYY MMM DD'
+  expect(dayjs(input, format, 'uk').valueOf()).toBe(moment(input, format, 'uk').valueOf())
+})
+
+it('parse month from string with locale in argument', () => {
+  const input = '2018 лютий 03'
+  const format = 'YYYY MMMM DD'
+
+  expect(dayjs(input, format, 'uk').valueOf()).toBe(moment(input, format, 'uk').valueOf())
+})
+
+it('return Invalid Date when parse corrupt string', () => {
+  const input = '2018 Turnip 03'
+  const format = 'YYYY MMMM DD'
+  expect(dayjs(input, format).format()).toBe('Invalid Date')
+})
+
+it('return Invalid Date when parse corrupt short string', () => {
+  const input = '2018 Dog 03'
+  const format = 'YYYY MMM DD'
+  expect(dayjs(input, format).format()).toBe('Invalid Date')
+})
+
+it('correctly parse month from string after changing locale globally', () => {
+  const input = '2018 лютий 03'
+  const format = 'YYYY MMMM DD'
+
+  const dayjsLocale = dayjs().$locale()
+  const momentLocale = moment.locale()
+  try {
+    dayjs.locale(uk)
+    moment.locale('uk')
+    expect(dayjs(input, format).valueOf()).toBe(moment(input, format).valueOf())
+  } finally {
+    dayjs.locale(dayjsLocale)
+    moment.locale(momentLocale)
+  }
+})
diff --git a/test/plugin/declarations.test.js b/test/plugin/declarations.test.js
new file mode 100644
--- /dev/null
+++ b/test/plugin/declarations.test.js
@@ -0,0 +1,13 @@
+import fs from 'fs'
+import path from 'path'
+
+const pluginDir = '../../src/plugin'
+const pluginTypeDir = '../../types/plugin'
+
+it('Plugin declarations', () => {
+  fs.readdirSync(path.join(__dirname, pluginDir))
+    .forEach((l) => {
+      expect(fs.existsSync(path.join(__dirname, pluginTypeDir, `${l}.d.ts`)))
+        .toBe(true)
+    })
+})