Repository: iamkun/dayjs
Pull request #2694 (2024-07-18 12:32:36)

D2M


Patch:
diff --git a/README.md b/README.md
index b50a700..74d8805 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ English | [简体中文](./docs/zh-cn/README.zh-CN.md) | [日本語](./docs/ja/R
                                                                              alt="Day.js" /></a></p>
 <p align="center">Fast <b>2kB</b> alternative to Moment.js with the same modern API</p>
 <p align="center">
-    <a href="https://unpkg.com/dayjs/dayjs.min.js"><img
+    <a href="https://bundlephobia.com/package/dayjs"><img
             src="https://img.shields.io/bundlephobia/minzip/dayjs?style=flat-square&color=%2345cc11"
             alt="Gzip Size"></a>
     <a href="https://www.npmjs.com/package/dayjs"><img src="https://img.shields.io/npm/v/dayjs.svg?style=flat-square&colorB=51C838"
@@ -115,8 +115,8 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
   <img width="70" src="https://user-images.githubusercontent.com/17680888/197092231-2367b5eb-1e43-467e-a311-23f7cd97b086.png">
 </a>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-<a href="https://github.com/alan-eu" target="_blank">
-  <img width="70" src="https://avatars.githubusercontent.com/u/18175329?s=52&v=4">
+<a href="https://github.com/ken-swyfft" target="_blank">
+  <img width="70" src="https://avatars.githubusercontent.com/u/65305317?v=4">
 </a>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 <a href="https://opencollective.com/sight-and-sound-ministries" target="_blank">
@@ -127,25 +127,22 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
   <img width="70" src="https://images.opencollective.com/chudovo/3c866f5/logo/256.png?height=256">
 </a>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-<a href="https://github.com/ken-swyfft" target="_blank">
-  <img width="70" src="https://avatars.githubusercontent.com/u/65305317?v=4">
-</a>
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-<a href="https://github.com/storyblok" target="_blank">
-  <img width="70" src="https://avatars.githubusercontent.com/u/13880908?s=200&v=4">
+<a href="https://github.com/alan-eu" target="_blank">
+  <img width="70" src="https://avatars.githubusercontent.com/u/18175329?s=52&v=4">
 </a>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 <a href="https://www.exoflare.com/open-source/?utm_source=dayjs&utm_campaign=open_source" target="_blank">
   <img width="70" src="https://user-images.githubusercontent.com/17680888/162761622-1407a849-0c41-4591-8aa9-f98114ec2092.png">
 </a>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+<a href="https://github.com/storyblok" target="_blank">
+  <img width="70" src="https://avatars.githubusercontent.com/u/13880908?s=200&v=4">
+</a>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 <a href="https://bestkru.com/" target="_blank">
   <img width="70" src="https://avatars.githubusercontent.com/u/159320286" alt="BestKru">
 </a>
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-<a href="https://opencollective.com/anonstories" target="_blank"><img width="70" src="https://images.opencollective.com/anonstories/7e826c0/avatar/256.png"></a>
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-<a href="https://opencollective.com/datawrapper" target="_blank"><img width="70" src="https://images.opencollective.com/datawrapper/c13e229/logo.png"></a>
+
 
 ## Contributors
 
diff --git a/docs/demo/index.js b/docs/demo/index.js
new file mode 100644
index 0000000..4299e30
--- /dev/null
+++ b/docs/demo/index.js
@@ -0,0 +1,9 @@
+import dayjs from 'dayjs'
+// basic usage
+dayjs().format()
+
+// parse
+dayjs('2018-08-08').format()
+
+// format
+dayjs().format('YYYY-MM-DD')
diff --git a/docs/sw/README-sw.md b/docs/sw/README-sw.md
new file mode 100644
index 0000000..df1d173
--- /dev/null
+++ b/docs/sw/README-sw.md
@@ -0,0 +1,163 @@
+Swahili | [English](../../README.md) | [Kireno](../pt-br/README-pt-br.md) | [Kichina](../zh-cn/README.zh-CN.md) | [Kijapani](../ja/README-ja.md) | [Kikorea](../ko/README-ko.md) | [Kihispania](../es-es/README-es-es.md) | [Kirusi](../ru/README-ru.md) | [Kituruki](../tr/README-tr.md) | [Sinhala](../si/README-si.md) | [Kiebrania](../he/README-he.md)
+
+<p align="center"><a href="https://day.js.org/" target="_blank" rel="noopener noreferrer"><img width="550"
+                                                                             src="https://user-images.githubusercontent.com/17680888/39081119-3057bbe2-456e-11e8-862c-646133ad4b43.png"
+                                                                             alt="Day.js" /></a></p>
+<p align="center">Fast <b>2kB</b> mbadala wa Moment.js ukiwa na API zinazofanana na za kisasa</p>
+<p align="center">
+    <a href="https://bundlephobia.com/package/dayjs"><img
+            src="https://img.shields.io/bundlephobia/minzip/dayjs?style=flat-square&color=%2345cc11"
+            alt="Gzip Size"></a>
+             <a href="https://www.npmjs.com/package/dayjs"><img src="https://img.shields.io/npm/v/dayjs.svg?style=flat-square&colorB=51C838"
+                                                       alt="NPM Version"></a>
+    <a href="https://github.com/iamkun/dayjs/actions/workflows/check.yml"><img
+            src="https://img.shields.io/github/actions/workflow/status/iamkun/dayjs/check.yml?style=flat-square" alt="Build Status"></a>
+    <a href="https://codecov.io/gh/iamkun/dayjs"><img
+            src="https://img.shields.io/codecov/c/github/iamkun/dayjs/master.svg?style=flat-square" alt="Codecov"></a>
+    <a href="https://github.com/iamkun/dayjs/blob/master/LICENSE"><img
+            src="https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square" alt="License"></a>
+    <br>
+    <a href="https://saucelabs.com/u/dayjs">
+        <img width="750" src="https://user-images.githubusercontent.com/17680888/40040137-8e3323a6-584b-11e8-9dba-bbe577ee8a7b.png" alt="Sauce Test Status">
+    </a>
+</p>
+
+> Day.js ni Maktaba ya JavaScript ya minimalist ambayo inachanganua, kuthibitisha, kudhibiti, na kuonyesha tarehe na nyakati kwa vivinjari vya kisasa na API inayofanana sana na Moment.js. Ikiwa unatumia Moment.js, tayari unajua jinsi ya kutumia Day.js.
+
+```js
+dayjs().startOf('month').add(1, 'day').set('year', 2018).format('YYYY-MM-DD HH:mm:ss');
+```
+
+* 🕒 Michoro na API maarufu za Moment.js
+* 💪 Hazibadiliki
+* 🔥 Zinaunganishwa
+* 🌐 Zinaruhusu I18n
+* 📦 Maktaba ndogo ya 2kb
+* 👫 Browser zote zinaruhusu
+
+---
+
+## Kuanza
+
+### Nyaraka
+
+Unaweza kutafuta maelekezo zaidi ya  API na nyaraka zingine kupitia tovuti ya [day.js.org](https://day.js.org/).
+
+### Kusakinisha
+
+```console
+npm install dayjs --save
+```
+
+📚[Maelekezo ya Kusakinisha](https://day.js.org/docs/en/installation/installation)
+
+### API
+
+Ni rahisi kutumia Day.js kupitisha, kuhakiki, kubadili na kuonesha tarehe na mda.
+
+```javascript
+dayjs('2018-08-08') // changanua
+
+dayjs().format('{YYYY} MM-DDTHH:mm:ss SSS [Z] A') // display
+
+dayjs().set('month', 3).month() // chukua na weka
+dayjs().add(1, 'year') // manipulate
+
+dayjs().isBefore(dayjs()) // query
+```
+
+📚[Maelekezo Ya API](https://day.js.org/docs/en/parse/parse)
+
+### I18n
+
+Day.js ina ruhusu kwa internalization.
+
+Lakini hakuna hata moja ambayo itawekwa katika utengezaji endapo utatumia.
+
+```javascript
+import 'dayjs/locale/es' // load on demand
+
+dayjs.locale('es') // use Spanish locale globally
+
+dayjs('2018-05-05').locale('zh-cn').format() // use Chinese Simplified locale in a specific instance
+```
+
+📚[Internationalization](https://day.js.org/docs/en/i18n/i18n)
+
+### Zana
+
+Zana ya Kujumuisha ni moduli inayojitegemea ambayo inaweza ikaweka katika Day.js ili kuweza kuongeza uwezo au kuongeza sifa.
+
+```javascript
+import advancedFormat from 'dayjs/plugin/advancedFormat' // Upakiaji kwa Mahitaji
+
+dayjs.extend(advancedFormat) // tumia plugin
+
+dayjs().format('Q Do k kk X x') // njia zaidi zilizopo
+```
+
+📚[Idadi Ya Plugin](https://day.js.org/docs/en/plugin/plugin)
+
+### Trend Zinazoweza Tumika
+
+<a href="https://npm-compare.com/moment,dayjs/#timeRange=THREE_YEARS" target="_blank">
+  <img src="https://user-images.githubusercontent.com/3455798/270162667-c7bd2ebe-675e-45c6-a2c9-dc67f3b65d6e.png">
+</a>
+
+## Wafadhili
+
+Toa mchango wako kwa huu mradi kwa kuwa mfadhili. Nembo yako itaonekana hapa pamoja na link ya kwenda kwenye tovuti yako.
+
+[[Kuwa mfadhili kupitia GitHub](https://github.com/sponsors/iamkun/)] [[Kuwa mfadhili kupitia OpenCollective](https://opencollective.com/dayjs#sponsor)]
+
+<a href="https://toyokumo.co.jp" target="_blank">
+  <img width="70" src="https://user-images.githubusercontent.com/17680888/197092231-2367b5eb-1e43-467e-a311-23f7cd97b086.png">
+</a>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+<a href="https://github.com/ken-swyfft" target="_blank">
+  <img width="70" src="https://avatars.githubusercontent.com/u/65305317?v=4">
+</a>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+<a href="https://opencollective.com/sight-and-sound-ministries" target="_blank">
+  <img width="70" src="https://user-images.githubusercontent.com/17680888/232316426-cb99b4cf-0ccb-4e73-a6ce-e16dba6aadf4.png">
+</a>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+<a href="https://chudovo.com/" target="_blank">
+  <img width="70" src="https://images.opencollective.com/chudovo/3c866f5/logo/256.png?height=256">
+</a>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+<a href="https://github.com/alan-eu" target="_blank">
+  <img width="70" src="https://avatars.githubusercontent.com/u/18175329?s=52&v=4">
+</a>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+<a href="https://www.exoflare.com/open-source/?utm_source=dayjs&utm_campaign=open_source" target="_blank">
+  <img width="70" src="https://user-images.githubusercontent.com/17680888/162761622-1407a849-0c41-4591-8aa9-f98114ec2092.png">
+</a>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+<a href="https://github.com/storyblok" target="_blank">
+  <img width="70" src="https://avatars.githubusercontent.com/u/13880908?s=200&v=4">
+</a>
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+<a href="https://bestkru.com/" target="_blank">
+  <img width="70" src="https://avatars.githubusercontent.com/u/159320286" alt="BestKru">
+</a>
+
+
+## Wachangiaji
+
+Huu mradi umefika hapa ulipo hapa shukrani ziende kwa watu wote wanao changia.
+
+Tafadhali tupe 💖 nyota/maua 💖 kutuunga mkono sisi.Ahsante.
+
+Na ahsante kwa wafadhili wote! 🙏
+
+<a href="https://opencollective.com/dayjs/backer/0/website?requireActive=false" target="_blank"><img width="35" src="https://opencollective.com/dayjs/backer/0/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/dayjs/backer/1/website?requireActive=false" target="_blank"><img width="35" src="https://opencollective.com/dayjs/backer/1/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/dayjs/backer/2/website?requireActive=false" target="_blank"><img width="35" src="https://opencollective.com/dayjs/backer/2/avatar.svg?requireActive=false"></a>
+<a href="https://opencollective.com/dayjs/backer/3/website?requireActive=false" target="_blank"><img width="35" src="https://opencollective.com/dayjs/backer/3/avatar.svg?requireActive=false"></a>
+<br />
+<a href="https://opencollective.com/dayjs#backers" target="_blank"><img src="https://opencollective.com/dayjs/contributors.svg?width=890" /></a>
+
+## Leseni
+
+Day.js ipo chini ya kibali cha [leseni ya MTI](./LICENSE).
\ No newline at end of file
diff --git a/src/plugin/negativeYear/index.js b/src/plugin/negativeYear/index.js
new file mode 100644
index 0000000..6fc90c0
--- /dev/null
+++ b/src/plugin/negativeYear/index.js
@@ -0,0 +1,29 @@
+export default (_, c, dayjs) => {
+  const proto = c.prototype
+
+  const parseDate = (cfg) => {
+    const { date, utc } = cfg
+    if (typeof date === 'string' && date.charAt(0) === '-') {
+      const normalData = date.slice(1)
+      let newDate = dayjs(normalData)
+      if (utc) {
+        newDate = dayjs.utc(normalData)
+      } else {
+        newDate = dayjs(normalData)
+      }
+      const fullYear = newDate.year()
+      if (date.indexOf(`-${fullYear}`) !== -1) {
+        return dayjs(newDate).subtract(fullYear * 2, 'year').toDate()
+      }
+      return 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/timezone/index.js b/src/plugin/timezone/index.js
index 832fb4a..bcacf9a 100644
--- a/src/plugin/timezone/index.js
+++ b/src/plugin/timezone/index.js
@@ -97,11 +97,18 @@ export default (o, c, d) => {
     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, { locale: this.$L }).$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)
+    const offset = (-Math.round(date.getTimezoneOffset() / 15) * 15) - diff
+    const isUTC = !Number(offset)
+    let ins
+    if (isUTC) { // if utcOffset is 0, turn it to UTC mode
+      ins = this.utcOffset(0, keepLocalTime)
+    } else {
+      ins = d(target, { locale: this.$L }).$set(MS, this.$ms)
+        .utcOffset(offset, true)
+      if (keepLocalTime) {
+        const newOffset = ins.utcOffset()
+        ins = ins.add(oldOffset - newOffset, MIN)
+      }
     }
     ins.$x.$timezone = timezone
     return ins
diff --git a/types/plugin/minMax.d.ts b/types/plugin/minMax.d.ts
index 4c5f6dc..7d0827f 100644
--- a/types/plugin/minMax.d.ts
+++ b/types/plugin/minMax.d.ts
@@ -4,8 +4,19 @@ declare const plugin: PluginFunc
 export = plugin
 
 declare module 'dayjs' {
-  export function max(dayjs: Dayjs[]): Dayjs | null
-  export function max(...dayjs: Dayjs[]): Dayjs | null
-  export function min(dayjs: Dayjs[]): Dayjs | null
-  export function min(...dayjs: Dayjs[]): Dayjs | null
+  export function max(dayjs: [Dayjs, ...Dayjs[]]): Dayjs
+  export function max(noDates: never[]): null
+  export function max(maybeDates: Dayjs[]): Dayjs | null
+
+  export function max(...dayjs: [Dayjs, ...Dayjs[]]): Dayjs
+  export function max(...noDates: never[]): null
+  export function max(...maybeDates: Dayjs[]): Dayjs | null
+
+  export function min(dayjs: [Dayjs, ...Dayjs[]]): Dayjs
+  export function min(noDates: never[]): null
+  export function min(maybeDates: Dayjs[]): Dayjs | null
+
+  export function min(...dayjs: [Dayjs, ...Dayjs[]]): Dayjs
+  export function min(...noDates: never[]): null
+  export function min(...maybeDates: Dayjs[]): Dayjs | null
 }
diff --git a/types/plugin/negativeYear.d.ts b/types/plugin/negativeYear.d.ts
new file mode 100644
index 0000000..43bb868
--- /dev/null
+++ b/types/plugin/negativeYear.d.ts
@@ -0,0 +1,4 @@
+import {PluginFunc} from 'dayjs'
+
+declare const plugin: PluginFunc
+export = plugin
\ No newline at end of file


Test patch:
diff --git a/test/plugin/negativeYear.test.js b/test/plugin/negativeYear.test.js
new file mode 100644
index 0000000..a9713fa
--- /dev/null
+++ b/test/plugin/negativeYear.test.js
@@ -0,0 +1,52 @@
+import MockDate from 'mockdate'
+import dayjs from 'dayjs'
+import negativeYear from '../../src/plugin/negativeYear'
+import utc from '../../src/plugin/utc'
+import { REGEX_PARSE } from '../../src/constant'
+
+
+dayjs.extend(negativeYear)
+dayjs.extend(utc)
+
+beforeEach(() => {
+  MockDate.set(new Date())
+})
+
+afterEach(() => {
+  MockDate.reset()
+})
+
+describe('negativeYear', () => {
+  it('parses negative years', () => {
+    expect(dayjs('-2020-01-01').year()).toBe(-2020)
+    const date = '-2021/01/03'
+    const date2 = '01/03/-2021'
+    const date3 = '01-03--2021'
+    const d = date.match(REGEX_PARSE)
+    expect(dayjs(date).format('YYYY-MM-DD')).toBe('-2021-01-03')
+    expect(dayjs(date2).format('YYYY-MM-DD')).toBe('Invalid Date')
+    expect(dayjs(date3).format()).toBe('Invalid Date')
+    expect(d).toBe(null)
+  })
+
+  it('does not parse non-negative years', () => {
+    expect(dayjs('2020-01-01').year()).toBe(2020)
+  })
+
+  it('works with other plugins', () => {
+    expect(dayjs.utc('-2020-01-01').year()).toBe(-2020)
+  })
+
+  it('Add and subtract with negative years', () => {
+    expect(dayjs('-2006').add(1, 'y')).toEqual(dayjs('-2005'))
+    expect(dayjs('-2006').subtract(1, 'y')).toEqual(dayjs('-2007'))
+    expect(dayjs('-2006').add(1, 'y').format('YYYY')).toBe(dayjs('-2005').format('YYYY'))
+    expect(dayjs('-2006').subtract(1, 'y').format('YYYY')).toBe(dayjs('-2007').format('YYYY'))
+  })
+
+  it('Compare date with negative years', () => {
+    expect(dayjs('-2006').isAfter(dayjs('-2007'))).toBeTruthy()
+    expect(dayjs('-2006').isBefore(dayjs('-2005'))).toBeTruthy()
+    expect(dayjs('-2006').isSame('-2006')).toBeTruthy()
+  })
+})
diff --git a/test/plugin/timezone.test.js b/test/plugin/timezone.test.js
index c4529c6..d83a03f 100644
--- a/test/plugin/timezone.test.js
+++ b/test/plugin/timezone.test.js
@@ -330,3 +330,25 @@ describe('startOf and endOf', () => {
     expect(tzWithLocality.startOf('week').format('YYYY-MM-DD')).toEqual('2023-02-15')
   })
 })
+
+
+describe('UTC timezone', () => {
+  it('TZ with UTC with Locale', () => {
+    const test1 = dayjs('2000-01-01T09:00:00+09:00').tz('Asia/Seoul').locale('en')
+    expect(test1.hour()).toBe(9)
+    const test2 = dayjs('2000-01-01T09:00:00+09:00').tz('Asia/Hong_Kong').locale('en')
+    expect(test2.hour()).toBe(8)
+    const test3 = dayjs('2000-01-01T09:00:00+09:00').tz('Etc/UTC').locale('en')
+    expect(test3.hour()).toBe(0)
+  })
+
+  it('TZ with UTC', () => {
+    const dayjs1 = dayjs('2000-01-01T09:01:00+09:00').tz('Etc/UTC', false)
+    expect(dayjs1.format()).toBe('2000-01-01T00:01:00Z')
+    const moment1 = moment('2000-01-01T09:01:00+09:00').tz('Etc/UTC', false)
+    expect(moment1.format()).toBe('2000-01-01T00:01:00Z')
+    const dayjs2 = dayjs('2000-01-01T09:01:00+09:00').tz('Etc/UTC', true)
+    const moment2 = moment('2000-01-01T09:01:00+09:00').tz('Etc/UTC', true)
+    expect(dayjs2.format()).toBe(moment2.format())
+  })
+})