fix: date/timepicker issue with dates in the future (#1391)

Fixes: HDX-2852

If a user used the time picker and chose today, but with a time in the future, existing code would "infer" that and assume it was for an earlier year. Since the UI time picker doesnt allow users to select a _day_ in the future, we should be smart to parse down the time to match now()
This commit is contained in:
Tom Alexander 2025-11-20 11:11:56 -05:00 committed by GitHub
parent c8ec7fa9d6
commit 5b7d646f79
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 79 additions and 10 deletions

View file

@ -0,0 +1,5 @@
---
"@hyperdx/app": patch
---
fix: date/timepicker issue with dates in the future

View file

@ -1,10 +1,13 @@
import { dateParser, parseTimeRangeInput } from '../utils';
describe('dateParser', () => {
let mockDate: Date;
beforeEach(() => {
// Mock current date to ensure consistent test results
jest.useFakeTimers();
jest.setSystemTime(new Date('2025-01-15T22:00'));
mockDate = new Date('2025-01-15T22:00:00');
jest.setSystemTime(mockDate);
});
afterEach(() => {
@ -44,6 +47,58 @@ describe('dateParser', () => {
it('parses non-future month name/date with current year', () => {
expect(dateParser('Jan 15')).toEqual(new Date('2025-01-15T12:00:00'));
});
it('clamps slightly future dates to now (within 1 day) - no year specified', () => {
// Input: 23:00. Now: 22:00. Should clamp to now (22:00)
const result = dateParser('Jan 15 23:00:00');
expect(result?.getTime()).toEqual(mockDate.getTime());
expect(result?.getFullYear()).toEqual(2025);
});
it('clamps slightly future dates to now (within 1 day) - year specified', () => {
// Explicit year should be preserved even if in future, but clamped to now
const result = dateParser('2025-01-15 23:00:00');
expect(result?.getTime()).toEqual(mockDate.getTime());
// Verify it didn't shift to 2024
expect(result?.getFullYear()).toEqual(2025);
});
it('shifts year back for dates more than 1 day in future with inferred year', () => {
// mocked time is 2025-01-15 22:00
// Jan 17 is more than 1 day in future, should shift to 2024
const result = dateParser('Jan 17 12:00:00');
expect(result).toEqual(new Date('2024-01-17T12:00:00'));
});
it('does NOT shift year back for dates more than 1 day in future with explicit year', () => {
// mocked time is 2025-01-15 22:00
// Jan 17, 2025 is more than 1 day in future, but year is explicit
const result = dateParser('2025-01-17 12:00:00');
expect(result).toEqual(new Date('2025-01-17T12:00:00'));
expect(result?.getFullYear()).toEqual(2025);
});
it('handles dates in the past correctly', () => {
// mocked time is 2025-01-15 22:00
const result = dateParser('Jan 10 12:00:00');
expect(result).toEqual(new Date('2025-01-10T12:00:00'));
expect(result?.getFullYear()).toEqual(2025);
});
it('handles edge case: exactly 1 day in the future', () => {
// mocked time is 2025-01-15 22:00:00
// Exactly 24 hours later: 2025-01-16 22:00:00
// Should be clamped since it's <= 1 day from now
const result = dateParser('Jan 16 22:00:00');
expect(result?.getTime()).toEqual(mockDate.getTime());
});
it('handles edge case: just over 1 day in the future', () => {
// mocked time is 2025-01-15 22:00:00
// 24 hours + 1 second later should shift year
const result = dateParser('Jan 16 22:00:01');
expect(result).toEqual(new Date('2024-01-16T22:00:01'));
});
});
describe('parseTimeRangeInput', () => {

View file

@ -6,11 +6,12 @@ function normalizeParsedDate(parsed?: chrono.ParsedComponents): Date | null {
return null;
}
if (parsed.isCertain('year')) {
return parsed.date();
}
const now = new Date();
const parsedDate = parsed.date();
// If all of the time components have been inferred, set the time components of now
// to match the parsed time components. This ensures that the comparison later on uses
// the same point in time when only worrying about dates.
if (
!(
parsed.isCertain('hour') ||
@ -19,19 +20,27 @@ function normalizeParsedDate(parsed?: chrono.ParsedComponents): Date | null {
parsed.isCertain('millisecond')
)
) {
// If all of the time components have been inferred, set the time components of now
// to match the parsed time components. This ensures that the comparison later on uses
// the same point in time when only worrying about dates.
now.setHours(parsed.get('hour') || 0);
now.setMinutes(parsed.get('minute') || 0);
now.setSeconds(parsed.get('second') || 0);
now.setMilliseconds(parsed.get('millisecond') || 0);
}
const parsedDate = parsed.date();
// Handle future dates:
// - If slightly in the future (within 1 day), clamp to now
// - If significantly in the future (>1 day) AND year was inferred, shift year back
// (e.g., "Dec 25" typed in June defaults to Dec 25 this year, but user likely meant last Dec 25)
if (parsedDate > now) {
parsedDate.setFullYear(parsedDate.getFullYear() - 1);
const oneDayFromNow = now.getTime() + ms('1d');
if (parsedDate.getTime() <= oneDayFromNow) {
// Slightly in the future: clamp to now
return now;
} else if (!parsed.isCertain('year')) {
// Significantly in the future with inferred year: shift year back
parsedDate.setFullYear(parsedDate.getFullYear() - 1);
}
}
return parsedDate;
}