Skip to content

Instantly share code, notes, and snippets.

@aborchew
Last active October 5, 2023 15:55
Show Gist options
  • Save aborchew/5adfb0519a357b26e6bf1cfd480f7b50 to your computer and use it in GitHub Desktop.
Save aborchew/5adfb0519a357b26e6bf1cfd480f7b50 to your computer and use it in GitHub Desktop.
ISODateString Type and Type Guard
// Validate the formatting of an ISO 8601 Date String
// The ISODateString Type will ensure that the days do not exceed those in the month, although YYYY-02-29 will always be valid
// The isISODateString Type Guard will validate formatting and ensure that the date is valid, also taking into account leap years
// If using a Regular Expression engine that supports Conditionals (JavaScript does not), this shorter expression does the same thing
// ^(?:(?<leap>\d{2}[02468][048]|^\d{2}[13579][26]|[048])|\d{4})-\b(?<d29>(?(leap)02|(?<d30>(?<d31>01|03|05|07|08|10|12)|(?:04|06|09|11))))|02\b-\b(?:0[1-9]|1[0-9]|(?(d31)(2[0-9]|30|31)|(?(d30)(2[0-9]|30)|(?(d29)2[0-9]|2[0-8]))))\b
// And the same without named groups:
// ^(?:(\d{2}[02468][048]|^\d{2}[13579][26]|[048])|\d{4})-\b((?(1)02|((01|03|05|07|08|10|12)|(?:04|06|09|11))))|02\b-\b(?:0[1-9]|1[0-9]|(?(4)(2[0-9]|30|31)|(?(3)(2[0-9]|30)|(?(2)2[0-9]|2[0-8]))))\b
import dayjs = require('dayjs');
export const ISO_DATE = Object.freeze({
FORMAT: 'YYYY-MM-DD',
// eslint-disable-next-line max-len
RGX: /^(?:\d{2}[02468][048]|^\d{2}[13579][26]|[048])-(?:\b(?:01|03|05|07|08|10|12)\b-\b(?:0[1-9]|[12][0-9]|3[01])\b|\b(?:04|06|09|11)\b-\b(?:0[1-9]|[12][0-9]|30)\b|\b(?:02)-\b(?:0[1-9]|(?:[12][0-9]))\b)|\d{4}-(?:\b(?:01|03|05|07|08|10|12)\b-\b(?:0[1-9]|[12][0-9]|3[01])\b|\b(?:04|06|09|11)\b-\b(?:0[1-9]|[12][0-9]|30)\b|\b(?:02)-\b(?:0[1-9]|(?:[12][0-8]))\b)$/,
});
type ISOYear = `${number}${number}${number}${number}`;
type INT_01_29 = `${0}${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}` | `${1 | 2}${number}`;
type INT_01_30 = INT_01_29 | '30';
type INT_01_31 = INT_01_30 | '31';
type ISODate =
`01-${INT_01_31}` |
`03-${INT_01_31}` |
`05-${INT_01_31}` |
`07-${INT_01_31}` |
`12-${INT_01_31}` |
`08-${INT_01_31}` |
`10-${INT_01_31}` |
`04-${INT_01_30}` |
`06-${INT_01_30}` |
`09-${INT_01_30}` |
`11-${INT_01_30}` |
`02-${INT_01_29}`;
export type ISODateString = string & `${ISOYear}-${ISODate}`;
export const isISODateString = (str: unknown): str is ISODateString => typeof str === 'string' && !!ISO_DATE.RGX.exec(str);
dayjs.extend((_option, dayjsClass) => {
// eslint-disable-next-line no-param-reassign
dayjsClass.prototype.toISODateString = function toISODateString() {
return this.format(ISO_DATE.FORMAT);
};
});
declare module 'dayjs' {
interface Dayjs {
toISODateString(): ISODateString;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment