From d5c9e6aa213e0224024852ace9670fbd2872973b Mon Sep 17 00:00:00 2001 From: Mike Dial <48921055+mdial89f@users.noreply.github.com> Date: Mon, 5 Feb 2024 17:35:11 -0500 Subject: [PATCH] feat(effective submission date): Set Initial Submission Date to the next valid business day. (#358) * functional * Add tests * remove unneeded info * remove throw * Correct logic * Naming * accommodate github * Update per paul's comments --- src/packages/shared-utils/package.json | 8 +++- .../shared-utils/seatool-date-helper.ts | 26 ++++++++++++ .../tests/seatool-date-helper.test.ts | 42 +++++++++++++++++++ src/services/api/handlers/submit.ts | 12 +++++- yarn.lock | 9 +++- 5 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 src/packages/shared-utils/tests/seatool-date-helper.test.ts diff --git a/src/packages/shared-utils/package.json b/src/packages/shared-utils/package.json index 4f5b354128..bb31bcdf44 100644 --- a/src/packages/shared-utils/package.json +++ b/src/packages/shared-utils/package.json @@ -3,5 +3,11 @@ "version": "0.0.0", "private": true, "license": "MIT", - "devDependencies": {} + "scripts": { + "test": "vitest" + }, + "devDependencies": {}, + "dependencies": { + "@18f/us-federal-holidays": "^4.0.0" + } } diff --git a/src/packages/shared-utils/seatool-date-helper.ts b/src/packages/shared-utils/seatool-date-helper.ts index dd7ea2c255..b6a7e8f02d 100644 --- a/src/packages/shared-utils/seatool-date-helper.ts +++ b/src/packages/shared-utils/seatool-date-helper.ts @@ -1,4 +1,6 @@ import moment from "moment-timezone"; +import * as fedHolidays from '@18f/us-federal-holidays'; + // This manually accounts for the offset between the client's timezone and UTC. export const offsetForUtc = (date: Date): Date => { @@ -18,4 +20,28 @@ export const seaToolFriendlyTimestamp = (date?: Date): number => { // This takes an epoch string and converts it to a standard format for display export const formatSeatoolDate = (date: string): string => { return moment(date).tz("UTC").format("MM/DD/yyyy") +} + +export const getNextBusinessDayTimestamp = (date: Date = new Date()): number => { + let localeStringDate = date.toLocaleString("en-US", { timeZone: "America/New_York", dateStyle: "short" }); + let localeStringHours24 = date.toLocaleString("en-US", { timeZone: "America/New_York", hour: 'numeric', hour12: false }); + let localeDate = new Date(localeStringDate); + + console.log(`Evaluating ${localeStringDate} at ${localeStringHours24}`); + + const after5pmEST = parseInt(localeStringHours24,10) >= 17 + const isHoliday = fedHolidays.isAHoliday(localeDate) + const isWeekend = !(localeDate.getDay() % 6) + if(after5pmEST || isHoliday || isWeekend) { + let nextDate = localeDate; + nextDate.setDate(nextDate.getDate() + 1); + nextDate.setHours(12,0,0,0) + console.log("Current date is not valid. Will try " + nextDate) + return getNextBusinessDayTimestamp(nextDate) + } + + // Return the next business day's epoch for midnight UTC + let ret = offsetForUtc(localeDate).getTime(); + console.log('Current date is a valid business date. Will return ' + ret); + return ret; } \ No newline at end of file diff --git a/src/packages/shared-utils/tests/seatool-date-helper.test.ts b/src/packages/shared-utils/tests/seatool-date-helper.test.ts new file mode 100644 index 0000000000..33c4303cc8 --- /dev/null +++ b/src/packages/shared-utils/tests/seatool-date-helper.test.ts @@ -0,0 +1,42 @@ +import { it, describe, expect } from "vitest"; +import { getNextBusinessDayTimestamp } from "../seatool-date-helper"; + +describe("The getNextBusinessDayTimestamp function", () => { + it("identifies weekenends", () => { + let testDate = new Date(2024, 0, 27, 12, 0, 0); // Saturday, noon, utc + let nextDate = getNextBusinessDayTimestamp(testDate); + expect(nextDate).toEqual(Date.UTC(2024, 0, 29)); // Monday, midnight, utc + }); + + it("identifies holidays", () => { + let testDate = new Date(2024, 0, 15, 12, 0, 0); // MLK Day, a Monday + let nextDate = getNextBusinessDayTimestamp(testDate); + expect(nextDate).toEqual(Date.UTC(2024, 0, 16)); // Tuesday, midnight, utc + }); + + it("identifies submissions after 5pm eastern", () => { + let testDate = new Date(2024, 0, 17, 23, 0, 0); // Wednesday 11pm utc, Wednesday 6pm eastern + let nextDate = getNextBusinessDayTimestamp(testDate); + expect(nextDate).toEqual(Date.UTC(2024, 0, 18)); // Thursday, midnight, utc + }); + + it("identifies submissions before 5pm eastern", () => { + let testDate = new Date(2024, 0, 17, 10, 0, 0); // Wednesday 10am utc, Wednesday 5am eastern + let nextDate = getNextBusinessDayTimestamp(testDate); + expect(nextDate).toEqual(Date.UTC(2024, 0, 17)); // Wednesday, midnight, utc + }); + + it("handles combinations of rule violations", () => { + let testDate = new Date(2024, 0, 12, 23, 0, 0); // Friday 11pm utc, Friday 6pm eastern + let nextDate = getNextBusinessDayTimestamp(testDate); + // Submission is after 5pm, Saturday is a weekend, Sunday is a weekend, and Monday is MLK Day + expect(nextDate).toEqual(Date.UTC(2024, 0, 16)); // Tuesday, midnight utc + }); + + it("identifies valid business days", () => { + let testDate = new Date(2024, 0, 9, 15, 0, 0); // Tuesday 3pm utc, Tuesday 8am eastern + let nextDate = getNextBusinessDayTimestamp(testDate); + expect(nextDate).toEqual(Date.UTC(2024, 0, 9)); // Tuesday, midnight utc + }); + +}); diff --git a/src/services/api/handlers/submit.ts b/src/services/api/handlers/submit.ts index c827e19d53..d486fa8448 100644 --- a/src/services/api/handlers/submit.ts +++ b/src/services/api/handlers/submit.ts @@ -17,7 +17,10 @@ const config = { import { Kafka, Message } from "kafkajs"; import { PlanType, onemacSchema, transformOnemac } from "shared-types"; -import { seaToolFriendlyTimestamp } from "shared-utils"; +import { + getNextBusinessDayTimestamp, + seaToolFriendlyTimestamp, +} from "shared-utils"; import { buildStatusMemoQuery } from "../libs/statusMemo"; const kafka = new Kafka({ @@ -63,6 +66,11 @@ export const submit = async (event: APIGatewayEvent) => { } const today = seaToolFriendlyTimestamp(); + const submissionDate = getNextBusinessDayTimestamp(); + console.log( + "Initial Submission Date determined to be: " + + new Date(submissionDate).toISOString() + ); const pool = await sql.connect(config); console.log(body); const query = ` @@ -71,7 +79,7 @@ export const submit = async (event: APIGatewayEvent) => { ,'${body.state}' ,(Select Region_ID from SEA.dbo.States where State_Code = '${body.state}') ,(Select Plan_Type_ID from SEA.dbo.Plan_Types where Plan_Type_Name = '${body.authority}') - ,dateadd(s, convert(int, left(${today}, 10)), cast('19700101' as datetime)) + ,dateadd(s, convert(int, left(${submissionDate}, 10)), cast('19700101' as datetime)) ,dateadd(s, convert(int, left(${today}, 10)), cast('19700101' as datetime)) ,dateadd(s, convert(int, left(${body.proposedEffectiveDate}, 10)), cast('19700101' as datetime)) ,(Select SPW_Status_ID from SEA.dbo.SPW_Status where SPW_Status_DESC = 'Pending') diff --git a/yarn.lock b/yarn.lock index 88172f3e77..b80184cc22 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,6 +10,13 @@ d "1" es5-ext "^0.10.47" +"@18f/us-federal-holidays@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@18f/us-federal-holidays/-/us-federal-holidays-4.0.0.tgz#1c0c2963ffd3f52b7527ff8e3cd1ecc89b48533d" + integrity sha512-93eCrGIE8lZZvMByJz1Me76e4RIEuMq3n2Tn5yYgnXFB5ZhoLVe2/tWGnE+7VpRA6X1lPDln5YgChClIFP6kPA== + dependencies: + dayjs "^1.10.6" + "@aashutoshrathi/word-wrap@^1.2.3": version "1.2.6" resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" @@ -8918,7 +8925,7 @@ dateformat@^3.0.0: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -dayjs@^1.11.8: +dayjs@^1.10.6, dayjs@^1.11.8: version "1.11.10" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==