Repository: iamkun/dayjs (JavaScript)

Issue:
wrong day in my timezone (UTC+3)
**Describe the bug**
I noticed a utc test failing after midnight at my timezone.

**Expected behavior**
Run tests.

**Information**
 - Day.js dev branch
 - OS: macos
 - Node v14.11.0
 - Time zone:(UTC+3)

![Screenshot 2020-10-02 at 1 55 43](https://user-images.githubusercontent.com/130606/94871507-d0b0a900-0452-11eb-8cbf-2d72c9ea184f.png)

```
 FAIL  test/plugin/objectSupport.test.js
  ● Constructor from Object UTC

    expect(received).toBe(expected) // Object.is equality
    
    Expected value to be:
      "2020-10-02 15:25:50.125"
    Received:
      "2020-10-01 15:25:50.125"

      121 |   for (let i = 0; i < tests.length; i += 1) {
      122 |     expect(dayjs.utc(tests[i][0]).format(fmt)).toBe(tests[i][1])
    > 123 |     expect(moment.utc(tests[i][0]).format(fmt)).toBe(tests[i][1])
      124 |   }
      125 | })
      126 | it('Set from Object', () => {
      
      at Object.<anonymous> (test/plugin/objectSupport.test.js:123:49)


Test Suites: 1 failed, 58 passed, 59 total
Tests:       1 failed, 569 passed, 570 total
Snapshots:   0 total
Time:        3.982s
Ran all test suites.
```



Hints:

Patch:
diff --git a/src/locale/ar-kw.js b/src/locale/ar-kw.js
--- a/src/locale/ar-kw.js
+++ b/src/locale/ar-kw.js
@@ -3,9 +3,9 @@ import dayjs from 'dayjs'
 
 const locale = {
   name: 'ar-kw',
-  weekdays: 'الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'),
+  weekdays: 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'),
   months: 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'),
-  weekdaysShort: 'احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت'.split('_'),
+  weekdaysShort: 'احد_اثنين_ثلاثاء_اربعاء_خميس_جمعة_سبت'.split('_'),
   monthsShort: 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'),
   weekdaysMin: 'ح_ن_ث_ر_خ_ج_س'.split('_'),
   ordinal: n => n,
diff --git a/src/locale/ar-ma.js b/src/locale/ar-ma.js
--- a/src/locale/ar-ma.js
+++ b/src/locale/ar-ma.js
@@ -3,10 +3,10 @@ import dayjs from 'dayjs'
 
 const locale = {
   name: 'ar-ma',
-  weekdays: 'الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'),
+  weekdays: 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'),
   months: 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'),
   weekStart: 6,
-  weekdaysShort: 'احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت'.split('_'),
+  weekdaysShort: 'احد_إثنين_ثلاثاء_اربعاء_خميس_جمعة_سبت'.split('_'),
   monthsShort: 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'),
   weekdaysMin: 'ح_ن_ث_ر_خ_ج_س'.split('_'),
   ordinal: n => n,
diff --git a/src/locale/br.js b/src/locale/br.js
--- a/src/locale/br.js
+++ b/src/locale/br.js
@@ -1,6 +1,47 @@
 // Breton [br]
 import dayjs from 'dayjs'
 
+function lastNumber(number) {
+  if (number > 9) {
+    return lastNumber(number % 10)
+  }
+  return number
+}
+function softMutation(text) {
+  const mutationTable = {
+    m: 'v',
+    b: 'v',
+    d: 'z'
+  }
+  return mutationTable[text.charAt(0)] + text.substring(1)
+}
+function mutation(text, number) {
+  if (number === 2) {
+    return softMutation(text)
+  }
+  return text
+}
+function relativeTimeWithMutation(number, withoutSuffix, key) {
+  const format = {
+    mm: 'munutenn',
+    MM: 'miz',
+    dd: 'devezh'
+  }
+  return `${number} ${mutation(format[key], number)}`
+}
+function specialMutationForYears(number) {
+  switch (lastNumber(number)) {
+    case 1:
+    case 3:
+    case 4:
+    case 5:
+    case 9:
+      return `${number} bloaz`
+    default:
+      return `${number} vloaz`
+  }
+}
+
 const locale = {
   name: 'br',
   weekdays: 'Sul_Lun_Meurzh_Mercʼher_Yaou_Gwener_Sadorn'.split('_'),
@@ -18,6 +59,21 @@ const locale = {
     LLL: 'D [a viz] MMMM YYYY h[e]mm A',
     LLLL: 'dddd, D [a viz] MMMM YYYY h[e]mm A'
   },
+  relativeTime: {
+    future: 'a-benn %s',
+    past: '%s ʼzo',
+    s: 'un nebeud segondennoù',
+    m: 'ur vunutenn',
+    mm: relativeTimeWithMutation,
+    h: 'un eur',
+    hh: '%d eur',
+    d: 'un devezh',
+    dd: relativeTimeWithMutation,
+    M: 'ur miz',
+    MM: relativeTimeWithMutation,
+    y: 'ur bloaz',
+    yy: specialMutationForYears
+  },
   meridiem: hour => (hour < 12 ? 'a.m.' : 'g.m.') // a-raok merenn | goude merenn
 }
 
diff --git a/src/locale/ca.js b/src/locale/ca.js
--- a/src/locale/ca.js
+++ b/src/locale/ca.js
@@ -35,7 +35,16 @@ const locale = {
     y: 'un any',
     yy: '%d anys'
   },
-  ordinal: n => `${n}º`
+  ordinal: (n) => {
+    let ord
+
+    if (n === 1 || n === 3) ord = 'r'
+    else if (n === 2) ord = 'n'
+    else if (n === 4) ord = 't'
+    else ord = 'è'
+
+    return `${n}${ord}`
+  }
 }
 
 dayjs.locale(locale, null, true)
diff --git a/src/locale/de.js b/src/locale/de.js
--- a/src/locale/de.js
+++ b/src/locale/de.js
@@ -1,6 +1,28 @@
 // German [de]
 import dayjs from 'dayjs'
 
+const texts = {
+  s: 'ein paar Sekunden',
+  m: ['eine Minute', 'einer Minute'],
+  mm: '%d Minuten',
+  h: ['eine Stunde', 'einer Stunde'],
+  hh: '%d Stunden',
+  d: ['ein Tag', 'einem Tag'],
+  dd: ['%d Tage', '%d Tagen'],
+  M: ['ein Monat', 'einem Monat'],
+  MM: ['%d Monate', '%d Monaten'],
+  y: ['ein Jahr', 'einem Jahr'],
+  yy: ['%d Jahre', '%d Jahren']
+}
+
+function relativeTimeFormatter(number, withoutSuffix, key) {
+  let l = texts[key]
+  if (Array.isArray(l)) {
+    l = l[withoutSuffix ? 0 : 1]
+  }
+  return l.replace('%d', number)
+}
+
 const locale = {
   name: 'de',
   weekdays: 'Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag'.split('_'),
@@ -21,17 +43,17 @@ const locale = {
   relativeTime: {
     future: 'in %s',
     past: 'vor %s',
-    s: 'wenigen Sekunden',
-    m: 'einer Minute',
-    mm: '%d Minuten',
-    h: 'einer Stunde',
-    hh: '%d Stunden',
-    d: 'einem Tag',
-    dd: '%d Tagen',
-    M: 'einem Monat',
-    MM: '%d Monaten',
-    y: 'einem Jahr',
-    yy: '%d Jahren'
+    s: relativeTimeFormatter,
+    m: relativeTimeFormatter,
+    mm: relativeTimeFormatter,
+    h: relativeTimeFormatter,
+    hh: relativeTimeFormatter,
+    d: relativeTimeFormatter,
+    dd: relativeTimeFormatter,
+    M: relativeTimeFormatter,
+    MM: relativeTimeFormatter,
+    y: relativeTimeFormatter,
+    yy: relativeTimeFormatter
   }
 }
 
diff --git a/src/locale/sr-cyrl.js b/src/locale/sr-cyrl.js
--- a/src/locale/sr-cyrl.js
+++ b/src/locale/sr-cyrl.js
@@ -1,6 +1,42 @@
 // Serbian Cyrillic [sr-cyrl]
 import dayjs from 'dayjs'
 
+const translator = {
+  words: {
+    m: ['један минут', 'једног минута'],
+    mm: ['%d минут', '%d минута', '%d минута'],
+    h: ['један сат', 'једног сата'],
+    hh: ['%d сат', '%d сата', '%d сати'],
+    d: ['један дан', 'једног дана'],
+    dd: ['%d дан', '%d дана', '%d дана'],
+    M: ['један месец', 'једног месеца'],
+    MM: ['%d месец', '%d месеца', '%d месеци'],
+    y: ['једну годину', 'једне године'],
+    yy: ['%d годину', '%d године', '%d година']
+  },
+  correctGrammarCase(number, wordKey) {
+    if (number % 10 >= 1 && number % 10 <= 4 && (number % 100 < 10 || number % 100 >= 20)) {
+      return number % 10 === 1 ? wordKey[0] : wordKey[1]
+    }
+    return wordKey[2]
+  },
+  relativeTimeFormatter(number, withoutSuffix, key, isFuture) {
+    const wordKey = translator.words[key]
+
+    if (key.length === 1) {
+      // Nominativ
+      if (key === 'y' && withoutSuffix) return 'једна година'
+      return isFuture || withoutSuffix ? wordKey[0] : wordKey[1]
+    }
+
+    const word = translator.correctGrammarCase(number, wordKey)
+    // Nominativ
+    if (key === 'yy' && withoutSuffix && word === '%d годину') return `${number} година`
+
+    return word.replace('%d', number)
+  }
+}
+
 const locale = {
   name: 'sr-cyrl',
   weekdays: 'Недеља_Понедељак_Уторак_Среда_Четвртак_Петак_Субота'.split('_'),
@@ -12,26 +48,26 @@ const locale = {
   relativeTime: {
     future: 'за %s',
     past: 'пре %s',
-    s: 'секунда',
-    m: 'минут',
-    mm: '%d минута',
-    h: 'сат',
-    hh: '%d сати',
-    d: 'дан',
-    dd: '%d дана',
-    M: 'месец',
-    MM: '%d месеци',
-    y: 'година',
-    yy: '%d године'
+    s: 'неколико секунди',
+    m: translator.relativeTimeFormatter,
+    mm: translator.relativeTimeFormatter,
+    h: translator.relativeTimeFormatter,
+    hh: translator.relativeTimeFormatter,
+    d: translator.relativeTimeFormatter,
+    dd: translator.relativeTimeFormatter,
+    M: translator.relativeTimeFormatter,
+    MM: translator.relativeTimeFormatter,
+    y: translator.relativeTimeFormatter,
+    yy: translator.relativeTimeFormatter
   },
   ordinal: n => `${n}.`,
   formats: {
     LT: 'H:mm',
     LTS: 'H:mm:ss',
-    L: 'DD.MM.YYYY',
-    LL: 'D. MMMM YYYY',
-    LLL: 'D. MMMM YYYY H:mm',
-    LLLL: 'dddd, D. MMMM YYYY H:mm'
+    L: 'D. M. YYYY.',
+    LL: 'D. MMMM YYYY.',
+    LLL: 'D. MMMM YYYY. H:mm',
+    LLLL: 'dddd, D. MMMM YYYY. H:mm'
   }
 }
 
diff --git a/src/locale/sr.js b/src/locale/sr.js
--- a/src/locale/sr.js
+++ b/src/locale/sr.js
@@ -1,6 +1,42 @@
 // Serbian [sr]
 import dayjs from 'dayjs'
 
+const translator = {
+  words: {
+    m: ['jedan minut', 'jednog minuta'],
+    mm: ['%d minut', '%d minuta', '%d minuta'],
+    h: ['jedan sat', 'jednog sata'],
+    hh: ['%d sat', '%d sata', '%d sati'],
+    d: ['jedan dan', 'jednog dana'],
+    dd: ['%d dan', '%d dana', '%d dana'],
+    M: ['jedan mesec', 'jednog meseca'],
+    MM: ['%d mesec', '%d meseca', '%d meseci'],
+    y: ['jednu godinu', 'jedne godine'],
+    yy: ['%d godinu', '%d godine', '%d godina']
+  },
+  correctGrammarCase(number, wordKey) {
+    if (number % 10 >= 1 && number % 10 <= 4 && (number % 100 < 10 || number % 100 >= 20)) {
+      return number % 10 === 1 ? wordKey[0] : wordKey[1]
+    }
+    return wordKey[2]
+  },
+  relativeTimeFormatter(number, withoutSuffix, key, isFuture) {
+    const wordKey = translator.words[key]
+
+    if (key.length === 1) {
+      // Nominativ
+      if (key === 'y' && withoutSuffix) return 'jedna godina'
+      return isFuture || withoutSuffix ? wordKey[0] : wordKey[1]
+    }
+
+    const word = translator.correctGrammarCase(number, wordKey)
+    // Nominativ
+    if (key === 'yy' && withoutSuffix && word === '%d godinu') return `${number} godina`
+
+    return word.replace('%d', number)
+  }
+}
+
 const locale = {
   name: 'sr',
   weekdays: 'Nedelja_Ponedeljak_Utorak_Sreda_Četvrtak_Petak_Subota'.split('_'),
@@ -12,26 +48,26 @@ const locale = {
   relativeTime: {
     future: 'za %s',
     past: 'pre %s',
-    s: 'sekunda',
-    m: 'minut',
-    mm: '%d minuta',
-    h: 'sat',
-    hh: '%d sati',
-    d: 'dan',
-    dd: '%d dana',
-    M: 'mesec',
-    MM: '%d meseci',
-    y: 'godina',
-    yy: '%d godine'
+    s: 'nekoliko sekundi',
+    m: translator.relativeTimeFormatter,
+    mm: translator.relativeTimeFormatter,
+    h: translator.relativeTimeFormatter,
+    hh: translator.relativeTimeFormatter,
+    d: translator.relativeTimeFormatter,
+    dd: translator.relativeTimeFormatter,
+    M: translator.relativeTimeFormatter,
+    MM: translator.relativeTimeFormatter,
+    y: translator.relativeTimeFormatter,
+    yy: translator.relativeTimeFormatter
   },
   ordinal: n => `${n}.`,
   formats: {
     LT: 'H:mm',
     LTS: 'H:mm:ss',
-    L: 'DD.MM.YYYY',
-    LL: 'D. MMMM YYYY',
-    LLL: 'D. MMMM YYYY H:mm',
-    LLLL: 'dddd, D. MMMM YYYY H:mm'
+    L: 'D. M. YYYY.',
+    LL: 'D. MMMM YYYY.',
+    LLL: 'D. MMMM YYYY. H:mm',
+    LLLL: 'dddd, D. MMMM YYYY. H:mm'
   }
 }
 
diff --git a/src/plugin/arraySupport/index.js b/src/plugin/arraySupport/index.js
new file mode 100644
--- /dev/null
+++ b/src/plugin/arraySupport/index.js
@@ -0,0 +1,25 @@
+export default (o, c, dayjs) => {
+  const proto = c.prototype
+  const parseDate = (cfg) => {
+    const { date, utc } = cfg
+    if (Array.isArray(date)) {
+      if (utc) {
+        if (!date.length) {
+          return new Date()
+        }
+        return new Date(Date.UTC.apply(null, date))
+      }
+      if (date.length === 1) {
+        return dayjs(String(date[0])).toDate()
+      }
+      return new (Function.prototype.bind.apply(Date, [null].concat(date)))()
+    }
+    return date
+  }
+
+  const oldParse = proto.parse
+  proto.parse = function (cfg) {
+    cfg.date = parseDate.bind(this)(cfg)
+    oldParse.bind(this)(cfg)
+  }
+}
diff --git a/src/plugin/duration/index.js b/src/plugin/duration/index.js
--- a/src/plugin/duration/index.js
+++ b/src/plugin/duration/index.js
@@ -177,4 +177,13 @@ export default (option, Dayjs, dayjs) => {
     return wrapper(input, { $l }, unit)
   }
   dayjs.isDuration = isDuration
+
+  const oldAdd = Dayjs.prototype.add
+  Dayjs.prototype.add = function (addition, units) {
+    if (isDuration(addition)) {
+      addition = addition.asMilliseconds()
+      units = 'ms'
+    }
+    return oldAdd.bind(this)(addition, units)
+  }
 }
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
@@ -1,3 +1,5 @@
+import { t } from '../localizedFormat'
+
 export default (o, c, dayjs) => { // locale needed later
   const proto = c.prototype
   const getLocalePart = part => (part && (part.indexOf ? part : part.s))
@@ -11,6 +13,9 @@ export default (o, c, dayjs) => { // locale needed later
     return result.map((_, index) => (result[(index + (weekStart || 0)) % 7]))
   }
   const getDayjsLocaleObject = () => dayjs.Ls[dayjs.locale()]
+  const getLongDateFormat = (l, format) =>
+    l.formats[format] || t(l.formats[format.toUpperCase()])
+
   const localeData = function () {
     return {
       months: instance =>
@@ -23,7 +28,8 @@ export default (o, c, dayjs) => { // locale needed later
         (instance ? instance.format('dd') : getShort(this, 'weekdaysMin', 'weekdays', 2)),
       weekdaysShort: instance =>
         (instance ? instance.format('ddd') : getShort(this, 'weekdaysShort', 'weekdays', 3)),
-      longDateFormat: format => this.$locale().formats[format]
+      longDateFormat: format => getLongDateFormat(this.$locale(), format)
+
     }
   }
   proto.localeData = function () {
@@ -38,7 +44,8 @@ export default (o, c, dayjs) => { // locale needed later
       weekdaysShort: () => dayjs.weekdaysShort(),
       weekdaysMin: () => dayjs.weekdaysMin(),
       months: () => dayjs.months(),
-      monthsShort: () => dayjs.monthsShort()
+      monthsShort: () => dayjs.monthsShort(),
+      longDateFormat: format => getLongDateFormat(localeObject, format)
     }
   }
 
diff --git a/src/plugin/localizedFormat/index.js b/src/plugin/localizedFormat/index.js
--- a/src/plugin/localizedFormat/index.js
+++ b/src/plugin/localizedFormat/index.js
@@ -1,5 +1,8 @@
 import { FORMAT_DEFAULT } from '../../constant'
 
+export const t = format =>
+  format.replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g, (_, a, b) => a || b.slice(1))
+
 export default (o, c, d) => {
   const proto = c.prototype
   const oldFormat = proto.format
@@ -12,7 +15,6 @@ export default (o, c, d) => {
     LLLL: 'dddd, MMMM D, YYYY h:mm A'
   }
   d.en.formats = englishFormats
-  const t = format => format.replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g, (_, a, b) => a || b.slice(1))
   proto.format = function (formatStr = FORMAT_DEFAULT) {
     const { formats = {} } = this.$locale()
     const result = formatStr.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g, (_, a, b) => {
diff --git a/src/plugin/objectSupport/index.js b/src/plugin/objectSupport/index.js
--- a/src/plugin/objectSupport/index.js
+++ b/src/plugin/objectSupport/index.js
@@ -1,4 +1,4 @@
-export default (o, c) => {
+export default (o, c, dayjs) => {
   const proto = c.prototype
   const isObject = obj => !(obj instanceof Date) && !(obj instanceof Array) && obj instanceof Object
   const prettyUnit = (u) => {
@@ -9,13 +9,16 @@ export default (o, c) => {
     const { date, utc } = cfg
     const $d = {}
     if (isObject(date)) {
-      const now = new Date()
+      if (!Object.keys(date).length) {
+        return new Date()
+      }
+      const now = utc ? dayjs.utc() : dayjs()
       Object.keys(date).forEach((k) => {
         $d[prettyUnit(k)] = date[k]
       })
-      const d = $d.day || ((!$d.year && !($d.month >= 0)) ? now.getDate() : 1)
-      const y = $d.year || now.getFullYear()
-      const M = $d.month >= 0 ? $d.month : ((!$d.year && !$d.day) ? now.getMonth() : 0)// eslint-disable-line no-nested-ternary,max-len
+      const d = $d.day || ((!$d.year && !($d.month >= 0)) ? now.date() : 1)
+      const y = $d.year || now.year()
+      const M = $d.month >= 0 ? $d.month : ((!$d.year && !$d.day) ? now.month() : 0)// eslint-disable-line no-nested-ternary,max-len
       const h = $d.hour || 0
       const m = $d.minute || 0
       const s = $d.second || 0
diff --git a/types/plugin/arraySupport.d.ts b/types/plugin/arraySupport.d.ts
new file mode 100755
--- /dev/null
+++ b/types/plugin/arraySupport.d.ts
@@ -0,0 +1,4 @@
+import { PluginFunc } from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
diff --git a/types/plugin/duration.d.ts b/types/plugin/duration.d.ts
--- a/types/plugin/duration.d.ts
+++ b/types/plugin/duration.d.ts
@@ -1,58 +1,61 @@
 import { PluginFunc } from 'dayjs'
 
 declare const plugin: PluginFunc
-export default plugin
+export as namespace plugin;
+export = plugin
 
-type DurationInputType = string | number | object
-type DurationAddType = number | object | Duration
+declare namespace plugin {
+  type DurationInputType = string | number | object
+  type DurationAddType = number | object | Duration
 
-export declare class Duration {
-  constructor (input: DurationInputType, unit?: string, locale?: string)
+  interface Duration {
+    new (input: DurationInputType, unit?: string, locale?: string): Duration
 
-  clone(): Duration
-  
-  humanize(withSuffix?: boolean): string
+    clone(): Duration
 
-  milliseconds(): number
-  asMilliseconds(): number
+    humanize(withSuffix?: boolean): string
 
-  seconds(): number
-  asSeconds(): number
+    milliseconds(): number
+    asMilliseconds(): number
 
-  minutes(): number
-  asMinutes(): number
+    seconds(): number
+    asSeconds(): number
 
-  hours(): number
-  asHours(): number
+    minutes(): number
+    asMinutes(): number
 
-  days(): number
-  asDays(): number
+    hours(): number
+    asHours(): number
 
-  weeks(): number
-  asWeeks(): number
+    days(): number
+    asDays(): number
 
-  months(): number
-  asMonths(): number
+    weeks(): number
+    asWeeks(): number
 
-  years(): number
-  asYears(): number
+    months(): number
+    asMonths(): number
 
-  as(unit: string): number
+    years(): number
+    asYears(): number
 
-  get(unit: string): number
+    as(unit: string): number
 
-  add(input: DurationAddType, unit? : string): Duration
-  
-  subtract(input: DurationAddType, unit? : string): Duration
+    get(unit: string): number
 
-  toJSON(): string
+    add(input: DurationAddType, unit? : string): Duration
 
-  toISOString(): string
+    subtract(input: DurationAddType, unit? : string): Duration
 
-  locale(locale: string): Duration
+    toJSON(): string
+
+    toISOString(): string
+
+    locale(locale: string): Duration
+  }
 }
 
 declare module 'dayjs' {
-  export function duration(input?: DurationInputType , unit?: string): Duration
-  export function isDuration(d: any): d is Duration
+  export function duration(input?: plugin.DurationInputType , unit?: string): plugin.Duration
+  export function isDuration(d: any): d is plugin.Duration
 }


Test patch:
diff --git a/test/locale/br.test.js b/test/locale/br.test.js
new file mode 100644
--- /dev/null
+++ b/test/locale/br.test.js
@@ -0,0 +1,54 @@
+import moment from 'moment'
+import MockDate from 'mockdate'
+import dayjs from '../../src'
+import relativeTime from '../../src/plugin/relativeTime'
+import '../../src/locale/br'
+
+dayjs.extend(relativeTime)
+
+beforeEach(() => {
+  MockDate.set(new Date())
+})
+
+afterEach(() => {
+  MockDate.reset()
+})
+
+it('Format Month with locale function', () => {
+  for (let i = 0; i <= 7; i += 1) {
+    const dayjsBR = dayjs().locale('br').add(i, 'day')
+    const momentBR = moment().locale('br').add(i, 'day')
+    const testFormat1 = 'DD MMMM YYYY MMM'
+    const testFormat2 = 'MMMM'
+    const testFormat3 = 'MMM'
+    expect(dayjsBR.format(testFormat1)).toEqual(momentBR.format(testFormat1))
+    expect(dayjsBR.format(testFormat2)).toEqual(momentBR.format(testFormat2))
+    expect(dayjsBR.format(testFormat3)).toEqual(momentBR.format(testFormat3))
+  }
+})
+
+it('RelativeTime: Time from X', () => {
+  const T = [
+    [44.4, 'second'], // a few seconds
+    [89.5, 'second'], // a minute
+    [130, 'second'], // two minutes
+    [43, 'minute'], // 44 minutes
+    [1, 'hour'], // 1 hour
+    [21, 'hour'], // 21 hours
+    [2, 'day'], // 2 days
+    [25, 'day'], // 25 days
+    [2, 'month'], // 2 months
+    [10, 'month'], // 10 months
+    [18, 'month'], // 2 years
+    [15, 'year'] // 15 years
+  ]
+
+  T.forEach((t) => {
+    dayjs.locale('br')
+    moment.locale('br')
+    expect(dayjs().from(dayjs().add(t[0], t[1])))
+      .toBe(moment().from(moment().add(t[0], t[1])))
+    expect(dayjs().from(dayjs().add(t[0], t[1]), true))
+      .toBe(moment().from(moment().add(t[0], t[1]), true))
+  })
+})
diff --git a/test/locale/de.test.js b/test/locale/de.test.js
new file mode 100644
--- /dev/null
+++ b/test/locale/de.test.js
@@ -0,0 +1,79 @@
+import MockDate from 'mockdate'
+import moment from 'moment'
+import dayjs from '../../src'
+import relativeTime from '../../src/plugin/relativeTime'
+import '../../src/locale/de'
+
+dayjs.extend(relativeTime)
+
+beforeEach(() => {
+  MockDate.set(new Date())
+})
+
+afterEach(() => {
+  MockDate.reset()
+})
+
+it('German locale relative time in past and future with suffix', () => {
+  const cases = [
+    [1, 's', 'in ein paar Sekunden'],
+    [-1, 's', 'vor ein paar Sekunden'],
+    [1, 'm', 'in einer Minute'],
+    [-1, 'm', 'vor einer Minute'],
+    [1, 'h', 'in einer Stunde'],
+    [-1, 'h', 'vor einer Stunde'],
+    [1, 'd', 'in einem Tag'],
+    [-1, 'd', 'vor einem Tag'],
+    [1, 'M', 'in einem Monat'],
+    [-1, 'M', 'vor einem Monat'],
+    [2, 'd', 'in 2 Tagen'],
+    [-2, 'd', 'vor 2 Tagen'],
+    [10, 'd', 'in 10 Tagen'],
+    [-10, 'd', 'vor 10 Tagen'],
+    [6, 'm', 'in 6 Minuten'],
+    [-6, 'm', 'vor 6 Minuten'],
+    [5, 'h', 'in 5 Stunden'],
+    [-5, 'h', 'vor 5 Stunden'],
+    [3, 'M', 'in 3 Monaten'],
+    [-3, 'M', 'vor 3 Monaten'],
+    [4, 'y', 'in 4 Jahren'],
+    [-4, 'y', 'vor 4 Jahren']
+  ]
+  cases.forEach((c) => {
+    expect(dayjs().add(c[0], c[1]).locale('de').fromNow())
+      .toBe(c[2])
+    expect(dayjs().add(c[0], c[1]).locale('de').fromNow())
+      .toBe(moment().add(c[0], c[1]).locale('de').fromNow())
+  })
+})
+
+it('German locale relative time in past and future without suffix', () => {
+  const cases = [
+    [1, 's', 'ein paar Sekunden'],
+    [-1, 's', 'ein paar Sekunden'],
+    [1, 'm', 'eine Minute'],
+    [-1, 'm', 'eine Minute'],
+    [1, 'h', 'eine Stunde'],
+    [-1, 'h', 'eine Stunde'],
+    [1, 'd', 'ein Tag'],
+    [-1, 'd', 'ein Tag'],
+    [2, 'd', '2 Tage'],
+    [-2, 'd', '2 Tage'],
+    [10, 'd', '10 Tage'],
+    [-10, 'd', '10 Tage'],
+    [6, 'm', '6 Minuten'],
+    [-6, 'm', '6 Minuten'],
+    [5, 'h', '5 Stunden'],
+    [-5, 'h', '5 Stunden'],
+    [3, 'M', '3 Monate'],
+    [-3, 'M', '3 Monate'],
+    [4, 'y', '4 Jahre'],
+    [-4, 'y', '4 Jahre']
+  ]
+  cases.forEach((c) => {
+    expect(dayjs().add(c[0], c[1]).locale('de').fromNow(true))
+      .toBe(c[2])
+    expect(dayjs().add(c[0], c[1]).locale('de').fromNow(true))
+      .toBe(moment().add(c[0], c[1]).locale('de').fromNow(true))
+  })
+})
diff --git a/test/locale/sr-cyrl.test.js b/test/locale/sr-cyrl.test.js
new file mode 100644
--- /dev/null
+++ b/test/locale/sr-cyrl.test.js
@@ -0,0 +1,54 @@
+import MockDate from 'mockdate'
+import dayjs from '../../src'
+import relativeTime from '../../src/plugin/relativeTime'
+import '../../src/locale/sr-cyrl'
+
+dayjs.extend(relativeTime)
+
+beforeEach(() => {
+  MockDate.set(new Date())
+})
+
+afterEach(() => {
+  MockDate.reset()
+})
+
+it('Serbian cyrillic locale relative time in past and future', () => {
+  const cases = [
+    [1, 's', 'за неколико секунди', 'неколико секунди'],
+    [-1, 's', 'пре неколико секунди', 'неколико секунди'],
+    [4, 's', 'за неколико секунди', 'неколико секунди'],
+    [1, 'm', 'за један минут', 'један минут'],
+    [-1, 'm', 'пре једног минута', 'један минут'],
+    [4, 'm', 'за 4 минута', '4 минута'],
+    [5, 'm', 'за 5 минута', '5 минута'],
+    [21, 'm', 'за 21 минут', '21 минут'],
+    [1, 'h', 'за један сат', 'један сат'],
+    [-1, 'h', 'пре једног сата', 'један сат'],
+    [4, 'h', 'за 4 сата', '4 сата'],
+    [5, 'h', 'за 5 сати', '5 сати'],
+    [21, 'h', 'за 21 сат', '21 сат'],
+    [1, 'd', 'за један дан', 'један дан'],
+    [-1, 'd', 'пре једног дана', 'један дан'],
+    [4, 'd', 'за 4 дана', '4 дана'],
+    [5, 'd', 'за 5 дана', '5 дана'],
+    [21, 'd', 'за 21 дан', '21 дан'],
+    [1, 'M', 'за један месец', 'један месец'],
+    [-1, 'M', 'пре једног месеца', 'један месец'],
+    [4, 'M', 'за 4 месеца', '4 месеца'],
+    [5, 'M', 'за 5 месеци', '5 месеци'],
+    [10, 'M', 'за 10 месеци', '10 месеци'],
+    [1, 'y', 'за једну годину', 'једна година'],
+    [-1, 'y', 'пре једне године', 'једна година'],
+    [4, 'y', 'за 4 године', '4 године'],
+    [5, 'y', 'за 5 година', '5 година'],
+    [21, 'y', 'за 21 годину', '21 година']
+  ]
+
+  cases.forEach((c) => {
+    expect(dayjs().add(c[0], c[1]).locale('sr-cyrl').fromNow()).toBe(c[2])
+    expect(dayjs().add(c[0], c[1]).locale('sr-cyrl').fromNow(true)).toBe(c[3])
+    // TODO: compare to momentjs once logic and grammar are fixed there
+  })
+})
+
diff --git a/test/locale/sr.test.js b/test/locale/sr.test.js
new file mode 100644
--- /dev/null
+++ b/test/locale/sr.test.js
@@ -0,0 +1,55 @@
+import MockDate from 'mockdate'
+import dayjs from '../../src'
+import relativeTime from '../../src/plugin/relativeTime'
+import '../../src/locale/sr'
+
+dayjs.extend(relativeTime)
+
+beforeEach(() => {
+  MockDate.set(new Date())
+})
+
+afterEach(() => {
+  MockDate.reset()
+})
+
+it('Serbian locale relative time in past and future', () => {
+  const cases = [
+    [1, 's', 'za nekoliko sekundi', 'nekoliko sekundi'],
+    [-1, 's', 'pre nekoliko sekundi', 'nekoliko sekundi'],
+    [4, 's', 'za nekoliko sekundi', 'nekoliko sekundi'],
+    [1, 'm', 'za jedan minut', 'jedan minut'],
+    [-1, 'm', 'pre jednog minuta', 'jedan minut'],
+    [4, 'm', 'za 4 minuta', '4 minuta'],
+    [5, 'm', 'za 5 minuta', '5 minuta'],
+    [21, 'm', 'za 21 minut', '21 minut'],
+    [1, 'h', 'za jedan sat', 'jedan sat'],
+    [-1, 'h', 'pre jednog sata', 'jedan sat'],
+    [4, 'h', 'za 4 sata', '4 sata'],
+    [5, 'h', 'za 5 sati', '5 sati'],
+    [21, 'h', 'za 21 sat', '21 sat'],
+    [1, 'd', 'za jedan dan', 'jedan dan'],
+    [-1, 'd', 'pre jednog dana', 'jedan dan'],
+    [4, 'd', 'za 4 dana', '4 dana'],
+    [5, 'd', 'za 5 dana', '5 dana'],
+    [21, 'd', 'za 21 dan', '21 dan'],
+    [1, 'M', 'za jedan mesec', 'jedan mesec'],
+    [-1, 'M', 'pre jednog meseca', 'jedan mesec'],
+    [4, 'M', 'za 4 meseca', '4 meseca'],
+    [5, 'M', 'za 5 meseci', '5 meseci'],
+    [10, 'M', 'za 10 meseci', '10 meseci'],
+    [1, 'y', 'za jednu godinu', 'jedna godina'],
+    [-1, 'y', 'pre jedne godine', 'jedna godina'],
+    [4, 'y', 'za 4 godine', '4 godine'],
+    [5, 'y', 'za 5 godina', '5 godina'],
+    [21, 'y', 'za 21 godinu', '21 godina']
+  ]
+
+  cases.forEach((c) => {
+    // With suffix
+    expect(dayjs().add(c[0], c[1]).locale('sr').fromNow()).toBe(c[2])
+    // Without suffix
+    expect(dayjs().add(c[0], c[1]).locale('sr').fromNow(true)).toBe(c[3])
+    // TODO: compare to momentjs once logic and grammar are fixed there
+  })
+})
diff --git a/test/plugin/arraySupport.test.js b/test/plugin/arraySupport.test.js
new file mode 100755
--- /dev/null
+++ b/test/plugin/arraySupport.test.js
@@ -0,0 +1,52 @@
+import MockDate from 'mockdate'
+import moment from 'moment'
+import dayjs from '../../src'
+import arraySupport from '../../src/plugin/arraySupport'
+import utc from '../../src/plugin/utc'
+
+dayjs.extend(utc)
+dayjs.extend(arraySupport)
+
+beforeEach(() => {
+  MockDate.set(new Date())
+})
+
+afterEach(() => {
+  MockDate.reset()
+})
+
+describe('parse empty array', () => {
+  it('local', () => {
+    expect(dayjs([]).format())
+      .toBe(moment([]).format())
+  })
+  it('utc', () => {
+    expect(dayjs.utc([]).format())
+      .toBe(moment.utc([]).format())
+  })
+})
+
+const testArrs = [
+  [2010, 1, 14, 15, 25, 50, 125],
+  [2010],
+  [2010, 6],
+  [2010, 6, 10]
+]
+
+describe('parse array local', () => {
+  testArrs.forEach((testArr) => {
+    it(testArr, () => {
+      expect(dayjs(testArr).format())
+        .toBe(moment(testArr).format())
+    })
+  })
+})
+
+describe('parse array utc', () => {
+  testArrs.forEach((testArr) => {
+    it(testArr, () => {
+      expect(dayjs.utc(testArr).format())
+        .toBe(moment.utc(testArr).format())
+    })
+  })
+})
diff --git a/test/plugin/duration.test.js b/test/plugin/duration.test.js
--- a/test/plugin/duration.test.js
+++ b/test/plugin/duration.test.js
@@ -139,6 +139,12 @@ describe('Add', () => {
   expect(a.add({ days: 5 }).days()).toBe(6)
 })
 
+describe('Add duration', () => {
+  const a = dayjs('2020-10-01')
+  const days = dayjs.duration(2, 'days')
+  expect(a.add(days).format('YYYY-MM-DD')).toBe('2020-10-03')
+})
+
 describe('Subtract', () => {
   const a = dayjs.duration(3, 'days')
   const b = dayjs.duration(2, 'days')
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
@@ -38,7 +38,7 @@ describe('Instance localeData', () => {
       expect(dayjsLocaleData.weekdaysMin()).toEqual(momentLocaleData.weekdaysMin())
       expect(dayjsLocaleData.weekdaysShort(d)).toBe(momentLocaleData.weekdaysShort(m))
       expect(dayjsLocaleData.weekdaysShort()).toEqual(momentLocaleData.weekdaysShort())
-      const longDateFormats = ['LT', 'LTS', 'L', 'LL', 'LLL', 'LLLL']
+      const longDateFormats = ['LT', 'LTS', 'L', 'LL', 'LLL', 'LLLL', 'l', 'll', 'lll', 'llll']
       longDateFormats.forEach((f) => {
         expect(dayjsLocaleData.longDateFormat(f)).toEqual(momentLocaleData.longDateFormat(f))
       })
@@ -61,6 +61,10 @@ it('Global localeData', () => {
     expect(dayjsLocaleData.weekdays()).toEqual(momentLocaleData.weekdays())
     expect(dayjsLocaleData.weekdaysShort()).toEqual(momentLocaleData.weekdaysShort())
     expect(dayjsLocaleData.weekdaysMin()).toEqual(momentLocaleData.weekdaysMin())
+    const longDateFormats = ['LT', 'LTS', 'L', 'LL', 'LLL', 'LLLL', 'l', 'll', 'lll', 'llll']
+    longDateFormats.forEach((f) => {
+      expect(dayjsLocaleData.longDateFormat(f)).toEqual(momentLocaleData.longDateFormat(f))
+    })
   })
 })
 
diff --git a/test/plugin/objectSupport.test.js b/test/plugin/objectSupport.test.js
--- a/test/plugin/objectSupport.test.js
+++ b/test/plugin/objectSupport.test.js
@@ -21,6 +21,9 @@ const now = new Date()
 const currentYear = now.getFullYear()
 const currentMonth = utils.s(now.getMonth() + 1, 2, '0')
 const currentDate = utils.s(now.getDate(), 2, '0')
+const currentUTCYear = now.getUTCFullYear()
+const currentUTCMonth = utils.s(now.getUTCMonth() + 1, 2, '0')
+const currentUTCDate = utils.s(now.getUTCDate(), 2, '0')
 const fmt = 'YYYY-MM-DD HH:mm:ss.SSS'
 const tests = [
   [{ year: 2010 }, '2010-01-01 00:00:00.000'],
@@ -31,7 +34,8 @@ const tests = [
     {
       hour: 15, minute: 25, second: 50, millisecond: 125
     },
-    `${currentYear}-${currentMonth}-${currentDate} 15:25:50.125`],
+    `${currentYear}-${currentMonth}-${currentDate} 15:25:50.125`,
+    `${currentUTCYear}-${currentUTCMonth}-${currentUTCDate} 15:25:50.125`],
   [
     {
       year: 2010, month: 1, day: 12, hours: 1
@@ -110,6 +114,18 @@ const tests = [
     '2010-02-14 15:25:50.125'
   ]
 ]
+
+describe('parse empty object', () => {
+  it('local', () => {
+    expect(dayjs({}).format())
+      .toBe(moment({}).format())
+  })
+  it('utc', () => {
+    expect(dayjs.utc({}).format())
+      .toBe(moment.utc({}).format())
+  })
+})
+
 it('Constructor from Object', () => {
   for (let i = 0; i < tests.length; i += 1) {
     expect(dayjs(tests[i][0]).format(fmt)).toBe(tests[i][1])
@@ -119,8 +135,9 @@ it('Constructor from Object', () => {
 
 it('Constructor from Object UTC', () => {
   for (let i = 0; i < tests.length; i += 1) {
-    expect(dayjs.utc(tests[i][0]).format(fmt)).toBe(tests[i][1])
-    expect(moment.utc(tests[i][0]).format(fmt)).toBe(tests[i][1])
+    const result = tests[i][2] || tests[i][1]
+    expect(dayjs.utc(tests[i][0]).format(fmt)).toBe(result)
+    expect(moment.utc(tests[i][0]).format(fmt)).toBe(result)
   }
 })
 it('Set from Object', () => {