-
-
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.