Suite of AT Protocol TypeScript libraries built on web standards
1export const SECOND = 1000;
2export const MINUTE = SECOND * 60;
3export const HOUR = MINUTE * 60;
4export const DAY = HOUR * 24;
5
6const SEPARATORS_TO_ESCAPE = new Set([
7 "\\",
8 "^",
9 "$",
10 ".",
11 "|",
12 "?",
13 "*",
14 "+",
15 "(",
16 ")",
17 "[",
18 "]",
19 "{",
20 "}",
21]);
22
23function getStringSeparator(dateString: string): string {
24 const separator = /\D/.exec(dateString);
25 return separator ? separator[0] : "";
26}
27
28function getTimeStringSeparator(timeString: string): string {
29 const matches = timeString.match(/([^Z+\-\d])(?=\d+\1)/);
30 return Array.isArray(matches) ? matches[0] : "";
31}
32
33export function isValidDate(date: string, s = "-"): boolean {
34 if (SEPARATORS_TO_ESCAPE.has(s)) {
35 s = `\\${s}`;
36 }
37
38 const validator = new RegExp(
39 `^(?!0{4}${s}0{2}${s}0{2})((?=[0-9]{4}${s}(((0[^2])|1[0-2])|02(?=${s}(([0-1][0-9])|2[0-8])))${s}[0-9]{2})|(?=((([13579][26])|([2468][048])|(0[48]))0{2})|([0-9]{2}((((0|[2468])[48])|[2468][048])|([13579][26])))${s}02${s}29))([0-9]{4})${s}(?!((0[469])|11)${s}31)((0[1,3-9]|1[0-2])|(02(?!${s}3)))${s}(0[1-9]|[1-2][0-9]|3[0-1])$`,
40 );
41 return validator.test(date);
42}
43
44function isValidTime(
45 timeWithOffset: string,
46 s = ":",
47 isTimezoneCheckOn = false,
48): boolean {
49 const validator = new RegExp(
50 `^([0-1]|2(?=([0-3])|4${s}00))[0-9]${s}[0-5][0-9](${s}([0-5]|6(?=0))[0-9])?(\.[0-9]{1,9})?$`,
51 );
52
53 if (!isTimezoneCheckOn || !/[Z+\-]/.test(timeWithOffset)) {
54 return validator.test(timeWithOffset);
55 }
56
57 if (/Z$/.test(timeWithOffset)) {
58 return validator.test(timeWithOffset.replace("Z", ""));
59 }
60
61 const isPositiveTimezoneOffset = timeWithOffset.includes("+");
62 const [time, offset] = timeWithOffset.split(/[+-]/);
63
64 return validator.test(time) &&
65 isValidZoneOffset(
66 offset,
67 isPositiveTimezoneOffset,
68 getStringSeparator(offset),
69 );
70}
71
72function isValidZoneOffset(
73 offset: string,
74 isPositiveOffset: boolean,
75 s = ":",
76): boolean {
77 const validator = new RegExp(
78 isPositiveOffset
79 ? `^(0(?!(2${s}4)|0${s}3)|1(?=([0-1]|2(?=${s}[04])|[34](?=${s}0))))([03469](?=${s}[03])|[17](?=${s}0)|2(?=${s}[04])|5(?=${s}[034])|8(?=${s}[04]))${s}([03](?=0)|4(?=5))[05]$`
80 : `^(0(?=[^0])|1(?=[0-2]))([39](?=${s}[03])|[0-24-8](?=${s}00))${s}[03]0$`,
81 );
82 return validator.test(offset);
83}
84
85export function isValidISODateString(dateString: string): boolean {
86 const [date, timeWithOffset] = dateString.split("T");
87 const dateSeparator = getStringSeparator(date);
88 const isDateValid = isValidDate(date, dateSeparator);
89
90 if (!timeWithOffset) {
91 return false;
92 }
93
94 const timeStringSeparator = getTimeStringSeparator(timeWithOffset);
95 return isDateValid && isValidTime(timeWithOffset, timeStringSeparator, true);
96}
97
98export const lessThanAgoMs = (time: Date, range: number): boolean => {
99 return Date.now() < time.getTime() + range;
100};
101
102export const addHoursToDate = (hours: number, startingDate?: Date): Date => {
103 const currentDate = startingDate ? new Date(startingDate) : new Date();
104 currentDate.setHours(currentDate.getHours() + hours);
105 return currentDate;
106};
107
108export function toSimplifiedISOSafe(dateStr: string): string {
109 const date = new Date(dateStr);
110 if (isNaN(date.getTime())) {
111 return new Date(0).toISOString();
112 }
113 const iso = date.toISOString();
114 if (!isValidISODateString(iso)) {
115 return new Date(0).toISOString();
116 }
117 return iso;
118}