UNPKG

rrule-rust

Version:

RRule implementation for browsers and Node.js written in Rust

409 lines (408 loc) 14.5 kB
/** * Represents a date and time, either local or UTC. * * The generic type parameter `T` determines whether this is: * - `DateTime<Time>` - A date with time information * - `DateTime<undefined>` - A date without time information * * @example * ```typescript * // Create a date-only DateTime * const date = DateTime.date(2024, 1, 15); * console.log(date.year, date.month, date.day); // 2024, 1, 15 * * // Create a local DateTime * const local = DateTime.local(2024, 1, 15, 14, 30, 0); * console.log(local.time?.utc); // false * * // Create a UTC DateTime * const utc = DateTime.utc(2024, 1, 15, 14, 30, 0); * console.log(utc.time?.utc); // true * ``` */ export class DateTime { constructor(year, month, day, time) { this.year = year; this.month = month; this.day = day; this.time = time; } static create(year, month, day, hour, minute, second, utc) { if (hour !== undefined && minute !== undefined && second !== undefined && utc !== undefined) { const dt = new DateTime(year, month, day, { hour, minute, second, utc, }); dt.offset = utc ? 0 : undefined; return dt; } else { return new DateTime(year, month, day, undefined); } } /** * Creates a new date-only DateTime object (without time information). * * @param year - Year component (e.g., 2024) * @param month - Month component (1-12) * @param day - Day component (1-31) * @returns A DateTime instance without time information * * @example * ```typescript * const date = DateTime.date(2024, 1, 15); * console.log(date.toString()); // "20240115" * ``` */ static date(year, month, day) { return this.create(year, month, day); } /** * Creates a new local DateTime object (with time in local timezone). * This method is shorthand for `DateTime.create` with `utc` set to `false`. * * @param year - Year component (e.g., 2024) * @param month - Month component (1-12) * @param day - Day component (1-31) * @param hour - Hour component (0-23) * @param minute - Minute component (0-59) * @param second - Second component (0-59) * @returns A DateTime instance with local time * * @example * ```typescript * const value = DateTime.local(2024, 1, 15, 14, 30, 0); * console.log(value.time.utc); // false * console.log(value.toString()); // "20240115T143000" * ``` */ static local(year, month, day, hour, minute, second) { return DateTime.create(year, month, day, hour, minute, second, false); } /** * Creates a new UTC DateTime object (with time in UTC timezone). * This method is shorthand for `DateTime.create` with `utc` set to `true`. * * @param year - Year component (e.g., 2024) * @param month - Month component (1-12) * @param day - Day component (1-31) * @param hour - Hour component (0-23) * @param minute - Minute component (0-59) * @param second - Second component (0-59) * @returns A DateTime instance with UTC time * * @example * ```typescript * const value = DateTime.utc(2024, 1, 15, 14, 30, 0); * console.log(value.time.utc); // true * console.log(value.toString()); // "20240115T143000Z" * ``` */ static utc(year, month, day, hour, minute, second) { return DateTime.create(year, month, day, hour, minute, second, true); } /** * Creates a new UTC DateTime object from a JavaScript Date object. * * The resulting DateTime will have `utc` set to `true` and will represent * the same moment in time as the input Date object. * * @param date - A JavaScript Date object * @returns A DateTime instance with UTC time * * @example * ```typescript * const jsDate = new Date('2024-01-15T14:30:00.000Z'); * const datetime = DateTime.fromDate(jsDate); * console.log(datetime.year); // 2024 * console.log(datetime.month); // 1 * console.log(datetime.day); // 15 * console.log(datetime.time.hour); // 14 * console.log(datetime.time.minute); // 30 * console.log(datetime.time.second); // 0 * console.log(datetime.time.utc); // true * ``` */ static fromDate(date) { return DateTime.utc(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds()); } /** * Creates a new UTC DateTime object from a Unix timestamp in milliseconds. * * The resulting DateTime will have `utc` set to `true` and will represent * the moment in time corresponding to the given timestamp. * * @param timestamp - Unix timestamp in milliseconds since January 1, 1970 00:00:00 UTC * @returns A DateTime instance with UTC time * * @example * ```typescript * const timestamp = Date.UTC(2024, 0, 15, 14, 30, 0); // 1705329000000 * const datetime = DateTime.fromTimestamp(timestamp); * console.log(datetime.year); // 2024 * console.log(datetime.month); // 1 * console.log(datetime.day); // 15 * console.log(datetime.time.hour); // 14 * console.log(datetime.time.minute); // 30 * console.log(datetime.time.second); // 0 * console.log(datetime.time.utc); // true * ``` */ static fromTimestamp(timestamp) { return DateTime.fromDate(new Date(timestamp)); } static fromPlain(plain) { if ('hour' in plain) { return DateTime.create(plain.year, plain.month, plain.day, plain.hour, plain.minute, plain.second, plain.utc ?? false); } return DateTime.create(plain.year, plain.month, plain.day); } /** * Creates a new DateTime object from a string representation according to RFC 5545. * * Supported formats: * - `YYYYMMDD` - Date only (e.g., "20240115") * - `YYYYMMDDTHHMMSS` - Local date-time (e.g., "20240115T143000") * - `YYYYMMDDTHHMMSSZ` - UTC date-time (e.g., "20240115T143000Z") * * @param str - The RFC 5545 formatted string * @returns A DateTime instance * @throws {TypeError} If the string format is invalid * * @example * ```typescript * // Parse date only * const date = DateTime.fromString("20240115"); * * // Parse local date-time * const local = DateTime.fromString("20240115T143000"); * * // Parse UTC date-time * const utc = DateTime.fromString("20240115T143000Z"); * ``` */ static fromString(str) { if (!(str.length === 8 || (str.length <= 16 && str.length >= 15))) { throw new TypeError('Invalid date time string'); } const year = parseInt(str.slice(0, 4)); const month = parseInt(str.slice(4, 6)); const day = parseInt(str.slice(6, 8)); let hour = undefined; let minute = undefined; let second = undefined; let utc = undefined; if (isNaN(year) || isNaN(month) || isNaN(day)) { throw new TypeError('Invalid date'); } if (str.length > 8) { hour = parseInt(str.slice(9, 11)); minute = parseInt(str.slice(11, 13)); second = parseInt(str.slice(13, 15)); utc = str.endsWith('Z'); if (isNaN(hour) || isNaN(minute) || isNaN(second)) { throw new TypeError('Invalid time'); } return DateTime.create(year, month, day, hour, minute, second, utc); } else { return DateTime.create(year, month, day); } } /** @internal */ static fromNumbers(year, month, day, hour, minute, second, offset) { const dt = new DateTime(year, month, day, hour !== -1 ? { hour, minute, second, utc: offset === 0, } : undefined); dt.offset = offset === -1 ? undefined : offset; return dt; } /** @internal */ static fromInt32Array(arr) { return this.fromNumbers(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], arr[6]); } /** @internal */ static fromFlatInt32Array(raw) { const result = []; for (let i = 0; i < raw.length; i += 7) { result.push(this.fromNumbers(raw[i], raw[i + 1], raw[i + 2], raw[i + 3], raw[i + 4], raw[i + 5], raw[i + 6])); } return result; } /** @internal */ static toFlatInt32Array(datetimes) { const arr = new Int32Array(datetimes.length * 7); for (let i = 0; i < datetimes.length; i++) { const dt = datetimes[i]; const offset = i * 7; arr[offset] = dt.year; arr[offset + 1] = dt.month; arr[offset + 2] = dt.day; if (dt.time) { arr[offset + 3] = dt.time.hour; arr[offset + 4] = dt.time.minute; arr[offset + 5] = dt.time.second; arr[offset + 6] = dt.offset ?? -1; } else { arr[offset + 3] = -1; arr[offset + 4] = -1; arr[offset + 5] = -1; arr[offset + 6] = -1; } } return arr; } toPlain(options) { let plain; if (this.time) { plain = options?.stripUtc ? { year: this.year, month: this.month, day: this.day, hour: this.time.hour, minute: this.time.minute, second: this.time.second, } : { year: this.year, month: this.month, day: this.day, hour: this.time.hour, minute: this.time.minute, second: this.time.second, utc: this.time.utc, }; } else { plain = { year: this.year, month: this.month, day: this.day, }; } return plain; } /** * Converts the DateTime into an RFC 5545 formatted string. * * Format depends on the DateTime type: * - `YYYYMMDD` for date only * - `YYYYMMDDTHHMMSSZ` for UTC date-time * - `YYYYMMDDTHHMMSS` for local date-time * * @returns An RFC 5545 formatted string * * @example * ```typescript * const date = DateTime.date(2024, 1, 15); * console.log(date.toString()); // "20240115" * * const local = DateTime.local(2024, 1, 15, 14, 30, 0); * console.log(local.toString()); // "20240115T143000" * * const utc = DateTime.utc(2024, 1, 15, 14, 30, 0); * console.log(utc.toString()); // "20240115T143000Z" * ``` */ toString() { let str = this.year.toString().padStart(4, '0') + this.month.toString().padStart(2, '0') + this.day.toString().padStart(2, '0'); if (this.time) { str += 'T' + this.time.hour.toString().padStart(2, '0') + this.time.minute.toString().padStart(2, '0') + this.time.second.toString().padStart(2, '0') + (this.time.utc ? 'Z' : ''); } return str; } /** * Converts the DateTime to a Unix timestamp in milliseconds. * * This method requires timezone offset information to be available. The offset * is automatically set for: * - DateTime instances with `utc: true` * - DateTime instances generated by RRule methods (`all`, `between`, or iteration) * * @returns The Unix timestamp in milliseconds since January 1, 1970 00:00:00 UTC * @throws {Error} If timezone offset information is not available * * @example * ```typescript * const utc = DateTime.utc(2024, 1, 15, 14, 30, 0); * const timestamp = utc.toTimestamp(); * console.log(timestamp); // 1705328400000 * * // DateTime from RRule methods have offset information * const rrule = new RRule({ freq: Frequency.Daily, dtstart: DateTime.local(2024, 1, 1, 10, 0, 0) }); * const occurrences = rrule.all(); * const timestamp2 = occurrences[0].toTimestamp(); * ``` */ toTimestamp() { if (typeof this.offset === 'undefined') { throw new Error('There is no information about time zone offset'); } return this.toMilliseconds(); } /** * Converts the DateTime to a JavaScript Date object. * * This method requires timezone offset information to be available. The offset * is automatically set for: * - DateTime instances with `utc: true` * - DateTime instances generated by RRule methods (`all`, `between`, or iteration) * * @returns A JavaScript Date object representing the same moment in time * @throws {Error} If timezone offset information is not available * * @example * ```typescript * const utc = DateTime.utc(2024, 1, 15, 14, 30, 0); * const date = utc.toDate(); * console.log(date.toISOString()); // "2024-01-15T14:30:00.000Z" * * // DateTime from RRule methods have offset information * const rrule = new RRule({ freq: Frequency.Daily, dtstart: DateTime.utc(2024, 1, 1, 10, 0, 0) }); * const occurrences = rrule.all(); * const date2 = occurrences[0].toDate(); * ``` */ toDate() { if (typeof this.offset !== 'number') { throw new Error('There is no information about time zone offset'); } return new Date(this.toMilliseconds()); } /** @internal */ toInt32Array() { return new Int32Array([ this.year, this.month, this.day, this.time ? this.time.hour : -1, this.time ? this.time.minute : -1, this.time ? this.time.second : -1, this.offset ?? -1, ]); } toMilliseconds() { let time = Date.UTC(this.year, this.month - 1, this.day, this.time?.hour ?? 0, this.time?.minute ?? 0, this.time?.second ?? 0); time -= (this.offset ?? 0) * 1000; return time; } }