From d2e2032a228c9e3f41d5eb2f196829ab96d01725 Mon Sep 17 00:00:00 2001 From: Samuel Cedarbaum Date: Fri, 27 Oct 2023 17:26:17 -0400 Subject: [PATCH] Zero out time in GTFS date string conversions --- src/gtfs/calendar/CalendarFactory.ts | 61 +++++++++++++++++-------- src/gtfs/calendar/gftsDateUtils.spec.ts | 34 ++++++++++++++ src/gtfs/calendar/gtfsDateUtils.ts | 14 ++++++ src/gtfs/calendar/toGTFSDate.spec.ts | 13 ------ src/gtfs/calendar/toGTFSDate.ts | 6 --- src/index.ts | 2 +- 6 files changed, 90 insertions(+), 40 deletions(-) create mode 100644 src/gtfs/calendar/gftsDateUtils.spec.ts create mode 100644 src/gtfs/calendar/gtfsDateUtils.ts delete mode 100644 src/gtfs/calendar/toGTFSDate.spec.ts delete mode 100644 src/gtfs/calendar/toGTFSDate.ts diff --git a/src/gtfs/calendar/CalendarFactory.ts b/src/gtfs/calendar/CalendarFactory.ts index 1e03e34..e974857 100644 --- a/src/gtfs/calendar/CalendarFactory.ts +++ b/src/gtfs/calendar/CalendarFactory.ts @@ -1,23 +1,36 @@ import { Calendar, CalendarDate } from "../GTFS"; -import { toGTFSDate } from "./toGTFSDate"; +import { toGTFSDate, getDateFromGTFSString } from "./gtfsDateUtils"; /** * Creates calendars based on a set of calendar dates. */ export class CalendarFactory { - /** * Count the number of times a service runs or does not run on each day of the week and then decide whether to * create a calendar with that day enabled with exclude days when it is not running, or disabled with include * days when it is running. */ - public create(serviceId: string, calendarDates: CalendarDate[]): [Calendar, CalendarDate[]] { + public create( + serviceId: string, + calendarDates: CalendarDate[] + ): [Calendar, CalendarDate[]] { calendarDates.sort((a, b) => +a.date - +b.date); const startDate = calendarDates[0]; const endDate = calendarDates[calendarDates.length - 1]; - const [daysRunning, daysNotRunning] = this.splitCalendarDates(calendarDates, serviceId, startDate, endDate); - const calendar = this.createCalendar(serviceId, startDate, endDate, daysRunning, daysNotRunning); + const [daysRunning, daysNotRunning] = this.splitCalendarDates( + calendarDates, + serviceId, + startDate, + endDate + ); + const calendar = this.createCalendar( + serviceId, + startDate, + endDate, + daysRunning, + daysNotRunning + ); const newCalendarDates = this.getCalendarDates(daysRunning, daysNotRunning); return [calendar, newCalendarDates]; @@ -29,22 +42,28 @@ export class CalendarFactory { startDate: CalendarDate, endDate: CalendarDate ): [CalendarDate[][], CalendarDate[][]] { - const daysNotRunning: CalendarDate[][] = [[], [], [], [], [], [], []]; const daysRunning: CalendarDate[][] = [[], [], [], [], [], [], []]; const calendarDateIndex = this.indexCalendarDates(calendarDates); let i = startDate.date; - let date = this.getDateFromGTFSString(i); + let date = getDateFromGTFSString(i); while (i <= endDate.date) { const dow = date.getDay(); if (calendarDateIndex[i]) { - daysRunning[dow].push({ exception_type: 1, service_id: serviceId, date: i }); - } - else { - daysNotRunning[dow].push({ exception_type: 2, service_id: serviceId, date: i }); + daysRunning[dow].push({ + exception_type: 1, + service_id: serviceId, + date: i, + }); + } else { + daysNotRunning[dow].push({ + exception_type: 2, + service_id: serviceId, + date: i, + }); } date.setDate(date.getDate() + 1); @@ -54,7 +73,9 @@ export class CalendarFactory { return [daysRunning, daysNotRunning]; } - private indexCalendarDates(calendarDates: CalendarDate[]): Record { + private indexCalendarDates( + calendarDates: CalendarDate[] + ): Record { return calendarDates.reduce((index, calendarDate) => { index[calendarDate.date] = calendarDate; @@ -62,10 +83,6 @@ export class CalendarFactory { }, {}); } - private getDateFromGTFSString(i: string): Date { - return new Date(i.substr(0, 4) + "-" + i.substr(4, 2) + "-" + i.substr(6, 2)); - } - private createCalendar( serviceId: string, startDate: CalendarDate, @@ -83,7 +100,7 @@ export class CalendarFactory { wednesday: daysRunning[3].length > daysNotRunning[3].length ? 1 : 0, thursday: daysRunning[4].length > daysNotRunning[4].length ? 1 : 0, friday: daysRunning[5].length > daysNotRunning[5].length ? 1 : 0, - saturday: daysRunning[6].length > daysNotRunning[6].length ? 1 : 0 + saturday: daysRunning[6].length > daysNotRunning[6].length ? 1 : 0, }; } @@ -91,10 +108,14 @@ export class CalendarFactory { * For each day of the week check if the service runs more often than not. If it does, return the exclude days as * the calendar will have the day set to 1, if not then return the include days. */ - private getCalendarDates(daysRunning: CalendarDate[][], daysNotRunning: CalendarDate[][]): CalendarDate[] { + private getCalendarDates( + daysRunning: CalendarDate[][], + daysNotRunning: CalendarDate[][] + ): CalendarDate[] { return daysRunning.flatMap((runningDates, i) => { - return runningDates.length > daysNotRunning[i].length ? daysNotRunning[i] : runningDates; + return runningDates.length > daysNotRunning[i].length + ? daysNotRunning[i] + : runningDates; }); } - } diff --git a/src/gtfs/calendar/gftsDateUtils.spec.ts b/src/gtfs/calendar/gftsDateUtils.spec.ts new file mode 100644 index 0000000..8912071 --- /dev/null +++ b/src/gtfs/calendar/gftsDateUtils.spec.ts @@ -0,0 +1,34 @@ +import * as chai from "chai"; +import { toGTFSDate, getDateFromGTFSString } from "./gtfsDateUtils"; + +describe("toGTFSDate", () => { + it("returns a GTFS date string", () => { + const date = new Date("2019-06-04T00:00:00"); + const result = toGTFSDate(date); + + chai.expect(result).to.equal("20190604"); + }); +}); + +describe("getDateFromGTFSString", () => { + it("returns a JS Date from GTFS string", () => { + const result = getDateFromGTFSString("20190604"); + const dateDiff = result.getTime() - new Date(2019, 5, 4, 0, 0, 0).getTime(); + chai.expect(dateDiff).to.equal(0); + }); +}); + +describe("GTFS date roundtrip", () => { + it("Preserves GTFS dates when converting to/from JS dates", () => { + function validateGtfsDateRoundtrip(gtfsDate: string) { + const date = getDateFromGTFSString(gtfsDate); + const result = toGTFSDate(date); + chai.expect(result).to.equal(gtfsDate); + } + + validateGtfsDateRoundtrip("20190601"); + validateGtfsDateRoundtrip("20190604"); + validateGtfsDateRoundtrip("20191231"); + validateGtfsDateRoundtrip("20200101"); + }); +}); diff --git a/src/gtfs/calendar/gtfsDateUtils.ts b/src/gtfs/calendar/gtfsDateUtils.ts new file mode 100644 index 0000000..39bcc1e --- /dev/null +++ b/src/gtfs/calendar/gtfsDateUtils.ts @@ -0,0 +1,14 @@ +export function toGTFSDate(date: Date): string { + return ( + date.getFullYear() + + (date.getMonth() + 1).toString().padStart(2, "0") + + date.getDate().toString().padStart(2, "0") + ); +} + +export function getDateFromGTFSString(i: string): Date { + // Zero out the time portion of the date to avoid timezone issues + return new Date( + i.substr(0, 4) + "-" + i.substr(4, 2) + "-" + i.substr(6, 2) + "T00:00:00" + ); +} diff --git a/src/gtfs/calendar/toGTFSDate.spec.ts b/src/gtfs/calendar/toGTFSDate.spec.ts deleted file mode 100644 index a606012..0000000 --- a/src/gtfs/calendar/toGTFSDate.spec.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as chai from "chai"; -import { toGTFSDate } from "./toGTFSDate"; - -describe("toGTFSDate", () => { - - it("returns a GTFS date string", () => { - const date = new Date("2019-06-04"); - const result = toGTFSDate(date); - - chai.expect(result).to.equal("20190604"); - }); - -}); diff --git a/src/gtfs/calendar/toGTFSDate.ts b/src/gtfs/calendar/toGTFSDate.ts deleted file mode 100644 index 615513e..0000000 --- a/src/gtfs/calendar/toGTFSDate.ts +++ /dev/null @@ -1,6 +0,0 @@ - -export function toGTFSDate(date: Date): string { - return date.getFullYear() - + (date.getMonth() + 1).toString().padStart(2, "0") - + date.getDate().toString().padStart(2, "0"); -} diff --git a/src/index.ts b/src/index.ts index 64ff34c..70c6c56 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import * as yargs from "yargs"; import { Arguments } from "yargs"; import { Container } from "./Container"; -import { toGTFSDate } from "./gtfs/calendar/toGTFSDate"; +import { toGTFSDate } from "./gtfs/calendar/gtfsDateUtils"; import { RouteType } from "./gtfs/GTFS"; const args = yargs.argv as Arguments<{