diff --git a/apps/edr-passenger-api/src/common/utils/timezone.utils.ts b/apps/edr-passenger-api/src/common/utils/timezone.utils.ts index d21a8a7dc..325a14d1f 100644 --- a/apps/edr-passenger-api/src/common/utils/timezone.utils.ts +++ b/apps/edr-passenger-api/src/common/utils/timezone.utils.ts @@ -18,12 +18,11 @@ * parseEthiopianTime('2026-06-15') // Treats as midnight EAT */ export function parseEthiopianTime(dateInput: string | Date): Date { - if (dateInput instanceof Date) { - return dateInput; - } - - // Parse as local time (EAT) since TZ is set to Africa/Addis_Ababa - return new Date(dateInput); + if (dateInput instanceof Date) return dateInput; + // Already has timezone info (Z or +HH:MM) — parse directly as UTC + if (/Z$|[+-]\d{2}:\d{2}$/.test(dateInput)) return new Date(dateInput); + // Bare local string (e.g. "2026-07-23T21:00") — treat explicitly as EAT (UTC+3) + return new Date(dateInput + '+03:00'); } /** diff --git a/apps/edr-passenger-api/src/modules/search/search.service.ts b/apps/edr-passenger-api/src/modules/search/search.service.ts index 340d9fac9..91988ba55 100644 --- a/apps/edr-passenger-api/src/modules/search/search.service.ts +++ b/apps/edr-passenger-api/src/modules/search/search.service.ts @@ -153,8 +153,8 @@ export class SearchService { nationality?: string, ) { const [y, m, d] = dateStr.split('-').map(Number); - const requestedDate = new Date(y, m - 1, d, 0, 0, 0, 0); - const requestedNextDay = new Date(y, m - 1, d + 1, 0, 0, 0, 0); + const requestedDate = new Date(`${String(y)}-${String(m).padStart(2,'0')}-${String(d).padStart(2,'0')}T00:00:00+03:00`); + const requestedNextDay = new Date(`${String(y)}-${String(m).padStart(2,'0')}-${String(d + 1).padStart(2,'0')}T00:00:00+03:00`); const now = new Date(); const totalPassengers = adultCount + (childCount ?? 0); const NEEDED = 3; @@ -213,14 +213,15 @@ export class SearchService { nationality?: string, ) { const [y, m, d] = dateStr.split('-').map(Number); - const date = new Date(y, m - 1, d, 0, 0, 0, 0); - const nextDay = new Date(y, m - 1, d + 1, 0, 0, 0, 0); + const date = new Date(`${String(y)}-${String(m).padStart(2,'0')}-${String(d).padStart(2,'0')}T00:00:00+03:00`); + const nextDay = new Date(`${String(y)}-${String(m).padStart(2,'0')}-${String(d + 1).padStart(2,'0')}T00:00:00+03:00`); const now = new Date(); const totalPassengers = adultCount + (childCount ?? 0); const cutoffHours = await this.getCutoffHours(); const cutoffThreshold = new Date(now.getTime() + cutoffHours * 60 * 60 * 1000); - const isToday = now.getFullYear() === y && now.getMonth() === m - 1 && now.getDate() === d; + const nowInEAT = new Date(now.toLocaleString('en-US', { timeZone: 'Africa/Addis_Ababa' })); + const isToday = nowInEAT.getFullYear() === y && nowInEAT.getMonth() === m - 1 && nowInEAT.getDate() === d; const earliest = isToday ? cutoffThreshold : date; const schedules = await this.prisma.trainSchedule.findMany({ @@ -255,8 +256,8 @@ export class SearchService { nationality?: string, ) { const [y, m, d] = dateStr.split('-').map(Number); - const dayStart = new Date(y, m - 1, d, 0, 0, 0, 0); - const dayEnd = new Date(y, m - 1, d + 1, 0, 0, 0, 0); + const dayStart = new Date(`${String(y)}-${String(m).padStart(2,'0')}-${String(d).padStart(2,'0')}T00:00:00+03:00`); + const dayEnd = new Date(`${String(y)}-${String(m).padStart(2,'0')}-${String(d + 1).padStart(2,'0')}T00:00:00+03:00`); const leg2WindowEnd = new Date(dayEnd.getTime() + this.MAX_CONNECTION_MINUTES * 60_000); const totalPassengers = adultCount + (childCount ?? 0); @@ -442,8 +443,8 @@ export class SearchService { } const coachTypes = this.buildCoachTypeDetails(schedule, faresByClass, nationality, availabilityByClass); - const legDepartureAt = originStop.plannedDepartureAt ?? schedule.departureAt; - const legArrivalAt = destStop.plannedArrivalAt ?? schedule.arrivalAt; + const legDepartureAt = schedule.departureAt; + const legArrivalAt = schedule.arrivalAt; const displayCurrency = faresByClass[0]?.displayCurrency ?? resolveCurrencyFromNationality(nationality); diff --git a/apps/edr-passenger-web/backoffice/src/app/schedules/page.tsx b/apps/edr-passenger-web/backoffice/src/app/schedules/page.tsx index d9a0e7ba8..b02b7ec76 100644 --- a/apps/edr-passenger-web/backoffice/src/app/schedules/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/schedules/page.tsx @@ -300,7 +300,7 @@ export default function SchedulesPage() { const payload: any = { trainId: bulkForm.trainId, routeId: bulkForm.routeId, - startDateTime: bulkForm.startDateTime, + startDateTime: new Date(bulkForm.startDateTime).toISOString(), durationHours: parseInt(bulkForm.durationHours), repeatEveryDays: parseInt(bulkForm.repeatEveryDays), forNextDays: parseInt(bulkForm.forNextDays), @@ -389,11 +389,11 @@ export default function SchedulesPage() { const handleEditClick = (schedule: Schedule) => { setEditingSchedule(schedule); - // Format UTC ISO string as local YYYY-MM-DDTHH:mm for the picker + // Format UTC ISO string as EAT (Africa/Addis_Ababa) YYYY-MM-DDTHH:mm for the picker const toLocalDT = (iso: string) => { - const d = new Date(iso); + const eat = new Date(new Date(iso).toLocaleString('en-US', { timeZone: 'Africa/Addis_Ababa' })); const pad = (n: number) => String(n).padStart(2, '0'); - return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`; + return `${eat.getFullYear()}-${pad(eat.getMonth() + 1)}-${pad(eat.getDate())}T${pad(eat.getHours())}:${pad(eat.getMinutes())}`; }; const depStr = toLocalDT(schedule.departureAt);