mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
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:
parent
c8ec7fa9d6
commit
5b7d646f79
3 changed files with 79 additions and 10 deletions
5
.changeset/tame-paws-clap.md
Normal file
5
.changeset/tame-paws-clap.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
fix: date/timepicker issue with dates in the future
|
||||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue