Skip to content

Commit

Permalink
Fix incorrect year for 'week' precision if week started the previou…
Browse files Browse the repository at this point in the history
…s year

Also add an internal utility to get the normalized day index (so that Sunday is now 7 instead of 0).
  • Loading branch information
meduzen authored Apr 12, 2024
1 parent fad6c3f commit cb768ad
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 10 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ Nothing for now.

Compare with [last published version](https://github.com/meduzen/datetime-attribute/compare/1.3.3...main).

### Fixed

- Fix incorrect year for `datetime(date, 'week')` when the week started the previous year. For example, `2021-01-01` is a Friday and its week belongs to 2020 (as [per spec](./README.md#weeknumber). In that case, the output was `2021-53` instead of `2020-53`.

### Improved

- Improve type definitions where `?` and `undefined` are redundant by removing `undefined`:
Expand Down
27 changes: 19 additions & 8 deletions src/datetime.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,38 +34,49 @@ export function datetime(date = (new Date()), precision = 'day') {
// Cut last 4 digits of the year from the others to ease further computation.
if (year > 9999) {
[bigYearDigits, year] = (year / 10000).toFixed(4).split('.')
year = parseInt(year)

// Clone date (to leave it untouched) and set a 4-digits year in the clone.
date = new Date(date)
// Set a 4-digits year.
date.setFullYear(year)
}

/**
* If the week started the previous year (see `weekNumber` documentation) and
* the requested precision is 'week', we decrement the year by 1.
*/

const weekNum = precision == 'week' ? weekNumber(date) : null

if (weekNum == 53 && date.getMonth() == 0) {
year--
}

// Local datetime at milliseconds precision (1960-04-27T00:00:00.123)
const localMs = p(year.toString(), 4) // zero-pad years, as per spec
const localMs = p(year, 4) // zero-pad years, as per spec
+ '-' + p(date.getMonth() + 1) // `+ 1` because 0 is January and 11 is December
+ '-' + p(date.getDate())
+ 'T' + p(date.getHours())
+ ':' + p(date.getMinutes())
+ ':' + p(date.getSeconds())
+ '.' + p(date.getMilliseconds(), 3)

const addSignAndYearDigits = shouldAdd => shouldAdd ? sign + bigYearDigits : ''
const withSignAndYearDigits = shouldAdd => shouldAdd ? sign + bigYearDigits : ''

/**
* Extract substring from local date. When `start` is 0, the year is wanted:
* its sign and missing digits (for years with 5+ digits) are prepended.
*/

const local = (start, length) => addSignAndYearDigits(!start) + localMs.substr(start, length)
const utc = (start, length) => addSignAndYearDigits(!start) + date.toJSON().substr(start, length) + 'Z'
const local = (start, length) => withSignAndYearDigits(!start) + localMs.substr(start, length)
const utc = (start, length) => withSignAndYearDigits(!start) + date.toJSON().substr(start, length) + 'Z'

const formats = {
'year': () => local(0, 4), // 1960
'month': () => local(0, 7), // 1960-04
'day': () => local(0, 10), // 1960-04-27

'week': () => local(0, 5) + 'W' + p(weekNumber(date)), // 1960-W17
'yearless': () => local(5, 5), // 04-27
'week': () => local(0, 5) + 'W' + p(weekNum), // 1960-W17
'yearless': () => local(5, 5), // 04-27

'time': () => local(11, 5), // 00:00
'second': () => local(11, 8), // 00:00:00
Expand Down
16 changes: 15 additions & 1 deletion src/datetime.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, test } from 'vitest'

import { datetime, datetimeTz, tzOffset, utc } from './index.js'
import { getNormalizeDay } from './utils/date.js'

const togoIndependanceDay = new Date(1960, 3, 27)
const date = togoIndependanceDay // alias for the sake of brevity
Expand Down Expand Up @@ -87,10 +88,16 @@ describe('datetime', () => {
expect(datetime(december31th2020, 'week')).toBe('2020-W53')
})

test.todo('week on 2021-01-01 is 2020-W53', () => {
// 1st day of the year is after Thurdsay
test('week on 2021-01-01 is 2020-W53', () => {
expect(getNormalizeDay(january1st2021)).toBeGreaterThan(4)
expect(datetime(january1st2021, 'week')).toBe('2020-W53')
})

test('week on 2021-01-08 is 2021-W01', () => {
expect(datetime(new Date(2021, 0, 8), 'week')).toBe('2021-W01')
})

test('week on 2021-01-11 is 2021-W02', () => {
expect(datetime(january11th, 'week')).toBe('2021-W02')
})
Expand All @@ -99,6 +106,13 @@ describe('datetime', () => {
expect(datetime(january19th, 'week')).toBe('2021-W03')
})

// 1st day of the month is after Thurdsay
test('week on 2021-03-01 is 2021-W17', () => {
const march1st2021 = new Date(2021, 4, 1)
expect(getNormalizeDay(march1st2021)).toBeGreaterThan(4)
expect(datetime(march1st2021, 'week')).toBe('2021-W17')
})

test('week on 2021-12-31 is 2021-W52', () => {
expect(datetime(december31th2021, 'week')).toBe('2021-W52')
})
Expand Down
10 changes: 9 additions & 1 deletion src/utils/date.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function daysBetween(date, furtherDate) {
* @returns {number}
*/
export function weekNumber(date) {
const dayIndex = date.getDay() || 7 // normalize index because Sunday == 0
const dayIndex = getNormalizeDay(date) // normalize index because Sunday == 0

const sameWeekThursday = new Date(date)
sameWeekThursday.setDate(date.getDate() + 4 - dayIndex)
Expand All @@ -45,3 +45,11 @@ export function weekNumber(date) {

return Math.ceil(daysDifference / 7)
}

/**
* Get the day index of the week, except Sunday is 7 instead of 0.
*
* @param {Date} date
* @returns {number}
*/
export const getNormalizeDay = date => date.getDay() || 7

0 comments on commit cb768ad

Please sign in to comment.