@ui5/webcomponents-localization
Version:
Localization for UI5 Web Components
367 lines (357 loc) • 12.6 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2024 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
// Provides class sap.ui.core.date.Islamic
import UniversalDate from "./UniversalDate.js";
import CalendarType from "../CalendarType.js";
import Log from "../../../base/Log.js";
import Formatting from "../../../base/i18n/Formatting.js";
import _Calendars from "./_Calendars.js";
/**
* The Islamic date class
*
* @class
* The islamic date does conversion of day, month and year values based on tabular data indicating the start of a month.
* In case no tabular data is available for the date, a fallback calculated date will be used.
*
* @private
* @alias sap.ui.core.date.Islamic
* @extends sap.ui.core.date.UniversalDate
*/
var Islamic = UniversalDate.extend("sap.ui.core.date.Islamic", /** @lends sap.ui.core.date.Islamic.prototype */{
constructor: function () {
var aArgs = arguments;
if (aArgs.length > 1) {
aArgs = toGregorianArguments(aArgs);
}
this.oDate = this.createDate(Date, aArgs);
this.sCalendarType = CalendarType.Islamic;
}
});
Islamic.UTC = function () {
var aArgs = toGregorianArguments(arguments);
return Date.UTC.apply(Date, aArgs);
};
Islamic.now = function () {
return Date.now();
};
var BASE_YEAR = 1400,
// used for 2 digits "year" related method
GREGORIAN_EPOCH_DAYS = 1721425.5,
// Julian days since noon on January 1, 4713 BC
ISLAMIC_EPOCH_DAYS = 1948439.5,
// Julian days since noon on January 1, 4713 BC
ISLAMIC_MILLIS = -42521587200000,
// 7/16/622
ONE_DAY = 86400000;
var oCustomizationMap = null;
// Currently those are the two supported Islamic Calendar types in the ABAP
var aSupportedIslamicCalendarTypes = ["A", "B"];
/**
* Calculate islamic date from gregorian.
*
* @param {object} oGregorian A JS object containing day, month and year in the gregorian calendar
* @returns {object} The islamic date object created
* @private
*/
function toIslamic(oGregorian) {
var iGregorianYear = oGregorian.year,
iGregorianMonth = oGregorian.month,
iGregorianDay = oGregorian.day,
iIslamicYear,
iIslamicMonth,
iIslamicDay,
iMonths,
iDays,
iLeapAdj,
iJulianDay;
iLeapAdj = 0;
if (iGregorianMonth + 1 > 2) {
iLeapAdj = isGregorianLeapYear(iGregorianYear) ? -1 : -2;
}
iJulianDay = GREGORIAN_EPOCH_DAYS - 1 + 365 * (iGregorianYear - 1) + Math.floor((iGregorianYear - 1) / 4) + -Math.floor((iGregorianYear - 1) / 100) + Math.floor((iGregorianYear - 1) / 400) + Math.floor((367 * (iGregorianMonth + 1) - 362) / 12 + iLeapAdj + iGregorianDay);
iJulianDay = Math.floor(iJulianDay) + 0.5;
iDays = iJulianDay - ISLAMIC_EPOCH_DAYS;
iMonths = Math.floor(iDays / 29.530588853); // day/CalendarAstronomer.SYNODIC_MONTH
if (iMonths < 0) {
//negative means Islamic date before the Islamic's calendar start. So we do not apply customization.
iIslamicYear = Math.floor(iMonths / 12) + 1;
iIslamicMonth = iMonths % 12;
if (iIslamicMonth < 0) {
iIslamicMonth += 12;
}
iIslamicDay = iDays - monthStart(iIslamicYear, iIslamicMonth) + 1;
} else {
/* Guess the month start.
* Always also check the next month, since customization can
* differ. It can differ for not more than 3 days. so that
* checking the next month is enough.
*/
iMonths++;
/*
* Check the true month start for the given month. If it is
* later, check the previous month, until a suitable is found.
*/
while (getCustomMonthStartDays(iMonths) > iDays) {
iMonths--;
}
iIslamicYear = Math.floor(iMonths / 12) + 1;
iIslamicMonth = iMonths % 12;
iIslamicDay = iDays - getCustomMonthStartDays(12 * (iIslamicYear - 1) + iIslamicMonth) + 1;
}
return {
day: iIslamicDay,
month: iIslamicMonth,
year: iIslamicYear
};
}
/**
* Calculate gregorian date from islamic.
*
* @param {object} oIslamic A JS object containing day, month and year in the islamic calendar
* @returns {object} The gregorian date object created
* @private
*/
function toGregorian(oIslamic) {
var iIslamicYear = oIslamic.year,
iIslamicMonth = oIslamic.month,
iIslamicDate = oIslamic.day,
/* Islamic Calendar starts from 0001/0/1 (19 July 622 AD), so for any date before it customization is not needed */
iMonthStart = iIslamicYear < 1 ? monthStart(iIslamicYear, iIslamicMonth) : getCustomMonthStartDays(12 * (iIslamicYear - 1) + iIslamicMonth),
iJulianDay = iIslamicDate + iMonthStart + ISLAMIC_EPOCH_DAYS - 1,
iJulianDayNoon = Math.floor(iJulianDay - 0.5) + 0.5,
iDaysSinceGregorianEpoch = iJulianDayNoon - GREGORIAN_EPOCH_DAYS,
iQuadricent = Math.floor(iDaysSinceGregorianEpoch / 146097),
iQuadricentNormalized = mod(iDaysSinceGregorianEpoch, 146097),
iCent = Math.floor(iQuadricentNormalized / 36524),
iCentNormalized = mod(iQuadricentNormalized, 36524),
iQuad = Math.floor(iCentNormalized / 1461),
iQuadNormalized = mod(iCentNormalized, 1461),
iYearIndex = Math.floor(iQuadNormalized / 365),
iYear = iQuadricent * 400 + iCent * 100 + iQuad * 4 + iYearIndex,
iMonth,
iDay,
iGregorianYearStartDays,
iDayOfYear,
tjd,
tjd2,
iLeapAdj,
iLeapAdj2;
if (!(iCent == 4 || iYearIndex == 4)) {
iYear++;
}
iGregorianYearStartDays = GREGORIAN_EPOCH_DAYS + 365 * (iYear - 1) + Math.floor((iYear - 1) / 4) - Math.floor((iYear - 1) / 100) + Math.floor((iYear - 1) / 400);
iDayOfYear = iJulianDayNoon - iGregorianYearStartDays;
tjd = GREGORIAN_EPOCH_DAYS - 1 + 365 * (iYear - 1) + Math.floor((iYear - 1) / 4) - Math.floor((iYear - 1) / 100) + Math.floor((iYear - 1) / 400) + Math.floor(739 / 12 + (isGregorianLeapYear(iYear) ? -1 : -2) + 1);
iLeapAdj = 0;
if (iJulianDayNoon < tjd) {
iLeapAdj = 0;
} else {
iLeapAdj = isGregorianLeapYear(iYear) ? 1 : 2;
}
iMonth = Math.floor(((iDayOfYear + iLeapAdj) * 12 + 373) / 367);
tjd2 = GREGORIAN_EPOCH_DAYS - 1 + 365 * (iYear - 1) + Math.floor((iYear - 1) / 4) - Math.floor((iYear - 1) / 100) + Math.floor((iYear - 1) / 400);
iLeapAdj2 = 0;
if (iMonth > 2) {
iLeapAdj2 = isGregorianLeapYear(iYear) ? -1 : -2;
}
tjd2 += Math.floor((367 * iMonth - 362) / 12 + iLeapAdj2 + 1);
iDay = iJulianDayNoon - tjd2 + 1;
return {
day: iDay,
month: iMonth - 1,
year: iYear
};
}
function toGregorianArguments(aArgs) {
var aGregorianArgs = Array.prototype.slice.call(aArgs),
oIslamic,
oGregorian;
oIslamic = {
year: aArgs[0],
month: aArgs[1],
day: aArgs[2] !== undefined ? aArgs[2] : 1
};
oGregorian = toGregorian(oIslamic);
aGregorianArgs[0] = oGregorian.year;
aGregorianArgs[1] = oGregorian.month;
aGregorianArgs[2] = oGregorian.day;
return aGregorianArgs;
}
function initCustomizationMap() {
var sDateFormat, oCustomizationJSON;
oCustomizationMap = {};
sDateFormat = Formatting.getABAPDateFormat();
sDateFormat = _isSupportedIslamicCalendarType(sDateFormat) ? sDateFormat : "A"; // set "A" as a fall-back format always
oCustomizationJSON = Formatting.getCustomIslamicCalendarData();
oCustomizationJSON = oCustomizationJSON || [];
if (!oCustomizationJSON.length) {
Log.warning("No calendar customizations.");
return;
}
oCustomizationJSON.forEach(function (oEntry) {
if (oEntry.dateFormat === sDateFormat) {
var date = parseDate(oEntry.gregDate);
// no need to use UI5Date.getInstance as only the UTC timestamp is used
var iGregorianDate = new Date(Date.UTC(date.year, date.month - 1, date.day));
var iMillis = iGregorianDate.getTime();
var iIslamicMonthStartDays = (iMillis - ISLAMIC_MILLIS) / ONE_DAY;
date = parseDate(oEntry.islamicMonthStart);
var iIslamicMonths = (date.year - 1) * 12 + date.month - 1;
oCustomizationMap[iIslamicMonths] = iIslamicMonthStartDays;
}
});
Log.info("Working with date format: [" + sDateFormat + "] and customization: " + JSON.stringify(oCustomizationJSON));
}
function parseDate(sDate) {
return {
year: parseInt(sDate.substr(0, 4)),
month: parseInt(sDate.substr(4, 2)),
day: parseInt(sDate.substr(6, 2))
};
}
function getCustomMonthStartDays(months) {
if (!oCustomizationMap) {
initCustomizationMap();
}
var iIslamicMonthStartDays = oCustomizationMap[months];
if (!iIslamicMonthStartDays) {
var year = Math.floor(months / 12) + 1;
var month = months % 12;
iIslamicMonthStartDays = monthStart(year, month);
}
return iIslamicMonthStartDays;
}
function monthStart(year, month) {
return Math.ceil(29.5 * month) + (year - 1) * 354 + Math.floor((3 + 11 * year) / 30.0);
}
function mod(a, b) {
return a - b * Math.floor(a / b);
}
function isGregorianLeapYear(iYear) {
return !(iYear % 400) || !(iYear % 4) && !!(iYear % 100);
}
function _isSupportedIslamicCalendarType(sCalendarType) {
return aSupportedIslamicCalendarTypes.indexOf(sCalendarType) !== -1;
}
/**
* Get the islamic date from the this.oDate.
* @returns {object} The islamic date object created
*/
Islamic.prototype._getIslamic = function () {
return toIslamic({
day: this.oDate.getDate(),
month: this.oDate.getMonth(),
year: this.oDate.getFullYear()
});
};
/**
* Set the islamic date to the current this.oDate object.
* @param {object} oIslamic A JS object containing day, month and year in the islamic calendar
* @returns {number} <code>this</code> to allow method chaining
*/
Islamic.prototype._setIslamic = function (oIslamic) {
var oGregorian = toGregorian(oIslamic);
return this.oDate.setFullYear(oGregorian.year, oGregorian.month, oGregorian.day);
};
/**
* Get the islamic date from the this.oDate.
* @returns {object} The UTC date object created
*/
Islamic.prototype._getUTCIslamic = function () {
return toIslamic({
day: this.oDate.getUTCDate(),
month: this.oDate.getUTCMonth(),
year: this.oDate.getUTCFullYear()
});
};
/**
* Set the islamic date to the current this.oDate object.
* @param {object} oIslamic A JS object containing day, month and year in the islamic calendar
* @returns {number} <code>this</code> to allow method chaining
*/
Islamic.prototype._setUTCIslamic = function (oIslamic) {
var oGregorian = toGregorian(oIslamic);
return this.oDate.setUTCFullYear(oGregorian.year, oGregorian.month, oGregorian.day);
};
/*
* Override setters and getters specific to the islamic date
*/
Islamic.prototype.getDate = function (iDate) {
return this._getIslamic().day;
};
Islamic.prototype.getMonth = function () {
return this._getIslamic().month;
};
Islamic.prototype.getYear = function () {
return this._getIslamic().year - BASE_YEAR;
};
Islamic.prototype.getFullYear = function () {
return this._getIslamic().year;
};
Islamic.prototype.setDate = function (iDate) {
var oIslamic = this._getIslamic();
oIslamic.day = iDate;
return this._setIslamic(oIslamic);
};
Islamic.prototype.setMonth = function (iMonth, iDay) {
var oIslamic = this._getIslamic();
oIslamic.month = iMonth;
if (iDay !== undefined) {
oIslamic.day = iDay;
}
return this._setIslamic(oIslamic);
};
Islamic.prototype.setYear = function (iYear) {
var oIslamic = this._getIslamic();
oIslamic.year = iYear + BASE_YEAR;
return this._setIslamic(oIslamic);
};
Islamic.prototype.setFullYear = function (iYear, iMonth, iDay) {
var oIslamic = this._getIslamic();
oIslamic.year = iYear;
if (iMonth !== undefined) {
oIslamic.month = iMonth;
}
if (iDay !== undefined) {
oIslamic.day = iDay;
}
return this._setIslamic(oIslamic);
};
Islamic.prototype.getUTCDate = function (iDate) {
return this._getUTCIslamic().day;
};
Islamic.prototype.getUTCMonth = function () {
return this._getUTCIslamic().month;
};
Islamic.prototype.getUTCFullYear = function () {
return this._getUTCIslamic().year;
};
Islamic.prototype.setUTCDate = function (iDate) {
var oIslamic = this._getUTCIslamic();
oIslamic.day = iDate;
return this._setUTCIslamic(oIslamic);
};
Islamic.prototype.setUTCMonth = function (iMonth, iDay) {
var oIslamic = this._getUTCIslamic();
oIslamic.month = iMonth;
if (iDay !== undefined) {
oIslamic.day = iDay;
}
return this._setUTCIslamic(oIslamic);
};
Islamic.prototype.setUTCFullYear = function (iYear, iMonth, iDay) {
var oIslamic = this._getUTCIslamic();
oIslamic.year = iYear;
if (iMonth !== undefined) {
oIslamic.month = iMonth;
}
if (iDay !== undefined) {
oIslamic.day = iDay;
}
return this._setUTCIslamic(oIslamic);
};
_Calendars.set(CalendarType.Islamic, Islamic);
export default Islamic;