Repository: iamkun/dayjs (JavaScript)

Issue:
dayjs returns wrong object when created using year week string YYYY-w
**Describe the bug**
When creating dayjs object from year week string such as 2020-35, the created object contains the wrong month and date information.

For example:

`dayjs('2020-35', 'YYYY-w').  //Wed Jan 01 2020 00:00:00`
`dayjs('2020-35', 'YYYY-w').format('YYYY-w'). //2020-1`

**Expected behavior**
dayjs should return the right object.

**Information**
 - Day.js Version 1.8.33
 - OS: macos
 - Browser chrome84
 - Time zone: GMT+08:00

针对“周”的日期字符串解析异常
在已经加载`advancedFormat`插件的情况下，针对指定日期字符串的格式化解析结果错误：

```javascript
console.log(dayjs().format('YYYY年-第w周'));
console.log(dayjs().format('yyyy-第w周'));
console.log(dayjs('2024年-第24周', 'yyyy年-第w周'));
console.log(dayjs('2024年-第24周', 'YYYY年-第w周'));
```
![image](https://github.com/iamkun/dayjs/assets/49147660/bda9043b-5836-4146-9490-6c37887db330)



Discussion:
Day.js does not support parsing `w` token but will add it in the future. 

List of all available parsing tokens https://day.js.org/docs/en/parse/string-format#list-of-all-available-parsing-tokens
any progress?





Patch:
diff --git a/src/plugin/customParseFormat/index.js b/src/plugin/customParseFormat/index.js
index 771d13106..c7082833c 100644
--- a/src/plugin/customParseFormat/index.js
+++ b/src/plugin/customParseFormat/index.js
@@ -1,6 +1,6 @@
 import { u } from '../localizedFormat/utils'
 
-const formattingTokens = /(\[[^[]*\])|([-_:/.,()\s]+)|(A|a|YYYY|YY?|MM?M?M?|Do|DD?|hh?|HH?|mm?|ss?|S{1,3}|z|ZZ?)/g
+const formattingTokens = /(\[[^[]*\])|([-_:/.,()\s]+)|(A|a|Q|YYYY|YY?|ww?|MM?M?M?|Do|DD?|hh?|HH?|mm?|ss?|S{1,3}|z|ZZ?)/g
 
 const match1 = /\d/ // 0 - 9
 const match2 = /\d\d/ // 00 - 99
@@ -66,6 +66,9 @@ const expressions = {
   a: [matchWord, function (input) {
     this.afternoon = meridiemMatch(input, true)
   }],
+  Q: [match1, function (input) {
+    this.month = ((input - 1) * 3) + 1
+  }],
   S: [match1, function (input) {
     this.milliseconds = +input * 100
   }],
@@ -95,6 +98,8 @@ const expressions = {
       }
     }
   }],
+  w: [match1to2, addInput('week')],
+  ww: [match2, addInput('week')],
   M: [match1to2, addInput('month')],
   MM: [match2, addInput('month')],
   MMM: [matchWord, function (input) {
@@ -173,12 +178,12 @@ function makeParser(format) {
   }
 }
 
-const parseFormattedInput = (input, format, utc) => {
+const parseFormattedInput = (input, format, utc, dayjs) => {
   try {
     if (['x', 'X'].indexOf(format) > -1) return new Date((format === 'X' ? 1000 : 1) * input)
     const parser = makeParser(format)
     const {
-      year, month, day, hours, minutes, seconds, milliseconds, zone
+      year, month, day, hours, minutes, seconds, milliseconds, zone, week
     } = parser(input)
     const now = new Date()
     const d = day || ((!year && !month) ? now.getDate() : 1)
@@ -197,7 +202,12 @@ const parseFormattedInput = (input, format, utc) => {
     if (utc) {
       return new Date(Date.UTC(y, M, d, h, m, s, ms))
     }
-    return new Date(y, M, d, h, m, s, ms)
+    let newDate
+    newDate = new Date(y, M, d, h, m, s, ms)
+    if (week) {
+      newDate = dayjs(newDate).week(week).toDate()
+    }
+    return newDate
   } catch (e) {
     return new Date('') // Invalid Date
   }
@@ -224,12 +234,12 @@ export default (o, C, d) => {
       const isStrictWithLocale = args[3] === true
       const isStrict = isStrictWithoutLocale || isStrictWithLocale
       let pl = args[2]
-      if (isStrictWithLocale) [,, pl] = args
+      if (isStrictWithLocale) [, , pl] = args
       locale = this.$locale()
       if (!isStrictWithoutLocale && pl) {
         locale = d.Ls[pl]
       }
-      this.$d = parseFormattedInput(date, format, utc)
+      this.$d = parseFormattedInput(date, format, utc, d)
       this.init()
       if (pl && pl !== true) this.$L = this.locale(pl).$L
       // use != to treat


Test patch:
diff --git a/test/plugin/customParseFormat.test.js b/test/plugin/customParseFormat.test.js
index a4588e976..fb4030176 100644
--- a/test/plugin/customParseFormat.test.js
+++ b/test/plugin/customParseFormat.test.js
@@ -7,9 +7,11 @@ import '../../src/locale/zh-cn'
 import customParseFormat from '../../src/plugin/customParseFormat'
 import advancedFormat from '../../src/plugin/advancedFormat'
 import localizedFormats from '../../src/plugin/localizedFormat'
+import weekOfYear from '../../src/plugin/weekOfYear'
 
 dayjs.extend(customParseFormat)
 dayjs.extend(localizedFormats)
+dayjs.extend(weekOfYear) // test parse w, ww
 
 beforeEach(() => {
   MockDate.set(new Date())
@@ -437,3 +439,24 @@ it('parse X x', () => {
   dayjs.extend(advancedFormat)
   expect(dayjs(input2, format2, true).valueOf()).toBe(moment(input2, format2, true).valueOf())
 })
+
+it('parse Q, [Q]', () => {
+  const input1 = '2024-Q1'
+  const input2 = '2024-Q2'
+  const input3 = '2024-Q3'
+  const input4 = '2024-Q4'
+  const format = 'YYYY-[Q]Q'
+  expect(dayjs(input1, format).valueOf()).toBe(moment(input1, format).valueOf())
+  expect(dayjs(input2, format).valueOf()).toBe(moment(input2, format).valueOf())
+  expect(dayjs(input3, format).valueOf()).toBe(moment(input3, format).valueOf())
+  expect(dayjs(input4, format).valueOf()).toBe(moment(input4, format).valueOf())
+})
+
+it('parse w, ww', () => {
+  const input = '2024-w1'
+  const format1 = 'YYYY-[w]w'
+  expect(dayjs(input, format1).format(format1)).toBe(input)
+  const input2 = '2024-w32'
+  const format2 = 'YYYY-[w]ww'
+  expect(dayjs(input2, format2).format(format1)).toBe(input2)
+})