From 4ad5203fa99d588d4b72f78f2089d4af11cff082 Mon Sep 17 00:00:00 2001 From: biletskii Date: Wed, 18 Feb 2026 19:32:01 +0200 Subject: [PATCH] fix: resolve timezone bug in date comparison --- src/date.ts | 13 ++++++++----- test/date.test.ts | 10 +++++----- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/date.ts b/src/date.ts index c5ba5c8..f1d54af 100644 --- a/src/date.ts +++ b/src/date.ts @@ -6,15 +6,18 @@ * - 13-digit timestamps (Unix milliseconds) - used directly * - String representations of timestamps * - ISO date strings (e.g., "2025-06-01T12:30:00Z") - * - Simple date strings (e.g., "2025-06-01") - time defaults to 00:00:00 + * - Simple date strings (e.g., "2025-06-01") - time defaults to 00:00:00 UTC * - Date strings with space-separated time (e.g., "2025-06-01 12:30:00") * * Note: This function follows JavaScript Date constructor behavior for date parsing. * Some invalid dates like "2025-02-30" auto-correct to valid dates (becomes "2025-03-02"), * while malformed strings like "2025-13-01" or "2025-06-01T25:00:00" return null. * + * For simple date strings without time components, the function uses UTC methods to ensure + * consistent behavior across all timezones, avoiding timezone shift bugs in date comparisons. + * * @example - * coerceToDate('2025-06-01') // Returns Date object set to 2025-06-01 00:00:00 + * coerceToDate('2025-06-01') // Returns Date object set to 2025-06-01 00:00:00 UTC * coerceToDate('2025-06-01T12:30:00Z') // Returns Date object with specified time * coerceToDate(1748834578) // Returns Date object (10-digit timestamp in seconds) * coerceToDate(1748834578000) // Returns Date object (13-digit timestamp in milliseconds) @@ -53,12 +56,12 @@ export const coerceToDate = ( // Return null for invalid dates if (Number.isNaN(dateObj.getTime())) return null; - // If no time component is specified, set time to 00:00:00 - // this is because by default JS will set the time to midnight UTC for that date + // If no time component is specified, set time to 00:00:00 UTC + // Use setUTCHours to avoid timezone shifts const hasTimeComponent = /T\d{2}:\d{2}(:\d{2})?/.test(dateInput) || /\d{2}:\d{2}/.test(dateInput); if (!hasTimeComponent) { - dateObj.setHours(0, 0, 0, 0); + dateObj.setUTCHours(0, 0, 0, 0); } return dateObj; diff --git a/test/date.test.ts b/test/date.test.ts index 44fe655..1e1048d 100644 --- a/test/date.test.ts +++ b/test/date.test.ts @@ -72,14 +72,14 @@ describe('coerceToDate', () => { }); describe('simple date string inputs', () => { - it('should handle simple date strings and set time to 00:00:00', () => { + it('should handle simple date strings and set time to 00:00:00 UTC', () => { const dateString = '2025-06-01'; const result = coerceToDate(dateString); expect(result).toBeInstanceOf(Date); - expect(result?.getHours()).toBe(0); - expect(result?.getMinutes()).toBe(0); - expect(result?.getSeconds()).toBe(0); - expect(result?.getMilliseconds()).toBe(0); + expect(result?.getUTCHours()).toBe(0); + expect(result?.getUTCMinutes()).toBe(0); + expect(result?.getUTCSeconds()).toBe(0); + expect(result?.getUTCMilliseconds()).toBe(0); }); it('should handle date strings with slashes', () => {