407 lines
16 KiB
TypeScript
407 lines
16 KiB
TypeScript
/*
|
|
* Copyright 2020 Adobe. All rights reserved.
|
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
* governing permissions and limitations under the License.
|
|
*/
|
|
|
|
import {add, addTime, addZoned, constrain, constrainTime, cycleDate, cycleTime, cycleZoned, set, setTime, setZoned, subtract, subtractTime, subtractZoned} from './manipulation';
|
|
import {AnyCalendarDate, AnyTime, Calendar, CycleOptions, CycleTimeOptions, DateDuration, DateField, DateFields, DateTimeDuration, Disambiguation, TimeDuration, TimeField, TimeFields} from './types';
|
|
import {compareDate, compareTime} from './queries';
|
|
import {dateTimeToString, dateToString, timeToString, zonedDateTimeToString} from './string';
|
|
import {GregorianCalendar} from './calendars/GregorianCalendar';
|
|
import {toCalendarDateTime, toDate, toZoned, zonedToDate} from './conversion';
|
|
|
|
function shiftArgs(args: any[]) {
|
|
let calendar: Calendar = typeof args[0] === 'object'
|
|
? args.shift()
|
|
: new GregorianCalendar();
|
|
|
|
let era: string;
|
|
if (typeof args[0] === 'string') {
|
|
era = args.shift();
|
|
} else {
|
|
let eras = calendar.getEras();
|
|
era = eras[eras.length - 1];
|
|
}
|
|
|
|
let year = args.shift();
|
|
let month = args.shift();
|
|
let day = args.shift();
|
|
|
|
return [calendar, era, year, month, day];
|
|
}
|
|
|
|
/** A CalendarDate represents a date without any time components in a specific calendar system. */
|
|
export class CalendarDate {
|
|
// This prevents TypeScript from allowing other types with the same fields to match.
|
|
// i.e. a ZonedDateTime should not be be passable to a parameter that expects CalendarDate.
|
|
// If that behavior is desired, use the AnyCalendarDate interface instead.
|
|
// @ts-ignore
|
|
#type;
|
|
/** The calendar system associated with this date, e.g. Gregorian. */
|
|
public readonly calendar: Calendar;
|
|
/** The calendar era for this date, e.g. "BC" or "AD". */
|
|
public readonly era: string;
|
|
/** The year of this date within the era. */
|
|
public readonly year: number;
|
|
/**
|
|
* The month number within the year. Note that some calendar systems such as Hebrew
|
|
* may have a variable number of months per year. Therefore, month numbers may not
|
|
* always correspond to the same month names in different years.
|
|
*/
|
|
public readonly month: number;
|
|
/** The day number within the month. */
|
|
public readonly day: number;
|
|
|
|
constructor(year: number, month: number, day: number);
|
|
constructor(era: string, year: number, month: number, day: number);
|
|
constructor(calendar: Calendar, year: number, month: number, day: number);
|
|
constructor(calendar: Calendar, era: string, year: number, month: number, day: number);
|
|
constructor(...args: any[]) {
|
|
let [calendar, era, year, month, day] = shiftArgs(args);
|
|
this.calendar = calendar;
|
|
this.era = era;
|
|
this.year = year;
|
|
this.month = month;
|
|
this.day = day;
|
|
|
|
constrain(this);
|
|
}
|
|
|
|
/** Returns a copy of this date. */
|
|
copy(): CalendarDate {
|
|
if (this.era) {
|
|
return new CalendarDate(this.calendar, this.era, this.year, this.month, this.day);
|
|
} else {
|
|
return new CalendarDate(this.calendar, this.year, this.month, this.day);
|
|
}
|
|
}
|
|
|
|
/** Returns a new `CalendarDate` with the given duration added to it. */
|
|
add(duration: DateDuration): CalendarDate {
|
|
return add(this, duration);
|
|
}
|
|
|
|
/** Returns a new `CalendarDate` with the given duration subtracted from it. */
|
|
subtract(duration: DateDuration): CalendarDate {
|
|
return subtract(this, duration);
|
|
}
|
|
|
|
/** Returns a new `CalendarDate` with the given fields set to the provided values. Other fields will be constrained accordingly. */
|
|
set(fields: DateFields): CalendarDate {
|
|
return set(this, fields);
|
|
}
|
|
|
|
/**
|
|
* Returns a new `CalendarDate` with the given field adjusted by a specified amount.
|
|
* When the resulting value reaches the limits of the field, it wraps around.
|
|
*/
|
|
cycle(field: DateField, amount: number, options?: CycleOptions): CalendarDate {
|
|
return cycleDate(this, field, amount, options);
|
|
}
|
|
|
|
/** Converts the date to a native JavaScript Date object, with the time set to midnight in the given time zone. */
|
|
toDate(timeZone: string): Date {
|
|
return toDate(this, timeZone);
|
|
}
|
|
|
|
/** Converts the date to an ISO 8601 formatted string. */
|
|
toString(): string {
|
|
return dateToString(this);
|
|
}
|
|
|
|
/** Compares this date with another. A negative result indicates that this date is before the given one, and a positive date indicates that it is after. */
|
|
compare(b: AnyCalendarDate): number {
|
|
return compareDate(this, b);
|
|
}
|
|
}
|
|
|
|
/** A Time represents a clock time without any date components. */
|
|
export class Time {
|
|
// This prevents TypeScript from allowing other types with the same fields to match.
|
|
// @ts-ignore
|
|
#type;
|
|
/** The hour, numbered from 0 to 23. */
|
|
public readonly hour: number;
|
|
/** The minute in the hour. */
|
|
public readonly minute: number;
|
|
/** The second in the minute. */
|
|
public readonly second: number;
|
|
/** The millisecond in the second. */
|
|
public readonly millisecond: number;
|
|
|
|
constructor(
|
|
hour: number = 0,
|
|
minute: number = 0,
|
|
second: number = 0,
|
|
millisecond: number = 0
|
|
) {
|
|
this.hour = hour;
|
|
this.minute = minute;
|
|
this.second = second;
|
|
this.millisecond = millisecond;
|
|
constrainTime(this);
|
|
}
|
|
|
|
/** Returns a copy of this time. */
|
|
copy(): Time {
|
|
return new Time(this.hour, this.minute, this.second, this.millisecond);
|
|
}
|
|
|
|
/** Returns a new `Time` with the given duration added to it. */
|
|
add(duration: TimeDuration): Time {
|
|
return addTime(this, duration);
|
|
}
|
|
|
|
/** Returns a new `Time` with the given duration subtracted from it. */
|
|
subtract(duration: TimeDuration): Time {
|
|
return subtractTime(this, duration);
|
|
}
|
|
|
|
/** Returns a new `Time` with the given fields set to the provided values. Other fields will be constrained accordingly. */
|
|
set(fields: TimeFields): Time {
|
|
return setTime(this, fields);
|
|
}
|
|
|
|
/**
|
|
* Returns a new `Time` with the given field adjusted by a specified amount.
|
|
* When the resulting value reaches the limits of the field, it wraps around.
|
|
*/
|
|
cycle(field: TimeField, amount: number, options?: CycleTimeOptions): Time {
|
|
return cycleTime(this, field, amount, options);
|
|
}
|
|
|
|
/** Converts the time to an ISO 8601 formatted string. */
|
|
toString(): string {
|
|
return timeToString(this);
|
|
}
|
|
|
|
/** Compares this time with another. A negative result indicates that this time is before the given one, and a positive time indicates that it is after. */
|
|
compare(b: AnyTime): number {
|
|
return compareTime(this, b);
|
|
}
|
|
}
|
|
|
|
/** A CalendarDateTime represents a date and time without a time zone, in a specific calendar system. */
|
|
export class CalendarDateTime {
|
|
// This prevents TypeScript from allowing other types with the same fields to match.
|
|
// @ts-ignore
|
|
#type;
|
|
/** The calendar system associated with this date, e.g. Gregorian. */
|
|
public readonly calendar: Calendar;
|
|
/** The calendar era for this date, e.g. "BC" or "AD". */
|
|
public readonly era: string;
|
|
/** The year of this date within the era. */
|
|
public readonly year: number;
|
|
/**
|
|
* The month number within the year. Note that some calendar systems such as Hebrew
|
|
* may have a variable number of months per year. Therefore, month numbers may not
|
|
* always correspond to the same month names in different years.
|
|
*/
|
|
public readonly month: number;
|
|
/** The day number within the month. */
|
|
public readonly day: number;
|
|
/** The hour in the day, numbered from 0 to 23. */
|
|
public readonly hour: number;
|
|
/** The minute in the hour. */
|
|
public readonly minute: number;
|
|
/** The second in the minute. */
|
|
public readonly second: number;
|
|
/** The millisecond in the second. */
|
|
public readonly millisecond: number;
|
|
|
|
constructor(year: number, month: number, day: number, hour?: number, minute?: number, second?: number, millisecond?: number);
|
|
constructor(era: string, year: number, month: number, day: number, hour?: number, minute?: number, second?: number, millisecond?: number);
|
|
constructor(calendar: Calendar, year: number, month: number, day: number, hour?: number, minute?: number, second?: number, millisecond?: number);
|
|
constructor(calendar: Calendar, era: string, year: number, month: number, day: number, hour?: number, minute?: number, second?: number, millisecond?: number);
|
|
constructor(...args: any[]) {
|
|
let [calendar, era, year, month, day] = shiftArgs(args);
|
|
this.calendar = calendar;
|
|
this.era = era;
|
|
this.year = year;
|
|
this.month = month;
|
|
this.day = day;
|
|
this.hour = args.shift() || 0;
|
|
this.minute = args.shift() || 0;
|
|
this.second = args.shift() || 0;
|
|
this.millisecond = args.shift() || 0;
|
|
|
|
constrain(this);
|
|
}
|
|
|
|
/** Returns a copy of this date. */
|
|
copy(): CalendarDateTime {
|
|
if (this.era) {
|
|
return new CalendarDateTime(this.calendar, this.era, this.year, this.month, this.day, this.hour, this.minute, this.second, this.millisecond);
|
|
} else {
|
|
return new CalendarDateTime(this.calendar, this.year, this.month, this.day, this.hour, this.minute, this.second, this.millisecond);
|
|
}
|
|
}
|
|
|
|
/** Returns a new `CalendarDateTime` with the given duration added to it. */
|
|
add(duration: DateTimeDuration): CalendarDateTime {
|
|
return add(this, duration);
|
|
}
|
|
|
|
/** Returns a new `CalendarDateTime` with the given duration subtracted from it. */
|
|
subtract(duration: DateTimeDuration): CalendarDateTime {
|
|
return subtract(this, duration);
|
|
}
|
|
|
|
/** Returns a new `CalendarDateTime` with the given fields set to the provided values. Other fields will be constrained accordingly. */
|
|
set(fields: DateFields & TimeFields): CalendarDateTime {
|
|
return set(setTime(this, fields), fields);
|
|
}
|
|
|
|
/**
|
|
* Returns a new `CalendarDateTime` with the given field adjusted by a specified amount.
|
|
* When the resulting value reaches the limits of the field, it wraps around.
|
|
*/
|
|
cycle(field: DateField | TimeField, amount: number, options?: CycleTimeOptions): CalendarDateTime {
|
|
switch (field) {
|
|
case 'era':
|
|
case 'year':
|
|
case 'month':
|
|
case 'day':
|
|
return cycleDate(this, field, amount, options);
|
|
default:
|
|
return cycleTime(this, field, amount, options);
|
|
}
|
|
}
|
|
|
|
/** Converts the date to a native JavaScript Date object in the given time zone. */
|
|
toDate(timeZone: string, disambiguation?: Disambiguation): Date {
|
|
return toDate(this, timeZone, disambiguation);
|
|
}
|
|
|
|
/** Converts the date to an ISO 8601 formatted string. */
|
|
toString(): string {
|
|
return dateTimeToString(this);
|
|
}
|
|
|
|
/** Compares this date with another. A negative result indicates that this date is before the given one, and a positive date indicates that it is after. */
|
|
compare(b: CalendarDate | CalendarDateTime | ZonedDateTime): number {
|
|
let res = compareDate(this, b);
|
|
if (res === 0) {
|
|
return compareTime(this, toCalendarDateTime(b));
|
|
}
|
|
|
|
return res;
|
|
}
|
|
}
|
|
|
|
/** A ZonedDateTime represents a date and time in a specific time zone and calendar system. */
|
|
export class ZonedDateTime {
|
|
// This prevents TypeScript from allowing other types with the same fields to match.
|
|
// @ts-ignore
|
|
#type;
|
|
/** The calendar system associated with this date, e.g. Gregorian. */
|
|
public readonly calendar: Calendar;
|
|
/** The calendar era for this date, e.g. "BC" or "AD". */
|
|
public readonly era: string;
|
|
/** The year of this date within the era. */
|
|
public readonly year: number;
|
|
/**
|
|
* The month number within the year. Note that some calendar systems such as Hebrew
|
|
* may have a variable number of months per year. Therefore, month numbers may not
|
|
* always correspond to the same month names in different years.
|
|
*/
|
|
public readonly month: number;
|
|
/** The day number within the month. */
|
|
public readonly day: number;
|
|
/** The hour in the day, numbered from 0 to 23. */
|
|
public readonly hour: number;
|
|
/** The minute in the hour. */
|
|
public readonly minute: number;
|
|
/** The second in the minute. */
|
|
public readonly second: number;
|
|
/** The millisecond in the second. */
|
|
public readonly millisecond: number;
|
|
/** The IANA time zone identifier that this date and time is represented in. */
|
|
public readonly timeZone: string;
|
|
/** The UTC offset for this time, in milliseconds. */
|
|
public readonly offset: number;
|
|
|
|
constructor(year: number, month: number, day: number, timeZone: string, offset: number, hour?: number, minute?: number, second?: number, millisecond?: number);
|
|
constructor(era: string, year: number, month: number, day: number, timeZone: string, offset: number, hour?: number, minute?: number, second?: number, millisecond?: number);
|
|
constructor(calendar: Calendar, year: number, month: number, day: number, timeZone: string, offset: number, hour?: number, minute?: number, second?: number, millisecond?: number);
|
|
constructor(calendar: Calendar, era: string, year: number, month: number, day: number, timeZone: string, offset: number, hour?: number, minute?: number, second?: number, millisecond?: number);
|
|
constructor(...args: any[]) {
|
|
let [calendar, era, year, month, day] = shiftArgs(args);
|
|
let timeZone = args.shift();
|
|
let offset = args.shift();
|
|
this.calendar = calendar;
|
|
this.era = era;
|
|
this.year = year;
|
|
this.month = month;
|
|
this.day = day;
|
|
this.timeZone = timeZone;
|
|
this.offset = offset;
|
|
this.hour = args.shift() || 0;
|
|
this.minute = args.shift() || 0;
|
|
this.second = args.shift() || 0;
|
|
this.millisecond = args.shift() || 0;
|
|
|
|
constrain(this);
|
|
}
|
|
|
|
/** Returns a copy of this date. */
|
|
copy(): ZonedDateTime {
|
|
if (this.era) {
|
|
return new ZonedDateTime(this.calendar, this.era, this.year, this.month, this.day, this.timeZone, this.offset, this.hour, this.minute, this.second, this.millisecond);
|
|
} else {
|
|
return new ZonedDateTime(this.calendar, this.year, this.month, this.day, this.timeZone, this.offset, this.hour, this.minute, this.second, this.millisecond);
|
|
}
|
|
}
|
|
|
|
/** Returns a new `ZonedDateTime` with the given duration added to it. */
|
|
add(duration: DateTimeDuration): ZonedDateTime {
|
|
return addZoned(this, duration);
|
|
}
|
|
|
|
/** Returns a new `ZonedDateTime` with the given duration subtracted from it. */
|
|
subtract(duration: DateTimeDuration): ZonedDateTime {
|
|
return subtractZoned(this, duration);
|
|
}
|
|
|
|
/** Returns a new `ZonedDateTime` with the given fields set to the provided values. Other fields will be constrained accordingly. */
|
|
set(fields: DateFields & TimeFields, disambiguation?: Disambiguation): ZonedDateTime {
|
|
return setZoned(this, fields, disambiguation);
|
|
}
|
|
|
|
/**
|
|
* Returns a new `ZonedDateTime` with the given field adjusted by a specified amount.
|
|
* When the resulting value reaches the limits of the field, it wraps around.
|
|
*/
|
|
cycle(field: DateField | TimeField, amount: number, options?: CycleTimeOptions): ZonedDateTime {
|
|
return cycleZoned(this, field, amount, options);
|
|
}
|
|
|
|
/** Converts the date to a native JavaScript Date object. */
|
|
toDate(): Date {
|
|
return zonedToDate(this);
|
|
}
|
|
|
|
/** Converts the date to an ISO 8601 formatted string, including the UTC offset and time zone identifier. */
|
|
toString(): string {
|
|
return zonedDateTimeToString(this);
|
|
}
|
|
|
|
/** Converts the date to an ISO 8601 formatted string in UTC. */
|
|
toAbsoluteString(): string {
|
|
return this.toDate().toISOString();
|
|
}
|
|
|
|
/** Compares this date with another. A negative result indicates that this date is before the given one, and a positive date indicates that it is after. */
|
|
compare(b: CalendarDate | CalendarDateTime | ZonedDateTime): number {
|
|
// TODO: Is this a bad idea??
|
|
return this.toDate().getTime() - toZoned(b, this.timeZone).toDate().getTime();
|
|
}
|
|
}
|