Skip to content

Commit d69374f

Browse files
AvivAbachieran132claude
authored
fix: single line map timezone handling (#1536)
Co-authored-by: Eran Markus <eran132@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 7452f0d commit d69374f

5 files changed

Lines changed: 88 additions & 29 deletions

File tree

src/api/gtfsService.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { GTFS_API } from 'src/api/apiConfig'
2-
import dayjs from 'src/dayjs'
2+
import dayjs, { toIsraelTimezone } from 'src/dayjs'
33
import { BusRoute, fromGtfsRoute } from 'src/model/busRoute'
44
import { BusStop, fromGtfsStop } from 'src/model/busStop'
55

@@ -10,23 +10,25 @@ export async function getRoutesAsync(
1010
lineNumber?: string,
1111
signal?: AbortSignal,
1212
): Promise<BusRoute[]> {
13+
const fromDate = toIsraelTimezone(fromTimestamp).format('YYYY-MM-DD')
14+
const toDate = toIsraelTimezone(toTimestamp).format('YYYY-MM-DD')
15+
1316
const gtfsRoutes = await GTFS_API.gtfsRoutesListGet(
1417
{
1518
routeShortName: lineNumber,
1619
operatorRefs: operatorId,
1720
dateFrom: fromTimestamp.startOf('day').toDate(),
18-
dateTo: dayjs.min(toTimestamp.endOf('day'), dayjs()).toDate(),
21+
dateTo: dayjs.min(toTimestamp.endOf('day'), toIsraelTimezone()).toDate(),
1922
limit: 100,
2023
},
2124
{ signal },
2225
)
2326
const routes = Object.values(
2427
gtfsRoutes
25-
.filter(
26-
(route) =>
27-
route.date.getDate() >= fromTimestamp.date() &&
28-
route.date.getDate() <= toTimestamp.date(),
29-
)
28+
.filter((route) => {
29+
const routeDate = toIsraelTimezone(route.date).format('YYYY-MM-DD')
30+
return routeDate >= fromDate && routeDate <= toDate
31+
})
3032
.map((route) => fromGtfsRoute(route))
3133
.reduce(
3234
(agg, line) => {

src/dayjs.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ dayjs.extend(minMax)
1212
dayjs.extend(isoWeek)
1313

1414
// Set default timezone
15-
dayjs.tz.setDefault('Asia/Jerusalem')
15+
export const ISRAEL_TIMEZONE = 'Asia/Jerusalem'
16+
dayjs.tz.setDefault(ISRAEL_TIMEZONE)
17+
18+
export const toIsraelTimezone = (value?: dayjs.ConfigType) => dayjs(value).tz(ISRAEL_TIMEZONE)
1619

1720
// Set default locale
1821
dayjs.locale('he')

src/hooks/useSingleLineData.ts

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
22
import { getRoutesAsync, getRoutesByLineRef, getStopsForRouteAsync } from 'src/api/gtfsService'
3-
import dayjs from 'src/dayjs'
3+
import dayjs, { toIsraelTimezone } from 'src/dayjs'
44
import useVehicleLocations from 'src/hooks/useVehicleLocations'
55
import { BusRoute } from 'src/model/busRoute'
66
import { BusStop } from 'src/model/busStop'
77
import { SearchContext } from 'src/model/pageState'
8-
import { getServiceDayBounds } from 'src/model/serviceDay'
98
import { type Point, toPoint } from 'src/pages/components/map-related/map-types'
109
import { routeStartEnd, vehicleIDFormat } from 'src/pages/components/utils/rotueUtils'
1110
import {
1211
normalizeStartTimeToken,
1312
parseStartTimeToken,
1413
} from 'src/pages/components/utils/startTimeUtils'
1514

16-
const formatTime = (time: dayjs.Dayjs) => time.format('HH:mm')
15+
const formatTime = (time: dayjs.ConfigType) => toIsraelTimezone(time).format('HH:mm')
1716

1817
export const useSingleLineData = (
1918
operatorId?: string,
@@ -60,7 +59,7 @@ export const useSingleLineData = (
6059
}
6160

6261
const controller = new AbortController()
63-
const time = dayjs(search.timestamp)
62+
const time = toIsraelTimezone(search.timestamp)
6463

6564
getRoutesAsync(time, time, operatorId, lineNumber, controller.signal)
6665
.then((routes) => {
@@ -86,9 +85,9 @@ export const useSingleLineData = (
8685
return routes?.find((route) => route.key === routeKey)
8786
}, [routes, routeKey])
8887

89-
const [serviceDayStart, serviceDayEnd] = useMemo(() => {
90-
const { start, end } = getServiceDayBounds(dayjs(search.timestamp))
91-
return [start, end]
88+
const [today, tomorrow] = useMemo(() => {
89+
const today = toIsraelTimezone(search.timestamp).startOf('day')
90+
return [today, today.add(1, 'day')]
9291
}, [search.timestamp])
9392

9493
const validVehicleNumber = useMemo(() => {
@@ -98,8 +97,8 @@ export const useSingleLineData = (
9897
}, [vehicleNumber])
9998

10099
const { locations, isLoading: locationsAreLoading } = useVehicleLocations({
101-
from: serviceDayStart.valueOf(),
102-
to: serviceDayEnd.valueOf(),
100+
from: today.valueOf(),
101+
to: tomorrow.valueOf(),
103102
operatorRef: operatorId ? Number(operatorId) : undefined,
104103
lineRef: selectedRoute?.lineRef ? Number(selectedRoute.lineRef) : undefined,
105104
vehicleRef: validVehicleNumber,
@@ -121,8 +120,8 @@ export const useSingleLineData = (
121120
for (const position of positions) {
122121
const startTime = position.point?.siriRideScheduledStartTime
123122
if (!startTime) continue
124-
const dayjsTime = dayjs(startTime)
125-
if (!dayjsTime.isBefore(serviceDayStart) && dayjsTime.isBefore(serviceDayEnd)) {
123+
const dayjsTime = toIsraelTimezone(startTime)
124+
if (dayjsTime.isAfter(today) && dayjsTime.isBefore(tomorrow)) {
126125
const formattedTime = formatTime(dayjsTime)
127126
const key = `${formattedTime}|${position.point?.siriRideVehicleRef}`
128127
if (!uniqueTimes.has(key)) {
@@ -168,7 +167,7 @@ export const useSingleLineData = (
168167
}
169168

170169
fetchOptions()
171-
}, [positions, serviceDayStart, serviceDayEnd, vehicleNumber])
170+
}, [positions, today, tomorrow, vehicleNumber])
172171

173172
useEffect(() => {
174173
const parsedStartTime = parseStartTimeToken(startTime)
@@ -184,7 +183,7 @@ export const useSingleLineData = (
184183
const vehicleRef = position.point?.siriRideVehicleRef?.toString()
185184
if (!scheduledStart || !vehicleRef || !scheduledTime) return false
186185
return (
187-
formatTime(dayjs(scheduledStart)) === scheduledTime &&
186+
formatTime(scheduledStart) === scheduledTime &&
188187
(scheduledVehicle ? scheduledVehicle === vehicleRef : true) &&
189188
(scheduledLine ? scheduledLine === position.point?.siriRouteLineRef?.toString() : true)
190189
)
@@ -199,11 +198,7 @@ export const useSingleLineData = (
199198
const scheduledTime = parsedStartTime?.scheduledTime
200199
const scheduledLine = parsedStartTime?.lineRef
201200
const [hour, minute] = scheduledTime ? scheduledTime.split(':').map(Number) : [0, 0]
202-
const startTimeTimestamp = serviceDayStart
203-
.hour(hour)
204-
.minute(minute)
205-
.second(0)
206-
.millisecond(0)
201+
const startTimeTimestamp = today.hour(hour).minute(minute).second(0).millisecond(0)
207202
let routeIds: number[] | undefined
208203
if (selectedRoute?.routeIds && selectedRoute.routeIds.length > 0) {
209204
routeIds = selectedRoute.routeIds
@@ -224,7 +219,7 @@ export const useSingleLineData = (
224219
}
225220
}
226221
fetchStops()
227-
}, [selectedRoute?.routeIds, operatorId, startTime, serviceDayStart])
222+
}, [selectedRoute?.routeIds, operatorId, startTime, today])
228223

229224
return {
230225
positions: filteredPositions,

src/pages/singleLineMap/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
} from '@mui/material'
99
import { useContext, useState } from 'react'
1010
import { useTranslation } from 'react-i18next'
11-
import dayjs from 'src/dayjs'
11+
import dayjs, { toIsraelTimezone } from 'src/dayjs'
1212
import { useSingleLineData } from 'src/hooks/useSingleLineData'
1313
import LineNumberSelector from 'src/pages/components/LineSelector'
1414
import OperatorSelector from 'src/pages/components/OperatorSelector'
@@ -94,7 +94,7 @@ const SingleLineMapPage = () => {
9494
<Grid container spacing={2} size={{ xs: 12 }}>
9595
{/* choose date*/}
9696
<Grid size={{ sm: 4, xs: 12 }}>
97-
<DateSelector time={dayjs(timestamp)} onChange={handleTimestampChange} />
97+
<DateSelector time={toIsraelTimezone(timestamp)} onChange={handleTimestampChange} />
9898
</Grid>
9999
{/* choose operator */}
100100
<Grid size={{ sm: 4, xs: 12 }}>

tests/timezone.spec.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { expect, harOptions, setupTest, test, visitPage } from './utils'
2+
3+
const TIMEZONES = ['Asia/Jerusalem', 'America/New_York', 'Europe/London', 'Asia/Tokyo'] as const
4+
5+
const SINGLE_LINE_START_TIMES = [
6+
'04:30 (74-892-26)',
7+
'04:47 (61-265-26)',
8+
'05:00 (74-891-26)',
9+
'05:23 (74-899-26)',
10+
'05:33 (60-860-26)',
11+
'05:44 (74-893-26)',
12+
'05:54 (74-895-26)',
13+
] as const
14+
15+
const SINGLE_LINE_ROUTES = [
16+
'תחנת מוניות רמת גן דרך הטייסים-תל אביב יפו ⟵ תחנת מוניות תל אביב הכובשים-תל אביב יפו',
17+
'תחנת מוניות תל אביב הכובשים-תל אביב יפו ⟵ תחנת מוניות רמת גן דרך הטייסים-תל אביב יפו',
18+
] as const
19+
20+
for (const timezone of TIMEZONES) {
21+
test.describe(`Timezone: ${timezone}`, () => {
22+
test.use({ timezoneId: timezone })
23+
test(`single line map page route selector uses Israel timezone (${timezone})`, async ({
24+
page,
25+
advancedRouteFromHAR,
26+
}) => {
27+
await setupTest(page)
28+
await advancedRouteFromHAR('tests/HAR/singleline.har', harOptions)
29+
await visitPage(page, 'singleline_map_page_title')
30+
31+
await page.getByLabel('חברה מפעילה').click()
32+
await page.getByRole('option', { name: 'אודליה מוניות בעמ', exact: true }).click()
33+
await page.getByRole('textbox', { name: 'מספר קו' }).fill('16')
34+
await page.getByLabel(/בחירת מסלול נסיעה/).click()
35+
36+
await expect(page.getByRole('option')).toContainText(SINGLE_LINE_ROUTES)
37+
})
38+
39+
test(`single line map page start time list uses Israel timezone (${timezone})`, async ({
40+
page,
41+
advancedRouteFromHAR,
42+
}) => {
43+
await setupTest(page)
44+
await advancedRouteFromHAR('tests/HAR/singleline.har', harOptions)
45+
await visitPage(page, 'singleline_map_page_title')
46+
47+
await page.getByLabel('חברה מפעילה').click()
48+
await page.getByRole('option', { name: 'אודליה מוניות בעמ', exact: true }).click()
49+
await page.getByRole('textbox', { name: 'מספר קו' }).fill('16')
50+
await page.getByLabel(/בחירת מסלול נסיעה/).click()
51+
await page.getByRole('option', { name: SINGLE_LINE_ROUTES[0] }).click()
52+
53+
await page.getByLabel('בחירת שעת התחלה').click()
54+
await expect(page.getByRole('option', { name: SINGLE_LINE_START_TIMES[0] })).toBeVisible()
55+
56+
await expect(page.getByRole('option')).toContainText(SINGLE_LINE_START_TIMES)
57+
})
58+
})
59+
}

0 commit comments

Comments
 (0)