#178 - week-number based date extraction patterns for titles
This commit is contained in:
parent
f9c9c0be4e
commit
975f6ee26e
|
@ -1,3 +1,7 @@
|
||||||
|
import {
|
||||||
|
getDateForWeekOfYear
|
||||||
|
} from "../utils/week-of-year";
|
||||||
|
|
||||||
export const RomanNumberRegexStr: string = ' *([MDCLXVI]+)'; // Roman number
|
export const RomanNumberRegexStr: string = ' *([MDCLXVI]+)'; // Roman number
|
||||||
export const CompoundRomanNumberDotRegexStr: string = ' *([MDCLXVI]+(?:\\.[MDCLXVI]+)*)';// Compound Roman number with dot as separator
|
export const CompoundRomanNumberDotRegexStr: string = ' *([MDCLXVI]+(?:\\.[MDCLXVI]+)*)';// Compound Roman number with dot as separator
|
||||||
export const CompoundRomanNumberDashRegexStr: string = ' *([MDCLXVI]+(?:-[MDCLXVI]+)*)'; // Compound Roman number with dash as separator
|
export const CompoundRomanNumberDashRegexStr: string = ' *([MDCLXVI]+(?:-[MDCLXVI]+)*)'; // Compound Roman number with dash as separator
|
||||||
|
@ -9,6 +13,9 @@ export const CompoundNumberDashRegexStr: string = ' *(\\d+(?:-\\d+)*)'; // Compo
|
||||||
export const Date_dd_Mmm_yyyy_RegexStr: string = ' *([0-3]*[0-9]-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-\\d{4})'; // Date like 01-Jan-2020
|
export const Date_dd_Mmm_yyyy_RegexStr: string = ' *([0-3]*[0-9]-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-\\d{4})'; // Date like 01-Jan-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_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\\d{1,2} \\(\\d{2}-\\d{2}\\))'
|
||||||
|
export const Date_yyyy_Www_RegexStr: string = ' *(\\d{4}-W\\d{1,2})'
|
||||||
|
|
||||||
export const DOT_SEPARATOR = '.'
|
export const DOT_SEPARATOR = '.'
|
||||||
export const DASH_SEPARATOR = '-'
|
export const DASH_SEPARATOR = '-'
|
||||||
|
|
||||||
|
@ -123,3 +130,52 @@ export function getNormalizedDate_NormalizerFn_for(separator: string, dayIdx: nu
|
||||||
|
|
||||||
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_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})/
|
||||||
|
|
||||||
|
// Matching groups
|
||||||
|
const YEAR_IDX = 1
|
||||||
|
const WEEK_IDX = 2
|
||||||
|
const MONTH_IDX = 3
|
||||||
|
const DAY_IDX = 4
|
||||||
|
|
||||||
|
const DECEMBER = 12
|
||||||
|
const JANUARY = 1
|
||||||
|
|
||||||
|
export function getNormalizedDate_NormalizerFn_yyyy_Www_mm_dd(consumeWeek: boolean, weeksISO?: boolean) {
|
||||||
|
return (s: string): string | null => {
|
||||||
|
// Assumption - the regex date matched against input s, no extensive defensive coding needed
|
||||||
|
const matches = consumeWeek ? DateExtractor_yyyy_Www_Regex.exec(s) : DateExtractor_yyyy_Www_mm_dd_Regex.exec(s)
|
||||||
|
const yearStr = matches![YEAR_IDX]
|
||||||
|
let yearNumber = Number.parseInt(yearStr,10)
|
||||||
|
let monthNumber: number
|
||||||
|
let dayNumber: number
|
||||||
|
if (consumeWeek) {
|
||||||
|
const weekNumberStr = matches![WEEK_IDX]
|
||||||
|
const weekNumber = Number.parseInt(weekNumberStr, 10)
|
||||||
|
const dateForWeek = getDateForWeekOfYear(yearNumber, weekNumber, weeksISO)
|
||||||
|
monthNumber = dateForWeek.getMonth()+1 // 1 - 12
|
||||||
|
dayNumber = dateForWeek.getDate() // 1 - 31
|
||||||
|
// Be careful with edge dates, which can belong to previous or next year
|
||||||
|
if (weekNumber === 1) {
|
||||||
|
if (monthNumber === DECEMBER) {
|
||||||
|
yearNumber--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (weekNumber >= 50) {
|
||||||
|
if (monthNumber === JANUARY) {
|
||||||
|
yearNumber++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // ignore week
|
||||||
|
monthNumber = Number.parseInt(matches![MONTH_IDX],10)
|
||||||
|
dayNumber = Number.parseInt(matches![DAY_IDX], 10)
|
||||||
|
}
|
||||||
|
return `${prependWithZeros(`${yearNumber}`, YEAR_POSITIONS)}-${prependWithZeros(`${monthNumber}`, MONTH_POSITIONS)}-${prependWithZeros(`${dayNumber}`, DAY_POSITIONS)}//`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getNormalizedDate_yyyy_Www_mm_dd_NormalizerFn = getNormalizedDate_NormalizerFn_yyyy_Www_mm_dd(false)
|
||||||
|
export const getNormalizedDate_yyyy_WwwISO_NormalizerFn = getNormalizedDate_NormalizerFn_yyyy_Www_mm_dd(true, true)
|
||||||
|
export const getNormalizedDate_yyyy_Www_NormalizerFn = getNormalizedDate_NormalizerFn_yyyy_Www_mm_dd(true, false)
|
||||||
|
|
|
@ -19,9 +19,14 @@ import {
|
||||||
DASH_SEPARATOR,
|
DASH_SEPARATOR,
|
||||||
Date_dd_Mmm_yyyy_RegexStr,
|
Date_dd_Mmm_yyyy_RegexStr,
|
||||||
Date_Mmm_dd_yyyy_RegexStr,
|
Date_Mmm_dd_yyyy_RegexStr,
|
||||||
|
Date_yyyy_Www_mm_dd_RegexStr,
|
||||||
|
Date_yyyy_Www_RegexStr,
|
||||||
DOT_SEPARATOR,
|
DOT_SEPARATOR,
|
||||||
getNormalizedDate_dd_Mmm_yyyy_NormalizerFn,
|
getNormalizedDate_dd_Mmm_yyyy_NormalizerFn,
|
||||||
getNormalizedDate_Mmm_dd_yyyy_NormalizerFn,
|
getNormalizedDate_Mmm_dd_yyyy_NormalizerFn,
|
||||||
|
getNormalizedDate_yyyy_Www_mm_dd_NormalizerFn,
|
||||||
|
getNormalizedDate_yyyy_WwwISO_NormalizerFn,
|
||||||
|
getNormalizedDate_yyyy_Www_NormalizerFn,
|
||||||
getNormalizedNumber,
|
getNormalizedNumber,
|
||||||
getNormalizedRomanNumber,
|
getNormalizedRomanNumber,
|
||||||
NumberRegexStr,
|
NumberRegexStr,
|
||||||
|
@ -354,6 +359,9 @@ const InlineRegexSymbol_0_to_3: string = '\\[0-3]'
|
||||||
|
|
||||||
const Date_dd_Mmm_yyyy_RegexSymbol: string = '\\[dd-Mmm-yyyy]'
|
const Date_dd_Mmm_yyyy_RegexSymbol: string = '\\[dd-Mmm-yyyy]'
|
||||||
const Date_Mmm_dd_yyyy_RegexSymbol: string = '\\[Mmm-dd-yyyy]'
|
const Date_Mmm_dd_yyyy_RegexSymbol: string = '\\[Mmm-dd-yyyy]'
|
||||||
|
const Date_yyyy_Www_mm_dd_RegexSymbol: string = '\\[yyyy-Www (mm-dd)]'
|
||||||
|
const Date_yyyy_Www_RegexSymbol: string = '\\[yyyy-Www]'
|
||||||
|
const Date_yyyy_WwwISO_RegexSymbol: string = '\\[yyyy-WwwISO]'
|
||||||
|
|
||||||
const InlineRegexSymbol_CapitalLetter: string = '\\C'
|
const InlineRegexSymbol_CapitalLetter: string = '\\C'
|
||||||
const InlineRegexSymbol_LowercaseLetter: string = '\\l'
|
const InlineRegexSymbol_LowercaseLetter: string = '\\l'
|
||||||
|
@ -374,7 +382,10 @@ const sortingSymbolsArr: Array<string> = [
|
||||||
escapeRegexUnsafeCharacters(WordInASCIIRegexSymbol),
|
escapeRegexUnsafeCharacters(WordInASCIIRegexSymbol),
|
||||||
escapeRegexUnsafeCharacters(WordInAnyLanguageRegexSymbol),
|
escapeRegexUnsafeCharacters(WordInAnyLanguageRegexSymbol),
|
||||||
escapeRegexUnsafeCharacters(Date_dd_Mmm_yyyy_RegexSymbol),
|
escapeRegexUnsafeCharacters(Date_dd_Mmm_yyyy_RegexSymbol),
|
||||||
escapeRegexUnsafeCharacters(Date_Mmm_dd_yyyy_RegexSymbol)
|
escapeRegexUnsafeCharacters(Date_Mmm_dd_yyyy_RegexSymbol),
|
||||||
|
escapeRegexUnsafeCharacters(Date_yyyy_Www_mm_dd_RegexSymbol),
|
||||||
|
escapeRegexUnsafeCharacters(Date_yyyy_WwwISO_RegexSymbol),
|
||||||
|
escapeRegexUnsafeCharacters(Date_yyyy_Www_RegexSymbol),
|
||||||
]
|
]
|
||||||
|
|
||||||
const sortingSymbolsRegex = new RegExp(sortingSymbolsArr.join('|'), 'gi')
|
const sortingSymbolsRegex = new RegExp(sortingSymbolsArr.join('|'), 'gi')
|
||||||
|
@ -444,6 +455,9 @@ export const CompoundDotNumberNormalizerFn: NormalizerFn = (s: string) => getNor
|
||||||
export const CompoundDashNumberNormalizerFn: NormalizerFn = (s: string) => getNormalizedNumber(s, DASH_SEPARATOR)
|
export const CompoundDashNumberNormalizerFn: NormalizerFn = (s: string) => getNormalizedNumber(s, DASH_SEPARATOR)
|
||||||
export const Date_dd_Mmm_yyyy_NormalizerFn: NormalizerFn = (s: string) => getNormalizedDate_dd_Mmm_yyyy_NormalizerFn(s)
|
export const Date_dd_Mmm_yyyy_NormalizerFn: NormalizerFn = (s: string) => getNormalizedDate_dd_Mmm_yyyy_NormalizerFn(s)
|
||||||
export const Date_Mmm_dd_yyyy_NormalizerFn: NormalizerFn = (s: string) => getNormalizedDate_Mmm_dd_yyyy_NormalizerFn(s)
|
export const Date_Mmm_dd_yyyy_NormalizerFn: NormalizerFn = (s: string) => getNormalizedDate_Mmm_dd_yyyy_NormalizerFn(s)
|
||||||
|
export const Date_yyyy_Www_mm_dd_NormalizerFn: NormalizerFn = (s: string) => getNormalizedDate_yyyy_Www_mm_dd_NormalizerFn(s)
|
||||||
|
export const Date_yyyy_WwwISO_NormalizerFn: NormalizerFn = (s: string) => getNormalizedDate_yyyy_WwwISO_NormalizerFn(s)
|
||||||
|
export const Date_yyyy_Www_NormalizerFn: NormalizerFn = (s: string) => getNormalizedDate_yyyy_Www_NormalizerFn(s)
|
||||||
|
|
||||||
export enum AdvancedRegexType {
|
export enum AdvancedRegexType {
|
||||||
None, // to allow if (advancedRegex)
|
None, // to allow if (advancedRegex)
|
||||||
|
@ -456,7 +470,10 @@ export enum AdvancedRegexType {
|
||||||
WordInASCII,
|
WordInASCII,
|
||||||
WordInAnyLanguage,
|
WordInAnyLanguage,
|
||||||
Date_dd_Mmm_yyyy,
|
Date_dd_Mmm_yyyy,
|
||||||
Date_Mmm_dd_yyyy
|
Date_Mmm_dd_yyyy,
|
||||||
|
Date_yyyy_Www_mm_dd_yyyy,
|
||||||
|
Date_yyyy_WwwISO,
|
||||||
|
Date_yyyy_Www
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortingSymbolToRegexpStr: { [key: string]: RegExpSpecStr } = {
|
const sortingSymbolToRegexpStr: { [key: string]: RegExpSpecStr } = {
|
||||||
|
@ -510,6 +527,21 @@ const sortingSymbolToRegexpStr: { [key: string]: RegExpSpecStr } = {
|
||||||
regexpStr: Date_Mmm_dd_yyyy_RegexStr,
|
regexpStr: Date_Mmm_dd_yyyy_RegexStr,
|
||||||
normalizerFn: Date_Mmm_dd_yyyy_NormalizerFn,
|
normalizerFn: Date_Mmm_dd_yyyy_NormalizerFn,
|
||||||
advancedRegexType: AdvancedRegexType.Date_Mmm_dd_yyyy
|
advancedRegexType: AdvancedRegexType.Date_Mmm_dd_yyyy
|
||||||
|
},
|
||||||
|
[Date_yyyy_Www_mm_dd_RegexSymbol]: { // Intentionally retain character case
|
||||||
|
regexpStr: Date_yyyy_Www_mm_dd_RegexStr,
|
||||||
|
normalizerFn: Date_yyyy_Www_mm_dd_NormalizerFn,
|
||||||
|
advancedRegexType: AdvancedRegexType.Date_yyyy_Www_mm_dd_yyyy
|
||||||
|
},
|
||||||
|
[Date_yyyy_WwwISO_RegexSymbol]: { // Intentionally retain character case
|
||||||
|
regexpStr: Date_yyyy_Www_RegexStr,
|
||||||
|
normalizerFn: Date_yyyy_WwwISO_NormalizerFn,
|
||||||
|
advancedRegexType: AdvancedRegexType.Date_yyyy_WwwISO
|
||||||
|
},
|
||||||
|
[Date_yyyy_Www_RegexSymbol]: { // Intentionally retain character case
|
||||||
|
regexpStr: Date_yyyy_Www_RegexStr,
|
||||||
|
normalizerFn: Date_yyyy_Www_NormalizerFn,
|
||||||
|
advancedRegexType: AdvancedRegexType.Date_yyyy_Www
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {
|
import {
|
||||||
TAbstractFile,
|
TAbstractFile, TFile,
|
||||||
TFolder,
|
TFolder,
|
||||||
Vault
|
Vault
|
||||||
} from "obsidian";
|
} from "obsidian";
|
||||||
|
@ -21,6 +21,8 @@ import {
|
||||||
mockTFolderWithDateNamedChildren,
|
mockTFolderWithDateNamedChildren,
|
||||||
TIMESTAMP_DEEP_NEWEST,
|
TIMESTAMP_DEEP_NEWEST,
|
||||||
TIMESTAMP_DEEP_OLDEST,
|
TIMESTAMP_DEEP_OLDEST,
|
||||||
|
mockTFolderWithDateWeekNamedChildrenForISOvsUSweekNumberingTest,
|
||||||
|
mockTFolderWithDateWeekNamedChildren, mockTFile, mockTFolder,
|
||||||
} from "../mocks";
|
} from "../mocks";
|
||||||
import {
|
import {
|
||||||
SortingSpecProcessor
|
SortingSpecProcessor
|
||||||
|
@ -58,6 +60,116 @@ describe('sortFolderItems', () => {
|
||||||
'AAA Jan-01-2012'
|
'AAA Jan-01-2012'
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
it('should correctly handle yyyy-Www (mm-dd) pattern in file names', () => {
|
||||||
|
// given
|
||||||
|
const processor: SortingSpecProcessor = new SortingSpecProcessor()
|
||||||
|
const sortSpecTxt =
|
||||||
|
` ... \\[yyyy-Www (mm-dd)]
|
||||||
|
< a-z
|
||||||
|
------
|
||||||
|
`
|
||||||
|
const PARENT_PATH = 'parent/folder/path'
|
||||||
|
const sortSpecsCollection = processor.parseSortSpecFromText(
|
||||||
|
sortSpecTxt.split('\n'),
|
||||||
|
PARENT_PATH,
|
||||||
|
'file name with the sorting, irrelevant here'
|
||||||
|
)
|
||||||
|
|
||||||
|
const folder: TFolder = mockTFolderWithDateWeekNamedChildren(PARENT_PATH)
|
||||||
|
const sortSpec: CustomSortSpec = sortSpecsCollection?.sortSpecByPath![PARENT_PATH]!
|
||||||
|
|
||||||
|
const ctx: ProcessingContext = {}
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result: Array<TAbstractFile> = sortFolderItems(folder, folder.children, sortSpec, ctx, OS_alphabetical)
|
||||||
|
|
||||||
|
// then
|
||||||
|
const orderedNames = result.map(f => f.name)
|
||||||
|
expect(orderedNames).toEqual([
|
||||||
|
"GHI 2021-W1 (01-04)",
|
||||||
|
"DEF 2021-W9 (03-01).md",
|
||||||
|
"ABC 2021-W13 (03-29)",
|
||||||
|
"MNO 2021-W45 (11-08).md",
|
||||||
|
"JKL 2021-W52 (12-27).md",
|
||||||
|
"------.md"
|
||||||
|
])
|
||||||
|
})
|
||||||
|
it('should correctly handle yyyy-WwwISO pattern in file names', () => {
|
||||||
|
// given
|
||||||
|
const processor: SortingSpecProcessor = new SortingSpecProcessor()
|
||||||
|
const sortSpecTxt =
|
||||||
|
` /+ ... \\[yyyy-Www (mm-dd)]
|
||||||
|
/+ ... \\[yyyy-WwwISO]
|
||||||
|
< a-z
|
||||||
|
`
|
||||||
|
const PARENT_PATH = 'parent/folder/path'
|
||||||
|
const sortSpecsCollection = processor.parseSortSpecFromText(
|
||||||
|
sortSpecTxt.split('\n'),
|
||||||
|
PARENT_PATH,
|
||||||
|
'file name with the sorting, irrelevant here'
|
||||||
|
)
|
||||||
|
|
||||||
|
const folder: TFolder = mockTFolderWithDateWeekNamedChildrenForISOvsUSweekNumberingTest(PARENT_PATH)
|
||||||
|
const sortSpec: CustomSortSpec = sortSpecsCollection?.sortSpecByPath![PARENT_PATH]!
|
||||||
|
|
||||||
|
const ctx: ProcessingContext = {}
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result: Array<TAbstractFile> = sortFolderItems(folder, folder.children, sortSpec, ctx, OS_alphabetical)
|
||||||
|
|
||||||
|
// then
|
||||||
|
// ISO standard of weeks numbering
|
||||||
|
const orderedNames = result.map(f => f.name)
|
||||||
|
expect(orderedNames).toEqual([
|
||||||
|
'E 2021-W1 (01-01)',
|
||||||
|
'F ISO:2021-01-04 US:2020-12-28 2021-W1',
|
||||||
|
'A 2021-W10 (03-05).md',
|
||||||
|
'B ISO:2021-03-08 US:2021-03-01 2021-W10',
|
||||||
|
'C 2021-W51 (12-17).md',
|
||||||
|
'D ISO:2021-12-20 US:2021-12-13 2021-W51.md',
|
||||||
|
'FFF2 ISO:2021-12-27 US:2021-12-20 2021-W52.md',
|
||||||
|
'FFF1 ISO:2022-01-03 US:2021-12-27 2021-W53.md',
|
||||||
|
"------.md"
|
||||||
|
])
|
||||||
|
})
|
||||||
|
it('should correctly handle yyyy-Www pattern in file names', () => {
|
||||||
|
// given
|
||||||
|
const processor: SortingSpecProcessor = new SortingSpecProcessor()
|
||||||
|
const sortSpecTxt =
|
||||||
|
` /+ ... \\[yyyy-Www (mm-dd)]
|
||||||
|
/+ ... \\[yyyy-Www]
|
||||||
|
> a-z
|
||||||
|
`
|
||||||
|
const PARENT_PATH = 'parent/folder/path'
|
||||||
|
const sortSpecsCollection = processor.parseSortSpecFromText(
|
||||||
|
sortSpecTxt.split('\n'),
|
||||||
|
PARENT_PATH,
|
||||||
|
'file name with the sorting, irrelevant here'
|
||||||
|
)
|
||||||
|
|
||||||
|
const folder: TFolder = mockTFolderWithDateWeekNamedChildrenForISOvsUSweekNumberingTest(PARENT_PATH)
|
||||||
|
const sortSpec: CustomSortSpec = sortSpecsCollection?.sortSpecByPath![PARENT_PATH]!
|
||||||
|
|
||||||
|
const ctx: ProcessingContext = {}
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result: Array<TAbstractFile> = sortFolderItems(folder, folder.children, sortSpec, ctx, OS_alphabetical)
|
||||||
|
|
||||||
|
// then
|
||||||
|
// U.S. standard of weeks numbering
|
||||||
|
const orderedNames = result.map(f => f.name)
|
||||||
|
expect(orderedNames).toEqual([
|
||||||
|
'FFF1 ISO:2022-01-03 US:2021-12-27 2021-W53.md',
|
||||||
|
'FFF2 ISO:2021-12-27 US:2021-12-20 2021-W52.md',
|
||||||
|
'C 2021-W51 (12-17).md',
|
||||||
|
'D ISO:2021-12-20 US:2021-12-13 2021-W51.md',
|
||||||
|
'A 2021-W10 (03-05).md',
|
||||||
|
'B ISO:2021-03-08 US:2021-03-01 2021-W10',
|
||||||
|
'E 2021-W1 (01-01)',
|
||||||
|
'F ISO:2021-01-04 US:2020-12-28 2021-W1',
|
||||||
|
"------.md"
|
||||||
|
])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -65,3 +65,33 @@ export const mockTFolderWithDateNamedChildren = (name: string): TFolder => {
|
||||||
|
|
||||||
return mockTFolder(name, [child1, child2, child3, child4])
|
return mockTFolder(name, [child1, child2, child3, child4])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const mockTFolderWithDateWeekNamedChildren = (name: string): TFolder => {
|
||||||
|
// Assume ISO week numbers
|
||||||
|
const child0: TFile = mockTFile('------', 'md')
|
||||||
|
const child1: TFolder = mockTFolder('ABC 2021-W13 (03-29)')
|
||||||
|
const child2: TFile = mockTFile('DEF 2021-W9 (03-01)', 'md')
|
||||||
|
const child3: TFolder = mockTFolder('GHI 2021-W1 (01-04)')
|
||||||
|
const child4: TFile = mockTFile('JKL 2021-W52 (12-27)', 'md')
|
||||||
|
const child5: TFile = mockTFile('MNO 2021-W45 (11-08)', 'md')
|
||||||
|
|
||||||
|
return mockTFolder(name, [child0, child1, child2, child3, child4, child5])
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mockTFolderWithDateWeekNamedChildrenForISOvsUSweekNumberingTest = (name: string): TFolder => {
|
||||||
|
// Tricky to test handling of both ISO and U.S. weeks numbering.
|
||||||
|
// Sample year with different week numbers in ISO vs. U.S. is 2021 with 1st Jan on Fri, ISO != U.S.
|
||||||
|
// Plain files and folder names to match both week-only and week+date syntax
|
||||||
|
// Their relative ordering depends on week numbering
|
||||||
|
const child0: TFile = mockTFile('------', 'md')
|
||||||
|
const child1: TFile = mockTFile('A 2021-W10 (03-05)', 'md') // Tue date, (ISO) week number invalid, ignored
|
||||||
|
const child2: TFolder = mockTFolder('B ISO:2021-03-08 US:2021-03-01 2021-W10')
|
||||||
|
const child3: TFile = mockTFile('C 2021-W51 (12-17)', 'md') // Tue date, (ISO) week number invalid, ignored
|
||||||
|
const child4: TFile = mockTFile('D ISO:2021-12-20 US:2021-12-13 2021-W51', 'md')
|
||||||
|
const child5: TFolder = mockTFolder('E 2021-W1 (01-01)') // Tue date, to (ISO) week number invalid, ignored
|
||||||
|
const child6: TFolder = mockTFolder('F ISO:2021-01-04 US:2020-12-28 2021-W1')
|
||||||
|
const child7: TFile = mockTFile('FFF2 ISO:2021-12-27 US:2021-12-20 2021-W52', 'md')
|
||||||
|
const child8: TFile = mockTFile('FFF1 ISO:2022-01-03 US:2021-12-27 2021-W53', 'md') // Invalid week, should fall to next year
|
||||||
|
|
||||||
|
return mockTFolder(name, [child0, child1, child2, child3, child4, child5, child6, child7, child8])
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,9 @@ import {
|
||||||
convertPlainStringToRegex,
|
convertPlainStringToRegex,
|
||||||
Date_dd_Mmm_yyyy_NormalizerFn,
|
Date_dd_Mmm_yyyy_NormalizerFn,
|
||||||
Date_Mmm_dd_yyyy_NormalizerFn,
|
Date_Mmm_dd_yyyy_NormalizerFn,
|
||||||
|
Date_yyyy_Www_mm_dd_NormalizerFn,
|
||||||
|
Date_yyyy_Www_NormalizerFn,
|
||||||
|
Date_yyyy_WwwISO_NormalizerFn,
|
||||||
detectSortingSymbols,
|
detectSortingSymbols,
|
||||||
escapeRegexUnsafeCharacters,
|
escapeRegexUnsafeCharacters,
|
||||||
extractSortingSymbol,
|
extractSortingSymbol,
|
||||||
|
@ -367,6 +370,21 @@ const expectedSortSpecsExampleA: { [key: string]: CustomSortSpec } = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const txtInputExampleSortingSymbols: string = `
|
||||||
|
/folders Chapter \\.d+ ...
|
||||||
|
/:files ...section \\-r+.
|
||||||
|
% Appendix \\-d+ (attachments)
|
||||||
|
Plain syntax\\R+ ... works?
|
||||||
|
And this kind of... \\D+plain syntax???
|
||||||
|
Here goes ASCII word \\a+
|
||||||
|
\\A+. is for any modern language word
|
||||||
|
\\[dd-Mmm-yyyy] for the specific date format of 12-Apr-2024
|
||||||
|
\\[Mmm-dd-yyyy] for the specific date format of Apr-01-2024
|
||||||
|
\\[yyyy-Www (mm-dd)] Week number ignored
|
||||||
|
Week number interpreted in ISO standard \\[yyyy-WwwISO]
|
||||||
|
Week number interpreted in U.S. standard \\[yyyy-Www]
|
||||||
|
`
|
||||||
|
|
||||||
const expectedSortSpecsExampleSortingSymbols: { [key: string]: CustomSortSpec } = {
|
const expectedSortSpecsExampleSortingSymbols: { [key: string]: CustomSortSpec } = {
|
||||||
"mock-folder": {
|
"mock-folder": {
|
||||||
groups: [{
|
groups: [{
|
||||||
|
@ -427,26 +445,32 @@ const expectedSortSpecsExampleSortingSymbols: { [key: string]: CustomSortSpec }
|
||||||
regex: /^ *((?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-[0-3]*[0-9]-\d{4}) for the specific date format of Apr\-01\-2024$/i,
|
regex: /^ *((?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-[0-3]*[0-9]-\d{4}) for the specific date format of Apr\-01\-2024$/i,
|
||||||
normalizerFn: Date_Mmm_dd_yyyy_NormalizerFn
|
normalizerFn: Date_Mmm_dd_yyyy_NormalizerFn
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
type: CustomSortGroupType.ExactName,
|
||||||
|
regexPrefix: {
|
||||||
|
regex: /^ *(\d{4}-W\d{1,2} \(\d{2}-\d{2}\)) Week number ignored$/i,
|
||||||
|
normalizerFn: Date_yyyy_Www_mm_dd_NormalizerFn
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
type: CustomSortGroupType.ExactName,
|
||||||
|
regexPrefix: {
|
||||||
|
regex: /^Week number interpreted in ISO standard *(\d{4}-W\d{1,2})$/i,
|
||||||
|
normalizerFn: Date_yyyy_WwwISO_NormalizerFn
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
type: CustomSortGroupType.ExactName,
|
||||||
|
regexPrefix: {
|
||||||
|
regex: /^Week number interpreted in U\.S\. standard *(\d{4}-W\d{1,2})$/i,
|
||||||
|
normalizerFn: Date_yyyy_Www_NormalizerFn
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
type: CustomSortGroupType.Outsiders
|
type: CustomSortGroupType.Outsiders
|
||||||
}],
|
}],
|
||||||
targetFoldersPaths: ['mock-folder'],
|
targetFoldersPaths: ['mock-folder'],
|
||||||
outsidersGroupIdx: 9
|
outsidersGroupIdx: 12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const txtInputExampleSortingSymbols: string = `
|
|
||||||
/folders Chapter \\.d+ ...
|
|
||||||
/:files ...section \\-r+.
|
|
||||||
% Appendix \\-d+ (attachments)
|
|
||||||
Plain syntax\\R+ ... works?
|
|
||||||
And this kind of... \\D+plain syntax???
|
|
||||||
Here goes ASCII word \\a+
|
|
||||||
\\A+. is for any modern language word
|
|
||||||
\\[dd-Mmm-yyyy] for the specific date format of 12-Apr-2024
|
|
||||||
\\[Mmm-dd-yyyy] for the specific date format of Apr-01-2024
|
|
||||||
`
|
|
||||||
|
|
||||||
// Tricky elements captured:
|
// Tricky elements captured:
|
||||||
// - Order a-z. for by metadata is transformed to a-z (there is no notion of 'file extension' in metadata values)
|
// - Order a-z. for by metadata is transformed to a-z (there is no notion of 'file extension' in metadata values)
|
||||||
|
|
||||||
|
@ -520,8 +544,6 @@ const expectedSortSpecsExampleMDataExtractors2: { [key: string]: CustomSortSpec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
describe('SortingSpecProcessor', () => {
|
describe('SortingSpecProcessor', () => {
|
||||||
let processor: SortingSpecProcessor;
|
let processor: SortingSpecProcessor;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
import {_unitTests, getDateForWeekOfYear} from "../../utils/week-of-year"
|
||||||
|
|
||||||
|
const paramsForWeekOf1stOfJan = [
|
||||||
|
[2015,'2014-12-29T00:00:00.000Z','same as U.S.'], // 1st Jan on Thu, ISO = U.S.
|
||||||
|
[2020,'2019-12-30T00:00:00.000Z','same as U.S.'], // 1st Jan on Wed, ISO = U.S.
|
||||||
|
[2021,'2020-12-28T00:00:00.000Z','2021-01-04T00:00:00.000Z'], // 1st Jan on Fri, ISO != U.S.
|
||||||
|
[2022,'2021-12-27T00:00:00.000Z','2022-01-03T00:00:00.000Z'], // 1st Jan on Sat, ISO != U.S.
|
||||||
|
[2023,'2022-12-26T00:00:00.000Z','2023-01-02T00:00:00.000Z'], // 1st Jan on Sun, ISO != U.S.
|
||||||
|
[2024,'2024-01-01T00:00:00.000Z','same as U.S.'], // 1st Jan on Mon, ISO = U.S.
|
||||||
|
[2025,'2024-12-30T00:00:00.000Z','same as U.S.'] // 1st Jan on Wed, ISO = U.S.
|
||||||
|
]
|
||||||
|
|
||||||
|
const paramsFor10thWeek = [
|
||||||
|
[2019,'2019-03-04T00:00:00.000Z','same as U.S.'],
|
||||||
|
[1999,'1999-03-01T00:00:00.000Z','1999-03-08T00:00:00.000Z'],
|
||||||
|
[1683,'1683-03-01T00:00:00.000Z','1683-03-08T00:00:00.000Z'],
|
||||||
|
[1410,'1410-03-05T00:00:00.000Z','same as U.S.'],
|
||||||
|
[1996,'1996-03-04T00:00:00.000Z','same as U.S.'],
|
||||||
|
[2023,'2023-02-27T00:00:00.000Z','2023-03-06T00:00:00.000Z'],
|
||||||
|
[2025,'2025-03-03T00:00:00.000Z','same as U.S.']
|
||||||
|
]
|
||||||
|
|
||||||
|
describe('calculateMondayDateIn2stWeekOfYear', () => {
|
||||||
|
it.each(paramsForWeekOf1stOfJan)('year >%s< should result in %s (U.S.) and %s (ISO)', (year: number, dateOfMondayUS: string, dateOfMondayISO: string) => {
|
||||||
|
const dateUS = new Date(dateOfMondayUS).getTime()
|
||||||
|
const dateISO = 'same as U.S.' === dateOfMondayISO ? dateUS : new Date(dateOfMondayISO).getTime()
|
||||||
|
const mondayData = _unitTests.calculateMondayDateIn2stWeekOfYear(year)
|
||||||
|
expect(mondayData.mondayDateOf1stWeekUS).toBe(dateUS)
|
||||||
|
expect(mondayData.mondayDateOf1stWeekISO).toBe(dateISO)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getDateForWeekOfYear', () => {
|
||||||
|
it.each(paramsForWeekOf1stOfJan)('For year >%s< 1st week should start on %s (U.S.) and %s (ISO)', (year: number, dateOfMondayUS: string, dateOfMondayISO: string) => {
|
||||||
|
const dateUS = new Date(dateOfMondayUS)
|
||||||
|
const dateISO = 'same as U.S.' === dateOfMondayISO ? dateUS : new Date(dateOfMondayISO)
|
||||||
|
expect(getDateForWeekOfYear(year, 1)).toStrictEqual(dateUS)
|
||||||
|
expect(getDateForWeekOfYear(year, 1, true)).toStrictEqual(dateISO)
|
||||||
|
})
|
||||||
|
it.each(paramsFor10thWeek)('For year >%s< 10th week should start on %s (U.S.) and %s (ISO)', (year: number, dateOfMondayUS: string, dateOfMondayISO: string) => {
|
||||||
|
const dateUS = new Date(dateOfMondayUS)
|
||||||
|
const dateISO = 'same as U.S.' === dateOfMondayISO ? dateUS : new Date(dateOfMondayISO)
|
||||||
|
expect(getDateForWeekOfYear(year, 10)).toStrictEqual(dateUS)
|
||||||
|
expect(getDateForWeekOfYear(year, 10, true)).toStrictEqual(dateISO)
|
||||||
|
})
|
||||||
|
it('should correctly handle edge case - a year spanning 54 weeks (leap year staring on Sun)', () => {
|
||||||
|
// 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,54)).toStrictEqual(new Date('2012-12-31T00:00:00.000Z'))
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,56 @@
|
||||||
|
|
||||||
|
// Cache of start of years and number of days in the 1st week
|
||||||
|
interface MondayCache {
|
||||||
|
year: number // full year, e.g. 2015
|
||||||
|
mondayDateOf1stWeekUS: number // U.S. standard, can be in Dec of previous year
|
||||||
|
mondayDateOf1stWeekISO: number // ISO standard, when the first Thursday of the year determines week numbering
|
||||||
|
}
|
||||||
|
|
||||||
|
type YEAR = number
|
||||||
|
const DAY_OF_MILIS = 60*60*24*1000
|
||||||
|
const DAYS_IN_WEEK = 7
|
||||||
|
|
||||||
|
const MondaysCache: { [key: YEAR]: MondayCache } = {}
|
||||||
|
|
||||||
|
const calculateMondayDateIn1stWeekOfYear = (year: number): MondayCache => {
|
||||||
|
const firstSecondOfYear = new Date(`${year}-01-01T00:00:00.000Z`)
|
||||||
|
const SUNDAY = 0
|
||||||
|
const MONDAY = 1
|
||||||
|
const THURSDAY = 4
|
||||||
|
const FRIDAY = 5
|
||||||
|
const SATURDAY = 6
|
||||||
|
|
||||||
|
const dayOfWeek = firstSecondOfYear.getDay()
|
||||||
|
let daysToPrevMonday: number = 0 // For the Monday itself
|
||||||
|
if (dayOfWeek === SUNDAY) { // Sunday
|
||||||
|
daysToPrevMonday = DAYS_IN_WEEK - 1
|
||||||
|
} else if (dayOfWeek > MONDAY) { // Tue - Sat
|
||||||
|
daysToPrevMonday = dayOfWeek - MONDAY
|
||||||
|
}
|
||||||
|
|
||||||
|
// for U.S. the first week is the one with Jan 1st,
|
||||||
|
// for ISO standard, the first week is the one which contains the 1st Thursday of the year
|
||||||
|
const useISOoffset = [FRIDAY, SATURDAY, SUNDAY].includes(dayOfWeek) ? DAYS_IN_WEEK : 0
|
||||||
|
|
||||||
|
return {
|
||||||
|
year: year,
|
||||||
|
mondayDateOf1stWeekUS: new Date(firstSecondOfYear).setDate(firstSecondOfYear.getDate() - daysToPrevMonday),
|
||||||
|
mondayDateOf1stWeekISO: new Date(firstSecondOfYear).setDate(firstSecondOfYear.getDate() - daysToPrevMonday + useISOoffset),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 => {
|
||||||
|
const WEEK_OF_MILIS = DAYS_IN_WEEK * DAY_OF_MILIS
|
||||||
|
const dataOfMondayIn1stWeekOfYear = (MondaysCache[year] ??= calculateMondayDateIn1stWeekOfYear(year))
|
||||||
|
const mondayOfTheRequestedWeek = new Date(
|
||||||
|
(useISO ? dataOfMondayIn1stWeekOfYear.mondayDateOf1stWeekISO : dataOfMondayIn1stWeekOfYear.mondayDateOf1stWeekUS)
|
||||||
|
+ (weekNumber-1)*WEEK_OF_MILIS
|
||||||
|
)
|
||||||
|
|
||||||
|
return mondayOfTheRequestedWeek
|
||||||
|
}
|
||||||
|
|
||||||
|
export const _unitTests = {
|
||||||
|
calculateMondayDateIn2stWeekOfYear: calculateMondayDateIn1stWeekOfYear
|
||||||
|
}
|
Loading…
Reference in New Issue