Skip to content

Commit 5d0d52c

Browse files
authored
Merge pull request #539 from mikegreiner/fix/issue-324-yyyymmdd-date-format
Fix #324: Handle numeric YAML parsing for startDate/endDate with YYYYMMDD format
2 parents 8db8e76 + ad85dd0 commit 5d0d52c

10 files changed

Lines changed: 957 additions & 15 deletions

src/helper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function makeTimeFormat() {
2424
if (fmtSec !== "") {
2525
fmt += `:${fmtSec}`;
2626
}
27-
if (fmtHour.contains("h")) {
27+
if (fmtHour.includes("h")) {
2828
fmt += " a";
2929
}
3030
timeFormat.push(fmt);

src/main.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,11 @@ export default class Tracker extends Plugin {
304304
let dataMap: DataMap = new Map(); // {strDate: [query: value, ...]}
305305
let processInfo = new CollectingProcessInfo();
306306
processInfo.fileTotal = files.length;
307+
308+
// Store dates in local variables to ensure they're captured correctly in async closures
309+
// This fixes the issue where dates become null inside async functions
310+
const startDate = renderInfo.startDate ? renderInfo.startDate.clone() : null;
311+
const endDate = renderInfo.endDate ? renderInfo.endDate.clone() : null;
307312

308313
// Collect data from files, each file has one data point for each query
309314
const loopFilePromises = files.map(async (file) => {
@@ -427,15 +432,19 @@ export default class Tracker extends Plugin {
427432
skipThisFile = true;
428433
processInfo.fileNotInFormat++;
429434
} else {
430-
// console.log("file " + file.basename + " accepted");
431-
if (renderInfo.startDate !== null) {
432-
if (xDate < renderInfo.startDate) {
435+
// Date filtering: only include files within the specified date range
436+
// Use isBefore/isAfter with 'day' granularity for clear, readable date comparisons
437+
// Use local variables (startDate/endDate) captured before async loop to avoid closure issues
438+
if (startDate !== null && startDate.isValid()) {
439+
// Skip files with dates before the start date
440+
if (xDate.isBefore(startDate, 'day')) {
433441
skipThisFile = true;
434442
processInfo.fileOutOfDateRange++;
435443
}
436444
}
437-
if (renderInfo.endDate !== null) {
438-
if (xDate > renderInfo.endDate) {
445+
if (endDate !== null && endDate.isValid()) {
446+
// Skip files with dates after the end date
447+
if (xDate.isAfter(endDate, 'day')) {
439448
skipThisFile = true;
440449
processInfo.fileOutOfDateRange++;
441450
}
@@ -465,7 +474,9 @@ export default class Tracker extends Plugin {
465474
}
466475
}
467476
}
468-
if (skipThisFile) return;
477+
if (skipThisFile) {
478+
return;
479+
}
469480
// console.log(xValueMap);
470481
// console.log(`minDate: ${minDate}`);
471482
// console.log(`maxDate: ${maxDate}`);

src/parsing.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,14 +1276,21 @@ export function getRenderInfoFromYaml(
12761276

12771277
// startDate, endDate
12781278
// console.log("Parsing startDate");
1279-
if (typeof yaml.startDate === "string") {
1280-
if (/^([\-]?[0-9]+[\.][0-9]+|[\-]?[0-9]+)m$/.test(yaml.startDate)) {
1279+
// Handle both string and number types (YAML may parse numeric dates as numbers)
1280+
if (typeof yaml.startDate === "string" || typeof yaml.startDate === "number") {
1281+
let strStartDate: string;
1282+
if (typeof yaml.startDate === "number") {
1283+
strStartDate = yaml.startDate.toString();
1284+
} else {
1285+
strStartDate = yaml.startDate;
1286+
}
1287+
if (/^([\-]?[0-9]+[\.][0-9]+|[\-]?[0-9]+)m$/.test(strStartDate)) {
12811288
let errorMessage =
12821289
"'m' for 'minute' is too small for parameter startDate, please use 'd' for 'day' or 'M' for month";
12831290
return errorMessage;
12841291
}
1285-
let strStartDate = helper.getDateStringFromInputString(
1286-
yaml.startDate,
1292+
strStartDate = helper.getDateStringFromInputString(
1293+
strStartDate,
12871294
renderInfo.dateFormatPrefix,
12881295
renderInfo.dateFormatSuffix
12891296
);
@@ -1318,14 +1325,21 @@ export function getRenderInfoFromYaml(
13181325
}
13191326

13201327
// console.log("Parsing endDate");
1321-
if (typeof yaml.endDate === "string") {
1322-
if (/^([\-]?[0-9]+[\.][0-9]+|[\-]?[0-9]+)m$/.test(yaml.endDate)) {
1328+
// Handle both string and number types (YAML may parse numeric dates as numbers)
1329+
if (typeof yaml.endDate === "string" || typeof yaml.endDate === "number") {
1330+
let strEndDate: string;
1331+
if (typeof yaml.endDate === "number") {
1332+
strEndDate = yaml.endDate.toString();
1333+
} else {
1334+
strEndDate = yaml.endDate;
1335+
}
1336+
if (/^([\-]?[0-9]+[\.][0-9]+|[\-]?[0-9]+)m$/.test(strEndDate)) {
13231337
let errorMessage =
13241338
"'m' for 'minute' is too small for parameter endDate, please use 'd' for 'day' or 'M' for month";
13251339
return errorMessage;
13261340
}
1327-
let strEndDate = helper.getDateStringFromInputString(
1328-
yaml.endDate,
1341+
strEndDate = helper.getDateStringFromInputString(
1342+
strEndDate,
13291343
renderInfo.dateFormatPrefix,
13301344
renderInfo.dateFormatSuffix
13311345
);
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* Test to compare different date comparison methods
3+
*
4+
* Question: Is valueOf() the best method, or should we use isBefore/isAfter?
5+
*/
6+
7+
import * as helper from '../src/helper';
8+
import moment from 'moment';
9+
10+
describe('Date Comparison Methods', () => {
11+
beforeAll(() => {
12+
if (typeof (global as any).window === 'undefined') {
13+
(global as any).window = {};
14+
}
15+
(global as any).window.moment = moment;
16+
});
17+
18+
describe('valueOf() vs isBefore/isAfter', () => {
19+
it('should produce same results with valueOf() and isBefore/isAfter', () => {
20+
const startDate = helper.strToDate('20240201', 'YYYYMMDD');
21+
const endDate = helper.strToDate('20240203', 'YYYYMMDD');
22+
const testDate = helper.strToDate('20240202', 'YYYYMMDD');
23+
24+
// Method 1: valueOf() (current implementation)
25+
const inRangeValueOf =
26+
testDate.valueOf() >= startDate.valueOf() &&
27+
testDate.valueOf() <= endDate.valueOf();
28+
29+
// Method 2: isBefore/isAfter with 'day' granularity
30+
const inRangeMoment =
31+
!testDate.isBefore(startDate, 'day') &&
32+
!testDate.isAfter(endDate, 'day');
33+
34+
// Method 3: isSameOrAfter/isSameOrBefore
35+
const inRangeSame =
36+
testDate.isSameOrAfter(startDate, 'day') &&
37+
testDate.isSameOrBefore(endDate, 'day');
38+
39+
expect(inRangeValueOf).toBe(true);
40+
expect(inRangeMoment).toBe(true);
41+
expect(inRangeSame).toBe(true);
42+
});
43+
44+
it('should handle boundary dates correctly with both methods', () => {
45+
const startDate = helper.strToDate('20240201', 'YYYYMMDD');
46+
const endDate = helper.strToDate('20240203', 'YYYYMMDD');
47+
48+
// Test start date boundary
49+
const onStartDate = helper.strToDate('20240201', 'YYYYMMDD');
50+
expect(onStartDate.valueOf() >= startDate.valueOf()).toBe(true);
51+
expect(!onStartDate.isBefore(startDate, 'day')).toBe(true);
52+
expect(onStartDate.isSameOrAfter(startDate, 'day')).toBe(true);
53+
54+
// Test end date boundary
55+
const onEndDate = helper.strToDate('20240203', 'YYYYMMDD');
56+
expect(onEndDate.valueOf() <= endDate.valueOf()).toBe(true);
57+
expect(!onEndDate.isAfter(endDate, 'day')).toBe(true);
58+
expect(onEndDate.isSameOrBefore(endDate, 'day')).toBe(true);
59+
});
60+
61+
it('should handle dates normalized to start of day', () => {
62+
// strToDate normalizes to startOf('day'), so all dates should be at midnight
63+
const date1 = helper.strToDate('20240201', 'YYYYMMDD');
64+
const date2 = helper.strToDate('20240201', 'YYYYMMDD');
65+
66+
// Both should be at midnight (00:00:00)
67+
expect(date1.hours()).toBe(0);
68+
expect(date1.minutes()).toBe(0);
69+
expect(date1.seconds()).toBe(0);
70+
expect(date1.valueOf()).toBe(date2.valueOf());
71+
});
72+
});
73+
74+
describe('Performance and clarity', () => {
75+
it('should note that isBefore/isAfter is more explicit about day granularity', () => {
76+
// isBefore/isAfter with 'day' is more explicit about what we're comparing
77+
// valueOf() works but is less clear about granularity
78+
const date1 = helper.strToDate('20240201', 'YYYYMMDD');
79+
const date2 = helper.strToDate('20240201', 'YYYYMMDD');
80+
81+
// Both methods work, but isBefore with 'day' is more explicit
82+
expect(date1.isBefore(date2, 'day')).toBe(false);
83+
expect(date1.valueOf() < date2.valueOf()).toBe(false);
84+
});
85+
});
86+
});

0 commit comments

Comments
 (0)