diff --git a/src/core_modules/capture-core/components/FormFields/New/Fields/AgeField/AgeField.component.js b/src/core_modules/capture-core/components/FormFields/New/Fields/AgeField/AgeField.component.js index 2306b0854d..c160c34475 100644 --- a/src/core_modules/capture-core/components/FormFields/New/Fields/AgeField/AgeField.component.js +++ b/src/core_modules/capture-core/components/FormFields/New/Fields/AgeField/AgeField.component.js @@ -2,8 +2,6 @@ import * as React from 'react'; import { withStyles, withTheme } from '@material-ui/core/styles'; import { AgeField as UIAgeField } from 'capture-ui'; -import moment from 'moment'; -import { parseDate, convertMomentToDateFormatString } from '../../../../../utils/converters/date'; import { systemSettingsStore } from '../../../../../metaDataMemoryStores'; const getStyles = (theme: Theme) => ({ @@ -50,9 +48,6 @@ const AgeFieldPlain = (props: Props) => { return ( // $FlowFixMe[cannot-spread-inexact] automated comment diff --git a/src/core_modules/capture-core/utils/converters/date/convertStringToTemporal.js b/src/core_modules/capture-core/utils/converters/date/convertStringToTemporal.js new file mode 100644 index 0000000000..5eee8cc7a1 --- /dev/null +++ b/src/core_modules/capture-core/utils/converters/date/convertStringToTemporal.js @@ -0,0 +1,39 @@ +// @flow +import { Temporal } from '@js-temporal/polyfill'; +import { systemSettingsStore } from '../../../metaDataMemoryStores'; + +/** + * Converts a date string into a Temporal.PlainDate object using the system set calendar + * @export + * @param {*} string - dateString + * @returns {(Temporal.PlainDate | null)} + */ + +export function convertStringToTemporal(dateString: string): Temporal.PlainDate | null { + if (!dateString) { + return null; + } + try { + const dateWithHyphen = dateString.replace(/[\/\.]/g, '-'); + + const calendar = systemSettingsStore.get().calendar; + const dateFormat = systemSettingsStore.get().dateFormat; + + let year; let month; let day; + + if (dateFormat === 'YYYY-MM-DD') { + [year, month, day] = dateWithHyphen.split('-').map(Number); + } + if (dateFormat === 'DD-MM-YYYY') { + [day, month, year] = dateWithHyphen.split('-').map(Number); + } + return Temporal.PlainDate.from({ + year, + month, + day, + calendar, + }); + } catch (error) { + return ''; + } +} diff --git a/src/core_modules/capture-core/utils/converters/date/convertTemporalToString.js b/src/core_modules/capture-core/utils/converters/date/convertTemporalToString.js new file mode 100644 index 0000000000..a295b7984d --- /dev/null +++ b/src/core_modules/capture-core/utils/converters/date/convertTemporalToString.js @@ -0,0 +1,29 @@ +// @flow +import { Temporal } from '@js-temporal/polyfill'; +import { padWithZeros } from './padWithZeros'; +import { systemSettingsStore } from '../../../../capture-core/metaDataMemoryStores'; + +/** + * Converts a Temporal.PlainDate to a formatted date string (YYYY-MM-DD || DD-MM-YYYY) + * @param {Temporal.PlainDate} temporalDate - The Temporal date to convert + * @returns {string} Formatted date string, or empty string if invalid + */ + +export function convertTemporalToString(temporalDate: Temporal.PlainDate | null): string { + if (!temporalDate) { + return ''; + } + const dateFormat = systemSettingsStore.get().dateFormat; + + try { + const year = temporalDate.year; + const month = temporalDate.month; + const day = temporalDate.day; + + return dateFormat === 'YYYY-MM-DD' ? + `${padWithZeros(year, 4)}-${padWithZeros(month, 2)}-${padWithZeros(day, 2)}` : + `${padWithZeros(day, 2)}-${padWithZeros(month, 2)}-${padWithZeros(year, 4)}`; + } catch (error) { + return ''; + } +} diff --git a/src/core_modules/capture-core/utils/converters/date/index.js b/src/core_modules/capture-core/utils/converters/date/index.js index 511298b89d..11f200fb34 100644 --- a/src/core_modules/capture-core/utils/converters/date/index.js +++ b/src/core_modules/capture-core/utils/converters/date/index.js @@ -6,3 +6,5 @@ export { convertStringToDateFormat } from './stringToMomentDateFormat'; export { padWithZeros } from './padWithZeros'; export { convertIsoToLocalCalendar } from './convertIsoToLocalCalendar'; export { convertLocalToIsoCalendar } from './convertLocalToIsoCalendar'; +export { convertStringToTemporal } from './convertStringToTemporal'; +export { convertTemporalToString } from './convertTemporalToString'; diff --git a/src/core_modules/capture-ui/AgeField/AgeField.component.js b/src/core_modules/capture-ui/AgeField/AgeField.component.js index 78abcf4a21..b04b755613 100644 --- a/src/core_modules/capture-ui/AgeField/AgeField.component.js +++ b/src/core_modules/capture-ui/AgeField/AgeField.component.js @@ -1,7 +1,7 @@ // @flow import React, { Component } from 'react'; +import { Temporal } from '@js-temporal/polyfill'; import { isValidPositiveInteger } from 'capture-core-utils/validators/form'; -import { convertDateObjectToDateFormatString } from 'capture-core/utils/converters/date'; import { systemSettingsStore } from 'capture-core/metaDataMemoryStores'; import i18n from '@dhis2/d2-i18n'; import classNames from 'classnames'; @@ -12,6 +12,7 @@ import { AgeDateInput } from '../internal/AgeInput/AgeDateInput.component'; import defaultClasses from './ageField.module.css'; import { orientations } from '../constants/orientations.const'; import { withInternalChangeHandler } from '../HOC/withInternalChangeHandler'; +import { convertStringToTemporal, convertTemporalToString } from '../../capture-core/utils/converters/date'; type AgeValues = { date?: ?string, @@ -47,9 +48,6 @@ type Props = { inputMessageClasses: ?InputMessageClasses, inFocus?: ?boolean, shrinkDisabled?: ?boolean, - onParseDate: DateParser, - onGetFormattedDateStringFromMoment: DateStringFromMomentFormatter, - moment: any, dateCalendarTheme: Object, dateCalendarWidth?: ?any, datePopupAnchorPosition?: ?string, @@ -59,38 +57,26 @@ type Props = { datePlaceholder?: ?string, disabled?: ?boolean, }; -function getCalculatedValues( - dateValue: ?string, - onParseDate: DateParser, - onGetFormattedDateStringFromMoment: DateStringFromMomentFormatter, - moment: any, -): AgeValues { - const parseData = dateValue && onParseDate(dateValue); - if (!parseData || !parseData.isValid) { - return { - date: dateValue, - years: '', - months: '', - days: '', - }; - } - const dateFormat = systemSettingsStore.get().dateFormat; - const now = moment(convertDateObjectToDateFormatString(moment()), dateFormat); - const age = moment(parseData.momentDate); - const years = now.diff(age, 'years'); - age.add(years, 'years'); +function getCalculatedValues(dateValue: ?string): AgeValues { + const calendar = systemSettingsStore.get().calendar; + + const now = Temporal.Now.plainDateISO().withCalendar(calendar); - const months = now.diff(age, 'months'); - age.add(months, 'months'); + const age = convertStringToTemporal(dateValue); - const days = now.diff(age, 'days'); + const diff = now.since(age, { + largestUnit: 'years', + smallestUnit: 'days', + }); + + const date = convertTemporalToString(age); return { - date: onGetFormattedDateStringFromMoment(parseData.momentDate), - years: years.toString(), - months: months.toString(), - days: days.toString(), + date, + years: diff.years.toString(), + months: diff.months.toString(), + days: diff.days.toString(), }; } @@ -124,7 +110,7 @@ class D2AgeFieldPlain extends Component { } handleNumberBlur = (values: AgeValues) => { - const { onParseDate, onGetFormattedDateStringFromMoment, onRemoveFocus, moment } = this.props; + const { onRemoveFocus } = this.props; onRemoveFocus && onRemoveFocus(); if (D2AgeFieldPlain.isEmptyNumbers(values)) { @@ -136,28 +122,25 @@ class D2AgeFieldPlain extends Component { this.props.onBlur({ ...values, date: '' }); return; } - const dateFormat = systemSettingsStore.get().dateFormat; - const momentDate = moment(convertDateObjectToDateFormatString(moment(undefined, undefined, true)), dateFormat); - momentDate.subtract(D2AgeFieldPlain.getNumberOrZero(values.years), 'years'); - momentDate.subtract(D2AgeFieldPlain.getNumberOrZero(values.months), 'months'); - momentDate.subtract(D2AgeFieldPlain.getNumberOrZero(values.days), 'days'); - const calculatedValues = getCalculatedValues( - onGetFormattedDateStringFromMoment(momentDate), - onParseDate, - onGetFormattedDateStringFromMoment, - moment, - ); + + const calendar = systemSettingsStore.get().calendar; + + const now = Temporal.Now.plainDateISO().withCalendar(calendar); + + const calculatedDate = now.subtract({ + years: D2AgeFieldPlain.getNumberOrZero(values.years), + months: D2AgeFieldPlain.getNumberOrZero(values.months), + days: D2AgeFieldPlain.getNumberOrZero(values.days), + }); + + const calculatedValues = getCalculatedValues(convertTemporalToString(calculatedDate)); this.props.onBlur(calculatedValues); } handleDateBlur = (date: ?string, options: ?ValidationOptions) => { - const { onParseDate, onGetFormattedDateStringFromMoment, onRemoveFocus, moment } = this.props; + const { onRemoveFocus } = this.props; onRemoveFocus && onRemoveFocus(); - const calculatedValues = date ? getCalculatedValues( - date, - onParseDate, - onGetFormattedDateStringFromMoment, - moment) : null; + const calculatedValues = date ? getCalculatedValues(date) : null; this.props.onBlur(calculatedValues, options); } @@ -184,8 +167,6 @@ class D2AgeFieldPlain extends Component { datePopupAnchorPosition, dateCalendarTheme, dateCalendarLocale, - moment, - onParseDate, ...passOnProps } = this.props; return ( @@ -211,8 +192,6 @@ class D2AgeFieldPlain extends Component { shrinkDisabled, dateCalendarWidth, datePlaceholder, - moment, - onParseDate, ...passOnProps } = this.props;