diff --git a/app/pages/reservation/reservation-details/ReservationDetails.js b/app/pages/reservation/reservation-details/ReservationDetails.js index 0a78733db..eb45ef1cd 100644 --- a/app/pages/reservation/reservation-details/ReservationDetails.js +++ b/app/pages/reservation/reservation-details/ReservationDetails.js @@ -12,7 +12,7 @@ function ReservationDetails({ let reservationTime = ''; if (selectedTime) { - reservationTime = formatDetailsDatetimes(selectedTime.begin, selectedTime.end, t('common.unit.time.day.short')); + reservationTime = formatDetailsDatetimes(selectedTime.begin, selectedTime.end, t, t('common.unit.time.day.short')); } return ( diff --git a/app/pages/user-reservations/reservation-list/ReservationListItem.js b/app/pages/user-reservations/reservation-list/ReservationListItem.js index 860cb71df..9acc5dd6f 100644 --- a/app/pages/user-reservations/reservation-list/ReservationListItem.js +++ b/app/pages/user-reservations/reservation-list/ReservationListItem.js @@ -61,6 +61,7 @@ class ReservationListItem extends Component { beginFormat={beginFormat} end={reservation.end} endFormat={endFormat} + isMultiday={isReservationMultiday} /> { expect(timeRange.prop('end')).toBe(props.reservation.end); expect(timeRange.prop('beginFormat')).toBe('dddd, LLL'); expect(timeRange.prop('endFormat')).toBe('LT'); + expect(timeRange.prop('isMultiday')).toBe(false); }); }); @@ -105,6 +106,7 @@ describe('pages/user-reservations/reservation-list/ReservationListItem', () => { expect(timeRange.prop('end')).toBe(reservationA.end); expect(timeRange.prop('beginFormat')).toBe('D.M.YYYY HH:mm'); expect(timeRange.prop('endFormat')).toBe('D.M.YYYY HH:mm'); + expect(timeRange.prop('isMultiday')).toBe(true); }); }); diff --git a/app/shared/modals/reservation-info/ReservationEditForm.js b/app/shared/modals/reservation-info/ReservationEditForm.js index 190b09acf..0d56111ce 100644 --- a/app/shared/modals/reservation-info/ReservationEditForm.js +++ b/app/shared/modals/reservation-info/ReservationEditForm.js @@ -190,6 +190,7 @@ class UnconnectedReservationEditForm extends Component { beginFormat={beginFormat} end={reservation.end} endFormat={endFormat} + isMultiday={isReservationMultiday} /> ); return this.renderInfoRow(t('common.reservationTimeLabel'), staticReservationTime); diff --git a/app/shared/overnight-calendar/OvernightCalendar.js b/app/shared/overnight-calendar/OvernightCalendar.js index 52a311ec2..a6b5998dd 100644 --- a/app/shared/overnight-calendar/OvernightCalendar.js +++ b/app/shared/overnight-calendar/OvernightCalendar.js @@ -240,25 +240,25 @@ function OvernightCalendar({ {!isEditing && ( )} {isEditing && ( )} diff --git a/app/shared/overnight-calendar/overnightUtils.js b/app/shared/overnight-calendar/overnightUtils.js index 90388b7f3..f7edef214 100644 --- a/app/shared/overnight-calendar/overnightUtils.js +++ b/app/shared/overnight-calendar/overnightUtils.js @@ -1,5 +1,7 @@ import moment from 'moment'; +import { formatDatetimeToString } from '../../utils/timeUtils'; + /** * Handles setting the start and end dates when selecting a date range. * @param {Object} params @@ -339,13 +341,14 @@ export function setDatesTime(date, time) { * Combines date and time into a datetime string and returns it. * @param {Date} date * @param {string} time e.g. "12:00:00" + * @param {function} t * @returns {string} datetime string e.g. "2018-02-01T12:00:00Z" * or empty string if date or time is missing */ -export function getOvernightDatetime(date, time) { +export function getOvernightDatetime(date, time, t) { if (date && time) { const momentDate = setDatesTime(date, time); - return momentDate.format('D.M.YYYY HH:mm'); + return formatDatetimeToString(momentDate, t); } return ''; } diff --git a/app/shared/overnight-calendar/tests/OvernightCalendar.spec.js b/app/shared/overnight-calendar/tests/OvernightCalendar.spec.js index 0b44d4f19..468684aa6 100644 --- a/app/shared/overnight-calendar/tests/OvernightCalendar.spec.js +++ b/app/shared/overnight-calendar/tests/OvernightCalendar.spec.js @@ -131,13 +131,13 @@ describe('app/shared/overnight-calendar/OvernightCalendar', () => { expect(editSummary).toHaveLength(1); expect(editSummary.prop('datesSameAsInitial')).toBe(true); expect(editSummary.prop('duration')).toBeDefined(); - expect(editSummary.prop('endDatetime')).toBe('19.2.2024 09:00'); + expect(editSummary.prop('endDatetime')).toBe('19.2.2024 TimeSlots.selectedTime'); expect(editSummary.prop('isDurationBelowMin')).toBe(false); expect(editSummary.prop('minDuration')).toBe(defaultProps.resource.minPeriod); expect(editSummary.prop('onCancel')).toBe(defaultProps.onEditCancel); expect(editSummary.prop('onConfirm')).toBeDefined(); expect(editSummary.prop('selected')).toBe(selected); - expect(editSummary.prop('startDatetime')).toBe('18.2.2024 11:00'); + expect(editSummary.prop('startDatetime')).toBe('18.2.2024 TimeSlots.selectedTime'); }); }); }); diff --git a/app/shared/overnight-calendar/tests/overnightUtils.spec.js b/app/shared/overnight-calendar/tests/overnightUtils.spec.js index ce1bbfe9b..e07b2a076 100644 --- a/app/shared/overnight-calendar/tests/overnightUtils.spec.js +++ b/app/shared/overnight-calendar/tests/overnightUtils.spec.js @@ -533,16 +533,17 @@ describe('app/shared/overnight-calendar/overnightUtils', () => { }); describe('getOvernightDatetime', () => { + const t = (str, timeObj) => `${str} ${timeObj.time}`; test('returns correct datetime string', () => { const date = moment('2024-04-23').toDate(); const time = '10:30:00'; - const result = getOvernightDatetime(date, time); - expect(result).toBe('23.4.2024 10:30'); + const result = getOvernightDatetime(date, time, t); + expect(result).toBe('23.4.2024 TimeSlots.selectedTime 10:30'); }); test('returns empty string when date or time is missing', () => { - expect(getOvernightDatetime(null, null)).toBe(''); - expect(getOvernightDatetime(null, '10:30:00')).toBe(''); - expect(getOvernightDatetime(moment('2024-04-23').toDate(), null)).toBe(''); + expect(getOvernightDatetime(null, null, t)).toBe(''); + expect(getOvernightDatetime(null, '10:30:00', t)).toBe(''); + expect(getOvernightDatetime(moment('2024-04-23').toDate(), null, t)).toBe(''); }); }); diff --git a/app/shared/reservation-date/ReservationOvernightDate.js b/app/shared/reservation-date/ReservationOvernightDate.js index 560bea5a7..9ab74cc12 100644 --- a/app/shared/reservation-date/ReservationOvernightDate.js +++ b/app/shared/reservation-date/ReservationOvernightDate.js @@ -4,7 +4,7 @@ import moment from 'moment'; import iconClock from 'assets/icons/clock-o.svg'; import iconCalendar from 'assets/icons/calendar.svg'; -import { getPrettifiedDuration } from 'utils/timeUtils'; +import { getPrettifiedDuration, formatDatetimeToString } from 'utils/timeUtils'; import injectT from '../../i18n/injectT'; function ReservationOvernightDate({ beginDate, endDate, t }) { @@ -14,8 +14,8 @@ function ReservationOvernightDate({ beginDate, endDate, t }) { const reservationBegin = moment(beginDate); const reservationEnd = moment(endDate); - const begin = reservationBegin.format('D.M.YYYY HH:mm'); - const end = reservationEnd.format('D.M.YYYY HH:mm'); + const begin = formatDatetimeToString(reservationBegin, t); + const end = formatDatetimeToString(reservationEnd, t); const duration = getPrettifiedDuration(reservationBegin, reservationEnd, t('common.unit.time.day.short')); return ( diff --git a/app/shared/reservation-date/ReservationOvernightDate.spec.js b/app/shared/reservation-date/ReservationOvernightDate.spec.js index e80ae1203..a45057431 100644 --- a/app/shared/reservation-date/ReservationOvernightDate.spec.js +++ b/app/shared/reservation-date/ReservationOvernightDate.spec.js @@ -28,8 +28,8 @@ describe('shared/reservation-date/ReservationOvernightDate', () => { test('renders time texts', () => { const texts = wrapper.find('p.reservation-date__time'); expect(texts.length).toBe(3); - expect(texts.at(0).text()).toBe('common.time.begin: 29.1.2018 13:00'); - expect(texts.at(1).text()).toBe('common.time.end: 31.1.2018 09:00'); + expect(texts.at(0).text()).toBe('common.time.begin: 29.1.2018 TimeSlots.selectedTime'); + expect(texts.at(1).text()).toBe('common.time.end: 31.1.2018 TimeSlots.selectedTime'); expect(texts.at(2).text()).toBe('common.time.duration: 1common.unit.time.day.short 20h'); }); diff --git a/app/shared/time-range/TimeRange.js b/app/shared/time-range/TimeRange.js index e33952cbb..50dfae1ca 100644 --- a/app/shared/time-range/TimeRange.js +++ b/app/shared/time-range/TimeRange.js @@ -3,6 +3,9 @@ import moment from 'moment'; import PropTypes from 'prop-types'; import React from 'react'; +import { formatDatetimeToString } from '../../utils/timeUtils'; +import injectT from '../../i18n/injectT'; + function TimeRange(props) { const { begin, @@ -10,10 +13,17 @@ function TimeRange(props) { beginFormat, end, endFormat, + isMultiday, + t, } = props; const beginMoment = moment(begin); const endMoment = moment(end); - const rangeString = `${beginMoment.format(beginFormat)} \u2013 ${endMoment.format(endFormat)}`; + let rangeString = ''; + if (isMultiday) { + rangeString = `${formatDatetimeToString(beginMoment, t)} \u2013 ${formatDatetimeToString(endMoment, t)}`; + } else { + rangeString = `${beginMoment.format(beginFormat)} \u2013 ${endMoment.format(endFormat)}`; + } const ISORangeString = `${begin}/${end}`; return ( @@ -29,11 +39,14 @@ TimeRange.propTypes = { className: PropTypes.string, end: PropTypes.string.isRequired, endFormat: PropTypes.string, + isMultiday: PropTypes.bool, + t: PropTypes.func.isRequired, }; TimeRange.defaultProps = { beginFormat: 'dddd, LLL', endFormat: 'LT', + isMultiday: false, }; -export default TimeRange; +export default injectT(TimeRange); diff --git a/app/shared/time-range/TimeRange.spec.js b/app/shared/time-range/TimeRange.spec.js index 7348ef16c..60ec5689a 100644 --- a/app/shared/time-range/TimeRange.spec.js +++ b/app/shared/time-range/TimeRange.spec.js @@ -1,7 +1,7 @@ -import { shallow } from 'enzyme'; import moment from 'moment'; import React from 'react'; +import { shallowWithIntl } from 'utils/testUtils'; import TimeRange from './TimeRange'; describe('shared/time-range/TimeRange', () => { @@ -14,7 +14,7 @@ describe('shared/time-range/TimeRange', () => { }; function getWrapper(extraProps) { - return shallow(); + return shallowWithIntl(); } test('renders a time element', () => { @@ -45,5 +45,13 @@ describe('shared/time-range/TimeRange', () => { const expected = moment(defaultProps.end).format(defaultProps.endFormat); expect(rangeString).toContain(expected); }); + + describe('when time range is multiday', () => { + test('display correct range string', () => { + const timeElement = getWrapper({ isMultiday: true, end: '2015-10-12T14:00:00Z' }); + expect(timeElement).toHaveLength(1); + expect(timeElement.text()).toBe('11.10.2015 TimeSlots.selectedTime – 12.10.2015 TimeSlots.selectedTime'); + }); + }); }); }); diff --git a/app/utils/__tests__/timeUtils.spec.js b/app/utils/__tests__/timeUtils.spec.js index cdaacc94b..fb3a68cb1 100644 --- a/app/utils/__tests__/timeUtils.spec.js +++ b/app/utils/__tests__/timeUtils.spec.js @@ -28,7 +28,8 @@ import { formatTime, formatDateTime, formatDetailsDatetimes, - isMultiday + isMultiday, + formatDatetimeToString } from 'utils/timeUtils'; const moment = extendMoment(Moment); @@ -883,9 +884,18 @@ describe('Utils: timeUtils', () => { describe('formatDetailsDatetimes', () => { test.each([ ['2023-11-01T15:00:00Z', '2023-11-01T16:00:00Z', '1.11.2023 17:00–18:00 (1h)'], - ['2023-11-01T15:00:00Z', '2023-11-03T13:00:00Z', '1.11.2023 17:00 - 3.11.2023 15:00 (1d 22h)'], + ['2023-11-01T15:00:00Z', '2023-11-03T13:00:00Z', '1.11.2023 TimeSlots.selectedTime 17:00 - 3.11.2023 TimeSlots.selectedTime 15:00 (1d 22h)'], ])('returns correctly formatted datetime string with given params', (begin, end, expected) => { - expect(formatDetailsDatetimes(begin, end)).toBe(expected); + const t = (str, timeObj) => `${str} ${timeObj.time}`; + expect(formatDetailsDatetimes(begin, end, t)).toBe(expected); + }); + }); + + describe('formatDatetimeToString', () => { + const t = (str, timeObj) => `${str} ${timeObj.time}`; + test('returns correct string', () => { + expect(formatDatetimeToString('2023-11-01T15:00:00Z', t)).toBe('1.11.2023 TimeSlots.selectedTime 17:00'); + expect(formatDatetimeToString(moment('2023-11-01T15:00:00Z'), t)).toBe('1.11.2023 TimeSlots.selectedTime 17:00'); }); }); diff --git a/app/utils/timeUtils.js b/app/utils/timeUtils.js index 9e6a38f2f..7f9e12b18 100644 --- a/app/utils/timeUtils.js +++ b/app/utils/timeUtils.js @@ -353,12 +353,13 @@ function formatDateTime(datetime, targetFormat) { * D.M.YYYY HH:mm–HH:mm (1h 30min) or D.M.YYYY HH:mm - D.M.YYYY HH:mm (2d 5h) * @param {string} begin datetime * @param {string} end datetime + * @param {function} t * @returns {string} formatted datetime string */ -function formatDetailsDatetimes(begin, end, dayUnit = 'd') { +function formatDetailsDatetimes(begin, end, t, dayUnit = 'd') { if (isMultiday(begin, end)) { - const beginText = moment(begin).format('D.M.YYYY HH:mm'); - const endText = moment(end).format('D.M.YYYY HH:mm'); + const beginText = formatDatetimeToString(begin, t); + const endText = formatDatetimeToString(end, t); const duration = getPrettifiedDuration(begin, end, dayUnit); return `${beginText} - ${endText} (${duration})`; } @@ -369,6 +370,19 @@ function formatDetailsDatetimes(begin, end, dayUnit = 'd') { return `${beginText}–${endText} (${duration})`; } +/** + * Formats datetime to string + * @param {object|string} datetime any moment parsable format + * @param {function} t + * @returns {string} formatted datetime string e.g 20.10.2010 11:00 + */ +function formatDatetimeToString(datetime, t) { + const momentDatetime = moment(datetime); + const formattedDate = momentDatetime.format('D.M.YYYY'); + const formattedTime = t('TimeSlots.selectedTime', { time: momentDatetime.format('HH:mm') }); + return `${formattedDate} ${formattedTime}`; +} + /** * Check whether begin and end are on different days * @param {string} begin datetime @@ -403,4 +417,5 @@ export { formatDateTime, formatDetailsDatetimes, isMultiday, + formatDatetimeToString, };