Repository: iamkun/dayjs (JavaScript)

Issue:
1.10.4 broke duration creation type in typescript
**Describe the bug**
In your own documentation: https://day.js.org/docs/en/durations/add
```typescript
var a = dayjs.duration(1, 'd');
var b = dayjs.duration(2, 'd');

a.add(b).days(); // 3
```
it shows:
```
No overload matches this call.
  Overload 1 of 3, '(units: Partial<{ milliseconds: number; seconds: number; minutes: number; hours: number; days: number; months: number; years: number; weeks: number; }>): Duration', gave the following error.
    Argument of type 'Duration' is not assignable to parameter of type 'Partial<{ milliseconds: number; seconds: number; minutes: number; hours: number; days: number; months: number; years: number; weeks: number; }>'.
      Types of property 'milliseconds' are incompatible.
        Type '() => number' is not assignable to type 'number'.
  Overload 2 of 3, '(time: number, unit?: DurationUnitType | undefined): Duration', gave the following error.
    Argument of type 'Duration' is not assignable to parameter of type 'number'.
  Overload 3 of 3, '(ISO_8601: string): Duration', gave the following error.
    Argument of type 'Duration' is not assignable to parameter of type 'string'.ts(2769)
```

**Expected behavior**
No typescript type mismatch failure

**Information**
 - Day.js Version [e.g. v1.10.4]
 - OS: [windows]
 - Browser node 14.15.0
 - Time zone: not applicable



Hints:
maybe relate to #1338
cc @zardoy  to check this together

Patch:
diff --git a/build/index.js b/build/index.js
--- a/build/index.js
+++ b/build/index.js
@@ -35,23 +35,27 @@ async function listLocaleJson(localeArr) {
 
 (async () => {
   try {
+    /* eslint-disable no-restricted-syntax, no-await-in-loop */
+    // We use await-in-loop to make rollup run sequentially to save on RAM
     const locales = await promisifyReadDir(localePath)
-    locales.forEach((l) => {
-      build(configFactory({
+    for (const l of locales) {
+      // run builds sequentially to limit RAM usage
+      await build(configFactory({
         input: `./src/locale/${l}`,
         fileName: `./locale/${l}`,
         name: `dayjs_locale_${formatName(l)}`
       }))
-    })
+    }
 
     const plugins = await promisifyReadDir(path.join(__dirname, '../src/plugin'))
-    plugins.forEach((l) => {
-      build(configFactory({
-        input: `./src/plugin/${l}/index`,
-        fileName: `./plugin/${l}.js`,
-        name: `dayjs_plugin_${formatName(l)}`
+    for (const plugin of plugins) {
+      // run builds sequentially to limit RAM usage
+      await build(configFactory({
+        input: `./src/plugin/${plugin}/index`,
+        fileName: `./plugin/${plugin}.js`,
+        name: `dayjs_plugin_${formatName(plugin)}`
       }))
-    })
+    }
 
     build(configFactory({
       input: './src/index.js',
diff --git a/build/rollup.config.js b/build/rollup.config.js
--- a/build/rollup.config.js
+++ b/build/rollup.config.js
@@ -1,5 +1,5 @@
 const babel = require('rollup-plugin-babel')
-const uglify = require('rollup-plugin-uglify')
+const { terser } = require('rollup-plugin-terser')
 
 module.exports = (config) => {
   const { input, fileName, name } = config
@@ -13,7 +13,7 @@ module.exports = (config) => {
         babel({
           exclude: 'node_modules/**'
         }),
-        uglify()
+        terser()
       ]
     },
     output: {
@@ -22,7 +22,8 @@ module.exports = (config) => {
       name: name || 'dayjs',
       globals: {
         dayjs: 'dayjs'
-      }
+      },
+      compact: true
     }
   }
 }
diff --git a/src/locale/ar-dz.js b/src/locale/ar-dz.js
--- a/src/locale/ar-dz.js
+++ b/src/locale/ar-dz.js
@@ -17,6 +17,7 @@ const locale = {
     LLL: 'D MMMM YYYY HH:mm',
     LLLL: 'dddd D MMMM YYYY HH:mm'
   },
+  meridiem: hour => (hour > 12 ? 'ص' : 'م'),
   relativeTime: {
     future: 'في %s',
     past: 'منذ %s',
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
@@ -17,6 +17,7 @@ const locale = {
     LLL: 'D MMMM YYYY HH:mm',
     LLLL: 'dddd D MMMM YYYY HH:mm'
   },
+  meridiem: hour => (hour > 12 ? 'ص' : 'م'),
   relativeTime: {
     future: 'في %s',
     past: 'منذ %s',
diff --git a/src/locale/ar-ly.js b/src/locale/ar-ly.js
--- a/src/locale/ar-ly.js
+++ b/src/locale/ar-ly.js
@@ -10,6 +10,7 @@ const locale = {
   monthsShort: 'يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'),
   weekdaysMin: 'ح_ن_ث_ر_خ_ج_س'.split('_'),
   ordinal: n => n,
+  meridiem: hour => (hour > 12 ? 'ص' : 'م'),
   formats: {
     LT: 'HH:mm',
     LTS: 'HH:mm:ss',
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
@@ -18,6 +18,7 @@ const locale = {
     LLL: 'D MMMM YYYY HH:mm',
     LLLL: 'dddd D MMMM YYYY HH:mm'
   },
+  meridiem: hour => (hour > 12 ? 'ص' : 'م'),
   relativeTime: {
     future: 'في %s',
     past: 'منذ %s',
diff --git a/src/locale/ar-sa.js b/src/locale/ar-sa.js
--- a/src/locale/ar-sa.js
+++ b/src/locale/ar-sa.js
@@ -17,6 +17,7 @@ const locale = {
     LLL: 'D MMMM YYYY HH:mm',
     LLLL: 'dddd D MMMM YYYY HH:mm'
   },
+  meridiem: hour => (hour > 12 ? 'ص' : 'م'),
   relativeTime: {
     future: 'في %s',
     past: 'منذ %s',
diff --git a/src/locale/ar-tn.js b/src/locale/ar-tn.js
--- a/src/locale/ar-tn.js
+++ b/src/locale/ar-tn.js
@@ -18,6 +18,7 @@ const locale = {
     LLL: 'D MMMM YYYY HH:mm',
     LLLL: 'dddd D MMMM YYYY HH:mm'
   },
+  meridiem: hour => (hour > 12 ? 'ص' : 'م'),
   relativeTime: {
     future: 'في %s',
     past: 'منذ %s',
diff --git a/src/locale/bn.js b/src/locale/bn.js
--- a/src/locale/bn.js
+++ b/src/locale/bn.js
@@ -32,7 +32,7 @@ const locale = {
   weekdays: 'রবিবার_সোমবার_মঙ্গলবার_বুধবার_বৃহস্পতিবার_শুক্রবার_শনিবার'.split('_'),
   months: 'জানুয়ারি_ফেব্রুয়ারি_মার্চ_এপ্রিল_মে_জুন_জুলাই_আগস্ট_সেপ্টেম্বর_অক্টোবর_নভেম্বর_ডিসেম্বর'.split('_'),
   weekdaysShort: 'রবি_সোম_মঙ্গল_বুধ_বৃহস্পতি_শুক্র_শনি'.split('_'),
-  monthsShort: 'জানু_ফেব_মার্চ_এপ্র_মে_জুন_জুল_আগ_সেপ্ট_অক্টো_নভে_ডিসে'.split('_'),
+  monthsShort: 'জানু_ফেব্রু_মার্চ_এপ্রিল_মে_জুন_জুলাই_আগস্ট_সেপ্ট_অক্টো_নভে_ডিসে'.split('_'),
   weekdaysMin: 'রবি_সোম_মঙ্গ_বুধ_বৃহঃ_শুক্র_শনি'.split('_'),
   preparse(string) {
     return string.replace(/[১২৩৪৫৬৭৮৯০]/g, match => numberMap[match])
diff --git a/src/locale/br.js b/src/locale/br.js
--- a/src/locale/br.js
+++ b/src/locale/br.js
@@ -30,6 +30,7 @@ function relativeTimeWithMutation(number, withoutSuffix, key) {
   return `${number} ${mutation(format[key], number)}`
 }
 function specialMutationForYears(number) {
+  /* istanbul ignore next line */
   switch (lastNumber(number)) {
     case 1:
     case 3:
diff --git a/src/locale/es-do.js b/src/locale/es-do.js
--- a/src/locale/es-do.js
+++ b/src/locale/es-do.js
@@ -6,7 +6,7 @@ const locale = {
   weekdays: 'domingo_lunes_martes_miércoles_jueves_viernes_sábado'.split('_'),
   weekdaysShort: 'dom._lun._mar._mié._jue._vie._sáb.'.split('_'),
   weekdaysMin: 'do_lu_ma_mi_ju_vi_sá'.split('_'),
-  months: 'Enero_Febrero_Marzo_Abril_Mayo_Junio_Julio_Agosto_Septiembre_Octubre_Noviembre_Diciembre'.split('_'),
+  months: 'enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre'.split('_'),
   monthsShort: 'ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic'.split('_'),
   weekStart: 1,
   relativeTime: {
diff --git a/src/locale/es-pr.js b/src/locale/es-pr.js
--- a/src/locale/es-pr.js
+++ b/src/locale/es-pr.js
@@ -7,7 +7,7 @@ const locale = {
   weekdays: 'domingo_lunes_martes_miércoles_jueves_viernes_sábado'.split('_'),
   weekdaysShort: 'dom._lun._mar._mié._jue._vie._sáb.'.split('_'),
   weekdaysMin: 'do_lu_ma_mi_ju_vi_sá'.split('_'),
-  months: 'Enero_Febrero_Marzo_Abril_Mayo_Junio_Julio_Agosto_Septiembre_Octubre_Noviembre_Diciembre'.split('_'),
+  months: 'enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre'.split('_'),
   weekStart: 1,
   formats: {
     LT: 'h:mm A',
diff --git a/src/locale/es-us.js b/src/locale/es-us.js
--- a/src/locale/es-us.js
+++ b/src/locale/es-us.js
@@ -6,7 +6,7 @@ const locale = {
   weekdays: 'domingo_lunes_martes_miércoles_jueves_viernes_sábado'.split('_'),
   weekdaysShort: 'dom._lun._mar._mié._jue._vie._sáb.'.split('_'),
   weekdaysMin: 'do_lu_ma_mi_ju_vi_sá'.split('_'),
-  months: 'Enero_Febrero_Marzo_Abril_Mayo_Junio_Julio_Agosto_Septiembre_Octubre_Noviembre_Diciembre'.split('_'),
+  months: 'enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre'.split('_'),
   monthsShort: 'ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic'.split('_'),
   relativeTime: {
     future: 'en %s',
diff --git a/src/locale/es.js b/src/locale/es.js
--- a/src/locale/es.js
+++ b/src/locale/es.js
@@ -7,7 +7,7 @@ const locale = {
   weekdays: 'domingo_lunes_martes_miércoles_jueves_viernes_sábado'.split('_'),
   weekdaysShort: 'dom._lun._mar._mié._jue._vie._sáb.'.split('_'),
   weekdaysMin: 'do_lu_ma_mi_ju_vi_sá'.split('_'),
-  months: 'Enero_Febrero_Marzo_Abril_Mayo_Junio_Julio_Agosto_Septiembre_Octubre_Noviembre_Diciembre'.split('_'),
+  months: 'enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre'.split('_'),
   weekStart: 1,
   formats: {
     LT: 'H:mm',
diff --git a/src/locale/fi.js b/src/locale/fi.js
--- a/src/locale/fi.js
+++ b/src/locale/fi.js
@@ -47,6 +47,7 @@ const locale = {
   monthsShort: 'tammi_helmi_maalis_huhti_touko_kesä_heinä_elo_syys_loka_marras_joulu'.split('_'),
   ordinal: n => `${n}.`,
   weekStart: 1,
+  yearStart: 4,
   relativeTime: {
     future: '%s päästä',
     past: '%s sitten',
diff --git a/src/locale/ru.js b/src/locale/ru.js
--- a/src/locale/ru.js
+++ b/src/locale/ru.js
@@ -53,6 +53,7 @@ const locale = {
   months,
   monthsShort,
   weekStart: 1,
+  yearStart: 4,
   formats: {
     LT: 'H:mm',
     LTS: 'H:mm:ss',
@@ -76,7 +77,17 @@ const locale = {
     y: 'год',
     yy: relativeTimeWithPlural
   },
-  ordinal: n => n
+  ordinal: n => n,
+  meridiem: (hour) => {
+    if (hour < 4) {
+      return 'ночи'
+    } else if (hour < 12) {
+      return 'утра'
+    } else if (hour < 17) {
+      return 'дня'
+    }
+    return 'вечера'
+  }
 }
 
 dayjs.locale(locale, null, true)
diff --git a/src/locale/sl.js b/src/locale/sl.js
--- a/src/locale/sl.js
+++ b/src/locale/sl.js
@@ -9,7 +9,7 @@ const locale = {
   weekdaysShort: 'ned._pon._tor._sre._čet._pet._sob.'.split('_'),
   monthsShort: 'jan._feb._mar._apr._maj._jun._jul._avg._sep._okt._nov._dec.'.split('_'),
   weekdaysMin: 'ne_po_to_sr_če_pe_so'.split('_'),
-  ordinal: n => n,
+  ordinal: n => `${n}.`,
   formats: {
     LT: 'H:mm',
     LTS: 'H:mm:ss',
diff --git a/src/locale/sv.js b/src/locale/sv.js
--- a/src/locale/sv.js
+++ b/src/locale/sv.js
@@ -9,6 +9,7 @@ const locale = {
   months: 'januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december'.split('_'),
   monthsShort: 'jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec'.split('_'),
   weekStart: 1,
+  yearStart: 4,
   ordinal: (n) => {
     const b = n % 10
     const o = (b === 1) || (b === 2) ? 'a' : 'e'
diff --git a/src/locale/uk.js b/src/locale/uk.js
--- a/src/locale/uk.js
+++ b/src/locale/uk.js
@@ -43,7 +43,7 @@ const locale = {
   weekdaysShort: 'ндл_пнд_втр_срд_чтв_птн_сбт'.split('_'),
   weekdaysMin: 'нд_пн_вт_ср_чт_пт_сб'.split('_'),
   months,
-  monthsShort: 'січ_лют_бер_квiт_трав_черв_лип_серп_вер_жовт_лист_груд'.split('_'),
+  monthsShort: 'січ_лют_бер_квіт_трав_черв_лип_серп_вер_жовт_лист_груд'.split('_'),
   weekStart: 1,
   relativeTime: {
     future: 'за %s',
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
@@ -8,13 +8,14 @@ const match3 = /\d{3}/ // 000 - 999
 const match4 = /\d{4}/ // 0000 - 9999
 const match1to2 = /\d\d?/ // 0 - 99
 const matchSigned = /[+-]?\d+/ // -inf - inf
-const matchOffset = /[+-]\d\d:?(\d\d)?/ // +00:00 -00:00 +0000 or -0000 +00
+const matchOffset = /[+-]\d\d:?(\d\d)?|Z/ // +00:00 -00:00 +0000 or -0000 +00 or Z
 const matchWord = /\d*[^\s\d-_:/()]+/ // Word
 
 let locale = {}
 
 function offsetFromString(string) {
   if (!string) return 0
+  if (string === 'Z') return 0
   const parts = string.match(/([+-]|\d\d)/g)
   const minutes = +(parts[1] * 60) + (+parts[2] || 0)
   return minutes === 0 ? 0 : parts[0] === '+' ? -minutes : minutes // eslint-disable-line no-nested-ternary
diff --git a/src/plugin/devHelper/index.js b/src/plugin/devHelper/index.js
--- a/src/plugin/devHelper/index.js
+++ b/src/plugin/devHelper/index.js
@@ -1,5 +1,6 @@
 /* eslint-disable no-console */
 export default (o, c, d) => {
+  /* istanbul ignore next line */
   if (!process || process.env.NODE_ENV !== 'production') {
     const proto = c.prototype
     const oldParse = proto.parse
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
@@ -61,6 +61,10 @@ class Duration {
   constructor(input, unit, locale) {
     this.$d = {}
     this.$l = locale
+    if (input === undefined) {
+      this.$ms = 0
+      this.parseFromMilliseconds()
+    }
     if (unit) {
       return wrapper(input * unitToMS[prettyUnit(unit)], this)
     }
@@ -79,7 +83,9 @@ class Duration {
     if (typeof input === 'string') {
       const d = input.match(durationRegex)
       if (d) {
-        [,,
+        const properties = d.slice(2)
+        const numberD = properties.map(value => Number(value));
+        [
           this.$d.years,
           this.$d.months,
           this.$d.weeks,
@@ -87,7 +93,7 @@ class Duration {
           this.$d.hours,
           this.$d.minutes,
           this.$d.seconds
-        ] = d
+        ] = numberD
         this.calMilliseconds()
         return this
       }
diff --git a/src/plugin/timezone/index.js b/src/plugin/timezone/index.js
--- a/src/plugin/timezone/index.js
+++ b/src/plugin/timezone/index.js
@@ -36,8 +36,6 @@ const getDateTimeFormat = (timezone, options = {}) => {
 export default (o, c, d) => {
   let defaultTimezone
 
-  const localUtcOffset = d().utcOffset()
-
   const makeFormatParts = (timestamp, timezone, options = {}) => {
     const date = new Date(timestamp)
     const dtf = getDateTimeFormat(timezone, options)
@@ -96,9 +94,11 @@ export default (o, c, d) => {
 
   proto.tz = function (timezone = defaultTimezone, keepLocalTime) {
     const oldOffset = this.utcOffset()
-    const target = this.toDate().toLocaleString('en-US', { timeZone: timezone })
-    const diff = Math.round((this.toDate() - new Date(target)) / 1000 / 60)
-    let ins = d(target).$set(MS, this.$ms).utcOffset(localUtcOffset - diff, true)
+    const date = this.toDate()
+    const target = date.toLocaleString('en-US', { timeZone: timezone })
+    const diff = Math.round((date - new Date(target)) / 1000 / 60)
+    let ins = d(target).$set(MS, this.$ms)
+      .utcOffset((-Math.round(date.getTimezoneOffset() / 15) * 15) - diff, true)
     if (keepLocalTime) {
       const newOffset = ins.utcOffset()
       ins = ins.add(oldOffset - newOffset, MIN)
diff --git a/src/plugin/utc/index.js b/src/plugin/utc/index.js
--- a/src/plugin/utc/index.js
+++ b/src/plugin/utc/index.js
@@ -1,5 +1,26 @@
 import { MILLISECONDS_A_MINUTE, MIN } from '../../constant'
 
+const REGEX_VALID_OFFSET_FORMAT = /[+-]\d\d(?::?\d\d)?/g
+const REGEX_OFFSET_HOURS_MINUTES_FORMAT = /([+-]|\d\d)/g
+
+function offsetFromString(value = '') {
+  const offset = value.match(REGEX_VALID_OFFSET_FORMAT)
+
+  if (!offset) {
+    return null
+  }
+
+  const [indicator, hoursOffset, minutesOffset] = `${offset[0]}`.match(REGEX_OFFSET_HOURS_MINUTES_FORMAT) || ['-', 0, 0]
+  const totalOffsetInMinutes = (+hoursOffset * 60) + (+minutesOffset)
+
+  if (totalOffsetInMinutes === 0) {
+    return 0
+  }
+
+  return indicator === '+' ? totalOffsetInMinutes : -totalOffsetInMinutes
+}
+
+
 export default (option, Dayjs, dayjs) => {
   const proto = Dayjs.prototype
   dayjs.utc = function (date) {
@@ -59,6 +80,12 @@ export default (option, Dayjs, dayjs) => {
       }
       return oldUtcOffset.call(this)
     }
+    if (typeof input === 'string') {
+      input = offsetFromString(input)
+      if (input === null) {
+        return this
+      }
+    }
     const offset = Math.abs(input) <= 16 ? input * 60 : input
     let ins = this
     if (keepLocalTime) {
diff --git a/types/index.d.ts b/types/index.d.ts
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -9,7 +9,11 @@ declare function dayjs (date?: dayjs.ConfigType, format?: dayjs.OptionType, stri
 declare function dayjs (date?: dayjs.ConfigType, format?: dayjs.OptionType, locale?: string, strict?: boolean): dayjs.Dayjs
 
 declare namespace dayjs {
-  export type ConfigType = string | number | Date | Dayjs
+  interface ConfigTypeMap {
+    default: string | number | Date | Dayjs
+  }
+
+  export type ConfigType = ConfigTypeMap[keyof ConfigTypeMap]
 
   export type OptionType = { locale?: string, format?: string, utc?: boolean } | string | string[]
 
diff --git a/types/plugin/arraySupport.d.ts b/types/plugin/arraySupport.d.ts
--- a/types/plugin/arraySupport.d.ts
+++ b/types/plugin/arraySupport.d.ts
@@ -1,4 +1,10 @@
 import { PluginFunc } from 'dayjs'
 
+declare module 'dayjs' {
+  interface ConfigTypeMap {
+    arraySupport: [number?, number?, number?, number?, number?, number?, number?]
+  }
+}
+
 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
@@ -6,6 +6,15 @@ export as namespace plugin;
 export = plugin
 
 declare namespace plugin {
+  /**
+   * @deprecated Please use more strict types
+   */
+  type DurationInputType = string | number | object
+  /**
+   * @deprecated Please use more strict types
+   */
+  type DurationAddType = number | object | Duration
+  
   type DurationUnitsObjectType = Partial<{
     [unit in Exclude<UnitTypeLongPlural, "dates"> | "weeks"]: number
   }>;
@@ -14,6 +23,7 @@ declare namespace plugin {
     ((units: DurationUnitsObjectType) => Duration)
     & ((time: number, unit?: DurationUnitType) => Duration)
     & ((ISO_8601: string) => Duration)
+  type AddDurationType = CreateDurationType & ((duration: Duration) => Duration)
 
   interface Duration {
     new (input: string | number | object, unit?: string, locale?: string): Duration
@@ -50,9 +60,9 @@ declare namespace plugin {
 
     get(unit: DurationUnitType): number
 
-    add: CreateDurationType;
+    add: AddDurationType
     
-    subtract: CreateDurationType
+    subtract: AddDurationType
 
     toJSON(): string
 
@@ -75,4 +85,4 @@ declare module 'dayjs' {
    */
   export const duration: plugin.CreateDurationType;
   export function isDuration(d: any): d is plugin.Duration
-}
+}
\ No newline at end of file


Test patch:
diff --git a/test/locale.test.js b/test/locale.test.js
--- a/test/locale.test.js
+++ b/test/locale.test.js
@@ -17,7 +17,7 @@ const NOT_SUPPORTED_LOCALE_STRING = 'not_supported_locale_string'
 it('Uses spanish locale through constructor', () => { // not recommend
   expect(dayjs('2018-4-28', { locale: es })
     .format(format))
-    .toBe('sábado 28, Abril')
+    .toBe('sábado 28, abril')
 })
 
 it('set locale for one instance only', () => {
@@ -27,7 +27,7 @@ it('set locale for one instance only', () => {
 
   expect(dayjs('2018-4-28')
     .locale(es).format(format))
-    .toBe('sábado 28, Abril')
+    .toBe('sábado 28, abril')
 
   expect(dayjs('2018-4-28')
     .format(format))
@@ -40,7 +40,7 @@ it('set global locale', () => {
     .toBe('Saturday 28, April')
   dayjs.locale(es)
   expect(dayjs('2018-4-28').format(format))
-    .toBe('sábado 28, Abril')
+    .toBe('sábado 28, abril')
   dayjs.locale('en')
   expect(dayjs('2018-4-28').format(format))
     .toBe('Saturday 28, April')
@@ -63,10 +63,10 @@ it('immutable instance locale', () => {
   expect(origin.format(format))
     .toBe('Saturday 28, April')
   expect(origin.locale('es').format(format))
-    .toBe('sábado 28, Abril')
+    .toBe('sábado 28, abril')
   const changed = origin.locale('es')
   expect(changed.format(format))
-    .toBe('sábado 28, Abril')
+    .toBe('sábado 28, abril')
   expect(origin.format(format))
     .toBe('Saturday 28, April')
 })
@@ -86,30 +86,30 @@ describe('Instance locale inheritance', () => {
 
   it('Clone', () => {
     expect(esDayjs.clone().format(format))
-      .toBe('sábado 28, Abril')
+      .toBe('sábado 28, abril')
     expect(dayjs(esDayjs).format(format))
-      .toBe('sábado 28, Abril')
+      .toBe('sábado 28, abril')
   })
 
   it('StartOf EndOf', () => {
     expect(esDayjs.startOf('year').format(format))
-      .toBe('lunes 1, Enero')
+      .toBe('lunes 1, enero')
     expect(esDayjs.endOf('day').format(format))
-      .toBe('sábado 28, Abril')
+      .toBe('sábado 28, abril')
   })
 
   it('Set', () => {
     expect(esDayjs.set('year', 2017).format(format))
-      .toBe('viernes 28, Abril')
+      .toBe('viernes 28, abril')
   })
 
   it('Add', () => {
     expect(esDayjs.add(1, 'year').format(format))
-      .toBe('domingo 28, Abril')
+      .toBe('domingo 28, abril')
     expect(esDayjs.add(1, 'month').format(format))
-      .toBe('lunes 28, Mayo')
+      .toBe('lunes 28, mayo')
     expect(esDayjs.add(1, 'minute').format(format))
-      .toBe('sábado 28, Abril')
+      .toBe('sábado 28, abril')
   })
 
   it('dayjs.locale() returns locale name', () => {
diff --git a/test/locale/bn.test.js b/test/locale/bn.test.js
--- a/test/locale/bn.test.js
+++ b/test/locale/bn.test.js
@@ -35,6 +35,16 @@ it('Format Month with locale function', () => {
   }
 })
 
+it('Month short', () => {
+  const date = '2021-02-01T05:54:32.005Z'
+  const dayjsBN = dayjs(date)
+    .locale('bn')
+  const momentBN = moment(date)
+    .locale('bn')
+  const testFormat1 = 'DD MMMM YYYY MMM'
+  expect(dayjsBN.format(testFormat1)).toEqual(momentBN.format(testFormat1))
+})
+
 it('Preparse with locale function', () => {
   for (let i = 0; i <= 7; i += 1) {
     dayjs.locale('bn')
diff --git a/test/locale/ru.test.js b/test/locale/ru.test.js
--- a/test/locale/ru.test.js
+++ b/test/locale/ru.test.js
@@ -47,3 +47,10 @@ it('RelativeTime: Time from X', () => {
       .toBe(moment().from(moment().add(t[0], t[1]), true))
   })
 })
+
+it('Meridiem', () => {
+  expect(dayjs('2020-01-01 03:00:00').locale('ru').format('A')).toEqual('ночи')
+  expect(dayjs('2020-01-01 11:00:00').locale('ru').format('A')).toEqual('утра')
+  expect(dayjs('2020-01-01 16:00:00').locale('ru').format('A')).toEqual('дня')
+  expect(dayjs('2020-01-01 20:00:00').locale('ru').format('A')).toEqual('вечера')
+})
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
@@ -109,6 +109,13 @@ describe('Timezone Offset', () => {
     expect(result.valueOf()).toBe(moment(input, format).valueOf())
     expect(result.valueOf()).toBe(1606820400000)
   })
+  it('zulu', () => {
+    const input = '2021-01-26T15:38:43.000Z'
+    const format = 'YYYY-MM-DDTHH:mm:ss.SSSZ'
+    const result = dayjs(input, format)
+    expect(result.valueOf()).toBe(moment(input, format).valueOf())
+    expect(result.valueOf()).toBe(1611675523000)
+  })
   it('no timezone format token should parse in local time', () => {
     const input = '2020-12-01T20:00:00+01:00'
     const format = 'YYYY-MM-DD[T]HH:mm:ss'
@@ -347,3 +354,4 @@ it('parse a string for MMM month format with underscore delimiter', () => {
   const format2 = 'DD_MMM_YYYY_hhmmss'
   expect(dayjs(input2, format2).valueOf()).toBe(moment(input2, format2).valueOf())
 })
+
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
@@ -17,6 +17,10 @@ afterEach(() => {
 })
 
 describe('Creating', () => {
+  it('no argument', () => {
+    expect(dayjs.duration().toISOString()).toBe('P0D')
+    expect(dayjs.duration().asMilliseconds()).toBe(0)
+  })
   it('milliseconds', () => {
     expect(dayjs.duration(1, 'ms').toISOString()).toBe('PT0.001S')
     expect(dayjs.duration(100).toISOString()).toBe('PT0.1S')
diff --git a/test/plugin/timezone.test.js b/test/plugin/timezone.test.js
--- a/test/plugin/timezone.test.js
+++ b/test/plugin/timezone.test.js
@@ -101,7 +101,9 @@ describe('Convert', () => {
       const jun = _('2014-06-01T12:00:00Z')
       const dec = _('2014-12-01T12:00:00Z')
       expect(jun.tz('America/Los_Angeles').format('ha')).toBe('5am')
+      expect(jun.tz('America/Los_Angeles').utcOffset()).toBe(-7 * 60)
       expect(dec.tz('America/Los_Angeles').format('ha')).toBe('4am')
+      expect(dec.tz('America/Los_Angeles').utcOffset()).toBe(-8 * 60)
       expect(jun.tz(NY).format('ha')).toBe('8am')
       expect(dec.tz(NY).format('ha')).toBe('7am')
       expect(jun.tz(TOKYO).format('ha')).toBe('9pm')
diff --git a/test/plugin/utc.test.js b/test/plugin/utc.test.js
--- a/test/plugin/utc.test.js
+++ b/test/plugin/utc.test.js
@@ -226,6 +226,81 @@ describe('UTC Offset', () => {
     expect(dayjs().utcOffset()).toBe(moment().utcOffset())
     expect(dayjs().utc().utcOffset()).toBe(moment().utc().utcOffset())
   })
+
+  it('get utc offset with a number value', () => {
+    const time = '2021-02-28 19:40:10'
+    const hoursOffset = -8
+    const daysJS = dayjs(time).utc().utcOffset(hoursOffset * 60, true)
+    const momentJS = moment(time).utc(true).utcOffset(hoursOffset, true)
+
+    expect(daysJS.toISOString()).toEqual(momentJS.toISOString())
+    expect(daysJS.utcOffset()).toEqual(hoursOffset * 60)
+    expect(daysJS.utcOffset()).toEqual(momentJS.utcOffset())
+  })
+
+  it('get utc offset with a negative valid string value, format: HH:mm', () => {
+    const time = '2021-02-28 19:40:10'
+    const hoursOffset = -8
+    const daysJS = dayjs(time).utc().utcOffset(`-0${Math.abs(hoursOffset)}:00`, true)
+    const momentJS = moment(time).utc(true).utcOffset(`-0${Math.abs(hoursOffset)}:00`, true)
+
+    expect(daysJS.toISOString()).toEqual(momentJS.toISOString())
+    expect(daysJS.utcOffset()).toEqual(hoursOffset * 60)
+    expect(daysJS.utcOffset()).toEqual(momentJS.utcOffset())
+  })
+
+  it('get utc offset with a positive valid string value, format: HH:mm', () => {
+    const time = '2021-02-28 19:40:10'
+    const hoursOffset = 8
+    const daysJS = dayjs(time).utc().utcOffset(`+0${hoursOffset}:00`, true)
+    const momentJS = moment(time).utc(true).utcOffset(`+0${hoursOffset}:00`, true)
+
+    expect(daysJS.toISOString()).toEqual(momentJS.toISOString())
+    expect(daysJS.utcOffset()).toEqual(hoursOffset * 60)
+    expect(daysJS.utcOffset()).toEqual(momentJS.utcOffset())
+  })
+
+  it('get utc offset with a negative valid string value, format: HHmm', () => {
+    const time = '2021-02-28 19:40:10'
+    const hoursOffset = -8
+    const daysJS = dayjs(time).utc().utcOffset(`-0${Math.abs(hoursOffset)}00`, true)
+    const momentJS = moment(time).utc(true).utcOffset(`-0${Math.abs(hoursOffset)}00`, true)
+
+    expect(daysJS.toISOString()).toEqual(momentJS.toISOString())
+    expect(daysJS.utcOffset()).toEqual(hoursOffset * 60)
+    expect(daysJS.utcOffset()).toEqual(momentJS.utcOffset())
+  })
+
+  it('get utc offset with a positive valid string value, format: HHmm', () => {
+    const time = '2021-02-28 19:40:10'
+    const hoursOffset = 8
+    const daysJS = dayjs(time).utc().utcOffset(`+0${hoursOffset}00`, true)
+    const momentJS = moment(time).utc(true).utcOffset(`+0${hoursOffset}00`, true)
+
+    expect(daysJS.toISOString()).toEqual(momentJS.toISOString())
+    expect(daysJS.utcOffset()).toEqual(hoursOffset * 60)
+    expect(daysJS.utcOffset()).toEqual(momentJS.utcOffset())
+  })
+
+  it('get utc offset with an invalid string value, value: random', () => {
+    const time = '2021-02-28 19:40:10'
+    const daysJS = dayjs(time, { utc: true }).utc(true).utcOffset('random')
+    const momentJS = moment(time).utc(true).utcOffset('random')
+
+    expect(daysJS.toISOString()).toEqual(momentJS.toISOString())
+    expect(daysJS.utcOffset()).toEqual(0)
+    expect(daysJS.utcOffset()).toEqual(momentJS.utcOffset())
+  })
+
+  it('get utc offset with an invalid string value, value: 0', () => {
+    const time = '2021-02-28 19:40:10'
+    const daysJS = dayjs(time, { utc: true }).utc(true).utcOffset('+0000')
+    const momentJS = moment(time).utc(true).utcOffset('+0000')
+
+    expect(daysJS.toISOString()).toEqual(momentJS.toISOString())
+    expect(daysJS.utcOffset()).toEqual(0)
+    expect(daysJS.utcOffset()).toEqual(momentJS.utcOffset())
+  })
 })
 
 describe('Diff', () => {