/* * 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 {AnyCalendarDate, AnyDateTime, AnyTime, CycleOptions, CycleTimeOptions, DateDuration, DateField, DateFields, DateTimeDuration, Disambiguation, TimeDuration, TimeField, TimeFields} from './types'; import {CalendarDate, CalendarDateTime, Time, ZonedDateTime} from './CalendarDate'; import {epochFromDate, fromAbsolute, toAbsolute, toCalendar, toCalendarDateTime} from './conversion'; import {GregorianCalendar} from './calendars/GregorianCalendar'; import {Mutable} from './utils'; const ONE_HOUR = 3600000; export function add(date: CalendarDateTime, duration: DateTimeDuration): CalendarDateTime; export function add(date: CalendarDate, duration: DateDuration): CalendarDate; export function add(date: CalendarDate | CalendarDateTime, duration: DateTimeDuration): CalendarDate | CalendarDateTime; export function add(date: CalendarDate | CalendarDateTime, duration: DateTimeDuration) { let mutableDate: Mutable = date.copy(); let days = 'hour' in mutableDate ? addTimeFields(mutableDate, duration) : 0; addYears(mutableDate, duration.years || 0); if (mutableDate.calendar.balanceYearMonth) { mutableDate.calendar.balanceYearMonth(mutableDate, date); } mutableDate.month += duration.months || 0; balanceYearMonth(mutableDate); constrainMonthDay(mutableDate); mutableDate.day += (duration.weeks || 0) * 7; mutableDate.day += duration.days || 0; mutableDate.day += days; balanceDay(mutableDate); if (mutableDate.calendar.balanceDate) { mutableDate.calendar.balanceDate(mutableDate); } // Constrain in case adding ended up with a date outside the valid range for the calendar system. // The behavior here is slightly different than when constraining in the `set` function in that // we adjust smaller fields to their minimum/maximum values rather than constraining each field // individually. This matches the general behavior of `add` vs `set` regarding how fields are balanced. if (mutableDate.year < 1) { mutableDate.year = 1; mutableDate.month = 1; mutableDate.day = 1; } let maxYear = mutableDate.calendar.getYearsInEra(mutableDate); if (mutableDate.year > maxYear) { let isInverseEra = mutableDate.calendar.isInverseEra?.(mutableDate); mutableDate.year = maxYear; mutableDate.month = isInverseEra ? 1 : mutableDate.calendar.getMonthsInYear(mutableDate); mutableDate.day = isInverseEra ? 1 : mutableDate.calendar.getDaysInMonth(mutableDate); } if (mutableDate.month < 1) { mutableDate.month = 1; mutableDate.day = 1; } let maxMonth = mutableDate.calendar.getMonthsInYear(mutableDate); if (mutableDate.month > maxMonth) { mutableDate.month = maxMonth; mutableDate.day = mutableDate.calendar.getDaysInMonth(mutableDate); } mutableDate.day = Math.max(1, Math.min(mutableDate.calendar.getDaysInMonth(mutableDate), mutableDate.day)); return mutableDate; } function addYears(date: Mutable, years: number) { if (date.calendar.isInverseEra?.(date)) { years = -years; } date.year += years; } function balanceYearMonth(date: Mutable) { while (date.month < 1) { addYears(date, -1); date.month += date.calendar.getMonthsInYear(date); } let monthsInYear = 0; while (date.month > (monthsInYear = date.calendar.getMonthsInYear(date))) { date.month -= monthsInYear; addYears(date, 1); } } function balanceDay(date: Mutable) { while (date.day < 1) { date.month--; balanceYearMonth(date); date.day += date.calendar.getDaysInMonth(date); } while (date.day > date.calendar.getDaysInMonth(date)) { date.day -= date.calendar.getDaysInMonth(date); date.month++; balanceYearMonth(date); } } function constrainMonthDay(date: Mutable) { date.month = Math.max(1, Math.min(date.calendar.getMonthsInYear(date), date.month)); date.day = Math.max(1, Math.min(date.calendar.getDaysInMonth(date), date.day)); } export function constrain(date: Mutable) { if (date.calendar.constrainDate) { date.calendar.constrainDate(date); } date.year = Math.max(1, Math.min(date.calendar.getYearsInEra(date), date.year)); constrainMonthDay(date); } export function invertDuration(duration: DateTimeDuration): DateTimeDuration { let inverseDuration = {}; for (let key in duration) { if (typeof duration[key] === 'number') { inverseDuration[key] = -duration[key]; } } return inverseDuration; } export function subtract(date: CalendarDateTime, duration: DateTimeDuration): CalendarDateTime; export function subtract(date: CalendarDate, duration: DateDuration): CalendarDate; export function subtract(date: CalendarDate | CalendarDateTime, duration: DateTimeDuration): CalendarDate | CalendarDateTime { return add(date, invertDuration(duration)); } export function set(date: CalendarDateTime, fields: DateFields): CalendarDateTime; export function set(date: CalendarDate, fields: DateFields): CalendarDate; export function set(date: CalendarDate | CalendarDateTime, fields: DateFields) { let mutableDate: Mutable = date.copy(); if (fields.era != null) { mutableDate.era = fields.era; } if (fields.year != null) { mutableDate.year = fields.year; } if (fields.month != null) { mutableDate.month = fields.month; } if (fields.day != null) { mutableDate.day = fields.day; } constrain(mutableDate); return mutableDate; } export function setTime(value: CalendarDateTime, fields: TimeFields): CalendarDateTime; export function setTime(value: Time, fields: TimeFields): Time; export function setTime(value: Time | CalendarDateTime, fields: TimeFields) { let mutableValue: Mutable