diff --git a/src/layers/TralisLayer/TralisLayer.js b/src/layers/TralisLayer/TralisLayer.js index a11a58407..a25e61cb5 100644 --- a/src/layers/TralisLayer/TralisLayer.js +++ b/src/layers/TralisLayer/TralisLayer.js @@ -6,6 +6,7 @@ import { realtimeDelayStyle, sortByDelay, } from "mobility-toolbox-js/ol"; +import getDelayString from "../../utils/getDelayString"; /** * Trajserv value: 'Tram', 'Subway / Metro / S-Bahn', 'Train', 'Bus', 'Ferry', 'Cable Car', 'Gondola', 'Funicular', 'Long distance bus', 'Rail', @@ -52,6 +53,20 @@ const getRadius = (type, zoom) => { } }; +const getDelayText = (delayInMs, cancelled) => { + if (cancelled) { + return String.fromCodePoint(0x00d7); + } + + // no realtime or realtime is broken + if (delayInMs === null) { + return null; + } + + // Same function used by the RouteSchedule component + return getDelayString(delayInMs, true); +}; + class TralisLayer extends RealtimeLayer { constructor(options) { // You can filter the trains displayed using the following properties: @@ -83,6 +98,7 @@ class TralisLayer extends RealtimeLayer { style: realtimeDelayStyle, styleOptions: { getRadius, + getDelayText, }, sort: sortByDelay, fullTrajectoryStyle: fullTrajectoryDelayStyle, diff --git a/src/popups/PunctualityPopup/PunctualityPopup.js b/src/popups/PunctualityPopup/PunctualityPopup.js index 7c3dca74f..66766f37b 100644 --- a/src/popups/PunctualityPopup/PunctualityPopup.js +++ b/src/popups/PunctualityPopup/PunctualityPopup.js @@ -4,6 +4,7 @@ import { useDispatch, useSelector } from "react-redux"; import Feature from "ol/Feature"; import RouteSchedule from "react-spatial/components/RouteSchedule"; import TralisLayer from "../../layers/TralisLayer"; +import getDelayText, { getDelaySecure } from "../../utils/getDelayString"; function PunctualityPopup({ feature, layer }) { const dispatch = useDispatch(); @@ -53,28 +54,31 @@ function PunctualityPopup({ feature, layer }) { { - let timeInMs = milliseconds; - if (timeInMs < 0) { - timeInMs = 0; - } - const h = Math.floor(timeInMs / 3600000); - const m = (isArrival ? Math.ceil : Math.floor)( - (timeInMs % 3600000) / 60000, - ); + getDelayString={getDelayText} + renderArrivalDelay={(delay, stop, getDelayString, getDelayColor) => { + let delayToDisplay = delay; - if (h === 0 && m === 0) { - return "+0m"; - } - if (h === 0) { - return `+${m}m`; + // In some edge cases when arrival and departure time are in the + // same minute and delay too, we need to display the departure + // delay instead of the arrival delay to make sure the arrival + // time + ceiled delay is not greater than the departure time + + // floored delay. + // see TRAFWART-1702 + if ( + getDelaySecure(stop.departureDelay) + + stop.departureTime - + (getDelaySecure(delay) + stop.arrivalTime) < + 60000 + ) { + delayToDisplay = getDelaySecure(stop.departureDelay); } - return `+${h}h${m}m`; - }} - renderArrivalDelay={(delay, stop, getDelayString, getDelayColor) => { return ( - - {`${getDelayString?.(delay, true) || ""}`} + + {`${getDelayString?.(delayToDisplay, true) || ""}`} ); }} diff --git a/src/utils/getDelayString.js b/src/utils/getDelayString.js new file mode 100644 index 000000000..65e3b926c --- /dev/null +++ b/src/utils/getDelayString.js @@ -0,0 +1,41 @@ +// We round the minutes down for departure times and up for arrival times. +export const getDelaySecure = (milliseconds, isArrival) => { + let timeInMs = milliseconds; + if (timeInMs < 0) { + timeInMs = 0; + } + const h = Math.floor(timeInMs / 3600000); + const m = (isArrival ? Math.ceil : Math.floor)((timeInMs % 3600000) / 60000); + + return h * 3600000 + m * 60000; +}; + +// We round the minutes down for departure times and up for arrival times. +const getDelayString = (milliseconds, isArrival) => { + let timeInMs = milliseconds; + if (timeInMs < 0) { + timeInMs = 0; + } + timeInMs = getDelaySecure(timeInMs, isArrival); + const h = Math.floor(timeInMs / 3600000); + const m = (timeInMs % 3600000) / 60000; + + if (h === 0 && m === 0) { + return "+0m"; + } + if (h === 0) { + return `+${m}m`; + } + + let str = `+`; + if (h > 0) { + str += `${h}h`; + } + + if (m > 0) { + str += `${m}m`; + } + return str; +}; + +export default getDelayString; diff --git a/src/utils/getDelayString.test.js b/src/utils/getDelayString.test.js new file mode 100644 index 000000000..593f1e3a9 --- /dev/null +++ b/src/utils/getDelayString.test.js @@ -0,0 +1,24 @@ +import getDelayString from "./getDelayString"; + +describe("getDelayString", () => { + it('should return "+0m" when delay <= 0', () => { + expect(getDelayString(0)).toBe("+0m"); + expect(getDelayString(-1)).toBe("+0m"); + }); + + it('should return "+Xm" when delay is between hours and 0', () => { + expect(getDelayString(69000)).toBe("+1m"); + expect(getDelayString(129000)).toBe("+2m"); + }); + + it('should return "+XhXm" when delay is more than 1 hour', () => { + expect(getDelayString(3659000)).toBe("+1h"); + expect(getDelayString(3661000)).toBe("+1h1m"); + expect(getDelayString(36610000)).toBe("+10h10m"); + }); + + it("should ceil the result if it is an arrival time", () => { + expect(getDelayString(3659000, true)).toBe("+1h1m"); + expect(getDelayString(3659000)).toBe("+1h"); + }); +});