diff --git a/src/app/shared/state/time.store.spec.ts b/src/app/shared/state/time.store.spec.ts index 762a47f..9895c92 100644 --- a/src/app/shared/state/time.store.spec.ts +++ b/src/app/shared/state/time.store.spec.ts @@ -85,6 +85,22 @@ describe('TimeStore', () => { expect(time.date().toISOString()).toBe('2020-12-21T19:00:01.000Z'); }); + it('stops a running clock at either end of the window, at real time turned back into it', () => { + time.setDate(new Date('0001-01-10T00:00Z')); + time.setRate(-2_629_800); // a month a second, backwards + vi.advanceTimersByTime(60_000); + expect(time.date().toISOString()).toBe('0001-01-01T00:00:00.000Z'); + expect(time.rate()).toBe(1); + vi.advanceTimersByTime(1000); + expect(time.date().toISOString()).toBe('0001-01-01T00:00:01.000Z'); + + time.setDate(new Date('2999-12-01T00:00Z')); + time.setRate(2_629_800); + vi.advanceTimersByTime(60_000); + expect(time.date().toISOString()).toBe('3000-01-01T00:00:00.000Z'); + expect(time.rate()).toBe(-1); + }); + it('refuses a date the planets’ elements were never fitted for, and stays where it was', () => { const before = time.date().toISOString(); diff --git a/src/app/shared/state/time.store.ts b/src/app/shared/state/time.store.ts index ca66ffc..1101974 100644 --- a/src/app/shared/state/time.store.ts +++ b/src/app/shared/state/time.store.ts @@ -40,6 +40,10 @@ const WINDOW_MS = { min: Date.parse(`${CLOCK_WINDOW.min}Z`), max: Date.parse(`${CLOCK_WINDOW.max}Z`), }; +const WINDOW_JD = { + min: WINDOW_MS.min / MS_PER_DAY + JULIAN_DATE_AT_EPOCH, + max: WINDOW_MS.max / MS_PER_DAY + JULIAN_DATE_AT_EPOCH, +}; /** * The date the map is drawn for. @@ -65,9 +69,22 @@ export class TimeStore { private anchorJd = dateToJulianDate(); private anchorWallMs = Date.now(); - /** Julian date for this instant, at the rate the reader chose. */ + /** + * Julian date for this instant, at the rate the reader chose, held to {@link CLOCK_WINDOW}: a + * clock run past either end stops there, at real time turned back into the window, as if the + * reader had set that date. Unheld, a month a second carried it past AD 3000, where the planets' + * elements were never fitted, and before AD 1, where `toISOString` writes a six-digit year the + * date strip, the note and the date field cut in the wrong places ("-000001-12-01 00 UTC"). + */ julianDate(): number { - return this.anchorJd + ((Date.now() - this.anchorWallMs) * this.rate()) / MS_PER_DAY; + const jd = this.anchorJd + ((Date.now() - this.anchorWallMs) * this.rate()) / MS_PER_DAY; + if (jd >= WINDOW_JD.min && jd <= WINDOW_JD.max) { + return jd; + } + this.anchorJd = jd < WINDOW_JD.min ? WINDOW_JD.min : WINDOW_JD.max; + this.anchorWallMs = Date.now(); + this.rate.set(jd < WINDOW_JD.min ? 1 : -1); + return this.anchorJd; } /**