Add Julian calendar handling

This commit is contained in:
Quantum 2023-04-22 03:37:53 -04:00
parent 17229a8c2e
commit 56cef40dba
6 changed files with 140 additions and 28 deletions

View file

@ -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);
});
});

View file

@ -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);
}

25
common/src/julian.test.ts Normal file
View file

@ -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]);
});
});

9
common/src/julian.ts Normal file
View file

@ -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);
}

View file

@ -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> {
<span className="input-group-text">Gregorian<span className="hide-small">&nbsp;Date</span></span>
<input type="number" className={`form-control go-year ${this.validYear() ? '' : 'is-invalid'}`}
onChange={this.changeField.bind(this, 'goYear')} value={this.state.goYear}
min={startGregorian.getFullYear()} max={endGregorian.getFullYear()}/>
min={startGregorian[0]} max={endGregorian[0]}/>
<input type="number" className={`form-control go-month ${this.validMonth() ? '' : 'is-invalid'}`}
onChange={this.changeField.bind(this, 'goMonth')} value={this.state.goMonth}
min={1} max={12}/>

View file

@ -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 <div className="DayDetail">
<div className="DayDetail-gregorian">{jdnGregorian(jdn).toDateString()}</div>
<div className="DayDetail-gregorian">{jdnDate(jdn).toDateString()}</div>
<div className="DayDetail-lc">{jdnLongCount(jdn)}</div>
</div>;
}