234 lines
8.8 KiB
TypeScript
234 lines
8.8 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.
|
|
*/
|
|
|
|
// Portions of the code in this file are based on code from ICU.
|
|
// Original licensing can be found in the NOTICE file in the root directory of this source tree.
|
|
|
|
import {AnyCalendarDate, Calendar, CalendarIdentifier} from '../types';
|
|
import {CalendarDate} from '../CalendarDate';
|
|
|
|
const CIVIL_EPOC = 1948440; // CE 622 July 16 Friday (Julian calendar) / CE 622 July 19 (Gregorian calendar)
|
|
const ASTRONOMICAL_EPOC = 1948439; // CE 622 July 15 Thursday (Julian calendar)
|
|
const UMALQURA_YEAR_START = 1300;
|
|
const UMALQURA_YEAR_END = 1600;
|
|
const UMALQURA_START_DAYS = 460322;
|
|
|
|
function islamicToJulianDay(epoch: number, year: number, month: number, day: number): number {
|
|
return day +
|
|
Math.ceil(29.5 * (month - 1)) +
|
|
(year - 1) * 354 +
|
|
Math.floor((3 + 11 * year) / 30) +
|
|
epoch - 1;
|
|
}
|
|
|
|
function julianDayToIslamic(calendar: Calendar, epoch: number, jd: number) {
|
|
let year = Math.floor((30 * (jd - epoch) + 10646) / 10631);
|
|
let month = Math.min(12, Math.ceil((jd - (29 + islamicToJulianDay(epoch, year, 1, 1))) / 29.5) + 1);
|
|
let day = jd - islamicToJulianDay(epoch, year, month, 1) + 1;
|
|
|
|
return new CalendarDate(calendar, year, month, day);
|
|
}
|
|
|
|
function isLeapYear(year: number): boolean {
|
|
return (14 + 11 * year) % 30 < 11;
|
|
}
|
|
|
|
/**
|
|
* The Islamic calendar, also known as the "Hijri" calendar, is used throughout much of the Arab world.
|
|
* The civil variant uses simple arithmetic rules rather than astronomical calculations to approximate
|
|
* the traditional calendar, which is based on sighting of the crescent moon. It uses Friday, July 16 622 CE (Julian) as the epoch.
|
|
* Each year has 12 months, with either 354 or 355 days depending on whether it is a leap year.
|
|
* Learn more about the available Islamic calendars [here](https://cldr.unicode.org/development/development-process/design-proposals/islamic-calendar-types).
|
|
*/
|
|
export class IslamicCivilCalendar implements Calendar {
|
|
identifier: CalendarIdentifier = 'islamic-civil';
|
|
|
|
fromJulianDay(jd: number): CalendarDate {
|
|
return julianDayToIslamic(this, CIVIL_EPOC, jd);
|
|
}
|
|
|
|
toJulianDay(date: AnyCalendarDate): number {
|
|
return islamicToJulianDay(CIVIL_EPOC, date.year, date.month, date.day);
|
|
}
|
|
|
|
getDaysInMonth(date: AnyCalendarDate): number {
|
|
let length = 29 + date.month % 2;
|
|
if (date.month === 12 && isLeapYear(date.year)) {
|
|
length++;
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
getMonthsInYear(): number {
|
|
return 12;
|
|
}
|
|
|
|
getDaysInYear(date: AnyCalendarDate): number {
|
|
return isLeapYear(date.year) ? 355 : 354;
|
|
}
|
|
|
|
getMaximumMonthsInYear(): number {
|
|
return 12;
|
|
}
|
|
|
|
getMaximumDaysInMonth(): number {
|
|
return 30;
|
|
}
|
|
|
|
getYearsInEra(): number {
|
|
// 9999 gregorian
|
|
return 9665;
|
|
}
|
|
|
|
getEras(): string[] {
|
|
return ['AH'];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The Islamic calendar, also known as the "Hijri" calendar, is used throughout much of the Arab world.
|
|
* The tabular variant uses simple arithmetic rules rather than astronomical calculations to approximate
|
|
* the traditional calendar, which is based on sighting of the crescent moon. It uses Thursday, July 15 622 CE (Julian) as the epoch.
|
|
* Each year has 12 months, with either 354 or 355 days depending on whether it is a leap year.
|
|
* Learn more about the available Islamic calendars [here](https://cldr.unicode.org/development/development-process/design-proposals/islamic-calendar-types).
|
|
*/
|
|
export class IslamicTabularCalendar extends IslamicCivilCalendar {
|
|
identifier: CalendarIdentifier = 'islamic-tbla';
|
|
|
|
fromJulianDay(jd: number): CalendarDate {
|
|
return julianDayToIslamic(this, ASTRONOMICAL_EPOC, jd);
|
|
}
|
|
|
|
toJulianDay(date: AnyCalendarDate): number {
|
|
return islamicToJulianDay(ASTRONOMICAL_EPOC, date.year, date.month, date.day);
|
|
}
|
|
}
|
|
|
|
// Generated by scripts/generate-umalqura.js
|
|
const UMALQURA_DATA = 'qgpUDckO1AbqBmwDrQpVBakGkgepC9QF2gpcBS0NlQZKB1QLagutBa4ETwoXBYsGpQbVCtYCWwmdBE0KJg2VDawFtgm6AlsKKwWVCsoG6Qr0AnYJtgJWCcoKpAvSC9kF3AJtCU0FpQpSC6ULtAW2CVcFlwJLBaMGUgdlC2oFqworBZUMSg2lDcoF1gpXCasESwmlClILagt1BXYCtwhbBFUFqQW0BdoJ3QRuAjYJqgpUDbIN1QXaAlsJqwRVCkkLZAtxC7QFtQpVCiUNkg7JDtQG6QprCasEkwpJDaQNsg25CroEWworBZUKKgtVC1wFvQQ9Ah0JlQpKC1oLbQW2AjsJmwRVBqkGVAdqC2wFrQpVBSkLkgupC9QF2gpaBasKlQVJB2QHqgu1BbYCVgpNDiULUgtqC60FrgIvCZcESwalBqwG1gpdBZ0ETQoWDZUNqgW1BdoCWwmtBJUFygbkBuoK9QS2AlYJqgpUC9IL2QXqAm0JrQSVCkoLpQuyBbUJ1gSXCkcFkwZJB1ULagVrCisFiwpGDaMNygXWCtsEawJLCaUKUgtpC3UFdgG3CFsCKwVlBbQF2gntBG0BtgimClINqQ3UBdoKWwmrBFMGKQdiB6kLsgW1ClUFJQuSDckO0gbpCmsFqwRVCikNVA2qDbUJugQ7CpsETQqqCtUK2gJdCV4ELgqaDFUNsga5BroEXQotBZUKUguoC7QLuQXaAloJSgukDdEO6AZqC20FNQWVBkoNqA3UDdoGWwWdAisGFQtKC5ULqgWuCi4JjwwnBZUGqgbWCl0FnQI=';
|
|
let UMALQURA_MONTHLENGTH: Uint16Array;
|
|
let UMALQURA_YEAR_START_TABLE: Uint32Array;
|
|
|
|
function umalquraYearStart(year: number): number {
|
|
return UMALQURA_START_DAYS + UMALQURA_YEAR_START_TABLE[year - UMALQURA_YEAR_START];
|
|
}
|
|
|
|
function umalquraMonthLength(year: number, month: number): number {
|
|
let idx = (year - UMALQURA_YEAR_START);
|
|
let mask = (0x01 << (11 - (month - 1)));
|
|
if ((UMALQURA_MONTHLENGTH[idx] & mask) === 0) {
|
|
return 29;
|
|
} else {
|
|
return 30;
|
|
}
|
|
}
|
|
|
|
function umalquraMonthStart(year: number, month: number): number {
|
|
let day = umalquraYearStart(year);
|
|
for (let i = 1; i < month; i++) {
|
|
day += umalquraMonthLength(year, i);
|
|
}
|
|
return day;
|
|
}
|
|
|
|
function umalquraYearLength(year: number): number {
|
|
return UMALQURA_YEAR_START_TABLE[year + 1 - UMALQURA_YEAR_START] - UMALQURA_YEAR_START_TABLE[year - UMALQURA_YEAR_START];
|
|
}
|
|
|
|
/**
|
|
* The Islamic calendar, also known as the "Hijri" calendar, is used throughout much of the Arab world.
|
|
* The Umalqura variant is primarily used in Saudi Arabia. It is a lunar calendar, based on astronomical
|
|
* calculations that predict the sighting of a crescent moon. Month and year lengths vary between years
|
|
* depending on these calculations.
|
|
* Learn more about the available Islamic calendars [here](https://cldr.unicode.org/development/development-process/design-proposals/islamic-calendar-types).
|
|
*/
|
|
export class IslamicUmalquraCalendar extends IslamicCivilCalendar {
|
|
identifier: CalendarIdentifier = 'islamic-umalqura';
|
|
|
|
constructor() {
|
|
super();
|
|
if (!UMALQURA_MONTHLENGTH) {
|
|
UMALQURA_MONTHLENGTH = new Uint16Array(Uint8Array.from(atob(UMALQURA_DATA), c => c.charCodeAt(0)).buffer);
|
|
}
|
|
|
|
if (!UMALQURA_YEAR_START_TABLE) {
|
|
UMALQURA_YEAR_START_TABLE = new Uint32Array(UMALQURA_YEAR_END - UMALQURA_YEAR_START + 1);
|
|
|
|
let yearStart = 0;
|
|
for (let year = UMALQURA_YEAR_START; year <= UMALQURA_YEAR_END; year++) {
|
|
UMALQURA_YEAR_START_TABLE[year - UMALQURA_YEAR_START] = yearStart;
|
|
for (let i = 1; i <= 12; i++) {
|
|
yearStart += umalquraMonthLength(year, i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fromJulianDay(jd: number): CalendarDate {
|
|
let days = jd - CIVIL_EPOC;
|
|
let startDays = umalquraYearStart(UMALQURA_YEAR_START);
|
|
let endDays = umalquraYearStart(UMALQURA_YEAR_END);
|
|
if (days < startDays || days > endDays) {
|
|
return super.fromJulianDay(jd);
|
|
} else {
|
|
let y = UMALQURA_YEAR_START - 1;
|
|
let m = 1;
|
|
let d = 1;
|
|
while (d > 0) {
|
|
y++;
|
|
d = days - umalquraYearStart(y) + 1;
|
|
let yearLength = umalquraYearLength(y);
|
|
if (d === yearLength) {
|
|
m = 12;
|
|
break;
|
|
} else if (d < yearLength) {
|
|
let monthLength = umalquraMonthLength(y, m);
|
|
m = 1;
|
|
while (d > monthLength) {
|
|
d -= monthLength;
|
|
m++;
|
|
monthLength = umalquraMonthLength(y, m);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return new CalendarDate(this, y, m, (days - umalquraMonthStart(y, m) + 1));
|
|
}
|
|
}
|
|
|
|
toJulianDay(date: AnyCalendarDate): number {
|
|
if (date.year < UMALQURA_YEAR_START || date.year > UMALQURA_YEAR_END) {
|
|
return super.toJulianDay(date);
|
|
}
|
|
|
|
return CIVIL_EPOC + umalquraMonthStart(date.year, date.month) + (date.day - 1);
|
|
}
|
|
|
|
getDaysInMonth(date: AnyCalendarDate): number {
|
|
if (date.year < UMALQURA_YEAR_START || date.year > UMALQURA_YEAR_END) {
|
|
return super.getDaysInMonth(date);
|
|
}
|
|
|
|
return umalquraMonthLength(date.year, date.month);
|
|
}
|
|
|
|
getDaysInYear(date: AnyCalendarDate): number {
|
|
if (date.year < UMALQURA_YEAR_START || date.year > UMALQURA_YEAR_END) {
|
|
return super.getDaysInYear(date);
|
|
}
|
|
|
|
return umalquraYearLength(date.year);
|
|
}
|
|
}
|