Suite of AT Protocol TypeScript libraries built on web standards
21
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 115 lines 3.9 kB view raw
1/* Validates datetime string against atproto Lexicon 'datetime' format. 2 * Syntax is described at: https://atproto.com/specs/lexicon#datetime 3 */ 4export const ensureValidDatetime = (dtStr: string): void => { 5 const date = new Date(dtStr); 6 // must parse as ISO 8601; this also verifies semantics like month is not 13 or 00 7 if (isNaN(date.getTime())) { 8 throw new InvalidDatetimeError("datetime did not parse as ISO 8601"); 9 } 10 if (date.toISOString().startsWith("-")) { 11 throw new InvalidDatetimeError("datetime normalized to a negative time"); 12 } 13 // regex and other checks for RFC-3339 14 if ( 15 !/^[0-9]{4}-[01][0-9]-[0-3][0-9]T[0-2][0-9]:[0-6][0-9]:[0-6][0-9](.[0-9]{1,20})?(Z|([+-][0-2][0-9]:[0-5][0-9]))$/ 16 .test( 17 dtStr, 18 ) 19 ) { 20 throw new InvalidDatetimeError("datetime didn't validate via regex"); 21 } 22 if (dtStr.length > 64) { 23 throw new InvalidDatetimeError("datetime is too long (64 chars max)"); 24 } 25 if (dtStr.endsWith("-00:00")) { 26 throw new InvalidDatetimeError( 27 'datetime can not use "-00:00" for UTC timezone', 28 ); 29 } 30 if (dtStr.startsWith("000")) { 31 throw new InvalidDatetimeError( 32 "datetime so close to year zero not allowed", 33 ); 34 } 35}; 36 37/* Same logic as ensureValidDatetime(), but returns a boolean instead of throwing an exception. 38 */ 39export const isValidDatetime = (dtStr: string): boolean => { 40 try { 41 ensureValidDatetime(dtStr); 42 } catch (err) { 43 if (err instanceof InvalidDatetimeError) { 44 return false; 45 } 46 throw err; 47 } 48 49 return true; 50}; 51 52/* Takes a flexible datetime string and normalizes representation. 53 * 54 * This function will work with any valid atproto datetime (eg, anything which isValidDatetime() is true for). It *additionally* is more flexible about accepting datetimes that don't comply to RFC 3339, or are missing timezone information, and normalizing them to a valid datetime. 55 * 56 * One use-case is a consistent, sortable string. Another is to work with older invalid createdAt datetimes. 57 * 58 * Successful output will be a valid atproto datetime with millisecond precision (3 sub-second digits) and UTC timezone with trailing 'Z' syntax. Throws `InvalidDatetimeError` if the input string could not be parsed as a datetime, even with permissive parsing. 59 * 60 * Expected output format: YYYY-MM-DDTHH:mm:ss.sssZ 61 */ 62export const normalizeDatetime = (dtStr: string): string => { 63 if (isValidDatetime(dtStr)) { 64 const outStr = new Date(dtStr).toISOString(); 65 if (isValidDatetime(outStr)) { 66 return outStr; 67 } 68 } 69 70 // check if this permissive datetime is missing a timezone 71 if (!/.*(([+-]\d\d:?\d\d)|[a-zA-Z])$/.test(dtStr)) { 72 const date = new Date(dtStr + "Z"); 73 if (!isNaN(date.getTime())) { 74 const tzStr = date.toISOString(); 75 if (isValidDatetime(tzStr)) { 76 return tzStr; 77 } 78 } 79 } 80 81 // finally try parsing as simple datetime 82 const date = new Date(dtStr); 83 if (isNaN(date.getTime())) { 84 throw new InvalidDatetimeError( 85 "datetime did not parse as any timestamp format", 86 ); 87 } 88 const isoStr = date.toISOString(); 89 if (isValidDatetime(isoStr)) { 90 return isoStr; 91 } else { 92 throw new InvalidDatetimeError( 93 "datetime normalized to invalid timestamp string", 94 ); 95 } 96}; 97 98/* Variant of normalizeDatetime() which always returns a valid datetime strings. 99 * 100 * If a InvalidDatetimeError is encountered, returns the UNIX epoch time as a UTC datetime (1970-01-01T00:00:00.000Z). 101 */ 102export const normalizeDatetimeAlways = (dtStr: string): string => { 103 try { 104 return normalizeDatetime(dtStr); 105 } catch (err) { 106 if (err instanceof InvalidDatetimeError) { 107 return new Date(0).toISOString(); 108 } 109 throw err; 110 } 111}; 112 113/* Indicates a datetime string did not pass full atproto Lexicon datetime string format checks. 114 */ 115export class InvalidDatetimeError extends Error {}