UNPKG

@tubular/astronomy

Version:

Astronomical calculations for planetary positions, moon phases, eclipses, rise, transit, and set times, and more.

1 lines 1.63 MB
{"version":3,"file":"index.min.cjs","sources":["../node_modules/@tubular/time/src/common.ts","../node_modules/@tubular/time/src/calendar.ts","../node_modules/@tubular/time/src/locale-data.ts","../node_modules/@tubular/time/src/timezone.ts","../node_modules/@tubular/time/src/ut-converter.ts","../node_modules/@tubular/time/src/date-time.ts","../node_modules/@tubular/time/src/format-parse.ts","../node_modules/@tubular/time/src/timezone-small.ts","../node_modules/@tubular/time/src/timezone-large.ts","../node_modules/@tubular/time/src/timezone-large-alt.ts","../node_modules/@tubular/time/src/index.ts","../src/astro-constants.ts","../src/ecliptic.ts","../src/additional-orbiting-objects.ts","../src/astronomy-util.ts","../src/meeus-moon.ts","../src/pluto.ts","../src/vsop87-planets.ts","../src/solar-system.ts","../src/planetary-moons.ts","../src/jupiter-moons.ts","../src/event-finder.ts","../node_modules/@tubular/array-buffer-reader/src/array-buffer-reader.ts","../src/jupiter-info.ts","../src/saturn-moons.ts","../src/sky-observer.ts","../src/star-catalog.ts","../src/index.ts"],"sourcesContent":["import { floor } from '@tubular/math';\nimport { convertDigitsToAscii, isNumber, toNumber } from '@tubular/util';\n\nexport const MIN_YEAR = -271820;\nexport const MAX_YEAR = 275759;\n\nexport const MINUTE_MSEC = 60_000;\nexport const HOUR_MSEC = 3_600_000;\nexport const DAY_MSEC = 86_400_000;\nexport const DAY_SEC = 86_400;\nexport const DAY_MINUTES = 1440;\n\nexport const UNIX_TIME_ZERO_AS_JULIAN_DAY = 2440587.5;\nexport const JD_J2000 = 2451545.0; // Julian date for the J2000.0 epoch.\nexport const DELTA_TDT_SEC = 32.184;\nexport const DELTA_TDT_MSEC = 32184;\nexport const DELTA_TDT_DAYS = DELTA_TDT_SEC / DAY_SEC;\nexport const DELTA_MJD = 2400000.5;\n\nexport const enEras = ['BC', 'AD', 'Before Christ', 'Anno Domini'];\nexport const enMonths = ['January', 'February', 'March', 'April', 'May', 'June',\n 'July', 'August', 'September', 'October', 'November', 'December'];\nexport const enMonthsShort = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];\nexport const enWeekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];\nexport const enWeekdaysShort = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];\nexport const enWeekdaysMin = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];\n\n// Hacks to eliminate circular dependencies.\ntype Formatter = (dt: any, fmt: string, localeOverride?: string | string[]) => string;\nexport let formatter: Formatter;\nexport const setFormatter = (fmt: Formatter): any => formatter = fmt;\n\ntype DeltaTUpdater = (post2019values?: number[], lastKnownLeapSecond?: YMDDate) => void;\nexport let deltaTUpdater: DeltaTUpdater;\nexport const setDeltaTUpdater = (dtu: DeltaTUpdater): any => deltaTUpdater = dtu;\n\n/**\n * Specifies a calendar date by year, month, and day. Optionally provides day number and boolean flag indicating Julian\n * or Gregorian.\n */\nexport interface YMDDate {\n /** Year as signed integer (0 = 1 BCE, -1 = 2 BCE, etc.). */\n y?: number;\n year?: number;\n /** Quarter as 1-4. */\n q?: number;\n quarter?: number;\n /** Month as 1-12. */\n m?: number;\n month?: number;\n /** Day of month. */\n d?: number;\n day?: number;\n /** Day of week as 0-6 for Sunday-Saturday. */\n dow?: number;\n dayOfWeek?: number;\n /** Day of week month index, 1-5, e.g. 2 for 2nd Tuesday of the month. */\n dowmi?: number;\n dayOfWeekMonthIndex?: number;\n /** Day of year. */\n dy?: number;\n dayOfYear?: number;\n /** Day number where 1970-01-01 = 0. */\n n?: number;\n epochDay?: number;\n /** true if this is a Julian calendar date, false for Gregorian. */\n j?: boolean;\n isJulian?: boolean;\n /** ISO year for week of year. */\n yw?: number;\n yearByWeek?: number;\n /** ISO week of year. */\n w?: number;\n week?: number;\n /** ISO day or week. */\n dw?: number;\n dayByWeek?: number;\n /** Locale year for week of year. */\n ywl?: number;\n yearByWeekLocale?: number;\n /** Locale week of year. */\n wl?: number;\n weekLocale?: number;\n /** Locale day or week. */\n dwl?: number;\n dayByWeekLocale?: number;\n /** Error, if any. */\n error?: string;\n}\n\nexport interface DateAndTime extends YMDDate {\n hrs?: number;\n hour?: number;\n min?: number;\n minute?: number;\n sec?: number;\n second?: number;\n millis?: number;\n utcOffset?: number;\n dstOffset?: number;\n occurrence?: number;\n deltaTai?: number;\n\n /** Julian days, ephemeris. */\n jde?: number;\n /** Modified Julian days, ephemeris. */\n mjde?: number;\n /** Julian days, UT. */\n jdu?: number;\n /** Modified Julian days, UT. */\n mjdu?: number;\n}\n\nconst altFields = [\n ['y', 'year'], ['q', 'quarter'], ['m', 'month'], ['d', 'day'], ['dow', 'dayOfWeek'], ['dowmi', 'dayOfWeekMonthIndex'],\n ['dy', 'dayOfYear'], ['n', 'epochDay'],\n ['j', 'isJulian'], ['yw', 'yearByWeek'], ['w', 'week'], ['dw', 'dayByWeek'],\n ['ywl', 'yearByWeekLocale'], ['wl', 'weekLocale'], ['dwl', 'dayByWeekLocale'],\n ['hrs', 'hour'], ['min', 'minute'], ['sec', 'second']\n];\n\nconst fieldOrder = [\n 'y', 'q', 'm', 'd', 'dow', 'dowmi', 'dy', 'n', 'j',\n 'year', 'quarter', 'month', 'day', 'dayOfWeek', 'dayOfWeekMonthIndex', 'dayOfYear', 'epochDay', 'isJulian',\n 'yw', 'w', 'dw',\n 'yearByWeek', 'week', 'dayByWeek',\n 'ywl', 'wl', 'dwl',\n 'yearByWeekLocale', 'weekLocale', 'dayByWeekLocale',\n 'hrs', 'min', 'sec',\n 'hour', 'minute', 'second', 'millis',\n 'utcOffset', 'dstOffset', 'occurrence', 'deltaTai',\n 'jde', 'mjde', 'jdu', 'mjdu',\n 'error'\n];\n\nexport function syncDateAndTime<T extends YMDDate | DateAndTime>(obj: T): T {\n for (const [key1, key2] of altFields) {\n if (obj.hasOwnProperty(key1))\n obj[key2] = obj[key1];\n else if (obj.hasOwnProperty(key2))\n obj[key1] = obj[key2];\n }\n\n return obj;\n}\n\nexport function purgeAliasFields<T extends YMDDate | DateAndTime>(obj: T, keepLongForm = false): T {\n for (const [short, long] of altFields)\n delete obj[keepLongForm ? short : long];\n\n return obj;\n}\n\nconst minimalKeys =\n new Set(['y', 'year', 'm', 'month', 'd', 'day', 'hrs', 'hour', 'min', 'minute', 'sec', 'second', 'millis']);\n\nexport function minimizeFields<T extends YMDDate | DateAndTime>(obj: T): T {\n Object.keys(obj).forEach(key => {\n if (!minimalKeys.has(key))\n delete obj[key];\n });\n\n return obj;\n}\n\nexport function orderFields<T extends YMDDate | DateAndTime>(obj: T): T {\n for (const key of fieldOrder) {\n const value = obj[key];\n\n delete obj[key];\n\n if (value != null)\n obj[key] = value;\n }\n\n return obj;\n}\n\nexport function validateDateAndTime(obj: YMDDate | DateAndTime): void {\n const dt = obj as DateAndTime;\n\n Object.keys(obj).forEach(key => {\n if (key !== 'j' && key !== 'isJulian') {\n const value = obj[key];\n\n if (value != null) {\n if (/^(m?(deltaTai|jde|jdu))$/.test(key)) {\n if (!isNumber(value))\n throw new Error(`${key} must be a numeric value (${value})`);\n }\n else if (!isNumber(value) || value !== floor(value))\n throw new Error(`${key} must be an integer value (${value})`);\n }\n }\n });\n\n if (obj.y == null && obj.year == null && obj.yw == null && obj.yearByWeek == null &&\n obj.ywl == null && obj.yearByWeekLocale == null && obj.n == null && obj.epochDay == null &&\n dt.hrs == null && dt.hour == null && dt.jde == null && dt.mjde == null && dt.jdu == null && dt.mjdu == null)\n throw new Error('A year value, an epoch day, an hour value, or a Julian date value must be specified');\n}\n\nconst invalidDateTime = new Error('Invalid ISO date/time');\n\nexport function parseISODateTime(date: string, allowLeapSecond = false): DateAndTime {\n date = date.trim();\n\n let time: DateAndTime;\n let $ = /^([-+]?\\d+)-(\\d{1,2}(?=\\D|$))(?:-(\\d{1,2}))?/.exec(date);\n\n if ($ || ($ = /^([-+]?\\d{1,5}(?=[^-+:.Ww\\d]|$))/.exec(date)) || ($ = /^([-+]?\\d{4,})(\\d\\d)(\\d\\d)/.exec(date)))\n time = { y: toNumber($[1]), m: Number($[2] ?? 1), d: Number($[3] ?? 1) };\n else if (($ = /^([-+]?\\d+)-(W)(\\d+)(?:-(\\d))?/i.exec(date)) || ($ = /^([-+]?\\d{4,})(W)(\\d\\d)(\\d)?/i.exec(date))) {\n if ($[2] === 'W')\n time = { yw: toNumber($[1]), w: Number($[3]), dw: Number($[4] ?? 1) };\n else\n time = { ywl: toNumber($[1]), wl: Number($[3]), dwl: Number($[4] ?? 1) };\n }\n else if (($ = /^(\\d+)-(\\d+)/.exec(date)) || ($ = /^(\\d{4})(\\d{3})/.exec(date))) {\n time = { y: toNumber($[1]), dy: Number($[2]) };\n }\n else {\n $ = [''] as RegExpExecArray; // Keep trying to parse as time-only string\n time = {};\n }\n\n date = date.substr($[0].length).trim().replace(/^T\\s*/i, '');\n\n if (!date)\n Object.assign(time, { hrs: 0, min: 0, sec: 0 });\n else if (($ = /^(\\d{1,2})(?::(\\d{1,2}))(?::(?:(\\d{1,2})(?:[.,](\\d+))?))?(?=\\D|$)/.exec(date)) ||\n ($ = /^(\\d\\d)(?:(\\d\\d)(?:(\\d\\d)(?:[.,](\\d+))?)?)?(?=\\D|$)/.exec(date))) {\n Object.assign(time, {\n hrs: Number($[1]), min: Number($[2] ?? 0),\n sec: Number($[3] ?? 0), millis: Number(($[4] ?? '0').padEnd(3, '0').substr(0, 3)) });\n\n if ($[4] == null && time.millis === 0)\n delete time.millis;\n\n date = date.substr($[0].length).trim();\n }\n\n $ = /^([-+]\\d\\d(\\d{4}|\\d\\d|:\\d\\d(:\\d\\d)?)?)$/i.exec(date);\n\n if ($)\n time.utcOffset = parseTimeOffset($[1]);\n else if (date)\n throw invalidDateTime;\n\n const y = time.y ?? time.yw ?? time.ywl ?? 0;\n const m = time.m ?? 1;\n const w = time.w ?? time.wl ?? 1;\n const d = time.d ?? 1;\n\n if (y < MIN_YEAR || y > MAX_YEAR)\n throw new Error(`Invalid year: ${y}`);\n else if (m > 13)\n throw new Error(`Invalid month: ${m}`);\n else if (w > 53)\n throw new Error(`Invalid week: ${w}`);\n else if (d > 32)\n throw new Error(`Invalid day of month: ${d}`);\n else if (time.hrs > 23)\n throw new Error(`Invalid hour: ${time.hrs}`);\n else if (time.min > 59)\n throw new Error(`Invalid minute: ${time.min}`);\n else if (time.sec > 59 + +allowLeapSecond)\n throw new Error(`Invalid second: ${time.sec}`);\n else if (time.utcOffset && (time.utcOffset < -57600 || time.utcOffset > 57600))\n throw new Error(`Invalid UTC offset: ${$[1]}`);\n\n if (time.m != null)\n time.q = floor((time.m - 1) / 3) + 1;\n\n return syncDateAndTime(time);\n}\n\nexport function parseTimeOffset(offset: string, roundToMinutes = false): number {\n let sign = 1;\n\n if (offset.startsWith('-')) {\n sign = -1;\n offset = offset.substr(1);\n }\n else if (offset.startsWith('+'))\n offset = offset.substr(1);\n\n const parts = offset.includes(':') ?\n offset.split(':') :\n offset.match(/../g) ?? ['0'];\n let offsetSeconds = 60 * (60 * Number(parts[0]) + Number(parts[1] ?? 0));\n\n if (parts[2]) {\n const seconds = Number(parts[2]);\n\n if (roundToMinutes)\n offsetSeconds += (seconds < 30 ? 0 : 60);\n else\n offsetSeconds += seconds;\n }\n\n return sign * offsetSeconds;\n}\n\nexport function getDatePart(format: Intl.DateTimeFormat, date: number, partName: string): string;\nexport function getDatePart(fields: Intl.DateTimeFormatPart[], partName: string): string;\nexport function getDatePart(source: Intl.DateTimeFormat | Intl.DateTimeFormatPart[],\n dateOrPart: number | string, partName?: string): string {\n const parts = (source instanceof Intl.DateTimeFormat ? source.formatToParts(dateOrPart as number) : source);\n partName = partName ?? dateOrPart as string;\n const part = parts.find(part => part.type === partName);\n\n if (part)\n return part.value;\n else\n return '???';\n}\n\nexport function getDateValue(format: Intl.DateTimeFormat, date: number, partName: string): number;\nexport function getDateValue(fields: Intl.DateTimeFormatPart[], partName: string): number;\nexport function getDateValue(source: Intl.DateTimeFormat | Intl.DateTimeFormatPart[],\n dateOrPart: number | string, partName?: string): number {\n return toNumber(convertDigitsToAscii(getDatePart(source as any, dateOrPart as any, partName)));\n}\n","import { div_rd, div_tt0, floor, mod } from '@tubular/math';\nimport { isArray, isNumber, isObject, isString, padLeft } from '@tubular/util';\nimport { DateAndTime, DAY_MSEC, HOUR_MSEC, MINUTE_MSEC, syncDateAndTime, YMDDate } from './common';\n\nexport enum CalendarType { PURE_GREGORIAN, PURE_JULIAN }\nexport const GREGORIAN_CHANGE_MIN_YEAR = 300;\nexport const GREGORIAN_CHANGE_MAX_YEAR = 3900;\n\nexport const SUNDAY = 0;\nexport const MONDAY = 1;\nexport const TUESDAY = 2;\nexport const WEDNESDAY = 3;\nexport const THURSDAY = 4;\nexport const FRIDAY = 5;\nexport const SATURDAY = 6;\n\n// noinspection JSUnusedGlobalSymbols\nexport enum DayOfWeek { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }\n// noinspection JSUnusedGlobalSymbols\nexport enum Month { JANUARY = 1, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER }\n\n/**\n * Constant for indicating the last occurrence of a particular day of the week (e.g. the last Tuesday) of a given month.\n */\nexport const LAST = 6;\n\n/** @hidden */\nconst DISTANT_YEAR_PAST = -9999999;\n/** @hidden */\nconst DISTANT_YEAR_FUTURE = 9999999;\nconst FIRST_GREGORIAN_DAY_SGC = -141427; // 1582-10-15\n\n/**\n * Type allowing a year alone to be specified, a full date as a [[YMDDate]], or a full date as a numeric array in the\n * form [year, month, date].\n */\nexport type YearOrDate = number | YMDDate | number[];\n/**\n * Type for specifying the date when a calendar switches from Julian to Gregorian, or if the calendar is purely Julian\n * or purely Gregorian. As a string, the letters 'J' or 'G' can be used.\n */\nexport type GregorianChange = YMDDate | CalendarType | string | number[];\n\nfunction hasYearField(obj: any): boolean {\n return obj.y != null || obj.yw != null || obj.ywl != null ||\n obj.year != null || obj.yearByWeek != null || obj.yearByWeekLocale != null;\n}\n\n/** @hidden */\nexport function isGregorianType(obj: any): obj is GregorianChange {\n return isNumber(obj) ||\n (isArray(obj) && obj.length === 3 && obj.findIndex(n => !isNumber(n)) < 0) ||\n (isString(obj) && /^(g|j|(\\d+)-(\\d+)-(\\d+)|\\d{8})$/i.test(obj)) ||\n (isObject(obj) && hasYearField(obj));\n}\n\nconst lockError = new Error('This DateTime instance is locked and immutable');\n\n/** @hidden */\nexport function handleVariableDateArgs(yearOrDate: YearOrDate, month?: number, day?: number,\n calendar?: Calendar | 'g' | 'j', ignoreJ = false): number[] {\n let n: number;\n let j: boolean;\n let year: number;\n let dy: number;\n\n if (isNumber(yearOrDate))\n year = yearOrDate;\n else if (isArray(yearOrDate) && yearOrDate.length >= 3 && isNumber(yearOrDate[0]))\n return yearOrDate;\n else if (isArray(yearOrDate)) {\n year = yearOrDate[0];\n month = yearOrDate[1];\n }\n else if (isObject(yearOrDate)) {\n syncDateAndTime(yearOrDate);\n n = yearOrDate.n;\n j = ignoreJ ? undefined : yearOrDate.j;\n year = yearOrDate.y;\n dy = yearOrDate.dy;\n month = yearOrDate.m;\n day = yearOrDate.d;\n }\n\n if (year != null) {\n if (month == null && day == null && dy != null) {\n if (calendar === 'g' || j === false)\n return handleVariableDateArgs(getDateFromDayNumberGregorian(getDayNumberGregorian(year, 1, 0) + dy));\n else if (calendar === 'j' || j === true)\n return handleVariableDateArgs(getDateFromDayNumberJulian(getDayNumberJulian(year, 1, 0) + dy));\n else\n return handleVariableDateArgs(getDateFromDayNumber_SGC(getDayNumber_SGC(year, 1, 0) + dy));\n }\n else {\n month = month ?? 1;\n day = day ?? 1;\n }\n }\n else if (n != null) {\n if (calendar === 'g' || j === false)\n return handleVariableDateArgs(getDateFromDayNumberGregorian(n));\n else if (calendar === 'j' || j === true)\n return handleVariableDateArgs(getDateFromDayNumberJulian(n));\n else if (calendar) {\n ++(calendar as any).computeWeekValues;\n const ymd = handleVariableDateArgs(calendar.getDateFromDayNumber(n));\n --(calendar as any).computeWeekValues;\n return ymd;\n }\n else\n return handleVariableDateArgs(getDateFromDayNumber_SGC(n));\n }\n else\n throw new Error('Calendar: Invalid date arguments');\n\n return [year, month, day, j == null ? -1 : +j];\n}\n\n/**\n * Determine if a given date falls during the Julian calendar or the Gregorian calendar, given the standard\n * Gregorian change date of 1582-10-15.\n *\n * @param {YearOrDate} yearOrDate\n * @param {number} month\n * @param {number} day\n * @returns True if the date is Julian.\n */\nexport function isJulianCalendarDate_SGC(yearOrDate: YearOrDate, month?: number, day?: number): boolean {\n let year: number, j: number; [year, month, day, j] = handleVariableDateArgs(yearOrDate, month, day);\n\n return (j === 1 || year < 1582 || (year === 1582 && (month < 10 || month === 10 && day < 15)));\n}\n\n/**\n * Gets the day number for the given date, relative to 1970-01-01, using the standard Gregorian change date 1582-10-15.\n * @param yearOrDate\n * @param month\n * @param day\n * @returns Day number.\n */\nexport function getDayNumber_SGC(yearOrDate: YearOrDate, month?: number, day?: number): number {\n let year: number, j: number; [year, month, day, j] = handleVariableDateArgs(yearOrDate, month, day);\n\n while (month < 1) { month += 12; --year; }\n while (month > 12) { month -= 12; ++year; }\n\n if (j === 1 || (j !== 0 && isJulianCalendarDate_SGC(year, month, day)))\n return getDayNumberJulian(year, month, day);\n else\n return getDayNumberGregorian(year, month, day);\n}\n\n/**\n * Gets the day number for the given Gregorian calendar date, relative to 1970-01-01.\n * @param yearOrDate\n * @param month\n * @param day\n * @returns Day number.\n */\nexport function getDayNumberGregorian(yearOrDate: YearOrDate, month?: number, day?: number): number {\n let year: number; [year, month, day] = handleVariableDateArgs(yearOrDate, month, day, 'g');\n\n while (month < 1) { month += 12; --year; }\n while (month > 12) { month -= 12; ++year; }\n\n return 367 * year - div_rd(7 * (year + div_tt0(month + 9, 12)), 4) - div_tt0(3 * (div_tt0(year + div_tt0(month - 9, 7), 100) + 1), 4) +\n div_tt0(275 * month, 9) + day - 719559;\n}\n\n/**\n * Gets the day number for the given Julian calendar date, relative to 1970-01-01 Gregorian.\n * @param yearOrDate\n * @param month\n * @param day\n * @returns Day number.\n */\nexport function getDayNumberJulian(yearOrDate: YearOrDate, month?: number, day?: number): number {\n let year: number; [year, month, day] = handleVariableDateArgs(yearOrDate, month, day, 'j');\n\n while (month < 1) { month += 12; --year; }\n while (month > 12) { month -= 12; ++year; }\n\n return 367 * year - div_rd(7 * (year + div_tt0(month + 9, 12)), 4) + div_tt0(275 * month, 9) + day - 719561;\n}\n\n// noinspection JSUnusedLocalSymbols\n/**\n * Always returns 1. This function exists only to parallel getFirstDateInMonth, which isn't always 1 when the\n * Gregorian change date is not fixed.\n * @returns First date of calendar month.\n */\nexport function getFirstDateInMonth_SGC(_year: number, _month: number): number {\n return 1;\n}\n\n/**\n * The last date of the given calendar month, using the standard Gregorian change date 1582-10-15, e.g. 31 for\n * any January, 28 for non-leap-year February, 29 for leap-year February, etc.\n * @param year\n * @param month\n * @returns Last date of calendar month.\n */\nexport function getLastDateInMonth_SGC(year: number, month: number): number {\n if (month === 9 || month === 4 || month === 6 || month === 11)\n return 30;\n else if (month !== 2)\n return 31; // Works for pseudo-months 0 and 13 as well.\n else if (year % 4 === 0 && (year < 1583 || year % 100 !== 0 || year % 400 === 0))\n return 29;\n else\n return 28;\n}\n\n/**\n * The last date of the given Gregorian calendar month.\n * @param year\n * @param month\n * @returns Last date of calendar month.\n */\nexport function getLastDateInMonthGregorian(year: number, month: number): number {\n if (month === 9 || month === 4 || month === 6 || month === 11)\n return 30;\n else if (month !== 2)\n return 31; // Works for pseudo-months 0 and 13 as well.\n else if (year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0))\n return 29;\n else\n return 28;\n}\n\n/**\n * The last date of the given Gregorian calendar month.\n * @param year\n * @param month\n * @returns Last date of calendar month.\n */\nexport function getLastDateInMonthJulian(year: number, month: number): number {\n if (month === 9 || month === 4 || month === 6 || month === 11)\n return 30;\n else if (month !== 2)\n return 31; // Works for pseudo-months 0 and 13 as well.\n else if (year % 4 === 0)\n return 29;\n else\n return 28;\n}\n\n/**\n * Returns the number of days in the given calendar month. Since this\n * function is for the standard Gregorian change date of 1582-10-15,\n * it returns 21 for 1582/10, otherwise it returns the same value as\n * [[getLastDateInMonth_SGC]].\n * @param year\n * @param month\n * @returns Total number of days in the given month.\n */\nexport function getDaysInMonth_SGC(year: number, month: number): number {\n if (year === 1582 && month === 10)\n return 21;\n else if (month === 9 || month === 4 || month === 6 || month === 11)\n return 30;\n else if (month !== 2)\n return 31; // Works for pseudo-months 0 and 13 as well.\n else\n return getDayNumber_SGC(year, 3, 1) - getDayNumber_SGC(year, 2, 1);\n}\n\n/**\n * This typically returns 365, or 366 for a leap year, but for the year\n * 1582 it returns 355.\n * @param year\n * @returns Total number of days in the given year.\n */\nexport function getDaysInYear_SGC(year: number): number {\n return getDayNumber_SGC(year + 1, 1, 1) - getDayNumber_SGC(year, 1, 1);\n}\n\n/**\n * Get day of week for a given 1970-01-01-based day number.\n * @param dayNum 1970-01-01-based day number.\n * @return Day of week as 0-6: 0 for Sunday, 1 for Monday... 6 for Saturday.\n */\nexport function getDayOfWeek(dayNum: number): number {\n return mod(dayNum + 4, 7);\n}\n\n/**\n * Get day of week for a given date, assuming standard Gregorian change.\n * @param yearOrDateOrDayNum 1970-01-01-based day number (month and date must be left undefined) - OR -\n * YMDDate form y/m/d - OR - [y, m, d].\n * @param month\n * @param day\n * @return Day of week as 0-6: 0 for Sunday, 1 for Monday... 6 for Saturday.\n */\nexport function getDayOfWeek_SGC(yearOrDateOrDayNum: YearOrDate, month?: number, day?: number): number {\n if (isNumber(yearOrDateOrDayNum) && month == null)\n return mod(yearOrDateOrDayNum + 4, 7);\n else\n return getDayOfWeek(getDayNumber_SGC(yearOrDateOrDayNum, month, day));\n}\n\n/**\n * Get the date of the index-th day of the week of a given month, e.g. the date of the\n * first Wednesday or the third Monday or the last Friday of the month.\n * @param year Year.\n * @param month Month.\n * @param dayOfTheWeek The day of the week (e.g. 0 for Sunday, 2 for Tuesday, 6 for Saturday) for\n * which you wish to find the date.\n * @param index A value of 1-5, or LAST (6), for the occurrence of the specified day of the week.\n * @return 0 if the described day does not exist (e.g. there is no fifth Monday in the given month) or\n * the date of the specified day.\n */\nexport function getDateOfNthWeekdayOfMonth_SGC(year: number, month: number, dayOfTheWeek: number, index: number): number {\n const last: boolean = (index >= LAST);\n const day = 1;\n let dayNum: number = getDayNumber_SGC(year, month, day);\n const dayOfWeek = getDayOfWeek(dayNum);\n let ymd: YMDDate;\n let lastDay = 0;\n\n if (dayOfWeek === dayOfTheWeek && index === 1)\n return day;\n\n dayNum += mod(dayOfTheWeek - dayOfWeek, 7);\n ymd = getDateFromDayNumber_SGC(dayNum);\n\n while (ymd.m === month) {\n lastDay = ymd.d;\n\n if (--index === 0)\n return lastDay;\n\n dayNum += 7;\n ymd = getDateFromDayNumber_SGC(dayNum);\n }\n\n if (last)\n return lastDay;\n else\n return 0;\n}\n\nexport function getDayOfWeekInMonthCount_SGC(year: number, month: number, dayOfTheWeek: number): number {\n const firstDay = getDayNumber_SGC(year, month, getDateOfNthWeekdayOfMonth_SGC(year, month, dayOfTheWeek, 1));\n const nextMonth = getDayNumber_SGC(year, month + 1, 1);\n\n return div_tt0(nextMonth - firstDay - 1, 7) + 1;\n}\n\nexport function getDayOnOrAfter_SGC(year: number, month: number, dayOfTheWeek: number, minDate: number): number {\n const dayNum = getDayNumber_SGC(year, month, minDate);\n const dayOfWeek = getDayOfWeek(dayNum);\n const delta = mod(dayOfTheWeek - dayOfWeek, 7);\n\n if (year === 1582 && month === 10) {\n const ymd = getDateFromDayNumber_SGC(dayNum + delta);\n\n if (ymd.y !== year || ymd.m !== month)\n minDate = 0;\n else\n minDate = ymd.d;\n }\n else {\n minDate += delta;\n\n if (minDate > getLastDateInMonth_SGC(year, month))\n minDate = 0;\n }\n\n return minDate;\n}\n\nexport function getDayOnOrBefore_SGC(year: number, month: number, dayOfTheWeek: number, maxDate: number): number {\n const dayNum = getDayNumber_SGC(year, month, maxDate);\n const dayOfWeek = getDayOfWeek(dayNum);\n const delta = mod(dayOfWeek - dayOfTheWeek, 7);\n\n if (year === 1582 && month === 10) {\n const ymd = getDateFromDayNumber_SGC(dayNum - delta);\n\n if (ymd.y !== year || ymd.m !== month)\n maxDate = 0;\n else\n maxDate = ymd.d;\n }\n else {\n maxDate -= delta;\n\n if (maxDate < 0)\n maxDate = 0;\n }\n\n return maxDate;\n}\n\nexport function addDaysToDate_SGC(deltaDays: number, yearOrDate: YearOrDate, month?: number, day?: number): YMDDate {\n return getDateFromDayNumber_SGC(getDayNumber_SGC(yearOrDate, month, day) + deltaDays);\n}\n\nexport function getDateFromDayNumber_SGC(dayNum: number): YMDDate {\n if (dayNum >= FIRST_GREGORIAN_DAY_SGC)\n return getDateFromDayNumberGregorian(dayNum);\n else\n return getDateFromDayNumberJulian(dayNum);\n}\n\nexport function getDateFromDayNumberGregorian(dayNum: number): YMDDate {\n let year: number;\n let month: number;\n let day: number;\n let dayOfYear: number;\n let lastDay: number;\n\n year = Math.floor((dayNum + 719528) / 365.2425);\n\n while (dayNum < getDayNumberGregorian(year, 1, 1))\n --year;\n\n while (dayNum >= getDayNumberGregorian(year + 1, 1, 1))\n ++year;\n\n day = dayOfYear = dayNum - getDayNumberGregorian(year, 1, 1) + 1;\n\n for (month = 1; day > (lastDay = getLastDateInMonthGregorian(year, month)); ++month)\n day -= lastDay;\n\n return syncDateAndTime({ y: year, m: month, d: day, dy: dayOfYear, n: dayNum, j: false });\n}\n\nfor (let i = -207084; i <= 205084; ++i) {\n getDateFromDayNumberJulian(i);\n}\n\nexport function getDateFromDayNumberJulian(dayNum: number): YMDDate {\n let year: number;\n let month: number;\n let day: number;\n let lastDay: number;\n\n year = Math.floor((dayNum + 719530) / 365.25);\n day = dayNum - getDayNumberJulian(year, 1, 1) + 1;\n\n for (month = 1; day > (lastDay = getLastDateInMonthJulian(year, month)); ++month)\n day -= lastDay;\n\n return syncDateAndTime({ y: year, m: month, d: day, n: dayNum, j: true });\n}\n\nexport function millisFromDateTime_SGC(year: number, month: number, day: number, hour: number, minute: number, second?: number, millis?: number): number {\n millis = millis || 0;\n second = second || 0;\n\n return millis +\n second * 1000 +\n minute * MINUTE_MSEC +\n hour * HOUR_MSEC +\n getDayNumber_SGC(year, month, day) * DAY_MSEC;\n}\n\nexport function dateAndTimeFromMillis_SGC(ticks: number): DateAndTime {\n const wallTime = getDateFromDayNumber_SGC(div_rd(ticks, DAY_MSEC)) as DateAndTime;\n\n wallTime.millis = mod(ticks, 1000);\n ticks = div_rd(ticks, 1000);\n wallTime.sec = mod(ticks, 60);\n ticks = div_rd(ticks, 60);\n wallTime.min = mod(ticks, 60);\n ticks = div_rd(ticks, 60);\n wallTime.hrs = mod(ticks, 24);\n wallTime.utcOffset = 0;\n wallTime.dstOffset = 0;\n wallTime.occurrence = 1;\n\n return syncDateAndTime(wallTime);\n}\n\nexport function isValidDate_SGC(yearOrDate: YearOrDate, month?: number, day?: number): boolean {\n const [y, m, d, j] = handleVariableDateArgs(yearOrDate, month, day);\n let ymd: YMDDate;\n const n = getDayNumber_SGC({ y, m, d, j: j < 0 ? null : !!j });\n\n if (j < 0)\n ymd = getDateFromDayNumber_SGC(n);\n else if (j === 0)\n ymd = getDateFromDayNumberGregorian(n);\n else\n ymd = getDateFromDayNumberJulian(n);\n\n return (y === ymd.y && m === ymd.m && d === ymd.d);\n}\n\nexport function isValidDateGregorian(yearOrDate: YearOrDate, month?: number, day?: number): boolean {\n let year: number; [year, month, day] = handleVariableDateArgs(yearOrDate, month, day, 'g');\n const ymd: YMDDate = getDateFromDayNumberGregorian(getDayNumberGregorian(year, month, day));\n\n return (year === ymd.y && month === ymd.m && day === ymd.d);\n}\n\nexport function isValidDateJulian(yearOrDate: YearOrDate, month?: number, day?: number): boolean {\n let year: number; [year, month, day] = handleVariableDateArgs(yearOrDate, month, day, 'j');\n const ymd: YMDDate = getDateFromDayNumberJulian(getDayNumberJulian(year, month, day));\n\n return (year === ymd.y && month === ymd.m && day === ymd.d);\n}\n\nexport function getISOFormatDate(yearOrDate: YearOrDate, month?: number, day?: number): string {\n let year: number; [year, month, day] = handleVariableDateArgs(yearOrDate, month, day);\n\n const yyyy = (year < 0 ? '-' : '') + padLeft(Math.abs(year), 4, '0');\n const mm = padLeft(month, 2, '0');\n const dd = padLeft(day, 2, '0');\n\n return yyyy + '-' + mm + '-' + dd;\n}\n\nexport function parseISODate(date: string): YMDDate {\n let sign = 1;\n\n date = date.trim();\n\n if (date.startsWith('-')) {\n sign = -1;\n date = date.substring(1).trim();\n }\n\n let $ = /^(\\d+)-(\\d{1,2}(?=\\D))(?:-(\\d+))?$/.exec(date);\n\n if (!$)\n $ = /^(\\d{1,5})$/.exec(date);\n\n if (!$)\n $ = /^(\\d{4,})(\\d\\d)(\\d\\d)$/.exec(date);\n\n if (!$)\n throw new Error('Invalid ISO date');\n\n return syncDateAndTime({ y: Number($[1]) * sign, m: Number($[2] ?? 1), d: Number($[3] ?? 1) });\n}\n\nexport class Calendar {\n private gcYear = 1582;\n private gcMonth = 10;\n private gcDate = 15;\n private firstGregorianDay: number = FIRST_GREGORIAN_DAY_SGC;\n private firstDateInGCChangeMonth = 1;\n private lengthOfGCChangeMonth = 21;\n private lastJulianYear: number = Number.MIN_SAFE_INTEGER;\n private lastJulianMonth: number = Number.MIN_SAFE_INTEGER;\n private lastJulianDate = 4;\n\n protected _locked = false;\n\n constructor(gcYearOrDateOrType?: YearOrDate | CalendarType | string, gcMonth?: number, gcDate?: number) {\n if (gcYearOrDateOrType === CalendarType.PURE_GREGORIAN)\n this.setGregorianChange(DISTANT_YEAR_PAST, 0, 0);\n else if (gcYearOrDateOrType === CalendarType.PURE_JULIAN)\n this.setGregorianChange(DISTANT_YEAR_FUTURE, 0, 0);\n else if (arguments.length === 0 || gcYearOrDateOrType == null)\n this.setGregorianChange(1582, 10, 15);\n else\n this.setGregorianChange(gcYearOrDateOrType as YearOrDate | string, gcMonth, gcDate);\n }\n\n lock = (): this => this._lock();\n protected _lock(doLock = true): this {\n this._locked = this._locked || doLock;\n return this;\n }\n\n get locked(): boolean { return this._locked; }\n\n setPureGregorian(pureGregorian: boolean): this {\n if (this.locked)\n throw lockError;\n\n if (pureGregorian)\n this.setGregorianChange(DISTANT_YEAR_PAST, 0, 0);\n else\n this.setGregorianChange(1582, 10, 15);\n\n return this;\n }\n\n isPureGregorian(): boolean {\n return (this.gcYear <= DISTANT_YEAR_PAST);\n }\n\n setPureJulian(pureJulian: boolean): this {\n if (this.locked)\n throw lockError;\n\n if (pureJulian)\n this.setGregorianChange(DISTANT_YEAR_FUTURE, 0, 0);\n else\n this.setGregorianChange(1582, 10, 15);\n\n return this;\n }\n\n isPureJulian(): boolean {\n return (this.gcYear >= DISTANT_YEAR_FUTURE);\n }\n\n setGregorianChange(gcYearOrDate: YearOrDate | string, gcMonth?: number, gcDate?: number): this {\n if (this.locked)\n throw lockError;\n\n if (gcYearOrDate === 'g' || gcYearOrDate === 'G') {\n this.setPureGregorian(true);\n\n return this;\n }\n else if (gcYearOrDate === 'j' || gcYearOrDate === 'J') {\n this.setPureJulian(true);\n\n return this;\n }\n else if (isString(gcYearOrDate))\n gcYearOrDate = parseISODate(gcYearOrDate);\n else if (isObject(gcYearOrDate) && !isArray(gcYearOrDate) && (gcYearOrDate.y == null ||\n gcYearOrDate.m == null || gcYearOrDate.d == null || gcYearOrDate.j))\n throw new Error('Gregorian change date must be an explicit non-Julian y-m-d date');\n\n let gcYear: number; [gcYear, gcMonth, gcDate] = handleVariableDateArgs(gcYearOrDate as YearOrDate, gcMonth, gcDate, this);\n\n if (gcYear < GREGORIAN_CHANGE_MIN_YEAR) {\n if ((gcMonth !== 0 || gcDate !== 0) && gcYear > DISTANT_YEAR_PAST)\n throw new Error('Calendar: Gregorian change year cannot be less than ' + GREGORIAN_CHANGE_MIN_YEAR);\n\n this.firstGregorianDay = Number.MIN_SAFE_INTEGER;\n this.gcYear = DISTANT_YEAR_PAST;\n }\n else if (gcYear > GREGORIAN_CHANGE_MAX_YEAR) {\n if ((gcMonth !== 0 || gcDate !== 0) && gcYear < DISTANT_YEAR_FUTURE)\n throw new Error('Calendar: Gregorian change year cannot be greater than ' + GREGORIAN_CHANGE_MAX_YEAR);\n\n this.firstGregorianDay = Number.MAX_SAFE_INTEGER;\n this.gcYear = DISTANT_YEAR_FUTURE;\n }\n else if (!isValidDateGregorian(gcYear, gcMonth, gcDate))\n throw new Error('Calendar: Invalid Gregorian date: ' + getISOFormatDate(gcYear, gcMonth, gcDate));\n\n this.gcYear = gcYear;\n this.gcMonth = gcMonth;\n this.gcDate = gcDate;\n this.firstGregorianDay = getDayNumberGregorian(gcYear, gcMonth, gcDate);\n\n const lastJDay: YMDDate = getDateFromDayNumberJulian(this.firstGregorianDay - 1);\n\n this.lastJulianDate = lastJDay.d;\n this.lengthOfGCChangeMonth = getLastDateInMonthGregorian(gcYear, gcMonth);\n\n if (lastJDay.y === gcYear && lastJDay.m === gcMonth) {\n this.lastJulianYear = Number.MIN_SAFE_INTEGER; // Number.MIN_SAFE_INTEGER used to indicate mixed Julian/Gregorian transition month\n this.lastJulianMonth = Number.MIN_SAFE_INTEGER;\n this.firstDateInGCChangeMonth = 1;\n this.lengthOfGCChangeMonth -= gcDate - this.lastJulianDate - 1;\n }\n else {\n this.lastJulianYear = lastJDay.y;\n this.lastJulianMonth = lastJDay.m;\n this.firstDateInGCChangeMonth = gcDate;\n this.lengthOfGCChangeMonth -= gcDate - 1;\n }\n\n return this;\n }\n\n getGregorianChange(): YMDDate {\n return syncDateAndTime({ y: this.gcYear, m: this.gcMonth, d: this.gcDate, n: this.firstGregorianDay, j: false });\n }\n\n isJulianCalendarDate(yearOrDate: YearOrDate, month?: number, day?: number): boolean {\n let year: number, j: number; [year, month, day, j] = handleVariableDateArgs(yearOrDate, month, day, this);\n\n return (j === 1 || year < this.gcYear || (year === this.gcYear && (month < this.gcMonth ||\n month === this.gcMonth && day < this.gcDate)));\n }\n\n getDayNumber(yearOrDate: YearOrDate, month?: number, day?: number): number {\n // Note: month/day can be used internally to pass startOfWeek/minDaysInWeek.\n if (isObject(yearOrDate) && !isArray(yearOrDate)) {\n syncDateAndTime(yearOrDate);\n\n if (yearOrDate.y == null && (yearOrDate.yw != null || yearOrDate.ywl != null)) {\n const localeWeek = (yearOrDate.ywl != null);\n const year = yearOrDate.ywl ?? yearOrDate.yw;\n const startOfWeek = (localeWeek && month != null ? month : 1);\n const minDaysInWeek = (localeWeek && day != null ? day : 4);\n const week = (localeWeek ? yearOrDate.wl : yearOrDate.w) ?? /* istanbul ignore next: unreached sanity check */ 1;\n const dayOfWeek = (localeWeek ? yearOrDate.dwl : yearOrDate.dw) ?? /* istanbul ignore next: unreached sanity check */ 1;\n ++this.computeWeekValues;\n\n const w = this.getStartDateOfFirstWeekOfYear(year, startOfWeek, minDaysInWeek);\n const dayNum = w.n + (week - 1) * 7 + dayOfWeek - 1;\n\n yearOrDate = this.getDateFromDayNumber(dayNum);\n --this.computeWeekValues;\n }\n else if (yearOrDate.y != null && yearOrDate.m == null && yearOrDate.dy != null)\n yearOrDate = this.addDaysToDate(yearOrDate.dy - 1, { y: yearOrDate.y, m: 1, d: 1 });\n }\n\n let year: number, j: number; [year, month, day, j] = handleVariableDateArgs(yearOrDate, month, day, this);\n\n while (month < 1) { month += 12; --year; }\n while (month > 12) { month -= 12; ++year; }\n\n if (j < 0) {\n if (year === this.lastJulianYear && month === this.lastJulianMonth) {\n if (day > this.lastJulianDate)\n day = this.lastJulianDate + 1;\n }\n else if (year === this.gcYear && month === this.gcMonth && (day > this.lastJulianDate ||\n (this.lastJulianMonth !== this.gcMonth && this.lastJulianMonth > 0)) && day < this.gcDate) {\n day = this.gcDate;\n }\n }\n\n if (j === 1 || (j !== 0 && this.isJulianCalendarDate(year, month, day)))\n return getDayNumberJulian(year, month, day);\n else\n return getDayNumberGregorian(year, month, day);\n }\n\n protected computeWeekValues = 0; // To prevent infinite recursion, compute week values only when this is 0.\n\n /** @hidden */\n getDateFromDayNumber(dayNum: number, startingDayOfWeek?: number, minDaysInCalendarYear?: number): YMDDate {\n let result: YMDDate;\n\n if (dayNum >= this.firstGregorianDay)\n result = getDateFromDayNumberGregorian(dayNum);\n else\n result = getDateFromDayNumberJulian(dayNum);\n\n if (this.computeWeekValues === 0)\n [result.yw, result.w, result.dw] = this.getYearWeekAndWeekday(result, startingDayOfWeek, minDaysInCalendarYear);\n\n return syncDateAndTime(result);\n }\n\n getFirstDateInMonth(year: number, month: number): number {\n if (year === this.gcYear && month === this.gcMonth)\n return this.firstDateInGCChangeMonth;\n else\n return 1;\n }\n\n getLastDateInMonth(year: number, month: number): number {\n if (month === 0) {\n month = 12;\n --year;\n }\n else if (month === 13) {\n month = 1;\n ++year;\n }\n\n if (year === this.lastJulianYear && month === this.lastJulianMonth)\n return this.lastJulianDate;\n else if (month === 9 || month === 4 || month === 6 || month === 11)\n return 30;\n else if (month !== 2)\n return 31;\n else if (year % 4 === 0 && (year < this.gcYear || (year === this.gcYear && this.gcMonth > 2) || year % 100 !== 0 || year % 400 === 0))\n return 29;\n else\n return 28;\n }\n\n getDaysInMonth(year: number, month: number): number {\n if (month === 0) {\n month = 12;\n --year;\n }\n else if (month === 13) {\n month = 1;\n ++year;\n }\n\n if (year === this.gcYear && month === this.gcMonth)\n return this.lengthOfGCChangeMonth;\n else if (year === this.lastJulianYear && month === this.lastJulianMonth)\n return this.lastJulianDate;\n else if (month === 9 || month === 4 || month === 6 || month === 11)\n return 30;\n else if (month !== 2)\n return 31;\n else\n return this.getDayNumber(year, 3, 1) - this.getDayNumber(year, 2, 1);\n }\n\n getDaysInYear(year: number): number {\n return this.getDayNumber(year + 1, 1, 1) - this.getDayNumber(year, 1, 1);\n }\n\n getDayOfWeek(yearOrDateOrDayNum: YearOrDate, month?: number, day?: number): number {\n if (isNumber(yearOrDateOrDayNum) && month == null)\n return getDayOfWeek(yearOrDateOrDayNum);\n else\n return getDayOfWeek(this.getDayNumber(yearOrDateOrDayNum, month, day));\n }\n\n /**\n * @description Get the date of the index-th day of the week of a given month, e.g. the date of the\n * first Wednesday or the third Monday or the last Friday of the month.\n *\n * @param {number} year - Year.\n * @param {number} month - Month.\n * @param {number} dayOfTheWeek - The day of the week (e.g. 0 for Sunday, 2 for Tuesday, 6 for Saturday) for\n * which you wish to find the date.\n * @param {number} index - A value of 1-5, or LAST (6), for the occurrence of the specified day of the week.\n *\n * @return {number} 0 if the described day does not exist (e.g. there is no fifth Monday in the given month) or\n * the date of the specified day.\n */\n getDateOfNthWeekdayOfMonth(year: number, month: number, dayOfTheWeek: number, index: number): number {\n const last: boolean = (index >= LAST);\n const day = 1;\n let dayNum: number = this.getDayNumber(year, month, day);\n const dayOfWeek = getDayOfWeek(dayNum);\n let ymd: YMDDate;\n let lastDay = 0;\n\n if (dayOfWeek === dayOfTheWeek && index === 1)\n return day;\n\n dayNum += mod(dayOfTheWeek - dayOfWeek, 7);\n ymd = this.getDateFromDayNumber(dayNum);\n\n while (ymd.m === month) {\n lastDay = ymd.d;\n\n if (--index === 0)\n return lastDay;\n\n dayNum += 7;\n ymd = this.getDateFromDayNumber(dayNum);\n }\n\n if (last)\n return lastDay;\n else\n return 0;\n }\n\n getDayOfWeekInMonthCount(year: number, month: number, dayOfTheWeek: number): number {\n const firstDay = this.getDayNumber(year, month, this.getDateOfNthWeekdayOfMonth(year, month, dayOfTheWeek, 1));\n const nextMonth = this.getDayNumber(year, month + 1, 1);\n\n return div_tt0(nextMonth - firstDay - 1, 7) + 1;\n }\n\n getDayOfWeekInMonthIndex(yearOrDate: YearOrDate, month?: number, day?: number): number {\n let year: number; [year, month, day] = handleVariableDateArgs(yearOrDate, month, day, this);\n const firstDay = this.getDayNumber(year, month, 1);\n const dayNumber = this.getDayNumber(year, month, day);\n\n return div_rd(dayNumber - firstDay, 7) + 1;\n }\n\n getDayOnOrAfter(year: number, month: number, dayOfTheWeek: number, minDate: number): number {\n const dayNum = this.getDayNumber(year, month, minDate);\n const dayOfWeek = getDayOfWeek(dayNum);\n const delta = mod(dayOfTheWeek - dayOfWeek, 7);\n\n if (year === this.gcYear && month === this.gcMonth) {\n const ymd = this.getDateFromDayNumber(dayNum + delta);\n\n if (ymd.y !== year || ymd.m !== month)\n minDate = 0;\n else\n minDate = ymd.d;\n }\n else {\n minDate += delta;\n\n if (minDate > this.getLastDateInMonth(year, month))\n minDate = 0;\n }\n\n return minDate;\n }\n\n getDayOnOrBefore(year: number, month: number, dayOfTheWeek: number, maxDate: number): number {\n const dayNum = this.getDayNumber(year, month, maxDate);\n const dayOfWeek = getDayOfWeek(dayNum);\n const delta = mod(dayOfWeek - dayOfTheWeek, 7);\n\n if (year === this.gcYear && month === this.gcMonth) {\n const ymd = this.getDateFromDayNumber(dayNum - delta);\n\n if (ymd.y !== year || ymd.m !== month)\n maxDate = 0;\n else\n maxDate = ymd.d;\n }\n else {\n maxDate -= delta;\n\n if (maxDate < 0)\n maxDate = 0;\n }\n\n return maxDate;\n }\n\n addDaysToDate(deltaDays: number, yearOrDate: YearOrDate, month?: number, day?: number): YMDDate {\n return this.getDateFromDayNumber(this.getDayNumber(yearOrDate, month, day) + deltaDays);\n }\n\n getCalendarMonth(year: number, month: number, startingDayOfWeek = SUNDAY): YMDDate[] {\n const dates: YMDDate[] = [];\n let dateOffset: number;\n let dayNum = this.getDayNumber(year, month, this.getFirstDateInMonth(year, month));\n let ymd: YMDDate;\n let currMonth: number;\n\n // Step back (if necessary) to the nearest prior day matching the requested starting day of the week.\n dateOffset = mod(startingDayOfWeek - getDayOfWeek(dayNum), -7); // First time I recall ever wanting to use a negative modulus.\n dayNum += dateOffset; // dateOffset will be 0 or negative\n\n ymd = this.getDateFromDayNumber(dayNum, startingDayOfWeek);\n\n // This loop will fill in a calendar month's full set of dates in such a way as to obtain dates which\n // should be shown from previous and subsequent months, while also skipping over Julian-to-Gregorian\n // calendar switch-over dates.\n do {\n dates.push(ymd);\n ++dayNum;\n ++dateOffset;\n ymd = this.getDateFromDayNumber(dayNum);\n currMonth = ymd.m;\n // We've reached the end of the calendar when we're at a positive date offset, in a different month\n // than the requested month, and the day of week is back to the first day of the week of the calendar.\n // The first date to meet these criteria is just past the end of the calendar, and is not added to it.\n } while (dateOffset < 1 || currMonth === month || getDayOfWeek(dayNum) !== startingDayOfWeek);\n\n return dates;\n }\n\n isValidDate(year: number, month: number, day: number): boolean;\n isValidDate(yearOrDate: YMDDate | number[]): boolean;\n isValidDate(yearOrDate: YearOrDate, month?: number, day?: number): boolean {\n let year: number; [year, month, day] = handleVariableDateArgs(yearOrDate, month, day, this, true);\n const ymd = this.getDateFromDayNumber(this.getDayNumber(year, month, day));\n\n return (year === ymd.y && month === ymd.m && day === ymd.d);\n }\n\n normalizeDate(year: number, month: number, day: number): YMDDate;\n normalizeDate(yearOrDate: YMDDate | number[]): YMDDate;\n normalizeDate(yearOrDate: YearOrDate, month?: number, day?: number): YMDDate {\n let year: number; [year, month, day] = handleVariableDateArgs(yearOrDate, month, day, this, true);\n\n if (month < 1) {\n month += 12;\n year -= 1;\n }\n else if (month > 12) {\n month -= 12;\n year += 1;\n }\n\n if (!this.isValidDate(year, month, day)) {\n let d: number;\n\n if (day < (d = this.getFirstDateInMonth(year, month)))\n day = d;\n else if (day > (d = this.getLastDateInMonth(year, month)))\n day = d;\n else {\n const range = this.getMissingDateRange(year, month);\n\n if (range != null)\n day = range[1] + 1;\n }\n }\n\n return syncDateAndTime({ y: year, m: month, d: day });\n }\n\n getMissingDateRange(year: number, month: number): number[] | null {\n if (year === this.lastJulianYear && month === this.lastJulianMonth) {\n const lastDate = getLastDateInMonthJulian(year, month);\n\n if (lastDate > this.lastJulianDate)\n return [this.lastJulianDate + 1, lastDate];\n }\n else if (year === this.gcYear && month === this.gcMonth && this.gcDate > 1 && this.gcDate > this.lastJulianDate + 1)\n return [this.lastJulianDate + 1, this.gcDate - 1];\n\n return null;\n }\n\n getStartDateOfFirstWeekOfYear(year: number, startingDayOfWeek = 1, minDaysInCalendarYear = 4): YMDDate {\n let day = 1;\n\n // 7 is a special case, where start week is first full week *after* January 1st.\n if (minDaysInCalendarYear === 7) {\n ++day;\n --minDaysInCalendarYear;\n }\n\n const daysIntoWeek = mod(this.getDayOfWeek(year, 1, day) - startingDayOfWeek, 7);\n\n return this.addDaysToDate(-daysIntoWeek + (daysIntoWeek > 7 - minDaysInCalendarYear ? 7 : 0), year, 1, day);\n }\n\n getWeeksInYear(year: number, startingDayOfWeek = 1, minDaysInCalendarYear = 4): number {\n const w1 = this.getStartDateOfFirstWeekOfYear(year, startingDayOfWeek, minDaysInCalendarYear);\n const w2 = this.getStartDateOfFirstWeekOfYear(year + 1, startingDayOfWeek, minDaysInCalendarYear);\n\n return (w2.n - w1.n) / 7;\n }\n\n getYearWeekAndWeekday(year: number, month: number, day: number,\n startingDayOfWeek?: number, minDaysInCalendarYear?: number): number[];\n\n getYearWeekAndWeekday(date: YearOrDate | number,\n startingDayOfWeek?: number, minDaysInCalendarYear?: number): number[];\n\n getYearWeekAndWeekday(yearOrDate: YearOrDate, monthOrSDW: number, dayOrMDiCY: number,\n startingDayOfWeek?: number, minDaysInCalendarYear?: number): number[] {\n const [year, month, day] = handleVariableDateArgs(yearOrDate, monthOrSDW, dayOrMDiCY, this, true);\n\n if (isObject(yearOrDate)) {\n startingDayOfWeek = monthOrSDW;\n minDaysInCalendarYear = dayOrMDiCY;\n }\n\n startingDayOfWeek = startingDayOfWeek ?? 1;\n minDaysInCalendarYear = minDaysInCalendarYear ?? 4;\n ++this.computeWeekValues;\n\n let resultYear = year;\n let w = this.getStartDateOfFirstWeekOfYear(year, startingDayOfWeek, minDaysInCalendarYear);\n const w2 = this.getStartDateOfFirstWeekOfYear(year + 1, startingDayOfWeek, minDaysInCalendarYear);\n const dayNum = this.getDayNumber(year, month, day);\n\n if (w.n > dayNum) {\n w = this.getStartDateOfFirstWeekOfYear(year - 1, startingDayOfWeek, minDaysInCalendarYear);\n --resultYear;\n }\n else if (w2.n <= dayNum) {\n w = w2;\n ++resultYear;\n }\n\n --this.computeWeekValues;\n\n return [resultYear, floor((dayNum - w.n) / 7) +