UEA-Prodem
This commit is contained in:
+5
@@ -0,0 +1,5 @@
|
||||
import type { HijriDate } from "./types.js";
|
||||
/** Convert a Gregorian date to a Hijri date. */
|
||||
export declare function toHijriDate(date: Date): HijriDate;
|
||||
/** Convert a Hijri date back to the Gregorian calendar. */
|
||||
export declare function toGregorianDate(hijri: HijriDate): Date;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { gregorianToHijri, hijriToGregorian } from "@tabby_ai/hijri-converter";
|
||||
import { clampGregorianDate, clampHijriDate, GREGORIAN_MIN_DATE, getGregorianDateParts, } from "./range.js";
|
||||
/** Convert a Gregorian date to a Hijri date. */
|
||||
export function toHijriDate(date) {
|
||||
const clamped = clampGregorianDate(date);
|
||||
const { year, month, day } = getGregorianDateParts(clamped);
|
||||
// gregorianToHijri uses 1-indexed months
|
||||
const hijri = gregorianToHijri({ year, month, day });
|
||||
return {
|
||||
year: hijri.year,
|
||||
monthIndex: hijri.month - 1, // Convert to 0-indexed
|
||||
day: hijri.day,
|
||||
};
|
||||
}
|
||||
/** Convert a Hijri date back to the Gregorian calendar. */
|
||||
export function toGregorianDate(hijri) {
|
||||
const clamped = clampHijriDate(hijri);
|
||||
// hijriToGregorian expects 1-indexed months. Probe down from the candidate
|
||||
// day to handle invalid month/day combinations without throwing.
|
||||
for (let day = clamped.day; day >= 1; day -= 1) {
|
||||
try {
|
||||
const gregorian = hijriToGregorian({
|
||||
year: clamped.year,
|
||||
month: clamped.monthIndex + 1,
|
||||
day,
|
||||
});
|
||||
return clampGregorianDate(new Date(gregorian.year, gregorian.month - 1, gregorian.day));
|
||||
}
|
||||
catch {
|
||||
// Try a lower day for months that only have 29 days.
|
||||
}
|
||||
}
|
||||
// Fallback to the minimum supported Gregorian date if conversion probing
|
||||
// somehow fails for all days.
|
||||
return new Date(GREGORIAN_MIN_DATE);
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export declare function getDaysInMonth(year: number, monthIndex: number): number;
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { toGregorianDate, toHijriDate } from "./conversion.js";
|
||||
import { clampHijriDate } from "./range.js";
|
||||
const MAX_DAY_IN_HIJRI_MONTH = 30;
|
||||
const MIN_DAY_IN_HIJRI_MONTH = 29;
|
||||
export function getDaysInMonth(year, monthIndex) {
|
||||
const clamped = clampHijriDate({ year, monthIndex, day: 1 });
|
||||
for (let day = MAX_DAY_IN_HIJRI_MONTH; day >= MIN_DAY_IN_HIJRI_MONTH; day -= 1) {
|
||||
const candidateDate = toGregorianDate({
|
||||
year: clamped.year,
|
||||
monthIndex: clamped.monthIndex,
|
||||
day,
|
||||
});
|
||||
const roundTrip = toHijriDate(candidateDate);
|
||||
if (roundTrip.year === clamped.year &&
|
||||
roundTrip.monthIndex === clamped.monthIndex &&
|
||||
roundTrip.day === day) {
|
||||
return day;
|
||||
}
|
||||
}
|
||||
return MIN_DAY_IN_HIJRI_MONTH;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import type { HijriDate } from "./types.js";
|
||||
type GregorianDateParts = {
|
||||
year: number;
|
||||
month: number;
|
||||
day: number;
|
||||
};
|
||||
/** Returns Gregorian date parts while preserving years < 100. */
|
||||
export declare function getGregorianDateParts(date: Date): GregorianDateParts;
|
||||
/** Internal Gregorian lower bound supported by the Hijri converter. */
|
||||
export declare const GREGORIAN_MIN_DATE: Date;
|
||||
/** Internal Gregorian upper bound supported by the Hijri converter. */
|
||||
export declare const GREGORIAN_MAX_DATE: Date;
|
||||
/** Clamp a Gregorian date to the supported Hijri conversion range. */
|
||||
export declare function clampGregorianDate(date: Date): Date;
|
||||
/** Returns whether the provided Gregorian date was clamped. */
|
||||
export declare function wasGregorianDateClamped(date: Date): boolean;
|
||||
/** Clamp a Hijri date to the supported range and normalize month overflow. */
|
||||
export declare function clampHijriDate(date: HijriDate): HijriDate;
|
||||
/** Returns whether the provided Hijri date was clamped. */
|
||||
export declare function wasHijriDateClamped(date: HijriDate): boolean;
|
||||
export {};
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
const MAX_HIJRI_DAY = 30;
|
||||
const MONTHS_PER_YEAR = 12;
|
||||
const HIJRI_MIN = { year: 1343, monthIndex: 0, day: 1 };
|
||||
const HIJRI_MAX = { year: 1500, monthIndex: 11, day: 30 };
|
||||
const GREGORIAN_MIN = { year: 1924, month: 8, day: 1 };
|
||||
const GREGORIAN_MAX = { year: 2077, month: 11, day: 16 };
|
||||
function compareGregorianDates(left, right) {
|
||||
if (left.year !== right.year) {
|
||||
return left.year - right.year;
|
||||
}
|
||||
if (left.month !== right.month) {
|
||||
return left.month - right.month;
|
||||
}
|
||||
return left.day - right.day;
|
||||
}
|
||||
function createDate(parts) {
|
||||
return new Date(parts.year, parts.month - 1, parts.day);
|
||||
}
|
||||
function normalizeHijriMonth(year, monthIndex) {
|
||||
let normalizedYear = year + Math.trunc(monthIndex / MONTHS_PER_YEAR);
|
||||
let normalizedMonth = monthIndex % MONTHS_PER_YEAR;
|
||||
if (normalizedMonth < 0) {
|
||||
normalizedMonth += MONTHS_PER_YEAR;
|
||||
normalizedYear -= 1;
|
||||
}
|
||||
return { year: normalizedYear, monthIndex: normalizedMonth };
|
||||
}
|
||||
/** Returns Gregorian date parts while preserving years < 100. */
|
||||
export function getGregorianDateParts(date) {
|
||||
const useUTC = date.getFullYear() < 100;
|
||||
return {
|
||||
year: useUTC ? date.getUTCFullYear() : date.getFullYear(),
|
||||
month: (useUTC ? date.getUTCMonth() : date.getMonth()) + 1,
|
||||
day: useUTC ? date.getUTCDate() : date.getDate(),
|
||||
};
|
||||
}
|
||||
/** Internal Gregorian lower bound supported by the Hijri converter. */
|
||||
export const GREGORIAN_MIN_DATE = createDate(GREGORIAN_MIN);
|
||||
/** Internal Gregorian upper bound supported by the Hijri converter. */
|
||||
export const GREGORIAN_MAX_DATE = createDate(GREGORIAN_MAX);
|
||||
/** Clamp a Gregorian date to the supported Hijri conversion range. */
|
||||
export function clampGregorianDate(date) {
|
||||
const parts = getGregorianDateParts(date);
|
||||
if (compareGregorianDates(parts, GREGORIAN_MIN) < 0) {
|
||||
return createDate(GREGORIAN_MIN);
|
||||
}
|
||||
if (compareGregorianDates(parts, GREGORIAN_MAX) > 0) {
|
||||
return createDate(GREGORIAN_MAX);
|
||||
}
|
||||
return date;
|
||||
}
|
||||
/** Returns whether the provided Gregorian date was clamped. */
|
||||
export function wasGregorianDateClamped(date) {
|
||||
return clampGregorianDate(date) !== date;
|
||||
}
|
||||
/** Clamp a Hijri date to the supported range and normalize month overflow. */
|
||||
export function clampHijriDate(date) {
|
||||
const normalized = normalizeHijriMonth(date.year, date.monthIndex);
|
||||
const candidate = {
|
||||
year: normalized.year,
|
||||
monthIndex: normalized.monthIndex,
|
||||
day: Math.min(Math.max(date.day, 1), MAX_HIJRI_DAY),
|
||||
};
|
||||
if (candidate.year < HIJRI_MIN.year) {
|
||||
return { ...HIJRI_MIN };
|
||||
}
|
||||
if (candidate.year > HIJRI_MAX.year) {
|
||||
return { ...HIJRI_MAX };
|
||||
}
|
||||
if (candidate.year === HIJRI_MIN.year &&
|
||||
candidate.monthIndex < HIJRI_MIN.monthIndex) {
|
||||
return { ...HIJRI_MIN };
|
||||
}
|
||||
if (candidate.year === HIJRI_MAX.year &&
|
||||
candidate.monthIndex > HIJRI_MAX.monthIndex) {
|
||||
return { ...HIJRI_MAX };
|
||||
}
|
||||
if (candidate.year === HIJRI_MIN.year &&
|
||||
candidate.monthIndex === HIJRI_MIN.monthIndex &&
|
||||
candidate.day < HIJRI_MIN.day) {
|
||||
return { ...HIJRI_MIN };
|
||||
}
|
||||
if (candidate.year === HIJRI_MAX.year &&
|
||||
candidate.monthIndex === HIJRI_MAX.monthIndex &&
|
||||
candidate.day > HIJRI_MAX.day) {
|
||||
return { ...HIJRI_MAX };
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
/** Returns whether the provided Hijri date was clamped. */
|
||||
export function wasHijriDateClamped(date) {
|
||||
const clamped = clampHijriDate(date);
|
||||
return (clamped.year !== date.year ||
|
||||
clamped.monthIndex !== date.monthIndex ||
|
||||
clamped.day !== date.day);
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
export type HijriDate = {
|
||||
year: number;
|
||||
monthIndex: number;
|
||||
day: number;
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
Reference in New Issue
Block a user