/** * DateTimeOffset functions. * * Note: DateOffset instances are always DateObjects in local * timezone (because JS dates are all kinds of messed up). * A local date returns UTC epoch when `.getTime()` is called. * * However, this means that in order to construct an UTC date * from a DateOffset with offset of +5 hours, you first need * to subtract those 5 hours, than add the "local" offset. * As said, all kinds of messed up. * * Basically; invariant: date.getTime() always return UTC time. */ import { int64, fromFloat64, toFloat64 } from "./BigInt.ts"; import DateTime, { create as createDate, dateOffsetToString, daysInMonth, parseRaw, ticksToUnixEpochMilliseconds, unixEpochMillisecondsToTicks } from "./Date.ts"; import { FSharpRef } from "./Types.ts"; import { Exception, compareDates, DateTimeKind, IDateTime, IDateTimeOffset, padWithZeros } from "./Util.ts"; export default function DateTimeOffset(value: number, offset?: number) { checkOffsetInRange(offset); const d = new Date(value) as IDateTimeOffset; d.offset = offset != null ? offset : new Date().getTimezoneOffset() * -60_000; return d; } export function offset(value: IDateTimeOffset): number { return value.offset || 0; } function checkOffsetInRange(offset?: number) { if (offset != null && offset !== 0) { if (offset % 60_000 !== 0) { throw new Exception("Offset must be specified in whole minutes."); } if (Math.abs(offset / 3600000) > 14) { throw new Exception("Offset must be within plus or minus 14 hours."); } } } export function fromDate(date: IDateTime, offset?: number) { let offset2: number = 0; switch (date.kind) { case DateTimeKind.Utc: if(offset != null && offset !== 0) { throw new Exception("The UTC Offset for Utc DateTime instances must be 0."); } offset2 = 0; break; case DateTimeKind.Local: offset2 = date.getTimezoneOffset() * -60_000; if(offset != null && offset !== offset2) { throw new Exception("The UTC Offset of the local dateTime parameter does not match the offset argument."); } break; case DateTimeKind.Unspecified: default: if(offset == null) { offset2 = date.getTimezoneOffset() * -60_000; } else { offset2 = offset; } break; } return DateTimeOffset(date.getTime(), offset2); } export function fromDateTime(dateOnly: IDateTime, timeOnly: number, offset: number) { return DateTimeOffset(dateOnly.getTime() - offset + timeOnly, offset); } export function fromTicks(ticks: int64, offset: number) { const ms = ticksToUnixEpochMilliseconds(ticks) - offset; return DateTimeOffset(ms, offset); } export function fromUnixTimeMilliseconds(ms: int64) { return DateTimeOffset(toFloat64(ms), 0) } export function fromUnixTimeSeconds(seconds: int64) { return DateTimeOffset(toFloat64(seconds * 1000n), 0) } export function getUtcTicks(date: IDateTimeOffset) { return unixEpochMillisecondsToTicks(date.getTime(), 0); } export function minValue() { // This is "0001-01-01T00:00:00.000Z", actual JS min value is -8640000000000000 return DateTimeOffset(-62135596800000, 0); } export function maxValue() { // This is "9999-12-31T23:59:59.999Z", actual JS max value is 8640000000000000 return DateTimeOffset(253402300799999, 0); } export function parse(str: string): IDateTimeOffset { const [date, offsetMatch] = parseRaw(str); const offset = offsetMatch == null ? date.getTimezoneOffset() * -60_000 : (offsetMatch === "Z" ? 0 : offsetMatch * 60_000); return DateTimeOffset(date.getTime(), offset); } export function tryParse(v: string, defValue: FSharpRef): boolean { try { defValue.contents = parse(v); return true; } catch (_err) { return false; } } export function create( year: number, month: number, day: number, h: number, m: number, s: number, ms: number, offset?: number) { if (offset == null) { offset = ms; ms = 0; } checkOffsetInRange(offset); let date = new Date(Date.UTC(year, month - 1, day, h, m, s, ms) - offset); if (year <= 99) { date.setUTCFullYear(year, month - 1, day); } const dateValue = date.getTime(); if (isNaN(dateValue)) { throw new Exception("The parameters describe an unrepresentable Date"); } return DateTimeOffset(dateValue, offset); } export function now() { const date = new Date(); const offset = date.getTimezoneOffset() * -60_000; return DateTimeOffset(date.getTime(), offset); } export function utcNow() { const date = new Date(); // const offset = date.getTimezoneOffset() * -60_000; return DateTimeOffset(date.getTime(), 0); } export function toUniversalTime(date: IDateTimeOffset): Date { return DateTime(date.getTime(), DateTimeKind.Utc); } export function toLocalTime(date: IDateTimeOffset): Date { return DateTime(date.getTime(), DateTimeKind.Local); } export function localDateTime(date: IDateTimeOffset) : Date { return DateTime(date.getTime(), DateTimeKind.Local); } export function timeOfDay(d: IDateTimeOffset) { const d2 = new Date(d.getTime() + offset(d)); return d2.getUTCHours() * 3600000 + d2.getUTCMinutes() * 60_000 + d2.getUTCSeconds() * 1000 + d2.getUTCMilliseconds(); } export function date(d: IDateTimeOffset) { const d2 = new Date(d.getTime() + offset(d)); return createDate(d2.getUTCFullYear(), d2.getUTCMonth() + 1, d2.getUTCDate(), 0, 0, 0, 0); } export function day(d: IDateTimeOffset) { return new Date(d.getTime() + offset(d)).getUTCDate(); } export function hour(d: IDateTimeOffset) { return new Date(d.getTime() + offset(d)).getUTCHours(); } export function millisecond(d: IDateTimeOffset) { return new Date(d.getTime() + offset(d)).getUTCMilliseconds(); } export function minute(d: IDateTimeOffset) { return new Date(d.getTime() + offset(d)).getUTCMinutes(); } export function month(d: IDateTimeOffset) { return new Date(d.getTime() + offset(d)).getUTCMonth() + 1; } export function second(d: IDateTimeOffset) { return new Date(d.getTime() + offset(d)).getUTCSeconds(); } export function year(d: IDateTimeOffset) { return new Date(d.getTime() + offset(d)).getUTCFullYear(); } export function dayOfWeek(d: IDateTimeOffset) { return new Date(d.getTime() + offset(d)).getUTCDay(); } export function dayOfYear(d: IDateTimeOffset) { const d2 = new Date(d.getTime() + offset(d)); const _year = d2.getUTCFullYear(); const _month = d2.getUTCMonth() + 1; let _day = d2.getUTCDate(); for (let i = 1; i < _month; i++) { _day += daysInMonth(_year, i); } return _day; } export function add(d: IDateTimeOffset, ts: number) { return DateTimeOffset(d.getTime() + ts, offset(d)); } export function addDays(d: IDateTimeOffset, v: number) { return add(d, v * 86400000); } export function addHours(d: IDateTimeOffset, v: number) { return add(d, v * 3600000); } export function addMinutes(d: IDateTimeOffset, v: number) { return add(d, v * 60_000); } export function addSeconds(d: IDateTimeOffset, v: number) { return add(d, v * 1000); } export function addMilliseconds(d: IDateTimeOffset, v: number) { return add(d, v); } export function addTicks(d: IDateTimeOffset, v: int64) { return add(d, toFloat64(v / 10000n)); } export function addYears(d: IDateTimeOffset, v: number) { const newMonth = d.getUTCMonth() + 1; const newYear = d.getUTCFullYear() + v; const _daysInMonth = daysInMonth(newYear, newMonth); const newDay = Math.min(_daysInMonth, d.getUTCDate()); return create(newYear, newMonth, newDay, d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds(), offset(d)); } export function addMonths(d: IDateTimeOffset, v: number) { const d2 = new Date(d.getTime() + offset(d)); let newMonth = d2.getUTCMonth() + 1 + v; let newMonth_ = 0; let yearOffset = 0; if (newMonth > 12) { newMonth_ = newMonth % 12; yearOffset = Math.floor(newMonth / 12); newMonth = newMonth_; } else if (newMonth < 1) { newMonth_ = 12 + newMonth % 12; yearOffset = Math.floor(newMonth / 12) + (newMonth_ === 12 ? -1 : 0); newMonth = newMonth_; } const newYear = d2.getUTCFullYear() + yearOffset; const _daysInMonth = daysInMonth(newYear, newMonth); const newDay = Math.min(_daysInMonth, d2.getUTCDate()); return create(newYear, newMonth, newDay, d2.getUTCHours(), d2.getUTCMinutes(), d2.getUTCSeconds(), d2.getUTCMilliseconds(), offset(d)); } export function subtract(d: IDateTimeOffset, that: Input): Output { return typeof that === "number" ? DateTimeOffset(d.getTime() - that, offset(d)) as Output : d.getTime() - that.getTime() as Output; } export function equals(d1: IDateTimeOffset, d2: IDateTimeOffset) { return d1.getTime() === d2.getTime(); } export function equalsExact(d1: IDateTimeOffset, d2: IDateTimeOffset) { return d1.getTime() === d2.getTime() && d1.offset === d2.offset; } export function compare(d1: IDateTimeOffset, d2: IDateTimeOffset) { return compareDates(d1, d2); } export const compareTo = compare; export function op_Addition(x: IDateTimeOffset, y: number) { return add(x, y); } export function op_Subtraction(x: IDateTimeOffset, y: Input): Output { return subtract(x, y); } export function toOffset(d: IDateTimeOffset, offset: number): IDateTimeOffset { return DateTimeOffset(d.getTime(), offset); } export function toUnixTimeMilliseconds(d: IDateTimeOffset): int64 { return fromFloat64(d.getTime()); } export function toUnixTimeSeconds(d: IDateTimeOffset): int64 { return fromFloat64(d.getTime() / 1000.0); }