From 56cef40dbab030af46e2454888c74e820d6c2bfe Mon Sep 17 00:00:00 2001 From: Quantum Date: Sat, 22 Apr 2023 03:37:53 -0400 Subject: [PATCH] Add Julian calendar handling --- common/src/gregorian.test.ts | 64 ++++++++++++++++++++++++++---------- common/src/gregorian.ts | 58 +++++++++++++++++++++++++++++--- common/src/julian.test.ts | 25 ++++++++++++++ common/src/julian.ts | 9 +++++ frcal/src/App.tsx | 8 ++--- frcal/src/Calendar.tsx | 4 +-- 6 files changed, 140 insertions(+), 28 deletions(-) create mode 100644 common/src/julian.test.ts create mode 100644 common/src/julian.ts diff --git a/common/src/gregorian.test.ts b/common/src/gregorian.test.ts index b37ac49..8652c68 100644 --- a/common/src/gregorian.test.ts +++ b/common/src/gregorian.test.ts @@ -1,4 +1,4 @@ -import {gregorianJDN, jdnGregorian} from './gregorian'; +import {gregorianJDN, jdnDate, jdnGregorian, JulianDay, JulianMonth} from './gregorian'; describe('gregorianJDN', () => { it('works', () => { @@ -15,25 +15,55 @@ describe('gregorianJDN', () => { expect(gregorianJDN(19720, 8, 14)).toBe(8923868); expect(gregorianJDN(7504, 7, 22)).toBe(4462042); }); + + it('transition to Julian works', () => { + expect(gregorianJDN(2000, 1, 1, 2299161)).toBe(2451545); + expect(gregorianJDN(1969, 7, 20, 2299161)).toBe(2440423); + expect(gregorianJDN(1582, 10, 15, 2299161)).toBe(2299161); + expect(gregorianJDN(1582, 10, 4, 2299161)).toBe(2299160); + expect(gregorianJDN(1066, 10, 14, 2299161)).toBe(2110701); + expect(gregorianJDN(0, 12, 25, 2299161)).toBe(1721417); + expect(gregorianJDN(-4712, 1, 1, 2299161)).toBe(0); + }); }); describe('jdnGregorian', () => { it('works', () => { - expect(jdnGregorian(0)).toEqual(new Date(-4713, 10, 24)); - expect(jdnGregorian(2299160)).toEqual(new Date(1582, 9, 14)); - expect(jdnGregorian(2299161)).toEqual(new Date(1582, 9, 15)); - expect(jdnGregorian(2361221)).toEqual(new Date(1752, 8, 13)); - expect(jdnGregorian(2361222)).toEqual(new Date(1752, 8, 14)); - expect(jdnGregorian(2451545)).toEqual(new Date(2000, 0, 1)); - expect(jdnGregorian(-8512316)).toEqual(new Date(-28019, 11, 20)); - expect(jdnGregorian(-8534852)).toEqual(new Date(-28080, 3, 8)); - expect(jdnGregorian(2653462)).toEqual(new Date(2552, 9, 30)); - expect(jdnGregorian(3271156)).toEqual(new Date(4244, 0, 8)); - expect(jdnGregorian(-666477)).toEqual(new Date(-6537, 1, 23)); - expect(jdnGregorian(2397854)).toEqual(new Date(1852, 11, 31)); - expect(jdnGregorian(-1211235)).toEqual(new Date(-8029, 7, 26)); - expect(jdnGregorian(-91680)).toEqual(new Date(-4964, 10, 20)); - expect(jdnGregorian(-5605876)).toEqual(new Date(-20061, 6, 14)); - expect(jdnGregorian(-295121)).toEqual(new Date(-5521, 10, 19)); + function checkJDN(jdn: number, year: number, month: JulianMonth, day: JulianDay) { + expect(jdnGregorian(jdn)).toEqual([year, month, day]); + expect(jdnDate(jdn)).toEqual(new Date(year, month - 1, day)); + } + + checkJDN(0, -4713, 11, 24); + checkJDN(2299160, 1582, 10, 14); + checkJDN(2299161, 1582, 10, 15); + checkJDN(2361221, 1752, 9, 13); + checkJDN(2361222, 1752, 9, 14); + checkJDN(2451545, 2000, 1, 1); + checkJDN(-8512316, -28019, 12, 20); + checkJDN(-8534852, -28080, 4, 8); + checkJDN(2653462, 2552, 10, 30); + checkJDN(3271156, 4244, 1, 8); + checkJDN(-666477, -6537, 2, 23); + checkJDN(2397854, 1852, 12, 31); + checkJDN(-1211235, -8029, 8, 26); + checkJDN(-91680, -4964, 11, 20); + checkJDN(-5605876, -20061, 7, 14); + checkJDN(-295121, -5521, 11, 19); + }); + + it('transition to Julian works', () => { + function checkJDN(jdn: number, year: number, month: JulianMonth, day: JulianDay) { + expect(jdnGregorian(jdn, 2299161)).toEqual([year, month, day]); + expect(jdnDate(jdn, 2299161)).toEqual(new Date(year, month - 1, day)); + } + + checkJDN(2451545, 2000, 1, 1); + checkJDN(2440423, 1969, 7, 20); + checkJDN(2299161, 1582, 10, 15); + checkJDN(2299160, 1582, 10, 4); + checkJDN(2110701, 1066, 10, 14); + checkJDN(1721417, 0, 12, 25); + checkJDN(0, -4712, 1, 1); }); }); diff --git a/common/src/gregorian.ts b/common/src/gregorian.ts index 330e9d6..a918aa3 100644 --- a/common/src/gregorian.ts +++ b/common/src/gregorian.ts @@ -1,21 +1,69 @@ -export function gregorianJDN(year: number, month: number, day: number): number { +export type JulianMonth = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; +export type JulianDay = + | 1 + | 2 + | 3 + | 4 + | 5 + | 6 + | 7 + | 8 + | 9 + | 10 + | 11 + | 12 + | 13 + | 14 + | 15 + | 16 + | 17 + | 18 + | 19 + | 20 + | 21 + | 22 + | 23 + | 24 + | 25 + | 26 + | 27 + | 28 + | 29 + | 30 + | 31; + +export type JulianDate = [number, JulianMonth, JulianDay]; + +export function gregorianJDN(year: number, month: number, day: number, julian_before?: number): number { const g = year + 4716 - (month <= 2 ? 1 : 0); const f = (month + 9) % 12; const e = Math.floor(1461 * g / 4) + day - 1402; const J = e + Math.floor((153 * f + 2) / 5); + + if (julian_before != null && J < julian_before) + return J; + const dg = 38 - Math.floor(Math.floor((g + 184) / 100) * 3 / 4); return J + dg; } -export function dateJDN(date: Date) { - return gregorianJDN(date.getFullYear(), date.getMonth() + 1, date.getDate()); +export function dateJDN(date: Date, julian_before?: number) { + return gregorianJDN(date.getFullYear(), date.getMonth() + 1, date.getDate(), julian_before); } -export function jdnGregorian(jdn: number): Date { - const e = 4 * (jdn + 1401 + Math.floor(Math.floor((4 * jdn + 274277) / 146097) * 3 / 4) - 38) + 3; +export function jdnGregorian(jdn: number, julian_before?: number): JulianDate { + const dg = julian_before == null || jdn >= julian_before ? + Math.floor(Math.floor((4 * jdn + 274277) / 146097) * 3 / 4) - 38 : + 0; + const e = 4 * (jdn + 1401 + dg) + 3; const h = 5 * Math.floor((e % 1461 + 1461) % 1461 / 4) + 2; const day = Math.floor((h % 153 + 153) % 153 / 5) + 1; const month = (Math.floor(h / 153) + 2) % 12 + 1; const year = Math.floor(e / 1461) - 4716 + Math.floor((14 - month) / 12); + return [year, month as JulianMonth, day as JulianDay]; +} + +export function jdnDate(jdn: number, julian_before?: number): Date { + const [year, month, day] = jdnGregorian(jdn, julian_before); return new Date(year, month - 1, day); } diff --git a/common/src/julian.test.ts b/common/src/julian.test.ts new file mode 100644 index 0000000..aa0b9c1 --- /dev/null +++ b/common/src/julian.test.ts @@ -0,0 +1,25 @@ +import {jdnJulian, julianJDN} from './julian'; + +describe('gregorianJDN', () => { + it('works', () => { + expect(julianJDN(2000, 1, 1)).toBe(2451558); + expect(julianJDN(1969, 7, 20)).toBe(2440436); + expect(julianJDN(1582, 10, 15)).toBe(2299171); + expect(julianJDN(1582, 10, 4)).toBe(2299160); + expect(julianJDN(1066, 10, 14)).toBe(2110701); + expect(julianJDN(0, 12, 25)).toBe(1721417); + expect(julianJDN(-4712, 1, 1)).toBe(0); + }); +}); + +describe('jdnGregorian', () => { + it('works', () => { + expect(jdnJulian(2451558)).toEqual([2000, 1, 1]); + expect(jdnJulian(2440436)).toEqual([1969, 7, 20]); + expect(jdnJulian(2299171)).toEqual([1582, 10, 15]); + expect(jdnJulian(2299160)).toEqual([1582, 10, 4]); + expect(jdnJulian(2110701)).toEqual([1066, 10, 14]); + expect(jdnJulian(1721417)).toEqual([0, 12, 25]); + expect(jdnJulian(0)).toEqual([-4712, 1, 1]); + }); +}); diff --git a/common/src/julian.ts b/common/src/julian.ts new file mode 100644 index 0000000..3360f85 --- /dev/null +++ b/common/src/julian.ts @@ -0,0 +1,9 @@ +import {gregorianJDN, jdnGregorian, JulianDate} from './gregorian'; + +export function julianJDN(year: number, month: number, day: number): number { + return gregorianJDN(year, month, day, Infinity); +} + +export function jdnJulian(jdn: number): JulianDate { + return jdnGregorian(jdn, Infinity); +} diff --git a/frcal/src/App.tsx b/frcal/src/App.tsx index 850bd6f..b9f87f6 100644 --- a/frcal/src/App.tsx +++ b/frcal/src/App.tsx @@ -80,8 +80,8 @@ class App extends React.Component<{}, AppState> { } validYear() { - return /^-?\d+$/.test(this.state.goYear) && startGregorian.getFullYear() <= +this.state.goYear && - +this.state.goYear <= endGregorian.getFullYear(); + return /^-?\d+$/.test(this.state.goYear) && startGregorian[0] <= +this.state.goYear && + +this.state.goYear <= endGregorian[0]; } validMonth() { @@ -112,7 +112,7 @@ class App extends React.Component<{}, AppState> { onDateChange = (todayJDN: number) => { this.setState({todayJDN}); - } + }; render() { return <> @@ -130,7 +130,7 @@ class App extends React.Component<{}, AppState> { Gregorian Date + min={startGregorian[0]} max={endGregorian[0]}/> diff --git a/frcal/src/Calendar.tsx b/frcal/src/Calendar.tsx index a9e118d..5859255 100644 --- a/frcal/src/Calendar.tsx +++ b/frcal/src/Calendar.tsx @@ -13,7 +13,7 @@ import { monthName, startYear, } from '@common/french'; -import {jdnGregorian} from '@common/gregorian'; +import {jdnDate} from '@common/gregorian'; import {jdnLongCount} from '@common/longCount'; type MonthProps = { @@ -31,7 +31,7 @@ function DecadeName({name}: { name: string }): JSX.Element { function DayDetail({jdn}: { jdn: number }): JSX.Element { return
-
{jdnGregorian(jdn).toDateString()}
+
{jdnDate(jdn).toDateString()}
{jdnLongCount(jdn)}
; }