From 169e06d83cef5116f0e39c74d2c497f04ba6278d Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 29 May 2024 16:55:35 +0200 Subject: [PATCH 1/5] refactor: update isValidDate return type for improved type safety --- packages/time/src/tests/isValidDate.test.ts | 38 +++++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/packages/time/src/tests/isValidDate.test.ts b/packages/time/src/tests/isValidDate.test.ts index 57a3556..188c31c 100644 --- a/packages/time/src/tests/isValidDate.test.ts +++ b/packages/time/src/tests/isValidDate.test.ts @@ -1,16 +1,32 @@ -import {describe, expect, test} from 'vitest'; -import {isValidDate} from '../utils/isValidDate'; +import { describe, expect, test } from 'vitest' +import { isValidDate } from '../utils/isValidDate' describe('isValidDate', () => { test('should return true for a valid date', () => { - expect(isValidDate(new Date())).toBe(true); - }); + const date = new Date(); + expect(isValidDate(date)).toBe(true) + }) - test('should return false for an invalid date', () => { - expect(isValidDate(new Date("invalid"))).toBe(false); - }); + test.each([ + '2021-10-10', + new Date('invalid'), + {}, + undefined, + null, + NaN, + 0, + ])('should return false for invalid date %p', (date) => { + expect(isValidDate(date)).toBe(false) + }) - test("should return false for null", () => { - expect(isValidDate(null)).toBe(false); - }); -}); \ No newline at end of file + test('should assert type guards correctly', () => { + const notADate = 'not a date'; + if (isValidDate(notADate)) { + expect(notADate).toBeInstanceOf(Date) + notADate.getDate() + } else { + // @ts-expect-error + notADate.getTime() + } + }) +}) From 75cb7b331a5c2ee41263f45509378197e9645b60 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Wed, 29 May 2024 16:55:47 +0200 Subject: [PATCH 2/5] refactor: update isValidDate return type for improved type safety --- packages/time/src/utils/isValidDate.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/time/src/utils/isValidDate.ts b/packages/time/src/utils/isValidDate.ts index 987df31..c0d1599 100644 --- a/packages/time/src/utils/isValidDate.ts +++ b/packages/time/src/utils/isValidDate.ts @@ -4,9 +4,6 @@ * @param date Date * @returns boolean */ -export function isValidDate(date: any): boolean { - if (Object.prototype.toString.call(date) !== '[object Date]') { - return false; - } - return date.getTime() === date.getTime(); -} \ No newline at end of file +export function isValidDate(date: unknown): date is Date { + return date instanceof Date && !isNaN(date.getTime()); +} From 5dca8d5e763a5e7cb2879ae836fec9cd16070a89 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 30 May 2024 17:52:05 +0200 Subject: [PATCH 3/5] refactor: update isValidDate return type for improved type safety --- packages/time/src/tests/isValidDate.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/time/src/tests/isValidDate.test.ts b/packages/time/src/tests/isValidDate.test.ts index 188c31c..4d01970 100644 --- a/packages/time/src/tests/isValidDate.test.ts +++ b/packages/time/src/tests/isValidDate.test.ts @@ -25,8 +25,10 @@ describe('isValidDate', () => { expect(notADate).toBeInstanceOf(Date) notADate.getDate() } else { - // @ts-expect-error - notADate.getTime() + expect(() => { + // @ts-expect-error + notADate.getTime() + }).toThrowError() } }) }) From e9e4be9c36c35283dded374d5475822fd888d203 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Thu, 30 May 2024 22:29:49 +0200 Subject: [PATCH 4/5] revert: isValidDate util --- packages/time/src/tests/isValidDate.test.ts | 38 ++++++--------------- packages/time/src/utils/isValidDate.ts | 9 +++-- 2 files changed, 16 insertions(+), 31 deletions(-) diff --git a/packages/time/src/tests/isValidDate.test.ts b/packages/time/src/tests/isValidDate.test.ts index 4d01970..01afdc6 100644 --- a/packages/time/src/tests/isValidDate.test.ts +++ b/packages/time/src/tests/isValidDate.test.ts @@ -1,34 +1,16 @@ -import { describe, expect, test } from 'vitest' -import { isValidDate } from '../utils/isValidDate' +import {describe, expect, test} from 'vitest'; +import {isValidDate} from '../utils/isValidDate'; describe('isValidDate', () => { test('should return true for a valid date', () => { - const date = new Date(); - expect(isValidDate(date)).toBe(true) + expect(isValidDate(new Date())).toBe(true); }) - test.each([ - '2021-10-10', - new Date('invalid'), - {}, - undefined, - null, - NaN, - 0, - ])('should return false for invalid date %p', (date) => { - expect(isValidDate(date)).toBe(false) - }) + test('should return false for an invalid date', () => { + expect(isValidDate(new Date("invalid"))).toBe(false); + }); - test('should assert type guards correctly', () => { - const notADate = 'not a date'; - if (isValidDate(notADate)) { - expect(notADate).toBeInstanceOf(Date) - notADate.getDate() - } else { - expect(() => { - // @ts-expect-error - notADate.getTime() - }).toThrowError() - } - }) -}) + test("should return false for null", () => { + expect(isValidDate(null)).toBe(false); + }); +}); diff --git a/packages/time/src/utils/isValidDate.ts b/packages/time/src/utils/isValidDate.ts index c0d1599..987df31 100644 --- a/packages/time/src/utils/isValidDate.ts +++ b/packages/time/src/utils/isValidDate.ts @@ -4,6 +4,9 @@ * @param date Date * @returns boolean */ -export function isValidDate(date: unknown): date is Date { - return date instanceof Date && !isNaN(date.getTime()); -} +export function isValidDate(date: any): boolean { + if (Object.prototype.toString.call(date) !== '[object Date]') { + return false; + } + return date.getTime() === date.getTime(); +} \ No newline at end of file From c0e5713351728fea93ed5d73b48e7894c20a5a64 Mon Sep 17 00:00:00 2001 From: Bart Krakowski Date: Mon, 3 Jun 2024 00:21:46 +0200 Subject: [PATCH 5/5] docs: add the choosing-a-date-time-handling-solution adr --- adr/choosing-a-date-time-handling-solution.md | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 adr/choosing-a-date-time-handling-solution.md diff --git a/adr/choosing-a-date-time-handling-solution.md b/adr/choosing-a-date-time-handling-solution.md new file mode 100644 index 0000000..03aae49 --- /dev/null +++ b/adr/choosing-a-date-time-handling-solution.md @@ -0,0 +1,91 @@ +# ADR: Choosing a Date and Time Handling Solution + +## Status + +Accepted, decided on Temporal API. + +## Context + +In modern web applications, accurate and efficient handling of dates and times is crucial. Traditional JavaScript `Date`` objects have several limitations and pitfalls, including: +- Lack of support for different calendars +- Difficulties with time zone conversions +- Ambiguous and error-prone APIs for date and time manipulation + +These issues often lead to bugs and inefficiencies in handling date and time data, especially in complex applications that require extensive date and time manipulations, such as calendar applications. + +## Decision + +After evaluating several options, we have decided to adopt the Temporal API for all date and time handling in our application. Temporal is a new standard for working with dates and times in JavaScript, designed to provide a more reliable, readable, and easy-to-use API. + +### Evaluation of Alternatives + +1. **JavaScript `Date`** + - **Pros**: + - Built-in and requires no additional dependencies. + - Supported natively in all JavaScript environments. + - **Cons**: + - Ambiguous and error-prone API. + - Poor support for time zones and different calendars. + - Mutable objects, leading to potential bugs. + - Date arithmetic is complex and error-prone. + +2. **moment.js** + - **Pros**: + - Widely used and battle-tested. + - Rich feature set for date manipulation and formatting. + - **Cons**: + - Large library size, affecting bundle size. + - Mutable objects, leading to potential bugs. + - Moment.js is in maintenance mode, with no new features planned. + +3. **date-fns** + - **Pros**: + - Modular approach, allowing selective imports to reduce bundle size. + - Immutable and functional API. + - Good support for date arithmetic and formatting. + - **Cons**: + - Limited support for time zones without additional libraries. + - Requires combining multiple functions for complex operations. + +### Reasons for Choosing Temporal + +1. **Clarity and Readability**: Temporal's API is designed to be intuitive and readable. For example, creating a date in Temporal is straightforward and unambiguous: + ```javascript + const date = Temporal.PlainDate.from('2024-06-01'); + ``` + This is much clearer than the equivalent using `Date`: + ```javascript + const date = new Date('2024-06-01'); + ``` + +2. **Immutability**: Temporal objects are immutable, meaning that once created, they cannot be changed. This immutability prevents common bugs related to date manipulation and makes the code easier to reason about. + +3. **Comprehensive Support for Calendars and Time Zones**: Temporal natively supports different calendars and time zones, making it ideal for applications that need to handle internationalization and localization. + +4. **Precise Arithmetic Operations**: Temporal provides precise and easy-to-use methods for date and time arithmetic, avoiding the pitfalls of JavaScript `Date` arithmetic: + ```javascript + const nextMonth = date.add({ months: 1 }); + ``` + +5. **Better Error Handling**: Temporal's API is designed to throw errors for invalid operations, which helps in catching bugs early in the development process. + +6. **Standardization**: Temporal is an upcoming standard for date and time handling in JavaScript, which means it will have long-term support and improvements from the community and browser vendors. + +## Consequences + +### Positive + +- **Reduced Bugs**: By using Temporal, we expect to see a reduction in date and time-related bugs, especially those related to time zone conversions and date arithmetic. +- **Improved Code Quality**: The clarity and immutability of Temporal will lead to more readable and maintainable code. +- **Enhanced Features**: Support for multiple calendars and time zones will allow us to build more robust and feature-rich internationalized applications. + +### Negative + +- **Learning Curve**: Developers will need to learn the new Temporal API, which might slow down initial development. +- **Polyfill Requirement**: Until Temporal is fully supported in all target environments, we will need to include a polyfill, which may slightly increase the bundle size. + +## Conclusion + +After evaluating various options for date and time handling in JavaScript, we have decided to adopt the Temporal API. Temporal provides a robust, readable, and reliable API that addresses the shortcomings of the existing `Date` object and offers comprehensive support for internationalization and time zone handling. Despite the initial learning curve and the need for a polyfill, the long-term benefits make Temporal the best choice for our application's needs. + +By documenting this decision, we ensure that future developers understand the rationale behind adopting Temporal and can build upon this foundation with confidence.