Repository: iamkun/dayjs (JavaScript)

Issue:
weekOfYear `week` method doesn't seems to handle week number 53 for 'fr' locale
**Describe the bug**
I have an issue with the _dayjs_ `week()` method compared with the _moment.js_ one.
My issue is probably linked to this one [#772 ](https://github.com/iamkun/dayjs/issues/772)

**Expected behavior**
I tried with that date: `2020-12-31`, which is supposed to be a day in the week number 53. (if i refer to this http://www.whatweekisit.org/ or any french calendar).

Here is an example of what _moment.js_ `week()` method return for 'fr' and 'en-gb' locales: https://runkit.com/4rno/5f05ee0eef35c50013368d1b
`moment.js` returns week 53. Which is correct

But, I have different results with _dayjs_: https://runkit.com/4rno/5f05ebfc841930001389a53e
In 'fr' locale, it returns week number 1. But in 'en-gb' locale, it returns correctly week 53.

It seems to be linked with the parameter `yearStart` in locale config files. But i don't know exactly how it is supposed to work.
Or maybe i'm wrong somewhere.

**Information**
 - Day.js Version v1.8.29
 - OS: [Mac OS]
 - Browser [Chrome 83]
 - Time zone: [CET]



Hints:

Patch:
diff --git a/src/index.js b/src/index.js
--- a/src/index.js
+++ b/src/index.js
@@ -229,7 +229,7 @@ class Dayjs {
       const date = this.clone().set(C.DATE, 1)
       date.$d[name](arg)
       date.init()
-      this.$d = date.set(C.DATE, Math.min(this.$D, date.daysInMonth())).toDate()
+      this.$d = date.set(C.DATE, Math.min(this.$D, date.daysInMonth())).$d
     } else if (name) this.$d[name](arg)
 
     this.init()
diff --git a/src/locale/es-pr.js b/src/locale/es-pr.js
new file mode 100644
--- /dev/null
+++ b/src/locale/es-pr.js
@@ -0,0 +1,41 @@
+// Spanish (Puerto Rico) [es-PR]
+import dayjs from 'dayjs'
+
+const locale = {
+  name: 'es-pr',
+  monthsShort: 'ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic'.split('_'),
+  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('_'),
+  weekStart: 1,
+  formats: {
+    LT: 'h:mm A',
+    LTS: 'h:mm:ss A',
+    L: 'MM/DD/YYYY',
+    LL: 'D [de] MMMM [de] YYYY',
+    LLL: 'D [de] MMMM [de] YYYY h:mm A',
+    LLLL: 'dddd, D [de] MMMM [de] YYYY h:mm A'
+  },
+  relativeTime: {
+    future: 'en %s',
+    past: 'hace %s',
+    s: 'unos segundos',
+    m: 'un minuto',
+    mm: '%d minutos',
+    h: 'una hora',
+    hh: '%d horas',
+    d: 'un día',
+    dd: '%d días',
+    M: 'un mes',
+    MM: '%d meses',
+    y: 'un año',
+    yy: '%d años'
+  },
+  ordinal: n => `${n}º`
+}
+
+dayjs.locale(locale, null, true)
+
+export default locale
+
diff --git a/src/locale/fr.js b/src/locale/fr.js
--- a/src/locale/fr.js
+++ b/src/locale/fr.js
@@ -9,6 +9,7 @@ const locale = {
   months: 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'),
   monthsShort: 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'),
   weekStart: 1,
+  yearStart: 4,
   formats: {
     LT: 'HH:mm',
     LTS: 'HH:mm:ss',
diff --git a/src/locale/ht.js b/src/locale/ht.js
new file mode 100644
--- /dev/null
+++ b/src/locale/ht.js
@@ -0,0 +1,40 @@
+// Haitian Creole (Haiti) [ht]
+import dayjs from 'dayjs'
+
+const locale = {
+  name: 'ht',
+  weekdays: 'dimanch_lendi_madi_mèkredi_jedi_vandredi_samdi'.split('_'),
+  months: 'janvye_fevriye_mas_avril_me_jen_jiyè_out_septanm_oktòb_novanm_desanm'.split('_'),
+  weekdaysShort: 'dim._len._mad._mèk._jed._van._sam.'.split('_'),
+  monthsShort: 'jan._fev._mas_avr._me_jen_jiyè._out_sept._okt._nov._des.'.split('_'),
+  weekdaysMin: 'di_le_ma_mè_je_va_sa'.split('_'),
+  ordinal: n => n,
+  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'
+  },
+  relativeTime: {
+    future: 'nan %s',
+    past: 'sa gen %s',
+    s: 'kèk segond',
+    m: 'yon minit',
+    mm: '%d minit',
+    h: 'inèdtan',
+    hh: '%d zè',
+    d: 'yon jou',
+    dd: '%d jou',
+    M: 'yon mwa',
+    MM: '%d mwa',
+    y: 'yon ane',
+    yy: '%d ane'
+  }
+}
+
+dayjs.locale(locale, null, true)
+
+export default locale
+
diff --git a/types/index.d.ts b/types/index.d.ts
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -6,12 +6,12 @@ declare function dayjs (date?: dayjs.ConfigType): dayjs.Dayjs
 
 declare function dayjs (date?: dayjs.ConfigType, format?: dayjs.OptionType, strict?: boolean): dayjs.Dayjs
 
-declare function dayjs (date?: dayjs.ConfigType, format?: dayjs.OptionType, locale?: string, strict?: boolean): dayjs.Dayjs
+declare function dayjs (date?: dayjs.ConfigType, format?: dayjs.OptionType, locale?: LocalePresetType, strict?: boolean): dayjs.Dayjs
 
 declare namespace dayjs {
   export type ConfigType = string | number | Date | Dayjs
 
-  export type OptionType = { locale?: string, format?: string, utc?: boolean } | string | string[]
+  export type OptionType = { locale?: LocalePresetType, format?: string, utc?: boolean } | string | string[]
 
   type UnitTypeShort = 'd' | 'M' | 'y' | 'h' | 'm' | 's' | 'ms'
   export type UnitType = 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'month' | 'year' | 'date' | UnitTypeShort;
@@ -96,16 +96,16 @@ declare namespace dayjs {
 
     isAfter(date: ConfigType, unit?: OpUnitType): boolean
 
-    locale(): string
+    locale(): LocalePresetType
 
-    locale(preset: string | ILocale, object?: Partial<ILocale>): Dayjs
+    locale(preset: LocalePresetType | ILocale, object?: Partial<ILocale>): Dayjs
   }
 
   export type PluginFunc<T = unknown> = (option: T, c: typeof Dayjs, d: typeof dayjs) => void
 
   export function extend<T = unknown>(plugin: PluginFunc<T>, option?: T): Dayjs
 
-  export function locale(preset?: string | ILocale, object?: Partial<ILocale>, isLocal?: boolean): string
+  export function locale(preset?: LocalePresetType | ILocale, object?: Partial<ILocale>, isLocal?: boolean): string
 
   export function isDayjs(d: any): d is Dayjs
 
diff --git a/types/locale/types.d.ts b/types/locale/types.d.ts
--- a/types/locale/types.d.ts
+++ b/types/locale/types.d.ts
@@ -31,3 +31,140 @@ declare interface ILocale {
     yy: string
   }>
 }
+
+declare type LocalePresetType =
+  | 'af'
+  | 'ar-dz'
+  | 'ar-kw'
+  | 'ar-ly'
+  | 'ar-ma'
+  | 'ar-sa'
+  | 'ar-tn'
+  | 'ar'
+  | 'az'
+  | 'be'
+  | 'bg'
+  | 'bi'
+  | 'bm'
+  | 'bn'
+  | 'bo'
+  | 'br'
+  | 'bs'
+  | 'ca'
+  | 'cs'
+  | 'cv'
+  | 'cy'
+  | 'da'
+  | 'de-at'
+  | 'de-ch'
+  | 'de'
+  | 'dv'
+  | 'el'
+  | 'en-SG'
+  | 'en-au'
+  | 'en-ca'
+  | 'en-gb'
+  | 'en-ie'
+  | 'en-il'
+  | 'en-in'
+  | 'en-nz'
+  | 'en-tt'
+  | 'en'
+  | 'eo'
+  | 'es-do'
+  | 'es-us'
+  | 'es'
+  | 'et'
+  | 'eu'
+  | 'fa'
+  | 'fi'
+  | 'fo'
+  | 'fr-ca'
+  | 'fr-ch'
+  | 'fr'
+  | 'fy'
+  | 'ga'
+  | 'gd'
+  | 'gl'
+  | 'gom-latn'
+  | 'gu'
+  | 'he'
+  | 'hi'
+  | 'hr'
+  | 'hu'
+  | 'hy-am'
+  | 'id'
+  | 'is'
+  | 'it-ch'
+  | 'it'
+  | 'ja'
+  | 'jv'
+  | 'ka'
+  | 'kk'
+  | 'km'
+  | 'kn'
+  | 'ko'
+  | 'ku'
+  | 'ky'
+  | 'lb'
+  | 'lo'
+  | 'lt'
+  | 'lv'
+  | 'me'
+  | 'mi'
+  | 'mk'
+  | 'ml'
+  | 'mn'
+  | 'mr'
+  | 'ms-my'
+  | 'ms'
+  | 'mt'
+  | 'my'
+  | 'nb'
+  | 'ne'
+  | 'nl-be'
+  | 'nl'
+  | 'nn'
+  | 'oc-lnc'
+  | 'pa-in'
+  | 'pl'
+  | 'pt-br'
+  | 'pt'
+  | 'ro'
+  | 'ru'
+  | 'rw'
+  | 'sd'
+  | 'se'
+  | 'si'
+  | 'sk'
+  | 'sl'
+  | 'sq'
+  | 'sr-cyrl'
+  | 'sr'
+  | 'ss'
+  | 'sv'
+  | 'sw'
+  | 'ta'
+  | 'te'
+  | 'tet'
+  | 'tg'
+  | 'th'
+  | 'tk'
+  | 'tl-ph'
+  | 'tlh'
+  | 'tr'
+  | 'tzl'
+  | 'tzm-latn'
+  | 'tzm'
+  | 'ug-cn'
+  | 'uk'
+  | 'ur'
+  | 'uz-latn'
+  | 'uz'
+  | 'vi'
+  | 'x-pseudo'
+  | 'yo'
+  | 'zh-cn'
+  | 'zh-hk'
+  | 'zh-tw'
+  | 'zh'


Test patch:
diff --git a/test/plugin/utc-utcOffset.test.js b/test/plugin/utc-utcOffset.test.js
--- a/test/plugin/utc-utcOffset.test.js
+++ b/test/plugin/utc-utcOffset.test.js
@@ -48,7 +48,7 @@ it('clone', () => {
 
 it('immutable', () => {
   const d = dayjs()
-  const du = d.utcOffset(9)
+  const du = d.utcOffset(d.utcOffset() + 1)
   expect(d.utcOffset()).not.toBe(du.utcOffset())
   expect(d.format()).not.toBe(du.format())
 })
@@ -76,6 +76,30 @@ test('change hours when changing the utc offset in UTC mode', () => {
   expect(d.utcOffset(-1380).format('HH:mm')).toBe('07:31')
 })
 
+test('correctly set and add hours in offset mode', () => {
+  const d10 = dayjs('2000-01-30T06:31:00+10:00').utcOffset(10)
+  const dm8 = dayjs('2000-01-30T06:31:00-08:00').utcOffset(-8)
+
+  expect(d10.hour(5).hour()).toBe(5)
+  expect(d10.hour(5).add(1, 'hour').hour()).toBe(6)
+  expect(d10.hour(5).add(-10, 'hour').hour()).toBe(19)
+
+  expect(dm8.hour(5).hour()).toBe(5)
+  expect(dm8.hour(5).add(1, 'hour').hour()).toBe(6)
+  expect(dm8.hour(5).add(-10, 'hour').hour()).toBe(19)
+})
+
+test('keep hours when adding month in offset mode', () => {
+  const d10 = dayjs('2000-01-30T06:31:00+10:00').utcOffset(10)
+  const dm8 = dayjs('2000-01-30T06:31:00-08:00').utcOffset(-8)
+
+  expect(d10.add(1, 'month').hour()).toBe(6)
+  expect(dm8.add(1, 'month').hour()).toBe(6)
+
+  expect(d10.add(-2, 'month').hour()).toBe(6)
+  expect(dm8.add(-2, 'month').hour()).toBe(6)
+})
+
 test('utc costrustor', () => {
   const d = new Date(2019, 8, 11, 0, 0, 0).getTime()
   expect(moment(d).utc().utcOffset(480).valueOf())