-
-
Save MrChocolatine/367fb2a35d02f6175cc8ccb3d3a20054 to your computer and use it in GitHub Desktop.
| // 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`; |
This approach is somewhat flawed. ISODate isn't a type—it's a format. The actual type would still be a string.
According to the ISO 8601 standard, timezones can include a Z to indicate UTC or an offset.. For example, you could define a type like this:
type TDateISODateTimeZone = 'Z' | `+${THours}:${TMinutes}` | `-${THours}:${TMinutes}`;The Offset can actually be empty (eg. 2021-01-01T14:42:34.678) which is implicit +0 = Z.
That said, it's important to differentiate between types and validators. What you’re really looking for here is a validator, such as:
// sadly will say true for `2021-01-01T14:42:34.678ZASDFADFADF` as well
moment(value, moment.ISO_8601, true).isValid();In my opinion, it's best to avoid relying heavily on ISODate formats. Instead, work with Date objects in your application logic, and if you need to transfer data, use a more reliable format like epoch milliseconds (UTC).
I'm currently just using:
export function isValidISODate(value: string): value is ReturnType<typeof Date.prototype.toISOString> {
const byMoment: boolean = moment(value, moment.ISO_8601, true).isValid(); // tests only format, not if date is valid
const byJs: boolean = Number.isSafeInteger(new Date(value).getTime()); // getTime can return NaN or non-safe-integers but isSafeInteger doesnt allow non-safe-integer nor NaN nor Infinity.
return byJs && byMoment;
}If Typescript ever decided to add a type for ISODate (highly doubt that) then this method would just return it as is.
Here's what I've been using:
My aim was not to make this perfect but to help point people in the right direction.