/** * Formats a Date object to ServiceNow UTC datetime format (YYYY-MM-DD HH:MM:SS) * @param date - The Date object to format (defaults to current time) * @returns Formatted UTC datetime string * * @example * const formatted = formatToUTC(new Date()); * // Returns: '1970-01-01 09:45:00' * * @example * const formatted = formatToUTC(); * // Returns current time in UTC: '2026-02-27 07:43:00' */ export function formatToUTC(date: Date = new Date()): string { const year = date.getUTCFullYear() const month = String(date.getUTCMonth() + 1).padStart(2, '0') const day = String(date.getUTCDate()).padStart(2, '0') const hour = String(date.getUTCHours()).padStart(2, '0') const minute = String(date.getUTCMinutes()).padStart(2, '0') const second = String(date.getUTCSeconds()).padStart(2, '0') return `${year}-${month}-${day} ${hour}:${minute}:${second}` } /** * Converts a datetime string from a specified timezone to UTC. * Similar to timeFieldToXML but handles full date and time, not just time of day. * * @param dateTimeStr - DateTime string in format 'YYYY-MM-DD HH:MM:SS' * @param timeZone - Optional IANA timezone string (e.g., 'America/New_York', 'Asia/Kolkata'). Defaults to system timezone. * @returns Formatted UTC datetime string in 'YYYY-MM-DD HH:MM:SS' format * * @example * // Convert '2026-03-02 14:30:00' from IST to UTC * const utcDate = dateTimeFieldToXML('2026-03-02 14:30:00', 'Asia/Kolkata') * // Returns '2026-03-02 09:00:00' */ export function dateTimeFieldToXML(dateTimeStr: string, timeZone?: string): string { // Parse the datetime string const match = dateTimeStr.match(/^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})$/) if (!match) { throw new Error(`Invalid datetime format: ${dateTimeStr}. Expected format: YYYY-MM-DD HH:MM:SS`) } // If timezone is 'floating' or not provided, use system timezone const targetTimeZone = timeZone === 'floating' || !timeZone ? Intl.DateTimeFormat().resolvedOptions().timeZone : timeZone if (targetTimeZone === 'GMT' || targetTimeZone === 'UTC') { return dateTimeStr } const year = parseInt(match[1]!, 10) const month = parseInt(match[2]!, 10) const day = parseInt(match[3]!, 10) const hours = parseInt(match[4]!, 10) const minutes = parseInt(match[5]!, 10) const seconds = parseInt(match[6]!, 10) const padZero = (n: number) => String(n).padStart(2, '0') // Convert from specified timezone to UTC // Create a reference date to calculate the timezone offset const localDateStr = `${year}-${padZero(month)}-${padZero(day)}T${padZero(hours)}:${padZero(minutes)}:${padZero(seconds)}` // Create a Date treating the input as UTC first const utcDate = new Date(`${localDateStr}Z`) // Get what this UTC time looks like in the target timezone const formatter = new Intl.DateTimeFormat('en-US', { timeZone: targetTimeZone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, }) const parts = formatter.formatToParts(utcDate) const dateParts: Record = {} parts.forEach((part) => { if (part.type !== 'literal') { dateParts[part.type] = part.value } }) // Calculate the difference between what we want and what we got const targetYear = parseInt(dateParts['year']!, 10) const targetMonth = parseInt(dateParts['month']!, 10) const targetDay = parseInt(dateParts['day']!, 10) // Normalize hour 24 to 00 (Intl.DateTimeFormat with hour12:false can return 24 for midnight) const targetHour = parseInt(dateParts['hour']!, 10) % 24 const targetMinute = parseInt(dateParts['minute']!, 10) const targetSecond = parseInt(dateParts['second']!, 10) // Calculate the offset in milliseconds const wantedTime = new Date(year, month - 1, day, hours, minutes, seconds).getTime() const gotTime = new Date(targetYear, targetMonth - 1, targetDay, targetHour, targetMinute, targetSecond).getTime() const offset = gotTime - wantedTime // Apply the offset to get the correct UTC time const resultDate = new Date(utcDate.getTime() - offset) return formatToUTC(resultDate) } /** * Converts an XML datetime string (in UTC) to a datetime string in a specified timezone * @param utcDateTimeStr - UTC datetime string in format 'YYYY-MM-DD HH:MM:SS' * @param timeZone - Optional IANA timezone string (e.g., 'America/New_York', 'Asia/Kolkata'). If not provided, returns the UTC string as-is. * @returns Datetime string in the specified timezone in 'YYYY-MM-DD HH:MM:SS' format * * @example * // Convert '2026-03-02 09:00:00' UTC to IST * const localDateTime = convertXMLToDateTime('2026-03-02 09:00:00', 'Asia/Kolkata') * // Returns '2026-03-02 14:30:00' * * @example * // Without timezone, returns the UTC string as-is * const utcDateTime = convertXMLToDateTime('2026-03-02 09:00:00') * // Returns '2026-03-02 09:00:00' */ export function convertXMLToDateTime(utcDateTimeStr: string, timeZone?: string): string { // Parse the UTC datetime string const match = utcDateTimeStr.match(/^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})$/) if (!match) { throw new Error(`Invalid datetime format: ${utcDateTimeStr}. Expected format: YYYY-MM-DD HH:MM:SS`) } // If timezone is 'floating' or not provided, use system timezone const targetTimeZone = timeZone === 'floating' || !timeZone ? Intl.DateTimeFormat().resolvedOptions().timeZone : timeZone if (targetTimeZone === 'GMT' || targetTimeZone === 'UTC') { return utcDateTimeStr } const year = parseInt(match[1]!, 10) const month = parseInt(match[2]!, 10) const day = parseInt(match[3]!, 10) const hours = parseInt(match[4]!, 10) const minutes = parseInt(match[5]!, 10) const seconds = parseInt(match[6]!, 10) // Create a Date object from the UTC datetime const utcDate = new Date(Date.UTC(year, month - 1, day, hours, minutes, seconds)) // Format the date in the target timezone const formatter = new Intl.DateTimeFormat('en-US', { timeZone: targetTimeZone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, }) const parts = formatter.formatToParts(utcDate) const dateParts: Record = {} parts.forEach((part) => { if (part.type !== 'literal') { dateParts[part.type] = part.value } }) // Normalize hour 24 to 00 (Intl.DateTimeFormat with hour12:false can return 24 for midnight) const hour = dateParts['hour'] === '24' ? '00' : dateParts['hour'] return `${dateParts['year']}-${dateParts['month']}-${dateParts['day']} ${hour}:${dateParts['minute']}:${dateParts['second']}` } /** * Formats time data (hours, minutes, seconds) as a datetime string without timezone conversion. * Used for storing the original user-entered time value in the 'entered_time' field. * * @param timeData - Object containing hours, minutes, and seconds * @returns Formatted datetime string in 'YYYY-MM-DD HH:MM:SS' format with epoch date (1970-01-01) * * @example * formatTimeDataToDateTime({ hours: 14, minutes: 30, seconds: 0 }) * // Returns '1970-01-01 14:30:00' */ export function formatTimeDataToDateTime(timeData: { hours?: number; minutes?: number; seconds?: number }): string { const hours = String(timeData.hours || 0).padStart(2, '0') const minutes = String(timeData.minutes || 0).padStart(2, '0') const seconds = String(timeData.seconds || 0).padStart(2, '0') return `1970-01-01 ${hours}:${minutes}:${seconds}` }