#178 - explicit support for telling if the Www date should be same, earlier than the first day of the week or later than the last day of the week
- syntax W1 W1- W1+
This commit is contained in:
parent
2204982485
commit
f7c69b18f9
|
@ -17,15 +17,19 @@ export const Date_dd_Mmm_yyyy_RegexStr: string = ' *([0-3]*[0-9]-(?:Jan|Feb|Mar|
|
||||||
export const Date_Mmm_dd_yyyy_RegexStr: string = ' *((?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-[0-3]*[0-9]-\\d{4})'; // Date like Jan-01-2020
|
export const Date_Mmm_dd_yyyy_RegexStr: string = ' *((?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-[0-3]*[0-9]-\\d{4})'; // Date like Jan-01-2020
|
||||||
|
|
||||||
export const Date_yyyy_Www_mm_dd_RegexStr: string = ' *(\\d{4}-W[0-5]*[0-9] \\([0-3]*[0-9]-[0-3]*[0-9]\\))'
|
export const Date_yyyy_Www_mm_dd_RegexStr: string = ' *(\\d{4}-W[0-5]*[0-9] \\([0-3]*[0-9]-[0-3]*[0-9]\\))'
|
||||||
export const Date_yyyy_WwwISO_RegexStr: string = ' *(\\d{4}-W[0-5]*[0-9])'
|
export const Date_yyyy_WwwISO_RegexStr: string = ' *(\\d{4}-W[0-5]*[0-9][-+]?)'
|
||||||
export const Date_yyyy_Www_RegexStr: string = Date_yyyy_WwwISO_RegexStr
|
export const Date_yyyy_Www_RegexStr: string = Date_yyyy_WwwISO_RegexStr
|
||||||
|
|
||||||
export const DOT_SEPARATOR = '.'
|
export const DOT_SEPARATOR = '.' // ASCII 46
|
||||||
export const DASH_SEPARATOR = '-'
|
export const DASH_SEPARATOR = '-'
|
||||||
|
|
||||||
const SLASH_SEPARATOR = '/' // ASCII 47
|
const SLASH_SEPARATOR = '/' // ASCII 47, right before ASCII 48 = '0'
|
||||||
|
const COLON_SEPARATOR = ':' // ASCII 58, first non-digit character
|
||||||
const PIPE_SEPARATOR = '|' // ASCII 124
|
const PIPE_SEPARATOR = '|' // ASCII 124
|
||||||
|
|
||||||
|
const EARLIER_THAN_SLASH_SEPARATOR = DOT_SEPARATOR
|
||||||
|
const LATER_THAN_SLASH_SEPARATOR = COLON_SEPARATOR
|
||||||
|
|
||||||
export const DEFAULT_NORMALIZATION_PLACES = 8; // Fixed width of a normalized number (with leading zeros)
|
export const DEFAULT_NORMALIZATION_PLACES = 8; // Fixed width of a normalized number (with leading zeros)
|
||||||
|
|
||||||
// Property escapes:
|
// Property escapes:
|
||||||
|
@ -62,9 +66,9 @@ export function getNormalizedNumber(s: string = '', separator?: string, places?:
|
||||||
// guarantees correct order (/ = ASCII 47, | = ASCII 124)
|
// guarantees correct order (/ = ASCII 47, | = ASCII 124)
|
||||||
if (separator) {
|
if (separator) {
|
||||||
const components: Array<string> = s.split(separator).filter(s => s)
|
const components: Array<string> = s.split(separator).filter(s => s)
|
||||||
return `${components.map((c) => prependWithZeros(c, places ?? DEFAULT_NORMALIZATION_PLACES)).join(PIPE_SEPARATOR)}//`
|
return `${components.map((c) => prependWithZeros(c, places ?? DEFAULT_NORMALIZATION_PLACES)).join(PIPE_SEPARATOR)}${SLASH_SEPARATOR}${SLASH_SEPARATOR}`
|
||||||
} else {
|
} else {
|
||||||
return `${prependWithZeros(s, places ?? DEFAULT_NORMALIZATION_PLACES)}//`
|
return `${prependWithZeros(s, places ?? DEFAULT_NORMALIZATION_PLACES)}${SLASH_SEPARATOR}${SLASH_SEPARATOR}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,9 +112,9 @@ export function getNormalizedRomanNumber(s: string, separator?: string, places?:
|
||||||
// guarantees correct order (/ = ASCII 47, | = ASCII 124)
|
// guarantees correct order (/ = ASCII 47, | = ASCII 124)
|
||||||
if (separator) {
|
if (separator) {
|
||||||
const components: Array<string> = s.split(separator).filter(s => s)
|
const components: Array<string> = s.split(separator).filter(s => s)
|
||||||
return `${components.map((c) => prependWithZeros(romanToIntStr(c), places ?? DEFAULT_NORMALIZATION_PLACES)).join(PIPE_SEPARATOR)}//`
|
return `${components.map((c) => prependWithZeros(romanToIntStr(c), places ?? DEFAULT_NORMALIZATION_PLACES)).join(PIPE_SEPARATOR)}${SLASH_SEPARATOR}${SLASH_SEPARATOR}`
|
||||||
} else {
|
} else {
|
||||||
return `${prependWithZeros(romanToIntStr(s), places ?? DEFAULT_NORMALIZATION_PLACES)}//`
|
return `${prependWithZeros(romanToIntStr(s), places ?? DEFAULT_NORMALIZATION_PLACES)}${SLASH_SEPARATOR}${SLASH_SEPARATOR}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +132,7 @@ export function getNormalizedDate_NormalizerFn_for(separator: string, dayIdx: nu
|
||||||
const monthValue = months ? `${1 + MONTHS.indexOf(components[monthIdx])}` : components[monthIdx]
|
const monthValue = months ? `${1 + MONTHS.indexOf(components[monthIdx])}` : components[monthIdx]
|
||||||
const month = prependWithZeros(monthValue, MONTH_POSITIONS)
|
const month = prependWithZeros(monthValue, MONTH_POSITIONS)
|
||||||
const year = prependWithZeros(components[yearIdx], YEAR_POSITIONS)
|
const year = prependWithZeros(components[yearIdx], YEAR_POSITIONS)
|
||||||
return `${year}-${month}-${day}//`
|
return `${year}-${month}-${day}${SLASH_SEPARATOR}${SLASH_SEPARATOR}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,14 +141,18 @@ export const getNormalizedDate_yyyy_dd_mm_NormalizerFn = getNormalizedDate_Norma
|
||||||
export const getNormalizedDate_dd_Mmm_yyyy_NormalizerFn = getNormalizedDate_NormalizerFn_for('-', 0, 1, 2, MONTHS)
|
export const getNormalizedDate_dd_Mmm_yyyy_NormalizerFn = getNormalizedDate_NormalizerFn_for('-', 0, 1, 2, MONTHS)
|
||||||
export const getNormalizedDate_Mmm_dd_yyyy_NormalizerFn = getNormalizedDate_NormalizerFn_for('-', 1, 0, 2, MONTHS)
|
export const getNormalizedDate_Mmm_dd_yyyy_NormalizerFn = getNormalizedDate_NormalizerFn_for('-', 1, 0, 2, MONTHS)
|
||||||
|
|
||||||
|
const DateExtractor_orderModifier_earlier_than = '-'
|
||||||
|
const DateExtractor_orderModifier_later_than = '+'
|
||||||
|
|
||||||
const DateExtractor_yyyy_Www_mm_dd_Regex = /(\d{4})-W(\d{1,2}) \((\d{2})-(\d{2})\)/
|
const DateExtractor_yyyy_Www_mm_dd_Regex = /(\d{4})-W(\d{1,2}) \((\d{2})-(\d{2})\)/
|
||||||
const DateExtractor_yyyy_Www_Regex = /(\d{4})-W(\d{1,2})/
|
const DateExtractor_yyyy_Www_Regex = /(\d{4})-W(\d{1,2})([-+]?)/
|
||||||
|
|
||||||
// Matching groups
|
// Matching groups
|
||||||
const YEAR_IDX = 1
|
const YEAR_IDX = 1
|
||||||
const WEEK_IDX = 2
|
const WEEK_IDX = 2
|
||||||
const MONTH_IDX = 3
|
const MONTH_IDX = 3
|
||||||
const DAY_IDX = 4
|
const DAY_IDX = 4
|
||||||
|
const RELATIVE_ORDER_IDX = 3 // For the yyyy-Www only: yyyy-Www> or yyyy-Www<
|
||||||
|
|
||||||
const DECEMBER = 12
|
const DECEMBER = 12
|
||||||
const JANUARY = 1
|
const JANUARY = 1
|
||||||
|
@ -157,10 +165,19 @@ export function getNormalizedDate_NormalizerFn_yyyy_Www_mm_dd(consumeWeek: boole
|
||||||
let yearNumber = Number.parseInt(yearStr,10)
|
let yearNumber = Number.parseInt(yearStr,10)
|
||||||
let monthNumber: number
|
let monthNumber: number
|
||||||
let dayNumber: number
|
let dayNumber: number
|
||||||
|
let separator = SLASH_SEPARATOR // different values enforce relative > < order of same dates
|
||||||
|
let useLastDayOfWeek: boolean = false
|
||||||
if (consumeWeek) {
|
if (consumeWeek) {
|
||||||
const weekNumberStr = matches![WEEK_IDX]
|
const weekNumberStr = matches![WEEK_IDX]
|
||||||
const weekNumber = Number.parseInt(weekNumberStr, 10)
|
const weekNumber = Number.parseInt(weekNumberStr, 10)
|
||||||
const dateForWeek = getDateForWeekOfYear(yearNumber, weekNumber, weeksISO)
|
const orderModifier: string|undefined = matches![RELATIVE_ORDER_IDX]
|
||||||
|
if (orderModifier === DateExtractor_orderModifier_earlier_than) {
|
||||||
|
separator = EARLIER_THAN_SLASH_SEPARATOR
|
||||||
|
} else if (orderModifier === DateExtractor_orderModifier_later_than) {
|
||||||
|
separator = LATER_THAN_SLASH_SEPARATOR // Will also need to adjust the date to the last day of the week
|
||||||
|
useLastDayOfWeek = true
|
||||||
|
}
|
||||||
|
const dateForWeek = getDateForWeekOfYear(yearNumber, weekNumber, weeksISO, useLastDayOfWeek)
|
||||||
monthNumber = dateForWeek.getMonth()+1 // 1 - 12
|
monthNumber = dateForWeek.getMonth()+1 // 1 - 12
|
||||||
dayNumber = dateForWeek.getDate() // 1 - 31
|
dayNumber = dateForWeek.getDate() // 1 - 31
|
||||||
// Be careful with edge dates, which can belong to previous or next year
|
// Be careful with edge dates, which can belong to previous or next year
|
||||||
|
@ -178,7 +195,10 @@ export function getNormalizedDate_NormalizerFn_yyyy_Www_mm_dd(consumeWeek: boole
|
||||||
monthNumber = Number.parseInt(matches![MONTH_IDX],10)
|
monthNumber = Number.parseInt(matches![MONTH_IDX],10)
|
||||||
dayNumber = Number.parseInt(matches![DAY_IDX], 10)
|
dayNumber = Number.parseInt(matches![DAY_IDX], 10)
|
||||||
}
|
}
|
||||||
return `${prependWithZeros(`${yearNumber}`, YEAR_POSITIONS)}-${prependWithZeros(`${monthNumber}`, MONTH_POSITIONS)}-${prependWithZeros(`${dayNumber}`, DAY_POSITIONS)}//`
|
return `${prependWithZeros(`${yearNumber}`, YEAR_POSITIONS)}` +
|
||||||
|
`-${prependWithZeros(`${monthNumber}`, MONTH_POSITIONS)}` +
|
||||||
|
`-${prependWithZeros(`${dayNumber}`, DAY_POSITIONS)}` +
|
||||||
|
`${separator}${SLASH_SEPARATOR}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -458,3 +458,19 @@ describe('getNormalizedDate_yyyy_Www_mm_dd_NormalizerFn', () => {
|
||||||
expect(getNormalizedDate_yyyy_Www_mm_dd_NormalizerFn(s)).toBe(out)
|
expect(getNormalizedDate_yyyy_Www_mm_dd_NormalizerFn(s)).toBe(out)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('getNormalizedDate_yyyy_Www_NormalizerFn', () => {
|
||||||
|
/* ORDER for week numbers vs. dates of 1st day / last day of the week:
|
||||||
|
W1 - exactly on the first day of 1st week - the actual title then decides about relative order
|
||||||
|
W1- - before the first day of 1st week, yet after the last day of prev week)
|
||||||
|
W1+ - after the last day of 1st week, yet before the first day of next week)
|
||||||
|
*/
|
||||||
|
const params = [
|
||||||
|
['2012-W1', '2011-12-26//'],
|
||||||
|
['2012-W1+', '2012-01-01:/'],
|
||||||
|
['2012-W1-', '2011-12-26./'],
|
||||||
|
];
|
||||||
|
it.each(params)('>%s< should become %s', (s: string, out: string) => {
|
||||||
|
expect(getNormalizedDate_yyyy_Www_NormalizerFn(s)).toBe(out)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
@ -458,13 +458,13 @@ const expectedSortSpecsExampleSortingSymbols: { [key: string]: CustomSortSpec }
|
||||||
}, {
|
}, {
|
||||||
type: CustomSortGroupType.ExactName,
|
type: CustomSortGroupType.ExactName,
|
||||||
regexPrefix: {
|
regexPrefix: {
|
||||||
regex: /^Week number interpreted in ISO standard *(\d{4}-W[0-5]*[0-9])$/i,
|
regex: /^Week number interpreted in ISO standard *(\d{4}-W[0-5]*[0-9][-+]?)$/i,
|
||||||
normalizerFn: Date_yyyy_WwwISO_NormalizerFn
|
normalizerFn: Date_yyyy_WwwISO_NormalizerFn
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
type: CustomSortGroupType.ExactName,
|
type: CustomSortGroupType.ExactName,
|
||||||
regexPrefix: {
|
regexPrefix: {
|
||||||
regex: /^Week number interpreted in U\.S\. standard *(\d{4}-W[0-5]*[0-9])$/i,
|
regex: /^Week number interpreted in U\.S\. standard *(\d{4}-W[0-5]*[0-9][-+]?)$/i,
|
||||||
normalizerFn: Date_yyyy_Www_NormalizerFn
|
normalizerFn: Date_yyyy_Www_NormalizerFn
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
|
|
|
@ -43,9 +43,13 @@ describe('getDateForWeekOfYear', () => {
|
||||||
expect(getDateForWeekOfYear(year, 10)).toStrictEqual(dateUS)
|
expect(getDateForWeekOfYear(year, 10)).toStrictEqual(dateUS)
|
||||||
expect(getDateForWeekOfYear(year, 10, true)).toStrictEqual(dateISO)
|
expect(getDateForWeekOfYear(year, 10, true)).toStrictEqual(dateISO)
|
||||||
})
|
})
|
||||||
it('should correctly handle edge case - a year spanning 54 weeks (leap year staring on Sun)', () => {
|
it('should correctly handle edge case - a year spanning 54 weeks (leap year starting on Sun)', () => {
|
||||||
|
const USstandard = false
|
||||||
|
const SUNDAY = true
|
||||||
// This works in U.S. standard only, where 1st week can start on Sunday
|
// This works in U.S. standard only, where 1st week can start on Sunday
|
||||||
expect(getDateForWeekOfYear(2012,1)).toStrictEqual(new Date('2011-12-26T00:00:00.000Z'))
|
expect(getDateForWeekOfYear(2012,1)).toStrictEqual(new Date('2011-12-26T00:00:00.000Z'))
|
||||||
|
expect(getDateForWeekOfYear(2012,1, USstandard, SUNDAY)).toStrictEqual(new Date('2012-01-01T00:00:00.000Z'))
|
||||||
expect(getDateForWeekOfYear(2012,54)).toStrictEqual(new Date('2012-12-31T00:00:00.000Z'))
|
expect(getDateForWeekOfYear(2012,54)).toStrictEqual(new Date('2012-12-31T00:00:00.000Z'))
|
||||||
|
expect(getDateForWeekOfYear(2012,54, USstandard, SUNDAY)).toStrictEqual(new Date('2013-01-06T00:00:00.000Z'))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
// Cache of start of years and number of days in the 1st week
|
// Cache of start of years and number of days in the 1st week
|
||||||
interface MondayCache {
|
interface MondayCache {
|
||||||
year: number // full year, e.g. 2015
|
year: number // full year, e.g. 2015
|
||||||
mondayDateOf1stWeekUS: number // U.S. standard, can be in Dec of previous year
|
mondayDateOf1stWeekUS: number // U.S. standard, the 1st of Jan determines the first week, monday can be in Dec of previous year
|
||||||
|
sundayDateOf1stWeekUS: number
|
||||||
mondayDateOf1stWeekISO: number // ISO standard, when the first Thursday of the year determines week numbering
|
mondayDateOf1stWeekISO: number // ISO standard, when the first Thursday of the year determines week numbering
|
||||||
|
sundayDateOf1stWeekISO: number
|
||||||
}
|
}
|
||||||
|
|
||||||
type YEAR = number
|
type YEAR = number
|
||||||
|
@ -35,20 +37,25 @@ const calculateMondayDateIn1stWeekOfYear = (year: number): MondayCache => {
|
||||||
return {
|
return {
|
||||||
year: year,
|
year: year,
|
||||||
mondayDateOf1stWeekUS: new Date(firstSecondOfYear).setDate(firstSecondOfYear.getDate() - daysToPrevMonday),
|
mondayDateOf1stWeekUS: new Date(firstSecondOfYear).setDate(firstSecondOfYear.getDate() - daysToPrevMonday),
|
||||||
|
sundayDateOf1stWeekUS: new Date(firstSecondOfYear).setDate(firstSecondOfYear.getDate() - daysToPrevMonday + DAYS_IN_WEEK - 1),
|
||||||
mondayDateOf1stWeekISO: new Date(firstSecondOfYear).setDate(firstSecondOfYear.getDate() - daysToPrevMonday + useISOoffset),
|
mondayDateOf1stWeekISO: new Date(firstSecondOfYear).setDate(firstSecondOfYear.getDate() - daysToPrevMonday + useISOoffset),
|
||||||
|
sundayDateOf1stWeekISO: new Date(firstSecondOfYear).setDate(firstSecondOfYear.getDate() - daysToPrevMonday + useISOoffset + DAYS_IN_WEEK - 1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Week number = 1 to 54, U.S. standard by default, can also work in ISO (parameter driven)
|
// Week number = 1 to 54, U.S. standard by default, can also work in ISO (parameter driven)
|
||||||
export const getDateForWeekOfYear = (year: number, weekNumber: number, useISO?: boolean): Date => {
|
export const getDateForWeekOfYear = (year: number, weekNumber: number, useISO?: boolean, sunday?: boolean): Date => {
|
||||||
const WEEK_OF_MILIS = DAYS_IN_WEEK * DAY_OF_MILIS
|
const WEEK_OF_MILIS = DAYS_IN_WEEK * DAY_OF_MILIS
|
||||||
const dataOfMondayIn1stWeekOfYear = (MondaysCache[year] ??= calculateMondayDateIn1stWeekOfYear(year))
|
const dataOfMondayIn1stWeekOfYear = (MondaysCache[year] ??= calculateMondayDateIn1stWeekOfYear(year))
|
||||||
const mondayOfTheRequestedWeek = new Date(
|
const mondayOfTheRequestedWeek =
|
||||||
(useISO ? dataOfMondayIn1stWeekOfYear.mondayDateOf1stWeekISO : dataOfMondayIn1stWeekOfYear.mondayDateOf1stWeekUS)
|
(useISO ? dataOfMondayIn1stWeekOfYear.mondayDateOf1stWeekISO : dataOfMondayIn1stWeekOfYear.mondayDateOf1stWeekUS)
|
||||||
+ (weekNumber-1)*WEEK_OF_MILIS
|
+ (weekNumber-1)*WEEK_OF_MILIS
|
||||||
)
|
|
||||||
|
|
||||||
return mondayOfTheRequestedWeek
|
const sundayOfTheRequestedWeek =
|
||||||
|
(useISO ? dataOfMondayIn1stWeekOfYear.sundayDateOf1stWeekISO : dataOfMondayIn1stWeekOfYear.sundayDateOf1stWeekUS)
|
||||||
|
+ (weekNumber-1)*WEEK_OF_MILIS
|
||||||
|
|
||||||
|
return new Date(sunday ? sundayOfTheRequestedWeek : mondayOfTheRequestedWeek)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const _unitTests = {
|
export const _unitTests = {
|
||||||
|
|
Loading…
Reference in New Issue