diff --git a/ethiopic.d.ts b/ethiopic.d.ts new file mode 100644 index 000000000..25c8b9938 --- /dev/null +++ b/ethiopic.d.ts @@ -0,0 +1 @@ +export * from "./dist/cjs/ethiopic/index.d.ts"; diff --git a/ethiopic.js b/ethiopic.js new file mode 100644 index 000000000..8e859f82f --- /dev/null +++ b/ethiopic.js @@ -0,0 +1,4 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ +/* eslint-disable no-undef */ +const ethiopic = require("./dist/cjs/ethiopic/index.js"); +module.exports = ethiopic; diff --git a/examples/Ethiopic.test.tsx b/examples/Ethiopic.test.tsx new file mode 100644 index 000000000..c9f3964d1 --- /dev/null +++ b/examples/Ethiopic.test.tsx @@ -0,0 +1,16 @@ +import React from "react"; + +import { render } from "@/test/render"; + +import { Ethiopic } from "./Ethiopic.jsx"; + +const today = new Date(2021, 10, 25); + +beforeAll(() => jest.setSystemTime(today)); +afterAll(() => jest.useRealTimers()); + +beforeEach(() => { + render(); +}); + +test.todo("should render the Ethiopic calendar"); diff --git a/examples/Ethiopic.tsx b/examples/Ethiopic.tsx new file mode 100644 index 000000000..0f3aef54f --- /dev/null +++ b/examples/Ethiopic.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +import { DayPicker } from "react-day-picker/ethiopic"; + +export function Ethiopic() { + return ; +} diff --git a/examples/index.ts b/examples/index.ts index 0e1910563..bde95af30 100644 --- a/examples/index.ts +++ b/examples/index.ts @@ -18,6 +18,7 @@ export * from "./DisableNavigation"; export * from "./Dropdown"; export * from "./DropdownMonths"; export * from "./DropdownMultipleMonths"; +export * from "./Ethiopic"; export * from "./Fixedweeks"; export * from "./FocusRecursive"; export * from "./Footer"; diff --git a/package.json b/package.json index fc084be83..ec47e5ad6 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,16 @@ "default": "./dist/cjs/persian.js" } }, + "./ethiopic": { + "import": { + "types": "./dist/esm/ethiopic/index.d.ts", + "default": "./dist/esm/ethiopic/index.js" + }, + "require": { + "types": "./dist/cjs/ethiopic/index.d.ts", + "default": "./dist/cjs/ethiopic/index.js" + } + }, "./locale": { "import": { "types": "./dist/esm/locale.d.ts", diff --git a/react-day-picker.code-workspace b/react-day-picker.code-workspace index 0c71bb1e8..51f204563 100644 --- a/react-day-picker.code-workspace +++ b/react-day-picker.code-workspace @@ -57,6 +57,7 @@ "activityBar.inactiveForeground": "#d8d8d877", "activityBarBadge.background": "#dd006f", "activityBarBadge.foreground": "#e7e7e7" - } + }, + "cSpell.words": ["ethiopic", "gregorian", "julian"] } } diff --git a/src/ethiopic/index.test.tsx b/src/ethiopic/index.test.tsx new file mode 100644 index 000000000..d26b38a59 --- /dev/null +++ b/src/ethiopic/index.test.tsx @@ -0,0 +1,5 @@ +describe("DayPicker", () => { + test.todo("should render with default props"); + test.todo("should render with custom locale"); + test.todo("should render with custom numerals"); +}); diff --git a/src/ethiopic/index.tsx b/src/ethiopic/index.tsx new file mode 100644 index 000000000..c0c637325 --- /dev/null +++ b/src/ethiopic/index.tsx @@ -0,0 +1,71 @@ +import React from "react"; + +import type { Locale } from "date-fns"; + +import { + DateLib, + DateLibOptions, + DayPicker as DayPickerComponent +} from "../index.js"; +import type { DayPickerProps } from "../types/props.js"; + +import * as ethiopicDateLib from "./lib/index.js"; + +/** + * Render the Persian Calendar. + * + * @see https://daypicker.dev/docs/localization#persian-calendar + */ +export function DayPicker( + props: DayPickerProps & { + /** + * The locale to use in the calendar. + * + * @default `am-ET` + */ + locale?: Locale; + /** + * The numeral system to use when formatting dates. + * + * - `latn`: Latin (Western Arabic) + * - `arab`: Arabic-Indic + * - `arabext`: Eastern Arabic-Indic (Persian) + * - `deva`: Devanagari + * - `ethio`: Ethiopic + * - `beng`: Bengali + * - `guru`: Gurmukhi + * - `gujr`: Gujarati + * - `orya`: Oriya + * - `tamldec`: Tamil + * - `telu`: Telugu + * - `knda`: Kannada + * - `mlym`: Malayalam + * + * @defaultValue `ethio` Eastern Arabic-Indic (Persian) + * @see https://daypicker.dev/docs/translation#numeral-systems + */ + numerals?: DayPickerProps["numerals"]; + } +) { + const dateLib = getDateLib({ + locale: props.locale, + weekStartsOn: props.broadcastCalendar ? 1 : props.weekStartsOn, + firstWeekContainsDate: props.firstWeekContainsDate, + useAdditionalWeekYearTokens: props.useAdditionalWeekYearTokens, + useAdditionalDayOfYearTokens: props.useAdditionalDayOfYearTokens, + timeZone: props.timeZone + }); + return ( + + ); +} + +/** Returns the date library used in the calendar. */ +export const getDateLib = (options?: DateLibOptions) => { + return new DateLib(options, ethiopicDateLib); +}; diff --git a/src/ethiopic/lib/addDays.test.ts b/src/ethiopic/lib/addDays.test.ts new file mode 100644 index 000000000..7bc0f6a0c --- /dev/null +++ b/src/ethiopic/lib/addDays.test.ts @@ -0,0 +1 @@ +test.todo("addDays should correctly add days to an Ethiopic date"); diff --git a/src/ethiopic/lib/addDays.ts b/src/ethiopic/lib/addDays.ts new file mode 100644 index 000000000..91974defc --- /dev/null +++ b/src/ethiopic/lib/addDays.ts @@ -0,0 +1,12 @@ +/** + * Adds days to an Ethiopic date + * + * @param {Date} date - The original date + * @param {number} amount - The number of days to add + * @returns {Date} The new date + */ +export function addDays(date: Date, amount: number): Date { + const julianDay = Math.floor(date.getTime() / 86400000 + 2440587.5); + const newJulianDay = julianDay + amount; + return new Date((newJulianDay - 2440587.5) * 86400000); +} diff --git a/src/ethiopic/lib/addMonths.test.ts b/src/ethiopic/lib/addMonths.test.ts new file mode 100644 index 000000000..1b8806873 --- /dev/null +++ b/src/ethiopic/lib/addMonths.test.ts @@ -0,0 +1 @@ +test.todo("addMonths should correctly add months to an Ethiopic date"); diff --git a/src/ethiopic/lib/addMonths.ts b/src/ethiopic/lib/addMonths.ts new file mode 100644 index 000000000..85d2b4220 --- /dev/null +++ b/src/ethiopic/lib/addMonths.ts @@ -0,0 +1,16 @@ +import { toEthiopicDate, toGregorianDate } from "../utils/index.js"; + +/** + * Adds months to an Ethiopic date + * + * @param {Date} date - The original date + * @param {number} amount - The number of months to add + * @returns {Date} The new date + */ +export function addMonths(date: Date, amount: number): Date { + const { year, month, day } = toEthiopicDate(date); + const totalMonths = month + amount - 1; + const newYear = year + Math.floor(totalMonths / 12); + const newMonth = (totalMonths % 12) + 1; + return toGregorianDate({ year: newYear, month: newMonth, day }); +} diff --git a/src/ethiopic/lib/addWeeks.test.ts b/src/ethiopic/lib/addWeeks.test.ts new file mode 100644 index 000000000..67fa362aa --- /dev/null +++ b/src/ethiopic/lib/addWeeks.test.ts @@ -0,0 +1 @@ +test.todo("addWeeks should correctly add weeks to an Ethiopic date"); diff --git a/src/ethiopic/lib/addWeeks.ts b/src/ethiopic/lib/addWeeks.ts new file mode 100644 index 000000000..5363453f0 --- /dev/null +++ b/src/ethiopic/lib/addWeeks.ts @@ -0,0 +1,12 @@ +import { addDays } from "./addDays.js"; + +/** + * Adds weeks to an Ethiopic date + * + * @param {Date} date - The original date + * @param {number} amount - The number of weeks to add + * @returns {Date} The new date + */ +export function addWeeks(date: Date, amount: number): Date { + return addDays(date, amount * 7); +} diff --git a/src/ethiopic/lib/addYears.test.ts b/src/ethiopic/lib/addYears.test.ts new file mode 100644 index 000000000..09bfb117a --- /dev/null +++ b/src/ethiopic/lib/addYears.test.ts @@ -0,0 +1 @@ +test.todo("addYears should correctly add years to an Ethiopic date"); diff --git a/src/ethiopic/lib/addYears.ts b/src/ethiopic/lib/addYears.ts new file mode 100644 index 000000000..e80d26fd2 --- /dev/null +++ b/src/ethiopic/lib/addYears.ts @@ -0,0 +1,20 @@ +import { + toEthiopicDate, + isEthiopicLeapYear, + toGregorianDate +} from "../utils/index.js"; + +/** + * Adds years to an Ethiopic date + * + * @param {Date} date - The original date + * @param {number} amount - The number of years to add + * @returns {Date} The new date + */ +export function addYears(date: Date, amount: number): Date { + const { year, month, day } = toEthiopicDate(date); + const newYear = year + amount; + const newDay = + month === 13 && day === 6 && !isEthiopicLeapYear(newYear) ? 5 : day; + return toGregorianDate({ year: newYear, month, day: newDay }); +} diff --git a/src/ethiopic/lib/differenceInCalendarDays.test.ts b/src/ethiopic/lib/differenceInCalendarDays.test.ts new file mode 100644 index 000000000..95aa1e954 --- /dev/null +++ b/src/ethiopic/lib/differenceInCalendarDays.test.ts @@ -0,0 +1,3 @@ +test.todo( + "differenceInCalendarDays should correctly calculate the number of calendar days between two dates" +); diff --git a/src/ethiopic/lib/differenceInCalendarDays.ts b/src/ethiopic/lib/differenceInCalendarDays.ts new file mode 100644 index 000000000..8e642ff67 --- /dev/null +++ b/src/ethiopic/lib/differenceInCalendarDays.ts @@ -0,0 +1,23 @@ +import { toEthiopicDate, isEthiopicLeapYear } from "../utils/index.js"; + +/** + * Difference in calendar days + * + * @param {Date} dateLeft - The later date + * @param {Date} dateRight - The earlier date + * @returns {number} The number of calendar days between the two dates + */ +export function differenceInCalendarDays( + dateLeft: Date, + dateRight: Date +): number { + const leftYear = toEthiopicDate(dateLeft).year; + const rightYear = toEthiopicDate(dateRight).year; + const leapDays = Array.from( + { length: leftYear - rightYear }, + (_, i) => rightYear + i + ).filter(isEthiopicLeapYear).length; + return ( + Math.floor((dateLeft.getTime() - dateRight.getTime()) / 86400000) + leapDays + ); +} diff --git a/src/ethiopic/lib/differenceInCalendarMonths.ts b/src/ethiopic/lib/differenceInCalendarMonths.ts new file mode 100644 index 000000000..03cc06d8d --- /dev/null +++ b/src/ethiopic/lib/differenceInCalendarMonths.ts @@ -0,0 +1,25 @@ +import { toEthiopicDate, isEthiopicLeapYear } from "../utils/index.js"; + +/** + * Difference in calendar months + * + * @param {Date} dateLeft - The later date + * @param {Date} dateRight - The earlier date + * @returns {number} The number of calendar months between the two dates + */ +export function differenceInCalendarMonths( + dateLeft: Date, + dateRight: Date +): number { + const ethiopicLeft = toEthiopicDate(dateLeft); + const ethiopicRight = toEthiopicDate(dateRight); + const leapDays = Array.from( + { length: ethiopicLeft.year - ethiopicRight.year }, + (_, i) => ethiopicRight.year + i + ).filter(isEthiopicLeapYear).length; + return ( + (ethiopicLeft.year - ethiopicRight.year) * 12 + + (ethiopicLeft.month - ethiopicRight.month) + + leapDays + ); +} diff --git a/src/ethiopic/lib/eachMonthOfInterval.test.ts b/src/ethiopic/lib/eachMonthOfInterval.test.ts new file mode 100644 index 000000000..7cadbf2d7 --- /dev/null +++ b/src/ethiopic/lib/eachMonthOfInterval.test.ts @@ -0,0 +1,3 @@ +test.todo( + "should return an array of dates representing the start of each month in the interval" +); diff --git a/src/ethiopic/lib/eachMonthOfInterval.ts b/src/ethiopic/lib/eachMonthOfInterval.ts new file mode 100644 index 000000000..2acd82d2e --- /dev/null +++ b/src/ethiopic/lib/eachMonthOfInterval.ts @@ -0,0 +1,38 @@ +import type { Interval } from "date-fns"; + +import { toEthiopicDate, toGregorianDate } from "../utils/index.js"; + +/** + * Each month of an interval + * + * @param {Object} interval - The interval object + * @param {Date} interval.start - The start date of the interval + * @param {Date} interval.end - The end date of the interval + * @returns {Date[]} An array of dates representing the start of each month in + * the interval + */ +export function eachMonthOfInterval(interval: Interval): Date[] { + const start = toEthiopicDate(new Date(interval.start)); + const end = toEthiopicDate(new Date(interval.end)); + const dates: Date[] = []; + + let currentYear = start.year; + let currentMonth = start.month; + + while ( + currentYear < end.year || + (currentYear === end.year && currentMonth <= end.month) + ) { + dates.push( + toGregorianDate({ year: currentYear, month: currentMonth, day: 1 }) + ); + + currentMonth++; + if (currentMonth > 12) { + currentMonth = 1; + currentYear++; + } + } + + return dates; +} diff --git a/src/ethiopic/lib/endOfMonth.test.ts b/src/ethiopic/lib/endOfMonth.test.ts new file mode 100644 index 000000000..4fd3d089c --- /dev/null +++ b/src/ethiopic/lib/endOfMonth.test.ts @@ -0,0 +1 @@ +test.todo("should return the correct end of the month date for a given date"); diff --git a/src/ethiopic/lib/endOfMonth.ts b/src/ethiopic/lib/endOfMonth.ts new file mode 100644 index 000000000..dbc8c9a2c --- /dev/null +++ b/src/ethiopic/lib/endOfMonth.ts @@ -0,0 +1,17 @@ +import { + toEthiopicDate, + isEthiopicLeapYear, + toGregorianDate +} from "../utils/index.js"; + +/** + * End of month + * + * @param {Date} date - The original date + * @returns {Date} The end of the month + */ +export function endOfMonth(date: Date): Date { + const { year, month } = toEthiopicDate(date); + const daysInMonth = month === 13 ? (isEthiopicLeapYear(year) ? 6 : 5) : 30; + return toGregorianDate({ year, month, day: daysInMonth }); +} diff --git a/src/ethiopic/lib/endOfWeek.test.ts b/src/ethiopic/lib/endOfWeek.test.ts new file mode 100644 index 000000000..51706060d --- /dev/null +++ b/src/ethiopic/lib/endOfWeek.test.ts @@ -0,0 +1 @@ +test.todo("should return the correct end of the week date for a given date"); diff --git a/src/ethiopic/lib/endOfWeek.ts b/src/ethiopic/lib/endOfWeek.ts new file mode 100644 index 000000000..696761e92 --- /dev/null +++ b/src/ethiopic/lib/endOfWeek.ts @@ -0,0 +1,20 @@ +import { addDays } from "./addDays.js"; + +/** + * End of week + * + * @param {Date} date - The original date + * @param {Object} [options] - The options object + * @param {number} [options.weekStartsOn=0] - The index of the first day of the + * week (0 - Sunday). Default is `0` + * @returns {Date} The end of the week + */ +export function endOfWeek( + date: Date, + options?: { weekStartsOn?: number } +): Date { + const weekStartsOn = options?.weekStartsOn ?? 0; + const day = date.getDay(); + const diff = (7 - day + weekStartsOn - 1) % 7; + return addDays(date, diff); +} diff --git a/src/ethiopic/lib/endOfYear.test.ts b/src/ethiopic/lib/endOfYear.test.ts new file mode 100644 index 000000000..6b88f61e8 --- /dev/null +++ b/src/ethiopic/lib/endOfYear.test.ts @@ -0,0 +1 @@ +test.todo("should return the correct end of the year date for a given date"); diff --git a/src/ethiopic/lib/endOfYear.ts b/src/ethiopic/lib/endOfYear.ts new file mode 100644 index 000000000..f28854704 --- /dev/null +++ b/src/ethiopic/lib/endOfYear.ts @@ -0,0 +1,17 @@ +import { + toEthiopicDate, + isEthiopicLeapYear, + toGregorianDate +} from "../utils/index.js"; + +/** + * End of year + * + * @param {Date} date - The original date + * @returns {Date} The end of the year + */ +export function endOfYear(date: Date): Date { + const { year } = toEthiopicDate(date); + const day = isEthiopicLeapYear(year) ? 6 : 5; + return toGregorianDate({ year, month: 13, day }); +} diff --git a/src/ethiopic/lib/getMonth.test.ts b/src/ethiopic/lib/getMonth.test.ts new file mode 100644 index 000000000..addfd9364 --- /dev/null +++ b/src/ethiopic/lib/getMonth.test.ts @@ -0,0 +1 @@ +test.todo("should return the correct zero-based month index for a given date"); diff --git a/src/ethiopic/lib/getMonth.ts b/src/ethiopic/lib/getMonth.ts new file mode 100644 index 000000000..ef334c8f2 --- /dev/null +++ b/src/ethiopic/lib/getMonth.ts @@ -0,0 +1,12 @@ +import { toEthiopicDate } from "../utils/index.js"; + +/** + * Get month + * + * @param {Date} date - The original date + * @returns {number} The zero-based month index + */ +export function getMonth(date: Date): number { + const { month } = toEthiopicDate(date); + return month - 1; // Return zero-based month index +} diff --git a/src/ethiopic/lib/getWeek.test.ts b/src/ethiopic/lib/getWeek.test.ts new file mode 100644 index 000000000..f3a007310 --- /dev/null +++ b/src/ethiopic/lib/getWeek.test.ts @@ -0,0 +1 @@ +test.todo("should return the correct week number for a given date"); diff --git a/src/ethiopic/lib/getWeek.ts b/src/ethiopic/lib/getWeek.ts new file mode 100644 index 000000000..6032b828a --- /dev/null +++ b/src/ethiopic/lib/getWeek.ts @@ -0,0 +1,26 @@ +import { toGregorianDate, toEthiopicDate } from "../utils/index.js"; + +import { differenceInCalendarDays } from "./differenceInCalendarDays.js"; + +/** + * Get week + * + * @param {Date} date - The original date + * @param {Object} [options] - The options object + * @param {number} [options.weekStartsOn=0] - The index of the first day of the + * week (0 - Sunday). Default is `0` + * @returns {number} The week number + */ +export function getWeek( + date: Date, + options?: { weekStartsOn?: number } +): number { + const weekStartsOn = options?.weekStartsOn ?? 0; // Default to Sunday + const startOfYear = toGregorianDate({ + year: toEthiopicDate(date).year, + month: 1, + day: 1 + }); + const diffInDays = differenceInCalendarDays(date, startOfYear); + return Math.floor((diffInDays + weekStartsOn) / 7) + 1; +} diff --git a/src/ethiopic/lib/getYear.test.ts b/src/ethiopic/lib/getYear.test.ts new file mode 100644 index 000000000..32fb406fc --- /dev/null +++ b/src/ethiopic/lib/getYear.test.ts @@ -0,0 +1 @@ +test.todo("should return the correct Ethiopic year for a given date"); diff --git a/src/ethiopic/lib/getYear.ts b/src/ethiopic/lib/getYear.ts new file mode 100644 index 000000000..c8eac3d2b --- /dev/null +++ b/src/ethiopic/lib/getYear.ts @@ -0,0 +1,12 @@ +import { toEthiopicDate } from "../utils/index.js"; + +/** + * Get year + * + * @param {Date} date - The original date + * @returns {number} The year + */ +export function getYear(date: Date): number { + const { year } = toEthiopicDate(date); + return year; +} diff --git a/src/ethiopic/lib/index.ts b/src/ethiopic/lib/index.ts new file mode 100644 index 000000000..0809e9e8a --- /dev/null +++ b/src/ethiopic/lib/index.ts @@ -0,0 +1,23 @@ +export * from "./addDays.js"; +export * from "./addMonths.js"; +export * from "./addWeeks.js"; +export * from "./addYears.js"; +export * from "./differenceInCalendarDays.js"; +export * from "./differenceInCalendarMonths.js"; +export * from "./eachMonthOfInterval.js"; +export * from "./endOfMonth.js"; +export * from "./endOfWeek.js"; +export * from "./endOfYear.js"; +export * from "./getMonth.js"; +export * from "./getWeek.js"; +export * from "./getYear.js"; +export * from "./isSameDay.js"; +export * from "./isSameMonth.js"; +export * from "./isSameYear.js"; +export * from "./newDate.js"; +export * from "./setMonth.js"; +export * from "./setYear.js"; +export * from "./startOfDay.js"; +export * from "./startOfMonth.js"; +export * from "./startOfWeek.js"; +export * from "./startOfYear.js"; diff --git a/src/ethiopic/lib/isSameDay.test.ts b/src/ethiopic/lib/isSameDay.test.ts new file mode 100644 index 000000000..4363ac2fd --- /dev/null +++ b/src/ethiopic/lib/isSameDay.test.ts @@ -0,0 +1 @@ +test.todo("isSameDay should return true if two dates are on the same day"); diff --git a/src/ethiopic/lib/isSameDay.ts b/src/ethiopic/lib/isSameDay.ts new file mode 100644 index 000000000..90364abd9 --- /dev/null +++ b/src/ethiopic/lib/isSameDay.ts @@ -0,0 +1,18 @@ +import { toEthiopicDate } from "../utils/index.js"; + +/** + * Is same day + * + * @param {Date} dateLeft - The first date + * @param {Date} dateRight - The second date + * @returns {boolean} True if the two dates are on the same day + */ +export function isSameDay(dateLeft: Date, dateRight: Date): boolean { + const left = toEthiopicDate(dateLeft); + const right = toEthiopicDate(dateRight); + return ( + left.year === right.year && + left.month === right.month && + left.day === right.day + ); +} diff --git a/src/ethiopic/lib/isSameMonth.test.ts b/src/ethiopic/lib/isSameMonth.test.ts new file mode 100644 index 000000000..d5355b629 --- /dev/null +++ b/src/ethiopic/lib/isSameMonth.test.ts @@ -0,0 +1 @@ +test.todo("isSameMonth should return true if two dates are in the same month"); diff --git a/src/ethiopic/lib/isSameMonth.ts b/src/ethiopic/lib/isSameMonth.ts new file mode 100644 index 000000000..02f7d2bd6 --- /dev/null +++ b/src/ethiopic/lib/isSameMonth.ts @@ -0,0 +1,14 @@ +import { toEthiopicDate } from "../utils/index.js"; + +/** + * Is same month + * + * @param {Date} dateLeft - The first date + * @param {Date} dateRight - The second date + * @returns {boolean} True if the two dates are in the same month + */ +export function isSameMonth(dateLeft: Date, dateRight: Date): boolean { + const left = toEthiopicDate(dateLeft); + const right = toEthiopicDate(dateRight); + return left.year === right.year && left.month === right.month; +} diff --git a/src/ethiopic/lib/isSameYear.test.ts b/src/ethiopic/lib/isSameYear.test.ts new file mode 100644 index 000000000..a5bc214df --- /dev/null +++ b/src/ethiopic/lib/isSameYear.test.ts @@ -0,0 +1 @@ +test.todo("isSameYear should return true if two dates are in the same year"); diff --git a/src/ethiopic/lib/isSameYear.ts b/src/ethiopic/lib/isSameYear.ts new file mode 100644 index 000000000..28c146e44 --- /dev/null +++ b/src/ethiopic/lib/isSameYear.ts @@ -0,0 +1,14 @@ +import { toEthiopicDate } from "../utils/index.js"; + +/** + * Is same year + * + * @param {Date} dateLeft - The first date + * @param {Date} dateRight - The second date + * @returns {boolean} True if the two dates are in the same year + */ +export function isSameYear(dateLeft: Date, dateRight: Date): boolean { + const left = toEthiopicDate(dateLeft); + const right = toEthiopicDate(dateRight); + return left.year === right.year; +} diff --git a/src/ethiopic/lib/newDate.test.ts b/src/ethiopic/lib/newDate.test.ts new file mode 100644 index 000000000..d798aaad6 --- /dev/null +++ b/src/ethiopic/lib/newDate.test.ts @@ -0,0 +1,3 @@ +describe("newDate", () => { + test.todo("should create a new Ethiopic date"); +}); diff --git a/src/ethiopic/lib/newDate.ts b/src/ethiopic/lib/newDate.ts new file mode 100644 index 000000000..c494ccefc --- /dev/null +++ b/src/ethiopic/lib/newDate.ts @@ -0,0 +1,13 @@ +import { toGregorianDate } from "../utils/index.js"; + +/** + * Creates a new Ethiopic date + * + * @param {number} year - The year of the Ethiopic date + * @param {number} monthIndex - The zero-based month index of the Ethiopic date + * @param {number} date - The day of the month of the Ethiopic date + * @returns {Date} The corresponding Gregorian date + */ +export function newDate(year: number, monthIndex: number, date: number): Date { + return toGregorianDate({ year, month: monthIndex + 1, day: date }); +} diff --git a/src/ethiopic/lib/setMonth.test.ts b/src/ethiopic/lib/setMonth.test.ts new file mode 100644 index 000000000..1004cfcca --- /dev/null +++ b/src/ethiopic/lib/setMonth.test.ts @@ -0,0 +1,3 @@ +describe("setMonth", () => { + test.todo("set the Ethiopic month for a given date"); +}); diff --git a/src/ethiopic/lib/setMonth.ts b/src/ethiopic/lib/setMonth.ts new file mode 100644 index 000000000..ab2207598 --- /dev/null +++ b/src/ethiopic/lib/setMonth.ts @@ -0,0 +1,13 @@ +import { toEthiopicDate, toGregorianDate } from "../utils/index.js"; + +/** + * Set month + * + * @param {Date} date - The original date + * @param {number} month - The zero-based month index + * @returns {Date} The new date with the month set + */ +export function setMonth(date: Date, month: number): Date { + const { year, day } = toEthiopicDate(date); + return toGregorianDate({ year, month: month + 1, day }); // Add 1 to month as it's zero-based +} diff --git a/src/ethiopic/lib/setYear.test.ts b/src/ethiopic/lib/setYear.test.ts new file mode 100644 index 000000000..cb6524ee8 --- /dev/null +++ b/src/ethiopic/lib/setYear.test.ts @@ -0,0 +1,3 @@ +describe("setYear", () => { + test.todo("set the Ethiopic year for a given date"); +}); diff --git a/src/ethiopic/lib/setYear.ts b/src/ethiopic/lib/setYear.ts new file mode 100644 index 000000000..e86877735 --- /dev/null +++ b/src/ethiopic/lib/setYear.ts @@ -0,0 +1,13 @@ +import { toEthiopicDate, toGregorianDate } from "../utils/index.js"; + +/** + * Set year + * + * @param {Date} date - The original date + * @param {number} year - The year to set + * @returns {Date} The new date with the year set + */ +export function setYear(date: Date, year: number): Date { + const { month, day } = toEthiopicDate(date); + return toGregorianDate({ year, month, day }); +} diff --git a/src/ethiopic/lib/startOfDay.test.ts b/src/ethiopic/lib/startOfDay.test.ts new file mode 100644 index 000000000..195e1963b --- /dev/null +++ b/src/ethiopic/lib/startOfDay.test.ts @@ -0,0 +1,3 @@ +describe("startOfDay", () => { + test.todo("should return the start of the Ethiopic day for a given date"); +}); diff --git a/src/ethiopic/lib/startOfDay.ts b/src/ethiopic/lib/startOfDay.ts new file mode 100644 index 000000000..b94e7d5e7 --- /dev/null +++ b/src/ethiopic/lib/startOfDay.ts @@ -0,0 +1,12 @@ +import { toEthiopicDate, toGregorianDate } from "../utils/index.js"; + +/** + * Start of day + * + * @param {Date} date - The original date + * @returns {Date} The start of the day + */ +export function startOfDay(date: Date): Date { + const { year, month, day } = toEthiopicDate(date); + return toGregorianDate({ year, month, day }); +} diff --git a/src/ethiopic/lib/startOfMonth.test.ts b/src/ethiopic/lib/startOfMonth.test.ts new file mode 100644 index 000000000..b9e725910 --- /dev/null +++ b/src/ethiopic/lib/startOfMonth.test.ts @@ -0,0 +1 @@ +test.todo("startOfMonth should return the start of the Ethiopic month"); diff --git a/src/ethiopic/lib/startOfMonth.ts b/src/ethiopic/lib/startOfMonth.ts new file mode 100644 index 000000000..8f4e156f6 --- /dev/null +++ b/src/ethiopic/lib/startOfMonth.ts @@ -0,0 +1,12 @@ +import { toEthiopicDate, toGregorianDate } from "../utils/index.js"; + +/** + * Start of month + * + * @param {Date} date - The original date + * @returns {Date} The start of the month + */ +export function startOfMonth(date: Date): Date { + const { year, month } = toEthiopicDate(date); + return toGregorianDate({ year, month, day: 1 }); +} diff --git a/src/ethiopic/lib/startOfWeek.test.ts b/src/ethiopic/lib/startOfWeek.test.ts new file mode 100644 index 000000000..37eb654e9 --- /dev/null +++ b/src/ethiopic/lib/startOfWeek.test.ts @@ -0,0 +1 @@ +test.todo("startOfWeek should return the start of the week"); diff --git a/src/ethiopic/lib/startOfWeek.ts b/src/ethiopic/lib/startOfWeek.ts new file mode 100644 index 000000000..ebf774cec --- /dev/null +++ b/src/ethiopic/lib/startOfWeek.ts @@ -0,0 +1,12 @@ +import { addDays } from "./addDays.js"; + +/** + * Start of week + * + * @param {Date} date - The original date + * @returns {Date} The start of the week + */ +export function startOfWeek(date: Date): Date { + const day = date.getDay(); + return addDays(date, -day); // Subtract days to get to Sunday (start of week) +} diff --git a/src/ethiopic/lib/startOfYear.test.ts b/src/ethiopic/lib/startOfYear.test.ts new file mode 100644 index 000000000..7031f7887 --- /dev/null +++ b/src/ethiopic/lib/startOfYear.test.ts @@ -0,0 +1 @@ +test.todo("startOfYear should return the start of the Ethiopic year"); diff --git a/src/ethiopic/lib/startOfYear.ts b/src/ethiopic/lib/startOfYear.ts new file mode 100644 index 000000000..6d2354625 --- /dev/null +++ b/src/ethiopic/lib/startOfYear.ts @@ -0,0 +1,12 @@ +import { toEthiopicDate, toGregorianDate } from "../utils/index.js"; + +/** + * Start of year + * + * @param {Date} date - The original date + * @returns {Date} The start of the year + */ +export function startOfYear(date: Date): Date { + const { year } = toEthiopicDate(date); + return toGregorianDate({ year, month: 1, day: 1 }); +} diff --git a/src/ethiopic/utils/EthiopicDate.ts b/src/ethiopic/utils/EthiopicDate.ts new file mode 100644 index 000000000..ae2ea143e --- /dev/null +++ b/src/ethiopic/utils/EthiopicDate.ts @@ -0,0 +1,5 @@ +export interface EthiopicDate { + year: number; + month: number; + day: number; +} diff --git a/src/ethiopic/utils/consts.ts b/src/ethiopic/utils/consts.ts new file mode 100644 index 000000000..c8e6f9069 --- /dev/null +++ b/src/ethiopic/utils/consts.ts @@ -0,0 +1,4 @@ +/** Julian Day number offset for the Ethiopic calendar */ +export const ETHIOPIC_EPOCH_OFFSET = -285019; + +// TODO: Add more constants diff --git a/src/ethiopic/utils/index.ts b/src/ethiopic/utils/index.ts new file mode 100644 index 000000000..1dc38356a --- /dev/null +++ b/src/ethiopic/utils/index.ts @@ -0,0 +1,4 @@ +export * from "./EthiopicDate.js"; +export * from "./isEthiopicLeapYear.js"; +export * from "./toEthiopicDate.js"; +export * from "./toGregorianDate.js"; diff --git a/src/ethiopic/utils/isEthiopicLeapYear.test.ts b/src/ethiopic/utils/isEthiopicLeapYear.test.ts new file mode 100644 index 000000000..ece0ec678 --- /dev/null +++ b/src/ethiopic/utils/isEthiopicLeapYear.test.ts @@ -0,0 +1,4 @@ +describe("isEthiopicLeapYear", () => { + test.todo("should return true for a leap year"); + test.todo("should return false for a non-leap year"); +}); diff --git a/src/ethiopic/utils/isEthiopicLeapYear.ts b/src/ethiopic/utils/isEthiopicLeapYear.ts new file mode 100644 index 000000000..d3eb04651 --- /dev/null +++ b/src/ethiopic/utils/isEthiopicLeapYear.ts @@ -0,0 +1,10 @@ +/** + * Checks if a given Ethiopic year is a leap year. + * + * @param year - The Ethiopic year. + * @returns True if the year is a leap year; otherwise, false. + */ + +export function isEthiopicLeapYear(year: number): boolean { + return year % 4 === 3; +} diff --git a/src/ethiopic/utils/toEthiopicDate.test.ts b/src/ethiopic/utils/toEthiopicDate.test.ts new file mode 100644 index 000000000..3d30b8285 --- /dev/null +++ b/src/ethiopic/utils/toEthiopicDate.test.ts @@ -0,0 +1,3 @@ +test.todo("should convert a Gregorian date to an Ethiopic date correctly"); +test.todo("should handle leap years correctly"); +test.todo("should handle dates before the Ethiopic epoch correctly"); diff --git a/src/ethiopic/utils/toEthiopicDate.ts b/src/ethiopic/utils/toEthiopicDate.ts new file mode 100644 index 000000000..a100b0d87 --- /dev/null +++ b/src/ethiopic/utils/toEthiopicDate.ts @@ -0,0 +1,21 @@ +import type { EthiopicDate } from "./EthiopicDate.js"; +import { ETHIOPIC_EPOCH_OFFSET } from "./consts.js"; + +/** + * Converts a Gregorian date to an Ethiopic date. + * + * @param gregorianDate - A JavaScript Date object representing the Gregorian + * date. + * @returns An EthiopicDate object. + */ +export function toEthiopicDate(gregorianDate: Date): EthiopicDate { + const julianDay = Math.floor(gregorianDate.getTime() / 86400000 + 2440587.5); + const ethiopicDayNumber = julianDay + ETHIOPIC_EPOCH_OFFSET; + + const year = Math.floor((4 * ethiopicDayNumber + 1463) / 1461); + const dayOfYear = ethiopicDayNumber - (365 * year + Math.floor(year / 4)); + const month = Math.floor(dayOfYear / 30) + 1; + const day = (dayOfYear % 30) + 1; + + return { year, month, day }; +} diff --git a/src/ethiopic/utils/toGregorianDate.test.ts b/src/ethiopic/utils/toGregorianDate.test.ts new file mode 100644 index 000000000..2937f87bf --- /dev/null +++ b/src/ethiopic/utils/toGregorianDate.test.ts @@ -0,0 +1,8 @@ +describe("toGregorianDate", () => { + test.todo("convert an Ethiopic date to the correct Gregorian date"); + test.todo("handle leap years correctly"); + test.todo("handle the last day of the year correctly"); + test.todo("handle the first day of the year correctly"); + test.todo("handle invalid dates gracefully"); + test.todo("handle edge cases for month and day values"); +}); diff --git a/src/ethiopic/utils/toGregorianDate.ts b/src/ethiopic/utils/toGregorianDate.ts new file mode 100644 index 000000000..5dd0ec151 --- /dev/null +++ b/src/ethiopic/utils/toGregorianDate.ts @@ -0,0 +1,19 @@ +import type { EthiopicDate } from "./EthiopicDate.js"; +import { ETHIOPIC_EPOCH_OFFSET } from "./consts.js"; + +/** + * Converts an Ethiopic date to a Gregorian date. + * + * @param ethiopicDate - An EthiopicDate object. + * @returns A JavaScript Date object representing the Gregorian date. + */ +export function toGregorianDate(ethiopicDate: EthiopicDate): Date { + const yearStartJulianDay = + 365 * ethiopicDate.year + + Math.floor(ethiopicDate.year / 4) - + ETHIOPIC_EPOCH_OFFSET; + const julianDay = + yearStartJulianDay + (ethiopicDate.month - 1) * 30 + (ethiopicDate.day - 1); + const gregorianTime = (julianDay - 2440587.5) * 86400000; + return new Date(gregorianTime); +} diff --git a/src/persian.tsx b/src/persian.tsx index 9a1a2ff2d..7af6b3c49 100644 --- a/src/persian.tsx +++ b/src/persian.tsx @@ -46,6 +46,7 @@ export function DayPicker( * - `arab`: Arabic-Indic * - `arabext`: Eastern Arabic-Indic (Persian) * - `deva`: Devanagari + * - `ethio`: Ethiopic * - `beng`: Bengali * - `guru`: Gurmukhi * - `gujr`: Gujarati diff --git a/src/types/props.ts b/src/types/props.ts index e9d8496f0..d75a8dd96 100644 --- a/src/types/props.ts +++ b/src/types/props.ts @@ -398,6 +398,7 @@ export interface PropsBase { * - `arab`: Arabic-Indic * - `arabext`: Eastern Arabic-Indic (Persian) * - `deva`: Devanagari + * - `ethio`: Ethiopic * - `beng`: Bengali * - `guru`: Gurmukhi * - `gujr`: Gujarati diff --git a/src/types/shared.ts b/src/types/shared.ts index be3cae5e5..27cb3a8a4 100644 --- a/src/types/shared.ts +++ b/src/types/shared.ts @@ -416,6 +416,7 @@ export type MoveFocusBy = * - `arab`: Arabic-Indic * - `arabext`: Eastern Arabic-Indic (Persian) * - `deva`: Devanagari + * - `ethio`: Ethiopic * - `beng`: Bengali * - `guru`: Gurmukhi * - `gujr`: Gujarati @@ -432,6 +433,7 @@ export type Numerals = | "arab" | "arabext" | "deva" + | "ethio" | "beng" | "guru" | "gujr"