diff --git a/CHANGELOG.md b/CHANGELOG.md index 85e19ed..450309d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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`: diff --git a/src/datetime.js b/src/datetime.js index 11d43ff..f81d21c 100644 --- a/src/datetime.js +++ b/src/datetime.js @@ -34,14 +34,25 @@ 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()) @@ -49,23 +60,23 @@ export function datetime(date = (new Date()), precision = 'day') { + ':' + 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 diff --git a/src/datetime.test.js b/src/datetime.test.js index 325051e..f9e5c9d 100644 --- a/src/datetime.test.js +++ b/src/datetime.test.js @@ -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 @@ -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') }) @@ -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') }) diff --git a/src/utils/date.js b/src/utils/date.js index 2ab2757..1ca8a51 100644 --- a/src/utils/date.js +++ b/src/utils/date.js @@ -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) @@ -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