Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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 () => {
Expand Down
49 changes: 43 additions & 6 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ class SubwayClient {

async arrivals(query: SubwayArrivalQuery): Promise<Arrival[]> {
if (this.mta.hostedApiEnabled()) {
return this.mta.hostedJson<Arrival[]>("/api/v1/subway/arrivals", query);
return hydrateArrivals(await this.mta.hostedJson<Arrival[]>("/api/v1/subway/arrivals", query));
}

await this.mta.ready();
Expand Down Expand Up @@ -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,
Expand All @@ -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),
}));
Comment on lines +200 to +207
}

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<Arrival[]> {
if (this.mta.hostedApiEnabled()) {
return this.mta.hostedJson<Arrival[]>("/api/v1/bus/arrivals", query);
return hydrateArrivals(await this.mta.hostedJson<Arrival[]>("/api/v1/bus/arrivals", query));
}

await this.mta.ready();
Expand All @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,12 @@ export interface Arrival {
stop: Stop;
direction: Direction;
headsign?: string;
arrivalTimestamp: number;
arrivalTime: string;
arrivalDate: Date;
Comment on lines +154 to +156
Comment on lines 153 to +156
departureTimestamp?: number;
departureTime?: string;
departureDate?: Date;
minutes: number;
tripId?: string;
realtime: boolean;
Expand Down
Loading