Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 36 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save MrChocolatine/367fb2a35d02f6175cc8ccb3d3a20054 to your computer and use it in GitHub Desktop.
Save MrChocolatine/367fb2a35d02f6175cc8ccb3d3a20054 to your computer and use it in GitHub Desktop.
TypeScript – How to accurately type dates in ISO 8601 format
// In TS, interfaces are "open" and can be extended
interface Date {
/**
* Give a more precise return type to the method `toISOString()`:
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
*/
toISOString(): TDateISO;
}
type TYear = `${number}${number}${number}${number}`;
type TMonth = `${number}${number}`;
type TDay = `${number}${number}`;
type THours = `${number}${number}`;
type TMinutes = `${number}${number}`;
type TSeconds = `${number}${number}`;
type TMilliseconds = `${number}${number}${number}`;
/**
* Represent a string like `2021-01-08`
*/
type TDateISODate = `${TYear}-${TMonth}-${TDay}`;
/**
* Represent a string like `14:42:34.678`
*/
type TDateISOTime = `${THours}:${TMinutes}:${TSeconds}.${TMilliseconds}`;
/**
* Represent a string like `2021-01-08T14:42:34.678Z` (format: ISO 8601).
*
* It is not possible to type more precisely (list every possible values for months, hours etc) as
* it would result in a warning from TypeScript:
* "Expression produces a union type that is too complex to represent. ts(2590)
*/
type TDateISO = `${TDateISODate}T${TDateISOTime}Z`;
@rizal111
Copy link

Thanks 👍

@NikitaKA
Copy link

NikitaKA commented Dec 1, 2022

Excellent!

@Sh4p3rd
Copy link

Sh4p3rd commented Dec 2, 2022

Yes! I might just need this 👌

@McWhorter
Copy link

Awesome. Thank you!

@danrivett
Copy link

This is good (and similar to what I have been using) but it has one important gotcha that I wasn't aware of:

${number} matches any number of digits and so the following still compiles:

const date: TDateISODate = '2023-03-123456789';

So using something like ${number}${number} can guarantee a minimum number of digits, but it cannot guarantee an exact, or maximum number of digits.

And using something like:

type Digit = `0` | `1` | `2` | `3` | `4` | `5` | `6` | `7` | `8` | `9`;

type Year = `${Digit}${Digit}${Digit}${Digit}`;

Starts to blow up with the same Expression produces a union type that is too complex to represent. ts(2590) error mentioned above when they are combined into larger types.

So because of this false sense of security, I'm less inclined to use this approach in the future, and I'm looking at other options such as runtime validation and type coercion as outlined in this video.

It's a bit more ceremony as you can't assign string literals directly to the types, but provides more safety.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment