From 37bbbe7ea5f14965891d3fd831c824c032c57838 Mon Sep 17 00:00:00 2001 From: Gary Tokman Date: Tue, 26 May 2026 22:17:12 -0400 Subject: [PATCH] fix: update dates --- index.test.ts | 40 ++++++++++++++++++++++++++++++++++++++++ index.ts | 49 +++++++++++++++++++++++++++++++++++++++++++------ src/types.ts | 4 ++++ 3 files changed, 87 insertions(+), 6 deletions(-) diff --git a/index.test.ts b/index.test.ts index 54d9b63..53ccde4 100644 --- a/index.test.ts +++ b/index.test.ts @@ -124,6 +124,10 @@ test("subway arrivals decode GTFS realtime and join in-memory static metadata", tripId: "A-trip-1", source: "mta-gtfs-rt", }); + expect(arrivals[0]?.arrivalTimestamp).toBe(1_700_000_300); + expect(arrivals[0]?.arrivalTime).toBe("2023-11-14T22:18:20.000Z"); + expect(arrivals[0]?.arrivalDate).toEqual(new Date("2023-11-14T22:18:20.000Z")); + expect(arrivals[0]?.arrivalDate).toBeInstanceOf(Date); }); test("bus arrivals normalize BusTime responses with in-memory static metadata", async () => { @@ -170,6 +174,42 @@ test("bus arrivals normalize BusTime responses with in-memory static metadata", stop: { id: "308214", name: "5 Av/Atlantic Av" }, source: "mta-bustime", }); + expect(arrivals[0]?.arrivalTimestamp).toBe(1_700_000_420); + expect(arrivals[0]?.arrivalTime).toBe("2023-11-14T22:20:20.000Z"); + expect(arrivals[0]?.arrivalDate).toEqual(new Date("2023-11-14T22:20:20.000Z")); + expect(arrivals[0]?.arrivalDate).toBeInstanceOf(Date); +}); + +test("hosted subway arrivals hydrate date fields from JSON", async () => { + const mta = new MTA({ + apiKey: "test-key", + apiBaseUrl: "https://api.example.com", + fetch: (async () => + Response.json([ + { + mode: "subway", + route: { id: "A", shortName: "A" }, + stop: { id: "A27", name: "Jay St-MetroTech" }, + direction: "north", + arrivalTime: "2023-11-14T22:18:20.000Z", + arrivalDate: "2023-11-14T22:18:20.000Z", + departureTime: "2023-11-14T22:19:20.000Z", + departureDate: "2023-11-14T22:19:20.000Z", + minutes: 3, + realtime: true, + source: "mta-gtfs-rt", + }, + ])) as unknown as typeof fetch, + }); + + const arrivals = await mta.subway.arrivals({ stopId: "A27", route: "A" }); + + expect(arrivals[0]?.arrivalTimestamp).toBe(1_700_000_300); + expect(arrivals[0]?.arrivalDate).toEqual(new Date("2023-11-14T22:18:20.000Z")); + expect(arrivals[0]?.arrivalDate).toBeInstanceOf(Date); + expect(arrivals[0]?.departureTimestamp).toBe(1_700_000_360); + expect(arrivals[0]?.departureDate).toEqual(new Date("2023-11-14T22:19:20.000Z")); + expect(arrivals[0]?.departureDate).toBeInstanceOf(Date); }); test("local stops.near requires in-memory static data", async () => { diff --git a/index.ts b/index.ts index 5822a50..2c2f7f5 100644 --- a/index.ts +++ b/index.ts @@ -110,7 +110,7 @@ class SubwayClient { async arrivals(query: SubwayArrivalQuery): Promise { if (this.mta.hostedApiEnabled()) { - return this.mta.hostedJson("/api/v1/subway/arrivals", query); + return hydrateArrivals(await this.mta.hostedJson("/api/v1/subway/arrivals", query)); } await this.mta.ready(); @@ -170,14 +170,20 @@ class SubwayClient { if (!event?.time) continue; const stop = this.mta.static.getStopOrParent(stopId) ?? fallbackStop(query.stopId); + const arrivalDate = new Date(event.time * 1000); + const departureDate = update.departure?.time ? new Date(update.departure.time * 1000) : undefined; arrivals.push({ mode: "subway", route, stop, direction, headsign: staticTrip?.headsign ?? undefined, - arrivalTime: new Date(event.time * 1000).toISOString(), - departureTime: update.departure?.time ? new Date(update.departure.time * 1000).toISOString() : undefined, + arrivalTimestamp: event.time, + arrivalTime: arrivalDate.toISOString(), + arrivalDate, + departureTimestamp: update.departure?.time, + departureTime: departureDate?.toISOString(), + departureDate, minutes: Math.max(0, Math.round((event.time * 1000 - now) / 60_000)), tripId: trip?.tripId, realtime: true, @@ -191,12 +197,40 @@ class SubwayClient { } } +function hydrateArrivals(arrivals: Arrival[]) { + return arrivals.map((arrival) => ({ + ...arrival, + arrivalTimestamp: arrival.arrivalTimestamp ?? dateToUnixSecondsRequired(arrival.arrivalDate ?? arrival.arrivalTime), + arrivalDate: hydrateRequiredDate(arrival.arrivalDate ?? arrival.arrivalTime), + departureTimestamp: arrival.departureTimestamp ?? dateToUnixSeconds(arrival.departureDate ?? arrival.departureTime), + departureDate: hydrateDate(arrival.departureDate ?? arrival.departureTime), + })); +} + +function hydrateRequiredDate(value: Date | string) { + return value instanceof Date ? value : new Date(value); +} + +function hydrateDate(value: Date | string | undefined) { + if (!value) return undefined; + return value instanceof Date ? value : new Date(value); +} + +function dateToUnixSecondsRequired(value: Date | string) { + return Math.floor(hydrateRequiredDate(value).getTime() / 1000); +} + +function dateToUnixSeconds(value: Date | string | undefined) { + if (!value) return undefined; + return dateToUnixSecondsRequired(value); +} + class BusClient { constructor(private readonly mta: MTA) {} async arrivals(query: BusArrivalQuery): Promise { if (this.mta.hostedApiEnabled()) { - return this.mta.hostedJson("/api/v1/bus/arrivals", query); + return hydrateArrivals(await this.mta.hostedJson("/api/v1/bus/arrivals", query)); } await this.mta.ready(); @@ -223,14 +257,17 @@ class BusClient { const expected = call.ExpectedArrivalTime ?? call.AimedArrivalTime; if (!expected) return undefined; const stop = this.mta.static.getStop(String(call.StopPointRef ?? query.stopId)) ?? fallbackStop(query.stopId); + const arrivalDate = new Date(expected); return { mode: "bus", route: routeWithFallback(this.mta.static.getRoute(routeId), routeId), stop, direction: "unknown", headsign: stringOrUndefined(mvj.DestinationName), - arrivalTime: new Date(expected).toISOString(), - minutes: Math.max(0, Math.round((Date.parse(expected) - now) / 60_000)), + arrivalTimestamp: Math.floor(arrivalDate.getTime() / 1000), + arrivalTime: arrivalDate.toISOString(), + arrivalDate, + minutes: Math.max(0, Math.round((arrivalDate.getTime() - now) / 60_000)), tripId: stringOrUndefined(mvj.FramedVehicleJourneyRef?.DatedVehicleJourneyRef), realtime: true, source: "mta-bustime", diff --git a/src/types.ts b/src/types.ts index 0db264b..37ef489 100644 --- a/src/types.ts +++ b/src/types.ts @@ -151,8 +151,12 @@ export interface Arrival { stop: Stop; direction: Direction; headsign?: string; + arrivalTimestamp: number; arrivalTime: string; + arrivalDate: Date; + departureTimestamp?: number; departureTime?: string; + departureDate?: Date; minutes: number; tripId?: string; realtime: boolean;