diff --git a/src/chronology/application/DateConverterFormChange.test.ts b/src/chronology/application/DateConverterFormChange.test.ts index ac9e65e9a..f26f5026f 100644 --- a/src/chronology/application/DateConverterFormChange.test.ts +++ b/src/chronology/application/DateConverterFormChange.test.ts @@ -51,7 +51,7 @@ it('handles setToGregorianDate scenario correctly', () => { setFormData: mockSetFormData, setScenario, }) - expect(mockDateConverter.setToGregorianDate).toBeCalledWith(-309, 3, 29) + expect(mockDateConverter.setToGregorianDate).toBeCalledWith(-310, 3, 29) }) it('handles setToJulianDate scenario correctly', () => { diff --git a/src/chronology/application/DateSelection.tsx b/src/chronology/application/DateSelection.tsx index 13cff382c..4a3fce987 100644 --- a/src/chronology/application/DateSelection.tsx +++ b/src/chronology/application/DateSelection.tsx @@ -14,6 +14,7 @@ import { } from 'chronology/ui/DateEditor/DateSelectionInput' import useDateSelectionState, { DateEditorStateProps, + DateSelectionState, } from 'chronology/application/DateSelectionState' type Props = { @@ -30,6 +31,28 @@ interface DateEditorProps extends DateEditorStateProps { isDisplayed: boolean } +function getSelectedDateAndValidation( + state: DateSelectionState, + savedDate?: MesopotamianDate +): { selectedDate?: MesopotamianDate; isSelectedDateValid: boolean } { + let isSelectedDateValid: boolean + let selectedDate: MesopotamianDate | undefined + try { + selectedDate = state.getDate() + const dateString = selectedDate.toString() + const isDatesNotSame = + savedDate === undefined || dateString !== savedDate.toString() + const isDateEmpty = dateString.replaceAll('SE', '') !== '' + const isAssyrianDateNotEmpty = + !state.isAssyrianDate || dateString !== '∅.∅.1' + isSelectedDateValid = + isDateEmpty && isAssyrianDateNotEmpty && isDatesNotSame + } catch { + isSelectedDateValid = false + } + return { selectedDate, isSelectedDateValid } +} + export function DateEditor({ date, setDate, @@ -55,27 +78,50 @@ export function DateEditor({ const dateOptionsInput = DateOptionsInput({ ...state }) const dateInputGroups = DateInputGroups({ ...state }) - const saveButton = ( + const deleteButton = ( ) - const deleteButton = ( + const { selectedDate, isSelectedDateValid } = getSelectedDateAndValidation( + state, + date + ) + + const saveButton = ( ) + const savedDateDisplay = date ? ( + <> + Saved date + + + ) : ( + '' + ) + const selectedDateDisplay = + selectedDate && isSelectedDateValid ? ( + <> + Selected date + + + ) : ( + '' + ) + const popover = ( {dateOptionsInput} {dateInputGroups} + {savedDateDisplay} + {selectedDateDisplay} {date && deleteButton} {saveButton} Saving... diff --git a/src/chronology/domain/Date.test.ts b/src/chronology/domain/Date.test.ts index 9339b703b..5cdf8fc42 100644 --- a/src/chronology/domain/Date.test.ts +++ b/src/chronology/domain/Date.test.ts @@ -109,7 +109,7 @@ describe('MesopotamianDate', () => { true ) expect(date.toString()).toBe( - '12.V.10 SE (30 August 302 BCE PJC | 25 August 301 BCE PGC)' + '12.V.10 SE (30 August 302 BCE PJC | 25 August 302 BCE PGC)' ) }) @@ -123,7 +123,7 @@ describe('MesopotamianDate', () => { true ) expect(date.toString()).toBe( - '∅.V.10 SE (ca. 19 August 302 BCE PJC | ca. 14 August 301 BCE PGC)' + '∅.V.10 SE (ca. 19 August 302 BCE PJC | ca. 14 August 302 BCE PGC)' ) }) @@ -137,7 +137,7 @@ describe('MesopotamianDate', () => { true ) expect(date.toString()).toBe( - '12.∅.10 SE (ca. 4 May 302 BCE PJC | ca. 29 April 301 BCE PGC)' + '12.∅.10 SE (ca. 4 May 302 BCE PJC | ca. 29 April 302 BCE PGC)' ) }) @@ -164,7 +164,7 @@ describe('MesopotamianDate', () => { false ) expect(date.toString()).toBe( - '12.V.10 Darius I (11 August 512 BCE PJC | 5 August 511 BCE PGC)' + '12.V.10 Darius I (11 August 512 BCE PJC | 5 August 512 BCE PGC)' ) }) @@ -179,7 +179,7 @@ describe('MesopotamianDate', () => { false ) expect(date.toString()).toBe( - '∅.V.10 Darius I (ca. 31 July 512 BCE PJC | ca. 25 July 511 BCE PGC)' + '∅.V.10 Darius I (ca. 31 July 512 BCE PJC | ca. 25 July 512 BCE PGC)' ) }) @@ -194,7 +194,7 @@ describe('MesopotamianDate', () => { false ) expect(date.toString()).toBe( - '12.∅.10 Darius I (ca. 16 April 512 BCE PJC | ca. 10 April 511 BCE PGC)' + '12.∅.10 Darius I (ca. 16 April 512 BCE PJC | ca. 10 April 512 BCE PGC)' ) }) @@ -209,7 +209,7 @@ describe('MesopotamianDate', () => { false ) expect(date.toString()).toBe( - '∅.V.10 Darius I (ca. 31 July 512 BCE PJC | ca. 25 July 511 BCE PGC)' + '∅.V.10 Darius I (ca. 31 July 512 BCE PJC | ca. 25 July 512 BCE PGC)' ) }) diff --git a/src/chronology/domain/DateBase.ts b/src/chronology/domain/DateBase.ts index 09e276f46..47261a9fd 100644 --- a/src/chronology/domain/DateBase.ts +++ b/src/chronology/domain/DateBase.ts @@ -95,23 +95,19 @@ export class MesopotamianDateBase { } toModernDate(calendar: 'Julian' | 'Gregorian' = 'Julian'): string { - const { year, month, day, isApproximate } = this.getDateApproximation() const dateProps = { - year, - month, - day, - isApproximate, + ...this.getDateApproximation(), calendar, } let julianDate = '' - if (this.isSeleucidEraApplicable(year)) { + if (this.isSeleucidEraApplicable(dateProps.year)) { julianDate = this.seleucidToModernDate(dateProps) } else if (this.isNabonassarEraApplicable()) { julianDate = this.getNabonassarEraDate(dateProps) } else if (this.isAssyrianDateApplicable()) { julianDate = this.getAssyrianDate({ calendar: 'Julian' }) } else if (this.isKingDateApplicable()) { - julianDate = this.kingToModernDate({ year, calendar: 'Julian' }) + julianDate = this.kingToModernDate({ ...dateProps, calendar: 'Julian' }) } return julianDate } @@ -144,24 +140,14 @@ export class MesopotamianDateBase { day: number isApproximate: boolean } { - let year = parseInt(this.year.value) - let month = parseInt(this.month.value) - let day = parseInt(this.day.value) - const isApproximate = this.isApproximate() - if (isNaN(month)) { - month = 1 - } - if (isNaN(day)) { - day = 1 - } - if (isNaN(year)) { - year = -1 - } + const year = parseInt(this.year.value) + const month = parseInt(this.month.value) + const day = parseInt(this.day.value) return { - year, - month, - day, - isApproximate: isApproximate, + year: isNaN(year) ? -1 : year, + month: isNaN(month) ? 1 : month, + day: isNaN(day) ? 1 : day, + isApproximate: this.isApproximate(), } } @@ -221,6 +207,7 @@ export class MesopotamianDateBase { } return '' } + private kingToModernDate({ year, calendar = 'Julian', diff --git a/src/chronology/domain/DateConverter.test.ts b/src/chronology/domain/DateConverter.test.ts index 7ff86ecd5..5b46ab542 100644 --- a/src/chronology/domain/DateConverter.test.ts +++ b/src/chronology/domain/DateConverter.test.ts @@ -3,8 +3,8 @@ import DateConverter from 'chronology/domain/DateConverter' const dateJulianEraBegin = { bcJulianYear: 311, cjdn: 1607923, - gregorianYear: -309, - bcGregorianYear: 310, + gregorianYear: -310, + bcGregorianYear: 311, gregorianMonth: 3, gregorianDay: 29, julianDay: 3, @@ -23,8 +23,8 @@ const dateJulianEraBegin = { } const dateNebuchadnezzarIIY43M12D28 = { - gregorianYear: -559, - bcGregorianYear: 560, + gregorianYear: -560, + bcGregorianYear: 561, gregorianMonth: 3, gregorianDay: 28, julianYear: -560, @@ -44,8 +44,8 @@ const dateNebuchadnezzarIIY43M12D28 = { } const dateNebuchadnezzarIIY23M10D14 = { - gregorianYear: -580, - bcGregorianYear: 581, + gregorianYear: -581, + bcGregorianYear: 582, gregorianMonth: 12, gregorianDay: 28, julianYear: -580, @@ -65,8 +65,8 @@ const dateNebuchadnezzarIIY23M10D14 = { } const dateSeleucidY100M12D26 = { - gregorianYear: -209, - bcGregorianYear: 210, + gregorianYear: -210, + bcGregorianYear: 211, gregorianMonth: 3, gregorianDay: 30, julianYear: -210, @@ -89,7 +89,6 @@ const dateSeleucidY100M12D26 = { describe('DateConverter', () => { let mesopotamianDate: DateConverter - beforeEach(() => { mesopotamianDate = new DateConverter() }) @@ -97,7 +96,7 @@ describe('DateConverter', () => { test('Check initial state', () => { const expected = dateJulianEraBegin expect(mesopotamianDate.calendar).toEqual(expected) - expect(mesopotamianDate.toDateString()).toEqual('29 March 310 BCE PGC') + expect(mesopotamianDate.toDateString()).toEqual('29 March 311 BCE PGC') expect(mesopotamianDate.toDateString('Julian')).toEqual( '3 April 311 BCE PJC' ) @@ -105,9 +104,9 @@ describe('DateConverter', () => { test('Set to Gregorian date', () => { const expected = dateNebuchadnezzarIIY43M12D28 - mesopotamianDate.setToGregorianDate(-559, 3, 28) + mesopotamianDate.setToGregorianDate(-560, 3, 28) expect(mesopotamianDate.calendar).toEqual(expected) - expect(mesopotamianDate.toDateString()).toEqual('28 March 560 BCE PGC') + expect(mesopotamianDate.toDateString()).toEqual('28 March 561 BCE PGC') expect(mesopotamianDate.toDateString('Julian')).toEqual( '3 April 561 BCE PJC' ) @@ -117,7 +116,7 @@ describe('DateConverter', () => { const expected = dateNebuchadnezzarIIY43M12D28 mesopotamianDate.setToJulianDate(-560, 4, 3) expect(mesopotamianDate.calendar).toEqual(expected) - expect(mesopotamianDate.toDateString()).toEqual('28 March 560 BCE PGC') + expect(mesopotamianDate.toDateString()).toEqual('28 March 561 BCE PGC') expect(mesopotamianDate.toDateString('Julian')).toEqual( '3 April 561 BCE PJC' ) @@ -127,7 +126,7 @@ describe('DateConverter', () => { const expected = dateNebuchadnezzarIIY23M10D14 mesopotamianDate.setToMesopotamianDate('Nebuchadnezzar II', 23, 10, 14) expect(mesopotamianDate.calendar).toEqual(expected) - expect(mesopotamianDate.toDateString()).toEqual('28 December 581 BCE PGC') + expect(mesopotamianDate.toDateString()).toEqual('28 December 582 BCE PGC') expect(mesopotamianDate.toDateString('Julian')).toEqual( '3 January 581 BCE PJC' ) @@ -137,7 +136,7 @@ describe('DateConverter', () => { mesopotamianDate.setToSeBabylonianDate(100, 12, 26) const expected = dateSeleucidY100M12D26 expect(mesopotamianDate.calendar).toEqual(expected) - expect(mesopotamianDate.toDateString()).toEqual('30 March 210 BCE PGC') + expect(mesopotamianDate.toDateString()).toEqual('30 March 211 BCE PGC') expect(mesopotamianDate.toDateString('Julian')).toEqual( '3 April 211 BCE PJC' ) diff --git a/src/chronology/domain/DateConverterCompute.ts b/src/chronology/domain/DateConverterCompute.ts index 06b2d19ee..1187b4769 100644 --- a/src/chronology/domain/DateConverterCompute.ts +++ b/src/chronology/domain/DateConverterCompute.ts @@ -64,7 +64,7 @@ export default class DateConverterCompute { ) const { quotient: alpha2b, remainder: m0 } = divmod(m1 + 2, 12) return { - year: a1 + alpha2b < 1 ? a1 + alpha2b + 1 : a1 + alpha2b, + year: a1 + alpha2b < 1 ? a1 + alpha2b : a1 + alpha2b, month: m0 + 1, day: Math.floor(epsilon1 / 5) + 1, } @@ -111,19 +111,17 @@ export default class DateConverterCompute { gregorianMonth: number gregorianDay: number }): number { - gregorianYear = gregorianYear < 1 ? gregorianYear - 1 : gregorianYear - const alpha1 = Math.floor((gregorianMonth - 3) / 12) - const m1 = (gregorianMonth - 3) % 12 - const a1 = gregorianYear + alpha1 - return ( - 365 * a1 + - Math.floor(a1 / 4) - - Math.floor(a1 / 100) + - Math.floor(a1 / 400) + - Math.floor((153 * m1 + 2) / 5) + - gregorianDay + - 1721119 - ) + if (gregorianMonth < 3) { + gregorianYear -= 1 + gregorianMonth += 12 + } + gregorianYear = gregorianYear === 0 ? -1 : gregorianYear + const monthDays = Math.floor(30.6001 * (gregorianMonth + 1)) + const century = Math.floor(gregorianYear / 100) + const leapYearCorrection = Math.floor(century / 4) + const fixedDay = 2 - century + leapYearCorrection + const yearDays = Math.floor(365.25 * (gregorianYear + 4716)) + return Math.floor(fixedDay + gregorianDay + yearDays + monthDays - 1524) } computeWeekDay(cjdn: number): number { diff --git a/src/chronology/ui/DateConverter/DateConverterForm.test.tsx b/src/chronology/ui/DateConverter/DateConverterForm.test.tsx index 79fa9ccb5..d1c0a92e6 100644 --- a/src/chronology/ui/DateConverter/DateConverterForm.test.tsx +++ b/src/chronology/ui/DateConverter/DateConverterForm.test.tsx @@ -62,7 +62,7 @@ describe('DateConverterForm', () => { expect(screen.getAllByLabelText(/cjdn|lunation/i)).toHaveLength(2) expect(optionToArray(screen.getByLabelText('Ruler'))).toStrictEqual(29) expect(screen.getAllByLabelText(/year/i).map(optionToArray)).toStrictEqual([ - 701, + 702, 702, 30, 701, @@ -82,7 +82,7 @@ describe('DateConverterForm', () => { it('renders initial form values correctly', () => { render() - expect(screen.getByLabelText('Year')).toHaveValue('-309') + expect(screen.getByLabelText('Year')).toHaveValue('-310') expect(screen.getByLabelText('Month')).toHaveValue('3') expect(screen.getByLabelText('Day')).toHaveValue('29') expect(screen.getByLabelText('Julian Year')).toHaveValue('-310') @@ -140,7 +140,7 @@ describe('DateConverterForm', () => { within(screen.getByLabelText('Year')).getByText('300 BCE') ) }) - expect(screen.getByLabelText('Year')).toHaveValue('-309') + expect(screen.getByLabelText('Year')).toHaveValue('-310') expect(screen.getByLabelText('Month')).toHaveValue('3') expect(screen.getByLabelText('Day')).toHaveValue('29') expect(screen.getByLabelText('Julian Year')).toHaveValue('-310') @@ -164,7 +164,7 @@ describe('DateConverterForm', () => { fireEvent.click(screen.getByText('Copy JSON')) }) const expected = { - gregorianYear: -309, + gregorianYear: -310, gregorianMonth: 3, gregorianDay: 29, julianYear: -310, @@ -176,7 +176,7 @@ describe('DateConverterForm', () => { seBabylonianYear: 1, lunationNabonassar: 5395, bcJulianYear: 311, - bcGregorianYear: 310, + bcGregorianYear: 311, mesopotamianDay: 1, mesopotamianMonthLength: 29, ruler: 'Seleucus I Nicator', diff --git a/src/chronology/ui/DateConverter/DateConverterForm.tsx b/src/chronology/ui/DateConverter/DateConverterForm.tsx index 292bb04a0..978183b65 100644 --- a/src/chronology/ui/DateConverter/DateConverterForm.tsx +++ b/src/chronology/ui/DateConverter/DateConverterForm.tsx @@ -15,7 +15,6 @@ import { handleDateConverterFormChange } from 'chronology/application/DateConver // ToDo: // - Errors: -// - January & February have issues (both Julean & Gregorian date drifts upon change) // - Check dates around 1 BCE / CE // - Fix errors with first and last ruler diff --git a/src/chronology/ui/DateEditor/DateSelection.test.tsx b/src/chronology/ui/DateEditor/DateSelection.test.tsx index af0fc2c46..454588cc6 100644 --- a/src/chronology/ui/DateEditor/DateSelection.test.tsx +++ b/src/chronology/ui/DateEditor/DateSelection.test.tsx @@ -126,8 +126,11 @@ describe('DateSelection', () => { act(() => { fireEvent.click(editButton) }) + const yearInput = screen.getByPlaceholderText('Year') + await act(async () => { + fireEvent.change(yearInput, { target: { value: '189' } }) + }) const saveButton = screen.getByText('Save') - fireEvent.click(saveButton) const loadingSpinner = screen.getByText('Saving...') expect(loadingSpinner).toBeInTheDocument() diff --git a/src/chronology/ui/DateEditor/DatesInTextSelection.test.tsx b/src/chronology/ui/DateEditor/DatesInTextSelection.test.tsx index 99a778be9..ba13c69fa 100644 --- a/src/chronology/ui/DateEditor/DatesInTextSelection.test.tsx +++ b/src/chronology/ui/DateEditor/DatesInTextSelection.test.tsx @@ -44,6 +44,12 @@ describe('DatesInTextSelection', () => { await act(async () => { fireEvent.click(addButton) }) + const dayInput = screen.getByPlaceholderText('Day') + const monthInput = screen.getByPlaceholderText('Month') + await act(async () => { + fireEvent.change(dayInput, { target: { value: '18' } }) + fireEvent.change(monthInput, { target: { value: '10' } }) + }) const saveButton = screen.getByText('Save') await act(async () => { fireEvent.click(saveButton) @@ -62,6 +68,12 @@ describe('DatesInTextSelection', () => { await act(async () => { fireEvent.click(editButton) }) + const dayInput = screen.getByPlaceholderText('Day') + const monthInput = screen.getByPlaceholderText('Month') + await act(async () => { + fireEvent.change(dayInput, { target: { value: '18' } }) + fireEvent.change(monthInput, { target: { value: '10' } }) + }) const saveButton = screen.getByText('Save') await act(async () => { fireEvent.click(saveButton)