fix(ag-grid): Ag Grid Date Filter timezone correction (#36270)

This commit is contained in:
amaannawab923
2026-01-07 08:27:20 -03:00
committed by GitHub
parent 9968393e4c
commit d7d94ba640
2 changed files with 167 additions and 15 deletions

View File

@@ -17,25 +17,53 @@
* under the License.
*/
const dateFilterComparator = (filterDate: Date, cellValue: Date) => {
/**
* Timezone-safe date comparator for AG Grid date filters.
*
* This comparator normalizes both dates to UTC midnight before comparison,
* fixing the off-by-one day bug that occurs when:
* - User's timezone differs from UTC
* - Data is stored in UTC but filtered in local time
* - Midnight boundary crossing due to timezone offset
*
* Bug references:
* - AG Grid Issue #8611: UTC Date Editor Problem
* - AG Grid Issue #3921: DateFilter timezone regression
*
*/
const dateFilterComparator = (
filterDate: Date,
cellValue: Date | null | undefined,
) => {
if (cellValue == null) {
return -1;
}
const cellDate = new Date(cellValue);
cellDate.setHours(0, 0, 0, 0);
if (Number.isNaN(cellDate?.getTime())) return -1;
if (Number.isNaN(cellDate.getTime())) {
return -1;
}
const cellDay = cellDate.getDate();
const cellMonth = cellDate.getMonth();
const cellYear = cellDate.getFullYear();
// Filter date from AG Grid uses local timezone (what the user selected)
const filterUTC = Date.UTC(
filterDate.getFullYear(),
filterDate.getMonth(),
filterDate.getDate(),
);
const filterDay = filterDate.getDate();
const filterMonth = filterDate.getMonth();
const filterYear = filterDate.getFullYear();
// Cell data is in UTC - extract UTC components to compare actual dates
const cellUTC = Date.UTC(
cellDate.getUTCFullYear(),
cellDate.getUTCMonth(),
cellDate.getUTCDate(),
);
if (cellYear < filterYear) return -1;
if (cellYear > filterYear) return 1;
if (cellMonth < filterMonth) return -1;
if (cellMonth > filterMonth) return 1;
if (cellDay < filterDay) return -1;
if (cellDay > filterDay) return 1;
if (cellUTC < filterUTC) {
return -1;
}
if (cellUTC > filterUTC) {
return 1;
}
return 0;
};

View File

@@ -0,0 +1,124 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import dateFilterComparator from '../../src/utils/dateFilterComparator';
test('returns 0 when filter date equals cell date', () => {
const filterDate = new Date(2003, 9, 8); // Oct 8, 2003 local
const cellDate = new Date('2003-10-08T12:00:00Z'); // Oct 8, 2003 UTC
expect(dateFilterComparator(filterDate, cellDate)).toBe(0);
});
test('returns -1 when cell date is before filter date', () => {
const filterDate = new Date(2003, 9, 10); // Oct 10, 2003
const cellDate = new Date('2003-10-08T12:00:00Z'); // Oct 8, 2003
expect(dateFilterComparator(filterDate, cellDate)).toBe(-1);
});
test('returns 1 when cell date is after filter date', () => {
const filterDate = new Date(2003, 9, 8); // Oct 8, 2003
const cellDate = new Date('2003-10-10T12:00:00Z'); // Oct 10, 2003
expect(dateFilterComparator(filterDate, cellDate)).toBe(1);
});
test('returns -1 when cell value is null', () => {
const filterDate = new Date(2003, 9, 8);
expect(dateFilterComparator(filterDate, null)).toBe(-1);
});
test('returns -1 when cell value is undefined', () => {
const filterDate = new Date(2003, 9, 8);
expect(dateFilterComparator(filterDate, undefined)).toBe(-1);
});
test('returns -1 when cell value is an invalid date string', () => {
const filterDate = new Date(2003, 9, 8);
const cellDate = new Date('invalid-date');
expect(dateFilterComparator(filterDate, cellDate)).toBe(-1);
});
test('handles year boundary - cell in previous year', () => {
const filterDate = new Date(2024, 0, 1); // Jan 1, 2024
const cellDate = new Date('2023-12-31T12:00:00Z'); // Dec 31, 2023
expect(dateFilterComparator(filterDate, cellDate)).toBe(-1);
});
test('handles year boundary - cell in next year', () => {
const filterDate = new Date(2023, 11, 31); // Dec 31, 2023
const cellDate = new Date('2024-01-01T12:00:00Z'); // Jan 1, 2024
expect(dateFilterComparator(filterDate, cellDate)).toBe(1);
});
test('handles month boundary - cell in previous month', () => {
const filterDate = new Date(2003, 9, 1); // Oct 1, 2003
const cellDate = new Date('2003-09-30T12:00:00Z'); // Sep 30, 2003
expect(dateFilterComparator(filterDate, cellDate)).toBe(-1);
});
test('handles month boundary - cell in next month', () => {
const filterDate = new Date(2003, 8, 30); // Sep 30, 2003
const cellDate = new Date('2003-10-01T12:00:00Z'); // Oct 1, 2003
expect(dateFilterComparator(filterDate, cellDate)).toBe(1);
});
test('matches UTC midnight timestamp', () => {
const filterDate = new Date(2003, 9, 8); // Oct 8, 2003
const cellDate = new Date('2003-10-08T00:00:00Z'); // Oct 8, 2003 00:00 UTC
expect(dateFilterComparator(filterDate, cellDate)).toBe(0);
});
test('matches UTC end-of-day timestamp', () => {
const filterDate = new Date(2003, 9, 8); // Oct 8, 2003
const cellDate = new Date('2003-10-08T23:59:59Z'); // Oct 8, 2003 23:59 UTC
expect(dateFilterComparator(filterDate, cellDate)).toBe(0);
});
test('correctly compares dates from ISO string cell values', () => {
const filterDate = new Date(2003, 9, 8);
const cellDate = new Date('2003-10-08T00:00:00Z');
expect(dateFilterComparator(filterDate, cellDate)).toBe(0);
});
test('handles cell value created from UTC timestamp', () => {
const filterDate = new Date(2003, 9, 8);
// Oct 8, 2003 12:00:00 UTC
const cellDate = new Date(Date.UTC(2003, 9, 8, 12, 0, 0));
expect(dateFilterComparator(filterDate, cellDate)).toBe(0);
});
test('compares only date components, ignoring time', () => {
const filterDate = new Date(2003, 9, 8, 0, 0, 0); // Oct 8 at midnight local
const cellDate = new Date('2003-10-08T18:30:45Z'); // Oct 8 at 6:30pm UTC
expect(dateFilterComparator(filterDate, cellDate)).toBe(0);
});