mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
Fix datetime format
This commit is contained in:
@@ -11,6 +11,7 @@ import { useEffect, useState } from "react";
|
||||
import { CheckCircle, Clock, Copy, Train, FileText } from "lucide-react";
|
||||
import { format } from "date-fns";
|
||||
import { isChild, isFirstChild } from "@/utils/fare-utils";
|
||||
import { toZonedDate } from "@/utils/format";
|
||||
|
||||
type BookingWithTicket = {
|
||||
id: string;
|
||||
@@ -383,7 +384,7 @@ export default function ConfirmationPage() {
|
||||
</p>
|
||||
<p className="font-semibold text-gray-900 dark:text-gray-100">
|
||||
{schedule.departureTime &&
|
||||
format(new Date(schedule.departureTime), "PPp")}
|
||||
format(toZonedDate(new Date(schedule.departureTime)), "PPp")}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
@@ -392,7 +393,7 @@ export default function ConfirmationPage() {
|
||||
</p>
|
||||
<p className="font-semibold text-gray-900 dark:text-gray-100">
|
||||
{schedule.arrivalTime &&
|
||||
format(new Date(schedule.arrivalTime), "PPp")}
|
||||
format(toZonedDate(new Date(schedule.arrivalTime)), "PPp")}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
@@ -456,7 +457,7 @@ export default function ConfirmationPage() {
|
||||
<p className="font-semibold text-gray-900 dark:text-gray-100">
|
||||
{inboundSchedule.departureTime &&
|
||||
format(
|
||||
new Date(inboundSchedule.departureTime),
|
||||
toZonedDate(new Date(inboundSchedule.departureTime)),
|
||||
"PPp",
|
||||
)}
|
||||
</p>
|
||||
@@ -468,7 +469,7 @@ export default function ConfirmationPage() {
|
||||
<p className="font-semibold text-gray-900 dark:text-gray-100">
|
||||
{inboundSchedule.arrivalTime &&
|
||||
format(
|
||||
new Date(inboundSchedule.arrivalTime),
|
||||
toZonedDate(new Date(inboundSchedule.arrivalTime)),
|
||||
"PPp",
|
||||
)}
|
||||
</p>
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { format } from "date-fns";
|
||||
import { formatTime, getTimePeriod } from "@/utils/format";
|
||||
import { formatTime, getTimePeriod, toZonedDate } from "@/utils/format";
|
||||
import { formatFare } from "@/utils/fare-utils";
|
||||
import {
|
||||
markManageBookingPaymentReturn,
|
||||
@@ -606,7 +606,7 @@ function BookingDetailContent() {
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
{booking.schedule?.departureAt
|
||||
? `${format(new Date(booking.schedule.departureAt), "EEE, MMM d")} · ${getTimePeriod(booking.schedule.departureAt)}`
|
||||
? `${format(toZonedDate(new Date(booking.schedule.departureAt)), "EEE, MMM d")} · ${getTimePeriod(booking.schedule.departureAt)}`
|
||||
: "N/A"}
|
||||
</div>
|
||||
<div className="text-base font-semibold text-gray-900 dark:text-white mt-2">
|
||||
@@ -655,7 +655,7 @@ function BookingDetailContent() {
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
{booking.schedule?.arrivalAt
|
||||
? `${format(new Date(booking.schedule.arrivalAt), "EEE, MMM d")} · ${getTimePeriod(booking.schedule.arrivalAt)}`
|
||||
? `${format(toZonedDate(new Date(booking.schedule.arrivalAt)), "EEE, MMM d")} · ${getTimePeriod(booking.schedule.arrivalAt)}`
|
||||
: "N/A"}
|
||||
</div>
|
||||
<div className="text-base font-semibold text-gray-900 dark:text-white mt-2">
|
||||
@@ -1014,7 +1014,7 @@ function BookingDetailContent() {
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
{booking.schedule?.departureAt
|
||||
? `${format(new Date(booking.schedule.departureAt), "EEE, MMM d")} · ${getTimePeriod(booking.schedule.departureAt)}`
|
||||
? `${format(toZonedDate(new Date(booking.schedule.departureAt)), "EEE, MMM d")} · ${getTimePeriod(booking.schedule.departureAt)}`
|
||||
: "N/A"}
|
||||
</div>
|
||||
<div className="text-base font-semibold text-gray-900 dark:text-white mt-2">
|
||||
@@ -1063,7 +1063,7 @@ function BookingDetailContent() {
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
{booking.schedule?.arrivalAt
|
||||
? `${format(new Date(booking.schedule.arrivalAt), "EEE, MMM d")} · ${getTimePeriod(booking.schedule.arrivalAt)}`
|
||||
? `${format(toZonedDate(new Date(booking.schedule.arrivalAt)), "EEE, MMM d")} · ${getTimePeriod(booking.schedule.arrivalAt)}`
|
||||
: "N/A"}
|
||||
</div>
|
||||
<div className="text-base font-semibold text-gray-900 dark:text-white mt-2">
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { apiClient } from "@/lib/api-client";
|
||||
import { format } from "date-fns";
|
||||
import { toZonedDate } from "@/utils/format";
|
||||
|
||||
type SearchMode = "pnr" | "phone";
|
||||
|
||||
@@ -220,7 +221,7 @@ export default function BookingLookupPage() {
|
||||
{b.schedule.originStation.name} → {b.schedule.destinationStation.name}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
{format(new Date(b.schedule.departureAt), "dd MMM yyyy, HH:mm")}
|
||||
{format(toZonedDate(new Date(b.schedule.departureAt)), "dd MMM yyyy, HH:mm")}
|
||||
{" · "}
|
||||
{b.adultCount} adult{b.adultCount !== 1 ? "s" : ""}
|
||||
{b.childCount > 0 && `, ${b.childCount} child${b.childCount !== 1 ? "ren" : ""}`}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { apiClient } from "@/lib/api-client";
|
||||
import { useState, useEffect } from "react";
|
||||
import { PaymentMethod } from "@/types";
|
||||
import { format } from "date-fns";
|
||||
import { formatTime, getTimePeriod } from '@/utils/format';
|
||||
import { formatTime, getTimePeriod, toZonedDate } from '@/utils/format';
|
||||
import { isChild, isFirstChild, formatFare } from '@/utils/fare-utils';
|
||||
import {
|
||||
CreditCard,
|
||||
@@ -289,7 +289,7 @@ export default function PaymentPage() {
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{schedule?.departureTime
|
||||
? `${format(new Date(schedule.departureTime), 'EEE, MMM d')} · ${getTimePeriod(schedule.departureTime)}`
|
||||
? `${format(toZonedDate(new Date(schedule.departureTime)), 'EEE, MMM d')} · ${getTimePeriod(schedule.departureTime)}`
|
||||
: ''}
|
||||
</div>
|
||||
<div className="text-sm font-semibold text-gray-900 dark:text-white mt-1">{schedule?.origin}</div>
|
||||
@@ -304,7 +304,7 @@ export default function PaymentPage() {
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{schedule?.arrivalTime
|
||||
? `${format(new Date(schedule.arrivalTime), 'EEE, MMM d')} · ${getTimePeriod(schedule.arrivalTime)}`
|
||||
? `${format(toZonedDate(new Date(schedule.arrivalTime)), 'EEE, MMM d')} · ${getTimePeriod(schedule.arrivalTime)}`
|
||||
: ''}
|
||||
</div>
|
||||
<div className="text-sm font-semibold text-gray-900 dark:text-white mt-1">{schedule?.destination}</div>
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
Star,
|
||||
} from "lucide-react";
|
||||
import { format } from "date-fns";
|
||||
import { formatTime, getTimePeriod } from "@/utils/format";
|
||||
import { formatTime, getTimePeriod, toZonedDate } from "@/utils/format";
|
||||
import { formatFare } from "@/utils/fare-utils";
|
||||
import { useCurrencySymbol } from "@/lib/useCurrencies";
|
||||
import { useState, useEffect } from "react";
|
||||
@@ -657,7 +657,7 @@ export default function ResultsPage() {
|
||||
<Calendar className="w-3.5 h-3.5" />
|
||||
Different date —{" "}
|
||||
{schedule.departureAt
|
||||
? format(new Date(schedule.departureAt), "EEEE, MMMM d, yyyy")
|
||||
? format(toZonedDate(new Date(schedule.departureAt)), "EEEE, MMMM d, yyyy")
|
||||
: "N/A"}
|
||||
</span>
|
||||
<span
|
||||
@@ -696,7 +696,7 @@ export default function ResultsPage() {
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
{schedule.departureAt
|
||||
? `${format(new Date(schedule.departureAt), "MMM d")} · ${getTimePeriod(schedule.departureAt)}`
|
||||
? `${format(toZonedDate(new Date(schedule.departureAt)), "MMM d")} · ${getTimePeriod(schedule.departureAt)}`
|
||||
: ""}
|
||||
</div>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
@@ -730,7 +730,7 @@ export default function ResultsPage() {
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 mt-0.5 flex items-center justify-center gap-1">
|
||||
<span>
|
||||
{schedule.arrivalAt
|
||||
? `${format(new Date(schedule.arrivalAt), "MMM d")} · ${getTimePeriod(schedule.arrivalAt)}`
|
||||
? `${format(toZonedDate(new Date(schedule.arrivalAt)), "MMM d")} · ${getTimePeriod(schedule.arrivalAt)}`
|
||||
: ""}
|
||||
</span>
|
||||
{isNextDay && (
|
||||
@@ -1213,7 +1213,7 @@ export default function ResultsPage() {
|
||||
{outboundScheduleData.origin} →{" "}
|
||||
{outboundScheduleData.destination}
|
||||
{outboundScheduleData.departureTime
|
||||
? ` · ${format(new Date(outboundScheduleData.departureTime), "EEE, MMM d, yyyy")}`
|
||||
? ` · ${format(toZonedDate(new Date(outboundScheduleData.departureTime)), "EEE, MMM d, yyyy")}`
|
||||
: ""}
|
||||
{outboundScheduleData.selectedSeatClassName
|
||||
? ` · ${outboundScheduleData.selectedSeatClassName}`
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useAuthStore } from '@/lib/auth-store';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { apiClient } from '@/lib/api-client';
|
||||
import { format } from 'date-fns';
|
||||
import { formatTime, getTimePeriod } from '@/utils/format';
|
||||
import { formatTime, getTimePeriod, toZonedDate } from '@/utils/format';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
import { isChild, isFirstChild, formatFare } from '@/utils/fare-utils';
|
||||
@@ -690,7 +690,7 @@ export default function ReviewPage() {
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
{outboundSchedule.departureTime
|
||||
? `${format(new Date(outboundSchedule.departureTime), 'EEE, MMM d')} · ${getTimePeriod(outboundSchedule.departureTime)}`
|
||||
? `${format(toZonedDate(new Date(outboundSchedule.departureTime)), 'EEE, MMM d')} · ${getTimePeriod(outboundSchedule.departureTime)}`
|
||||
: 'N/A'}
|
||||
</div>
|
||||
<div className="text-base font-semibold text-gray-900 dark:text-white mt-2">
|
||||
@@ -723,7 +723,7 @@ export default function ReviewPage() {
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
{outboundSchedule.arrivalTime
|
||||
? `${format(new Date(outboundSchedule.arrivalTime), 'EEE, MMM d')} · ${getTimePeriod(outboundSchedule.arrivalTime)}`
|
||||
? `${format(toZonedDate(new Date(outboundSchedule.arrivalTime)), 'EEE, MMM d')} · ${getTimePeriod(outboundSchedule.arrivalTime)}`
|
||||
: 'N/A'}
|
||||
</div>
|
||||
<div className="text-base font-semibold text-gray-900 dark:text-white mt-2">
|
||||
@@ -764,7 +764,7 @@ export default function ReviewPage() {
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
{inboundSchedule.departureTime
|
||||
? `${format(new Date(inboundSchedule.departureTime), 'EEE, MMM d')} · ${getTimePeriod(inboundSchedule.departureTime)}`
|
||||
? `${format(toZonedDate(new Date(inboundSchedule.departureTime)), 'EEE, MMM d')} · ${getTimePeriod(inboundSchedule.departureTime)}`
|
||||
: 'N/A'}
|
||||
</div>
|
||||
<div className="text-base font-semibold text-gray-900 dark:text-white mt-2">
|
||||
@@ -797,7 +797,7 @@ export default function ReviewPage() {
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
{inboundSchedule.arrivalTime
|
||||
? `${format(new Date(inboundSchedule.arrivalTime), 'EEE, MMM d')} · ${getTimePeriod(inboundSchedule.arrivalTime)}`
|
||||
? `${format(toZonedDate(new Date(inboundSchedule.arrivalTime)), 'EEE, MMM d')} · ${getTimePeriod(inboundSchedule.arrivalTime)}`
|
||||
: 'N/A'}
|
||||
</div>
|
||||
<div className="text-base font-semibold text-gray-900 dark:text-white mt-2">
|
||||
@@ -841,7 +841,7 @@ export default function ReviewPage() {
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
{selectedSchedule.departureTime
|
||||
? `${format(new Date(selectedSchedule.departureTime), 'EEE, MMM d')} · ${getTimePeriod(selectedSchedule.departureTime)}`
|
||||
? `${format(toZonedDate(new Date(selectedSchedule.departureTime)), 'EEE, MMM d')} · ${getTimePeriod(selectedSchedule.departureTime)}`
|
||||
: 'N/A'}
|
||||
</div>
|
||||
<div className="text-base font-semibold text-gray-900 dark:text-white mt-2">
|
||||
@@ -874,7 +874,7 @@ export default function ReviewPage() {
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
{selectedSchedule.arrivalTime
|
||||
? `${format(new Date(selectedSchedule.arrivalTime), 'EEE, MMM d')} · ${getTimePeriod(selectedSchedule.arrivalTime)}`
|
||||
? `${format(toZonedDate(new Date(selectedSchedule.arrivalTime)), 'EEE, MMM d')} · ${getTimePeriod(selectedSchedule.arrivalTime)}`
|
||||
: 'N/A'}
|
||||
</div>
|
||||
<div className="text-base font-semibold text-gray-900 dark:text-white mt-2">
|
||||
|
||||
@@ -105,17 +105,22 @@ interface PackageDetail {
|
||||
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
// Train schedules are stored/computed server-side in Ethiopian time (EAT - UTC+3) —
|
||||
// pinning display to the same zone keeps this page consistent with every other
|
||||
// booking page and the voucher PDF, regardless of the viewing device's own timezone.
|
||||
function fmt(iso: string, opts?: Intl.DateTimeFormatOptions): string {
|
||||
try {
|
||||
return new Date(iso).toLocaleDateString("en-US", opts ?? {
|
||||
return new Date(iso).toLocaleDateString("en-US", {
|
||||
weekday: "short", month: "short", day: "numeric",
|
||||
...opts,
|
||||
timeZone: "Africa/Addis_Ababa",
|
||||
});
|
||||
} catch { return iso; }
|
||||
}
|
||||
|
||||
function fmtTime(iso: string): string {
|
||||
try {
|
||||
return new Date(iso).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", hour12: true });
|
||||
return new Date(iso).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", hour12: true, timeZone: "Africa/Addis_Ababa" });
|
||||
} catch { return iso; }
|
||||
}
|
||||
|
||||
|
||||
@@ -346,8 +346,8 @@ export default function ProfilePage() {
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="w-4 h-4 text-gray-400" />
|
||||
<span className="text-gray-600 dark:text-gray-400">
|
||||
{booking.trip?.departureAt
|
||||
? new Date(booking.trip.departureAt).toLocaleDateString()
|
||||
{booking.trip?.departureAt
|
||||
? new Date(booking.trip.departureAt).toLocaleDateString('en-US', { timeZone: 'Africa/Addis_Ababa' })
|
||||
: 'N/A'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -59,12 +59,16 @@ interface HolidayPackage {
|
||||
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
// Pinned to Ethiopian time (EAT - UTC+3) — the zone train schedules are stored/computed
|
||||
// in server-side — so this matches every other booking page and the voucher PDF
|
||||
// regardless of the viewing device's own timezone.
|
||||
function fmtDate(iso: string, opts?: Intl.DateTimeFormatOptions): string {
|
||||
try {
|
||||
return new Date(iso).toLocaleDateString(
|
||||
"en-US",
|
||||
opts ?? { month: "short", day: "numeric" },
|
||||
);
|
||||
return new Date(iso).toLocaleDateString("en-US", {
|
||||
month: "short", day: "numeric",
|
||||
...opts,
|
||||
timeZone: "Africa/Addis_Ababa",
|
||||
});
|
||||
} catch {
|
||||
return iso;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import jsPDF from 'jspdf';
|
||||
import QRCode from 'qrcode';
|
||||
|
||||
// Train schedules are stored/computed server-side in Ethiopian time (EAT - UTC+3) — see
|
||||
// apps/edr-passenger-api's timezone.utils.ts. Pinning every date/time on the voucher to
|
||||
// this same zone (instead of the generating device's own local zone) is what keeps the
|
||||
// PDF's journey times matching what the booking pages show, regardless of the device.
|
||||
const APP_TIMEZONE = 'Africa/Addis_Ababa';
|
||||
|
||||
interface ScheduleInfo {
|
||||
trainNumber: string;
|
||||
trainName?: string;
|
||||
@@ -255,9 +261,9 @@ function drawJourneyLeg(doc: jsPDF, schedule: ScheduleInfo, legLabel: string | n
|
||||
|
||||
const dep = new Date(schedule.departureAt);
|
||||
doc.setTextColor(...BRAND); doc.setFontSize(11.5); doc.setFont('helvetica', 'bold');
|
||||
doc.text(dep.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }), margin + padX, topY + 18);
|
||||
doc.text(dep.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true, timeZone: APP_TIMEZONE }), margin + padX, topY + 18);
|
||||
doc.setTextColor(...MUTED); doc.setFontSize(7); doc.setFont('helvetica', 'normal');
|
||||
doc.text(dep.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' }), margin + padX, topY + 22);
|
||||
doc.text(dep.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', timeZone: APP_TIMEZONE }), margin + padX, topY + 22);
|
||||
|
||||
// Destination block (right-aligned)
|
||||
const dx = pageWidth - margin - padX;
|
||||
@@ -269,9 +275,9 @@ function drawJourneyLeg(doc: jsPDF, schedule: ScheduleInfo, legLabel: string | n
|
||||
|
||||
const arr = new Date(schedule.arrivalAt);
|
||||
doc.setTextColor(...BRAND); doc.setFontSize(11.5); doc.setFont('helvetica', 'bold');
|
||||
doc.text(arr.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }), dx, topY + 18, { align: 'right' });
|
||||
doc.text(arr.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true, timeZone: APP_TIMEZONE }), dx, topY + 18, { align: 'right' });
|
||||
doc.setTextColor(...MUTED); doc.setFontSize(7); doc.setFont('helvetica', 'normal');
|
||||
doc.text(arr.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' }), dx, topY + 22, { align: 'right' });
|
||||
doc.text(arr.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', timeZone: APP_TIMEZONE }), dx, topY + 22, { align: 'right' });
|
||||
|
||||
// Dashed route line with endpoint markers, connecting the two blocks
|
||||
const lineY = topY + 7.5;
|
||||
@@ -387,7 +393,7 @@ function drawFooter(doc: jsPDF, createdAt: string, contentY: number): void {
|
||||
doc.setFontSize(7.5); doc.setTextColor(...MUTED); doc.setFont('helvetica', 'normal');
|
||||
doc.text('edr_@edrsc.com · 9546 · www.edr.com', pageWidth / 2, footerY + 6, { align: 'center' });
|
||||
doc.setFontSize(6.5);
|
||||
doc.text(`Issued ${new Date(createdAt).toLocaleString('en-US')}`, pageWidth / 2, footerY + 10.5, { align: 'center' });
|
||||
doc.text(`Issued ${new Date(createdAt).toLocaleString('en-US', { timeZone: APP_TIMEZONE })}`, pageWidth / 2, footerY + 10.5, { align: 'center' });
|
||||
}
|
||||
|
||||
// ─── public API ──────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
import { format } from 'date-fns';
|
||||
|
||||
// The backend stores and computes every train schedule in Ethiopian time (EAT -
|
||||
// UTC+3) — see apps/edr-passenger-api's timezone.utils.ts and main.ts, which pin the
|
||||
// server process to this zone. The frontend previously formatted dates using
|
||||
// whatever timezone the viewing/generating device happened to be set to, which made
|
||||
// the on-screen booking pages and the downloaded voucher PDF disagree (by exactly
|
||||
// the device's offset from EAT) whenever that device wasn't itself in EAT. Pinning
|
||||
// every display here to the same explicit zone makes them agree by construction.
|
||||
const APP_TIMEZONE = 'Africa/Addis_Ababa';
|
||||
|
||||
// date-fns' `format()` always reads the Date object's LOCAL (device) getters — it has
|
||||
// no built-in timezone conversion. This shifts the Date so those local getters report
|
||||
// the wall-clock time in `timeZone` instead of the device's own zone, so `format()`
|
||||
// calls (here and at other call sites formatting journey times) produce EAT-correct
|
||||
// output regardless of the device. Exported for pages that call date-fns' `format()`
|
||||
// directly instead of going through the helpers below.
|
||||
export function toZonedDate(date: Date, timeZone: string = APP_TIMEZONE): Date {
|
||||
return new Date(date.toLocaleString('en-US', { timeZone }));
|
||||
}
|
||||
|
||||
export const formatCurrency = (amount: number, currency: string = 'ETB'): string => {
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
@@ -9,19 +28,19 @@ export const formatCurrency = (amount: number, currency: string = 'ETB'): string
|
||||
};
|
||||
|
||||
export const formatDate = (date: string | Date, formatStr: string = 'MMM dd, yyyy'): string => {
|
||||
return format(new Date(date), formatStr);
|
||||
return format(toZonedDate(new Date(date), APP_TIMEZONE), formatStr);
|
||||
};
|
||||
|
||||
export const formatDateTime = (date: string | Date): string => {
|
||||
return format(new Date(date), 'MMM dd, yyyy h:mm a');
|
||||
return format(toZonedDate(new Date(date), APP_TIMEZONE), 'MMM dd, yyyy h:mm a');
|
||||
};
|
||||
|
||||
export const formatTime = (date: string | Date): string => {
|
||||
return format(new Date(date), 'h:mm a');
|
||||
return format(toZonedDate(new Date(date), APP_TIMEZONE), 'h:mm a');
|
||||
};
|
||||
|
||||
export const getTimePeriod = (date: string | Date): string => {
|
||||
const hour = new Date(date).getHours();
|
||||
const hour = toZonedDate(new Date(date), APP_TIMEZONE).getHours();
|
||||
if (hour >= 5 && hour < 12) return 'Morning';
|
||||
if (hour >= 12 && hour < 17) return 'Afternoon';
|
||||
if (hour >= 17 && hour < 21) return 'Evening';
|
||||
|
||||
Reference in New Issue
Block a user