UNPKG

@progress/kendo-date-math

Version:

Kendo UI typescript package exporting functions for Date manipulations

584 lines (583 loc) 19.6 kB
import { MS_PER_HOUR, MS_PER_MINUTE } from '../constants'; import { cloneDate } from '../clone-date'; import { abbrTimezone } from './abbr-timezone'; import { offset } from './offset'; import { toLocalDate } from './to-local-date'; import { padNumber } from './pad-number'; const addMinutes = (date, minutes) => new Date(date.getTime() + minutes * MS_PER_MINUTE); const addHours = (date, hours) => new Date(date.getTime() + hours * MS_PER_HOUR); const dayAbbr = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]; const monthAbbr = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]; const datePrefix = (utcDate) => dayAbbr[utcDate.getUTCDay()] + ' ' + monthAbbr[utcDate.getUTCMonth()]; function isZoneMissingHour(date, timezone) { const currentOffset = offset(timezone, date); const prevHour = addHours(date, -1); const prevOffset = offset(timezone, prevHour); return currentOffset < prevOffset; } function shiftZoneMissingHour(utcDate, timezone) { // Adjust for missing hour during DST transition in timezone. const dstOffset = isZoneMissingHour(utcDate, timezone) ? 1 : 0; return addHours(utcDate, dstOffset); } function convertTimezoneUTC(utcLocal, fromTimezone, toTimezone) { if (fromTimezone === toTimezone) { return utcLocal; } const fromOffset = offset(fromTimezone, utcLocal); const toOffset = offset(toTimezone, utcLocal); const baseDiff = fromOffset - toOffset; const midDate = addMinutes(utcLocal, baseDiff); const midOffset = offset(toTimezone, midDate); const dstDiff = toOffset - midOffset; return addMinutes(utcLocal, baseDiff + dstDiff); } function formatOffset(tzOffset) { const sign = tzOffset <= 0 ? '+' : '-'; const value = Math.abs(tzOffset); const hours = padNumber(Math.floor(value / 60)); const minutes = padNumber(value % 60); return `GMT${sign}${hours}${minutes}`; } // Duplicate from kendo-recurrence function toUTCDateTime(localDate) { return new Date(Date.UTC(localDate.getFullYear(), localDate.getMonth(), localDate.getDate(), localDate.getHours(), localDate.getMinutes(), localDate.getSeconds(), localDate.getMilliseconds())); } /** * Represents a local date in a specified timezone. * * The following example demonstrates how to convert a local date to the specified timezone. * * @example * ```ts-no-run * import { ZonedDate } from '@progress/kendo-date-math'; * import '@progress/kendo-date-math/tz/America/New_York'; * * const date = new Date('2018-03-13T00:00:00Z'); * const tzDate = ZonedDate.fromLocalDate(date, 'America/New_York'); * * // If you run this example in GMT+0200, * // the output will be '2018-03-12T22:00:00.000Z'. * console.log(tzDate.toISOString()); * ``` * * The following example demonstrates how to convert between timezones. * * @example * ```ts-no-run * import { ZonedDate } from '@progress/kendo-date-math'; * import '@progress/kendo-date-math/tz/America/New_York'; * import '@progress/kendo-date-math/tz/America/Los_Angeles'; * * // Note the "Z" suffix for UTC dates. * const date = new Date('2018-03-12T22:00:00Z'); * * const tzDate = ZonedDate.fromLocalDate(date, 'America/New_York'); * const result = tzDate.toTimezone('America/Los_Angeles'); * * // Regardless of the browser timezone * // the output will be '2018-03-12T15:00:00.000Z'. * console.log(tzDate.toUTCDate()); * ``` */ export class ZonedDate { /** * Returns a cached local date that denotes the exact time in the set timezone. * * @return Date - A local date that denotes the exact time in the set timezone. * * This property is an alternative to `toLocalDate()` that returns a cached value instead of cloning it. * * > Modifying the returned instance will corrupt the `ZonedDate` state. */ get cachedLocalDate() { return this._localDate; } /** * Returns a cached `Date` instance with UTC date parts that are set to the local time in the set timezone. * * @returns Date - A `Date` with UTC date parts that are set to the local time in the set timezone. * * This property is an alternative to `toUTCDate()` that returns a cached value instead of cloning it. * * > Modifying the returned instance will corrupt the `ZonedDate` state. */ get cachedUTCDate() { return this._utcDate; } // tslint:disable:max-line-length /** * Converts an existing date to a specified timezone. * * If the `timezone` parameter is omitted, the `ZonedDate` defaults to the timezone of the browser. This concept is known as "floating date" because it does not represent a particular moment in time. Instead, its actual value depends on the current timezone of the browser. * * @param date - The local date that represents the actual time instance. * @param timezone - The ID of the timezone that will be assumed. For example, `Europe/Sofia`. * @return ZonedDate - The date in the specified timezone. * * @example * ```ts-no-run * import { ZonedDate } from '@progress/kendo-date-math'; * import '@progress/kendo-date-math/tz/America/New_York'; * * const date = new Date('2018-03-13T00:00:00'); * const tzDate = ZonedDate.fromLocalDate(date, 'America/New_York'); * * // If you run this example in GMT+0200, * // the output will be 'Mon Mar 12 2018 18:00:00 GMT+0200 (EET)'. * console.log(tzDate.toString()); * * // If you run this example in UTC, * // the output will be '2018-03-12T22:00:00.000Z'. * console.log(tzDate.toISOString()); * ``` */ // tslint:enable:max-line-length static fromLocalDate(date, timezone = '') { if (timezone === '') { const utcDate = toUTCDateTime(date); return ZonedDate.fromUTCDate(utcDate, timezone); } const utcDate = convertTimezoneUTC(date, 'Etc/UTC', timezone); const shiftZone = isZoneMissingHour(utcDate, timezone); const zoneOffset = offset(timezone, utcDate); let fixedOffset = 0; if (shiftZone) { // Adjust for the missing hour during the DST transition in the timezone. fixedOffset = zoneOffset > 0 ? -1 : 1; } const adjDate = addHours(utcDate, fixedOffset); return ZonedDate.fromUTCDate(adjDate, timezone); } // tslint:disable:max-line-length /** * Creates a date in a specific timezone from the UTC date parts of the supplied `Date`. * * If the `timezone` parameter is omitted, the `ZonedDate` defaults to the timezone of the browser. This concept is known as "floating date" because it does not represent a particular moment in time. Instead, its actual value depends on the current timezone of the browser. * * @param date - The UTC date that represents the time in the target zone. This time is not the actual time instant in UTC. * @param timezone - The ID of the timezone that will be assumed. For example, `Europe/Sofia`. * @return ZonedDate - The date in the specified timezone. * * @example * ```ts-no-run * import { ZonedDate } from '@progress/kendo-date-math'; * import '@progress/kendo-date-math/tz/America/New_York'; * * // Note the "Z" suffix for UTC dates. * const date = new Date('2018-03-12T18:00:00Z'); * * // Alternative syntax using Date.UTC * // const date = new Date(Date.UTC(2018, 2, 12, 18, 0)); * * const tzDate = ZonedDate.fromUTCDate(date, 'America/New_York'); * * // Regardless of the browser timezone * // the output will be 'Mon Mar 12 2018 18:00:00 GMT+0200 (EET)'. * console.log(tzDate.toString()); * * // Regardless of the browser timezone * // the output in UTC will be '2018-03-12T22:00:00.000Z'. * console.log(tzDate.toISOString()); * ``` */ // tslint:enable:max-line-length static fromUTCDate(utcDate, timezone = '') { return new ZonedDate(utcDate, timezone); } /** * Returns a local date that denotes the exact time in the set timezone. * * @return Date - A local date that denotes the exact time in the set timezone. * * @example * ```ts-no-run * import { ZonedDate } from '@progress/kendo-date-math'; * import '@progress/kendo-date-math/tz/America/New_York'; * * // Note the "Z" suffix for UTC dates. * const date = new Date('2018-03-12T18:00:00Z'); * const tzDate = ZonedDate.fromUTCDate(date, 'America/New_York'); * * // The local date represents the same moment in time as the ZonedDate: * // `2018-03-12T22:00:00.000Z`. * console.log(tzDate.toLocalDate().toISOString()); * * // The local date will apply the timezone of the browser. For example, * // `Tue Mar 13 2018 00:00:00 GMT+0200 (Eastern European Standard Time)`. * console.log(tzDate.toLocalDate().toString()) * ``` */ toLocalDate() { return cloneDate(this._localDate); } /** * Returns a `Date` instance with UTC date parts that are set to the local time in the set timezone. * * @returns Date - A `Date` with UTC date parts that are set to the local time in the set timezone. * * @example * ```ts-no-run * import { ZonedDate } from '@progress/kendo-date-math'; * import '@progress/kendo-date-math/tz/America/New_York'; * * // Note the "Z" suffix for UTC dates. * const date = new Date('2018-03-12T18:00:00Z'); * const tzDate = ZonedDate.fromUTCDate(date, 'America/New_York'); * * // Regardless of the browser timezone * // the output will be '2018-03-12T18:00:00.000Z'. * console.log(tzDate.toUTCDate()); * ``` */ toUTCDate() { return cloneDate(this._utcDate); } /** * Converts the date to the specified timezone. * * @param toTimezone - The timezone to which the values will be converted. For example, `America/Los_Angeles`. * @returns ZonedDate - The resulting zoned date. * * @example * ```ts-no-run * import { ZonedDate } from '@progress/kendo-date-math'; * import '@progress/kendo-date-math/tz/America/New_York'; * import '@progress/kendo-date-math/tz/America/Los_Angeles'; * * // Note the "Z" suffix for UTC dates. * const date = new Date('2018-03-12T22:00:00Z'); * * const tzDate = ZonedDate.fromLocalDate(date, 'America/New_York'); * const result = tzDate.toTimezone('America/Los_Angeles'); * * // Regardless of the browser timezone * // the output will be '2018-03-12T15:00:00.000Z'. * console.log(tzDate.toUTCDate()); * ``` */ toTimezone(toTimezone) { if (this.timezone === toTimezone) { return this.clone(); } const tzOffset = offset(this.timezone, this._utcDate); const date = addMinutes(this._utcDate, tzOffset); return ZonedDate.fromLocalDate(date, toTimezone); } /** * Returns a new instance that represents the same date. * * @returns Date - A copy of the instance of the current zoned date. */ clone() { return ZonedDate.fromUTCDate(this._utcDate, this.timezone); } // tslint:disable:max-line-length /** * Adds the specified number of days and returns a new instance with the resulting date in the same timezone. * * @param days - The number of days that will be added. * @returns ZonedDate - The resulting date. */ // tslint:enable:max-line-length addDays(days) { const newDate = new Date(this._utcDate.getTime()); newDate.setUTCDate(newDate.getUTCDate() + days); return ZonedDate.fromUTCDate(newDate, this.timezone); } // tslint:disable:max-line-length /** * Adds the specified number of milliseconds and returns a new instance with the resulting date in the same timezone. * * The method compensates for DST transitions and ensures that the resulting date occurs exactly after the set amount of time in the timezone. * * @param milliseconds - The number of days that will be added. * @returns ZonedDate - The resulting date. */ // tslint:enable:max-line-length addTime(milliseconds) { const utcDate = new Date(this._utcDate.getTime()); const utcMid = shiftZoneMissingHour(utcDate, this.timezone); utcMid.setTime(utcMid.getTime() + milliseconds); const utcResult = shiftZoneMissingHour(utcMid, this.timezone); return ZonedDate.fromUTCDate(utcResult, this.timezone); } // tslint:disable:max-line-length /** * Returns a new instance of the same zoned date having its time parts set to `00:00:00.000`. * * @returns ZonedDate - The same date having its time parts set to `00:00:00.000`. */ // tslint:enable:max-line-length stripTime() { const date = this._utcDate; const ticks = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0); return ZonedDate.fromUTCDate(new Date(ticks), this.timezone); } /** * @hidden */ getTime() { return this._localDate.getTime(); } /** * @hidden */ getTimezoneOffset() { return this.timezoneOffset; } /** * @hidden */ getFullYear() { return this._utcDate.getUTCFullYear(); } /** * @hidden */ getMonth() { return this._utcDate.getUTCMonth(); } /** * @hidden */ getDate() { return this._utcDate.getUTCDate(); } /** * @hidden */ getDay() { return this._utcDate.getUTCDay(); } /** * @hidden */ getHours() { return this._utcDate.getUTCHours(); } /** * @hidden */ getMinutes() { return this._utcDate.getUTCMinutes(); } /** * @hidden */ getSeconds() { return this._utcDate.getUTCSeconds(); } /** * @hidden */ getMilliseconds() { return this._utcDate.getUTCMilliseconds(); } // The local date UTC parts represent actual UTC time /** * @hidden */ getUTCDate() { return this._localDate.getUTCDate(); } /** * @hidden */ getUTCDay() { return this._localDate.getUTCDay(); } /** * @hidden */ getUTCFullYear() { return this._localDate.getUTCFullYear(); } /** * @hidden */ getUTCHours() { return this._localDate.getUTCHours(); } /** * @hidden */ getUTCMilliseconds() { return this._localDate.getUTCMilliseconds(); } /** * @hidden */ getUTCMinutes() { return this._localDate.getUTCMinutes(); } /** * @hidden */ getUTCMonth() { return this._localDate.getUTCMonth(); } /** @hidden */ getUTCSeconds() { return this._localDate.getUTCSeconds(); } /** @hidden */ setTime(time) { throw new Error("Method not implemented."); } /** @hidden */ setMilliseconds(ms) { throw new Error("Method not implemented."); } /** @hidden */ setUTCMilliseconds(ms) { throw new Error("Method not implemented."); } /** @hidden */ setSeconds(sec, ms) { throw new Error("Method not implemented."); } /** @hidden */ setUTCSeconds(sec, ms) { throw new Error("Method not implemented."); } /** @hidden */ setMinutes(min, sec, ms) { throw new Error("Method not implemented."); } /** @hidden */ setUTCMinutes(min, sec, ms) { throw new Error("Method not implemented."); } /** @hidden */ setHours(hours, min, sec, ms) { throw new Error("Method not implemented."); } /** @hidden */ setUTCHours(hours, min, sec, ms) { throw new Error("Method not implemented."); } /** @hidden */ setDate(date) { throw new Error("Method not implemented."); } /** @hidden */ setUTCDate(date) { throw new Error("Method not implemented."); } /** @hidden */ setMonth(month, date) { throw new Error("Method not implemented."); } /** @hidden */ setUTCMonth(month, date) { throw new Error("Method not implemented."); } /** @hidden */ setFullYear(year, month, date) { throw new Error("Method not implemented."); } /** @hidden */ setUTCFullYear(year, month, date) { throw new Error("Method not implemented."); } /** * @hidden */ toISOString() { return this._localDate.toISOString(); } /** * @hidden */ toJSON() { return this._localDate.toJSON(); } /** * @hidden */ toString() { const dateString = datePrefix(this._utcDate); const timeString = this.toTimeString(); return `${dateString} ${this.getDate()} ${this.getFullYear()} ${timeString}`; } /** @hidden */ toDateString() { return toLocalDate(this._utcDate).toDateString(); } /** @hidden */ toTimeString() { const hours = padNumber(this.getHours()); const minutes = padNumber(this.getMinutes()); const seconds = padNumber(this.getSeconds()); const time = `${hours}:${minutes}:${seconds}`; const tzOffset = formatOffset(this.timezoneOffset); let abbrev = abbrTimezone(this.timezone, this._utcDate); if (abbrev) { abbrev = ` (${abbrev})`; } return `${time} ${tzOffset}${abbrev}`; } toLocaleString(locales, options) { return this._localDate.toLocaleString(locales, options); } toLocaleDateString(locales, options) { return this._localDate.toLocaleDateString(locales, options); } toLocaleTimeString(locales, options) { return this._localDate.toLocaleTimeString(locales, options); } /** @hidden */ toUTCString() { return this.toTimezone('Etc/UTC').toString(); } /** * @returns - A primitive value. */ [Symbol.toPrimitive](hint) { if (hint === 'string' || hint === 'default') { return this.toString(); } return this._localDate.getTime(); } /** @hidden */ valueOf() { return this.getTime(); } /** @hidden */ getVarDate() { throw new Error('Not implemented.'); } /** @hidden */ format(_) { throw new Error('Not implemented.'); } /** @hidden */ formatUTC(_) { throw new Error('Not implemented.'); } // The constructor is aliased as a static fromUTCDate method // to clarify the meaning of the utcDate parameter. // // It can be confused for a local date time while it is in fact // treated as a UTC date that represents the local date in the timezone. constructor(utcDate, timezone) { this._utcDate = cloneDate(utcDate); this.timezone = timezone; if (timezone === '') { const localDate = toLocalDate(utcDate); this.timezoneOffset = localDate.getTimezoneOffset(); this._localDate = localDate; } else { const tzOffset = offset(timezone, utcDate); this.timezoneOffset = tzOffset; const localDate = shiftZoneMissingHour(utcDate, timezone); this._localDate = convertTimezoneUTC(localDate, timezone, 'Etc/UTC'); } } }