Skip to content

Commit

Permalink
feat: Added Hebrew Calendar System, hebrew tests, fix typescript issu…
Browse files Browse the repository at this point in the history
…es, code cleanup, introduced new isLeapYear function (rewrites dayjs plugin to work on all calendar systems)
  • Loading branch information
amirhmoradi committed Jun 2, 2023
1 parent 9734aac commit fd550e1
Show file tree
Hide file tree
Showing 26 changed files with 485 additions and 62 deletions.
23 changes: 15 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@
---


Day.js Calendar Systems Plugin extends Day.js library to allow the use of different calendar systems.
Day.js Calendar Systems Plugin extends Day.js library to allow the use of different non-gregorian calendar systems like:
* Persian (a.k.a.: Jalaali, Shamsi, Khorshidi),
* Arabic (a.k.a: Hijri, Islamic, Umalqura, Ghamari),
* Hebrew (a.k.a: Jewish),
* and more to come (PRs are welcome).

Dayjs initially supports only the Gregorian calendar, making it uncompatible for more than 200 milion users worldwide.
With this plugin, Day.js will be available to more than 200 million additional users worldwide (Estimated number of non-gregorian calendar users).

With the `@calidy/dayjs-calendarsystems` plugin, we bring the capacity to run and use all non-gregorian calendar systems (like persian, islamic, hebrew, ethiopian, indian,... ) to Dayjs.
With the `@calidy/dayjs-calendarsystems` plugin, we bring the capacity to run and use all non-gregorian calendar systems to Dayjs.


---
Expand All @@ -32,11 +36,14 @@ With the `@calidy/dayjs-calendarsystems` plugin, we bring the capacity to run an
- No need for hacks, use dayjs apis in the standard way.
- Small and light plugin, no overhead.
- Convert between different calendar systems.
- Default Gregorian calendar system included.
- Persian Calendar system available.
- Islamic (Hijri, Umalqura) Calendar system. Note: we will use the default "islamic-umalqura" calendar system for "islamic" calendar system.
- **[WIP]** Hebrew (Jewish) Calendar system.
- **[WIP]** Ethiopian Calendar system.
- 🌍 🗓️ 📅 Default Gregorian calendar system included.
- 🌍 🗓️ 🇮🇷 Persian Calendar system available.
- 🌍 🗓️ 🇸🇦 Islamic (Hijri, Umalqura) Calendar system. Note: we will use the default "islamic-umalqura" calendar system for "islamic" calendar system.
- 🌍 🗓️ 🇮🇱 Hebrew (Jewish) Calendar system.
- 🌍 🗓️ 🇪🇹 **[WIP]** Ethiopian Calendar system.
- 🌍 🗓️ 🇮🇳 **[TODO]** Indian Calendar system.
- 🌍 🗓️ 🇨🇳 **[TODO]** Chinese Calendar system.
- 🌍 ✅ Fixes translations of month names in Dayjs for non-gregorian and gregorian calendar systems (This is based on my knowledge, please PR to add more fixes).
- **[TODO]** Parse date strings from different calendar systems
- **[TODO]** Add more tests for all Dayjs Plugins

Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,21 @@
],
"size-limit": [
{
"limit": "2.99 KB",
"limit": "3.99 KB",
"path": "dayjs-calendarsystems.umd.min.js",
"ignore": [
"dayjs"
]
},
{
"limit": "2.99 KB",
"limit": "3.99 KB",
"path": "dayjs-calendarsystems.cjs.min.js",
"ignore": [
"dayjs"
]
},
{
"limit": "2.99 KB",
"limit": "3.99 KB",
"path": "dayjs-calendarsystems.esm.min.js",
"ignore": [
"dayjs"
Expand Down
13 changes: 8 additions & 5 deletions src/calendarSystems/CalendarSystemBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,7 @@ export default class CalendarSystemBase {
this.locale = locale;
this.intlCalendar = "gregory";
this.firstMonthNameEnglish = "January";
this.monthNamesLocalized = generateMonthNames(
locale,
"gregory",
"January"
);
this.monthNamesLocalized = generateMonthNames(locale, "gregory", "January");
}

convertFromGregorian(date) {
Expand All @@ -34,16 +30,23 @@ export default class CalendarSystemBase {
);
}

// Expects a zero-based month index
// Returns a zero-based month index
convertToGregorian(date) {
throw new Error(
"Method convertToGregorian must be implemented by subclass"
);
}

// Expects a zero-based month index
convertToJulian(date) {
throw new Error("Method convertToJulian must be implemented by subclass");
}

isLeapYear(calendarYear) {
throw new Error("Method isLeapYear must be implemented by subclass");
}

monthNames(locale, calendar, firstMonthName) {
throw new Error("Method monthNames must be implemented by subclass");
}
Expand Down
10 changes: 5 additions & 5 deletions src/calendarSystems/GregoryCalendarSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,7 @@ export default class GregoryCalendarSystem extends CalendarSystemBase {
this.locale = locale;
this.intlCalendar = "gregory";
this.firstMonthNameEnglish = "January";
this.monthNamesLocalized = generateMonthNames(
locale,
"gregory",
"January"
);
this.monthNamesLocalized = generateMonthNames(locale, "gregory", "January");
}

convertToJulian(calendarYear, calendarMonth, calendarDay) {
Expand Down Expand Up @@ -80,6 +76,10 @@ export default class GregoryCalendarSystem extends CalendarSystemBase {
};
}

isLeapYear() {
return year % 4 == 0 && !(year % 100 == 0 && year % 400 != 0);
}

monthNames(locale = "en", calendar = "gregory", firstMonthName = "January") {
return generateMonthNames(locale, calendar, firstMonthName);
}
Expand Down
109 changes: 109 additions & 0 deletions src/calendarSystems/HebrewCalendarSystem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* Hebrew Calendar System
*
* @file HebrewCalendarSystem.js
* @project dayjs-calendarsystems
* @license see LICENSE file included in the project
* @author Calidy.com, Amir Moradi (https://calidy.com/)
* @description see README.md file included in the project
*
*/

import CalendarSystemBase from "./CalendarSystemBase";
import * as CalendarUtils from "../calendarUtils/fourmilabCalendar";
import { generateMonthNames } from "../calendarUtils/IntlUtils";

export default class HebrewCalendarSystem extends CalendarSystemBase {
constructor(locale = "en") {
super();
this.firstDayOfWeek = 0; // Saturday
this.locale = locale;
this.intlCalendar = "hebrew";
this.firstMonthNameEnglish = "Nisan";
this.monthNamesLocalized = generateMonthNames(locale, "hebrew", "Nisan");
}

// Expects a zero-based month index
convertToJulian(calendarYear, calendarMonth, calendarDay) {
// calendarMonth = calendarMonth+1 because the *_to_jd function month is 1-based
return CalendarUtils.hebrew_to_jd(
calendarYear,
calendarMonth + 1,
calendarDay
);
}

convertFromGregorian(date) {
// extract year, month, day from date.
// date can be of type Date, Dayjs or object.
// if date is object, it should have year, month and day properties.
// if date is Dayjs, it should have $y, $M and $D properties.
// if date is Date, it should have getFullYear(), getMonth() and getDate() methods.
// if date is string, it should be in ISO format.
// if date is number, it should be in milliseconds.
// if date is undefined, it should be now.
// if date is null, it should be now.
if (date === undefined || date === null) {
date = new Date();
} else if (typeof date === "string") {
date = new Date(date);
} else if (typeof date === "number") {
date = new Date(date);
} else if (date instanceof Date) {
// do nothing
} else if (
date.$y !== undefined &&
date.$M !== undefined &&
date.$D !== undefined
) {
date = new Date(date.$y, date.$M, date.$D);
} else if (
date.year !== undefined &&
date.month !== undefined &&
date.day !== undefined
) {
date = new Date(date.year, date.month, date.day);
} else {
throw new Error("Invalid date");
}

const julianDay = CalendarUtils.gregorian_to_jd(
date.getFullYear(),
date.getMonth() + 1,
date.getDate()
);
const convertedDateArray = CalendarUtils.jd_to_hebrew(julianDay);
return {
year: convertedDateArray[0],
month: convertedDateArray[1] - 1, // -1 because the Persian month is 0-based
day: convertedDateArray[2],
};
}

// Expects a zero-based month index
// Returns a zero-based month index
convertToGregorian(calendarYear, calendarMonth, calendarDay) {
const julianDay = this.convertToJulian(
calendarYear,
calendarMonth,
calendarDay
);
const gregorianDateArray = CalendarUtils.jd_to_gregorian(julianDay);
return {
year: gregorianDateArray[0],
month: gregorianDateArray[1] - 1, // -1 because the Gregorian month is 0-based
day: gregorianDateArray[2],
};
}

isLeapYear() {
return CalendarUtils.hebrew_leap(this.$y);
}

monthNames(locale = "en", calendar = "hebrew", firstMonthName = "Nisan") {
return generateMonthNames(locale, calendar, firstMonthName);
}
getLocalizedMonthName(monthIndex) {
return this.monthNamesLocalized[monthIndex];
}
}
11 changes: 10 additions & 1 deletion src/calendarSystems/HijriCalendarSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export default class HijriCalendarSystem extends CalendarSystemBase {
};
}

// Returns a zero-based month index
// Expects a zero-based month index
convertToGregorian(calendarYear, calendarMonth, calendarDay) {
const julianDay = this.convertToJulian(
Expand All @@ -98,7 +99,15 @@ export default class HijriCalendarSystem extends CalendarSystemBase {
};
}

monthNames(locale = "en", calendar = "islamic-umalqura", firstMonthName = "Muharram") {
isLeapYear() {
return CalendarUtils.leap_islamic(this.$y);
}

monthNames(
locale = "en",
calendar = "islamic-umalqura",
firstMonthName = "Muharram"
) {
return generateMonthNames(locale, calendar, firstMonthName);
}
getLocalizedMonthName(monthIndex) {
Expand Down
4 changes: 4 additions & 0 deletions src/calendarSystems/PersianCalendarSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ export default class PersianCalendarSystem extends CalendarSystemBase {
};
}

isLeapYear() {
return CalendarUtils.leap_persiana(this.$y);
}

monthNames(
locale = "en",
calendar = "persian",
Expand Down
2 changes: 1 addition & 1 deletion src/calendarUtils/IntlUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function getLocalizedMonthName(monthIndex, locale = "en") {

function getMonthNames(locale, calendar = "persian") {
const cacheKey = `${locale}-${calendar}`;

if (!monthNamesCache[cacheKey]) {
const monthNames = [];
for (let i = 0; i < 12; i++) {
Expand Down
14 changes: 7 additions & 7 deletions src/calendarUtils/fourmilabCalendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -908,7 +908,7 @@ function hebrew_year_months(year)

function hebrew_delay_1(year)
{
var months, days, parts;
var months, day, parts;

months = Math.floor(((235 * year) - 234) / 19);
parts = 12084 + (13753 * months);
Expand Down Expand Up @@ -2197,9 +2197,9 @@ export {
gregorian_to_jd,
// hebrew_delay_1,
// hebrew_delay_2,
// hebrew_leap,
hebrew_leap,
// hebrew_month_days,
// hebrew_to_jd,
hebrew_to_jd,
// hebrew_year_days,
// hebrew_year_months,
// indian_civil_to_jd,
Expand All @@ -2208,7 +2208,7 @@ export {
// iso_to_julian,
// jd_to_french_revolutionary,
jd_to_gregorian,
// jd_to_hebrew,
jd_to_hebrew,
// jd_to_indian_civil,
jd_to_islamic,
// jd_to_iso,
Expand All @@ -2220,11 +2220,11 @@ export {
// jd_to_persian,
jd_to_persiana,
// julian_to_jd,
// leap_gregorian,
// leap_islamic,
leap_gregorian,
leap_islamic,
// leap_julian,
// leap_persian,
// leap_persiana,
leap_persiana,
// mayan_count_to_jd,
// n_weeks,
// nearest_weekday,
Expand Down
Loading

0 comments on commit fd550e1

Please sign in to comment.