From 725470a290f965c15099471d82d0d62264d3e4b1 Mon Sep 17 00:00:00 2001 From: Michael Xiao Date: Tue, 11 Nov 2025 12:12:56 -0500 Subject: [PATCH 1/2] Add optional weekend into market status --- package.json | 3 +- src/adapter/market-status.ts | 20 +++- src/validation/market-status.ts | 64 ++++++++++++ test/adapter/market-status.test.ts | 86 ++++++++++++++++ test/validation/market-status.test.ts | 143 ++++++++++++++++++++++++++ yarn.lock | 5 + 6 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 src/validation/market-status.ts create mode 100644 test/adapter/market-status.test.ts create mode 100644 test/validation/market-status.test.ts diff --git a/package.json b/package.json index 8661982f..bcb45310 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "pino-pretty": "13.1.2", "prom-client": "15.1.3", "redlock": "5.0.0-beta.2", - "ws": "8.18.3" + "ws": "8.18.3", + "@date-fns/tz": "1.4.1" }, "scripts": { "build": "rm -rf dist/src && mkdir -p ./dist/src && cp package.json dist/src && cp README.md dist/src && tsc && yarn pre-build-generator", diff --git a/src/adapter/market-status.ts b/src/adapter/market-status.ts index ecd7fdaf..4cf032dd 100644 --- a/src/adapter/market-status.ts +++ b/src/adapter/market-status.ts @@ -1,5 +1,7 @@ import { TransportGenerics } from '../transports' import { AdapterEndpoint } from './endpoint' +import { AdapterEndpointParams } from './types' +import { validateWeekend } from '../validation/market-status' /** * Base input parameter config that any [[MarketStatusEndpoint]] must extend @@ -17,6 +19,11 @@ export const marketStatusEndpointInputParametersDefinition = { options: ['regular', '24/5'], default: 'regular', }, + weekend: { + type: 'string', + description: + 'DHH-DHH:TZ, 520-020:America/New_York means Fri 20:00 to Sun 20:00 Eastern Time Zone', + }, } as const export enum MarketStatus { @@ -58,4 +65,15 @@ export type MarketStatusEndpointGenerics = TransportGenerics & { */ export class MarketStatusEndpoint< T extends MarketStatusEndpointGenerics, -> extends AdapterEndpoint {} +> extends AdapterEndpoint { + constructor(params: AdapterEndpointParams) { + params.customInputValidation = (req, _adapterSettings) => { + const data = req.requestContext.data as Record + if (data['type'] === '24/5') { + validateWeekend(data['weekend']) + } + return undefined + } + super(params) + } +} diff --git a/src/validation/market-status.ts b/src/validation/market-status.ts new file mode 100644 index 00000000..7a8a8b34 --- /dev/null +++ b/src/validation/market-status.ts @@ -0,0 +1,64 @@ +import { AdapterInputError } from '../validation/error' +import { TZDate } from '@date-fns/tz' + +export const validateWeekend = (weekend?: string) => { + const dayHour = /[0-6](0\d|1\d|2[0-3])/ + const timezonePattern = /[^\s]+/ + const regex = new RegExp(`^(${dayHour.source})-(${dayHour.source}):(${timezonePattern.source})$`) + + const match = weekend?.match(regex) + if (!match) { + throw new AdapterInputError({ + statusCode: 400, + message: '[Param: weekend] does not match format of DHH-DHH:TZ', + }) + } + + try { + // eslint-disable-next-line new-cap + Intl.DateTimeFormat(undefined, { timeZone: weekend?.split(':')[1] }) + } catch (error) { + throw new AdapterInputError({ + statusCode: 400, + message: `[Param: weekend] is not valid: ${error}`, + }) + } +} + +export const isWeekend = (weekend?: string) => { + validateWeekend(weekend) + + // Weekend looks like 520-020:America/New_York + const [range, tz] = (weekend || '').split(':') + const [start, end] = range.split('-') + + const startDay = Number(start[0]) + const startHour = Number(start.slice(1)) + const endDay = Number(end[0]) + const endHour = Number(end.slice(1)) + + const nowDay = TZDate.tz(tz).getDay() + const nowHour = TZDate.tz(tz).getHours() + + // Case 1: weekend does NOT wrap around the week + if (startDay < endDay || (startDay === endDay && startHour < endHour)) { + if (nowDay < startDay || nowDay > endDay) { + return false + } else if (nowDay === startDay && nowHour < startHour) { + return false + } else if (nowDay === endDay && nowHour >= endHour) { + return false + } + return true + } + + // Case 2: weekend wraps around (e.g. Fri → Sun) + if (nowDay > startDay || nowDay < endDay) { + return true + } else if (nowDay === startDay && nowHour >= startHour) { + return true + } else if (nowDay === endDay && nowHour < endHour) { + return true + } + return false +} diff --git a/test/adapter/market-status.test.ts b/test/adapter/market-status.test.ts new file mode 100644 index 00000000..e5aa06d8 --- /dev/null +++ b/test/adapter/market-status.test.ts @@ -0,0 +1,86 @@ +import test from 'ava' +import '../../src/adapter' +import { + MarketStatusEndpoint, + marketStatusEndpointInputParametersDefinition, + MarketStatusEndpointGenerics, +} from '../../src/adapter/market-status' +import { InputParameters } from '../../src/validation' +import { TestAdapter } from '../../src/util/testing-utils' +import { Adapter } from '../../src/adapter/basic' + +import { Transport } from '../../src/transports' +import { ResponseCache } from '../../src/cache/response' + +test('MarketStatusEndpoint - validates weekend when type is 24/5', async (t) => { + class MarketStatusTestTransport implements Transport { + name!: string + responseCache!: ResponseCache + + async initialize() {} + + async foregroundExecute() { + return { + data: { + result: 2, + statusString: 'OPEN', + }, + result: 2, + statusCode: 200, + timestamps: { + providerDataRequestedUnixMs: 0, + providerDataReceivedUnixMs: 0, + providerIndicatedTimeUnixMs: 0, + }, + } + } + } + + const adapter = new Adapter({ + name: 'TEST', + endpoints: [ + new MarketStatusEndpoint({ + name: 'test', + inputParameters: new InputParameters(marketStatusEndpointInputParametersDefinition), + transport: new MarketStatusTestTransport(), + }), + ], + }) + + const testAdapter = await TestAdapter.start( + adapter, + {} as { + testAdapter: TestAdapter + }, + ) + + const response1 = await testAdapter.request({ + market: 'BTC', + type: 'regular', + weekend: '520-020', + endpoint: 'test', + }) + t.is(response1.statusCode, 200, 'Should succeed with invalid weekend when type is regular') + + const response2 = await testAdapter.request({ + market: 'BTC', + type: '24/5', + weekend: '520-020', + endpoint: 'test', + }) + t.is(response2.statusCode, 400, 'Should fail with invalid weekend format') + t.true( + response2.json().error.message.includes('[Param: weekend] does not match format'), + 'Error message should mention weekend format', + ) + + const response3 = await testAdapter.request({ + market: 'BTC', + type: '24/5', + weekend: '520-020:America/New_York', + endpoint: 'test', + }) + t.is(response3.statusCode, 200, 'Should succeed with valid weekend') + + await testAdapter.api.close() +}) diff --git a/test/validation/market-status.test.ts b/test/validation/market-status.test.ts new file mode 100644 index 00000000..17795cb6 --- /dev/null +++ b/test/validation/market-status.test.ts @@ -0,0 +1,143 @@ +import test from 'ava' +import FakeTimers from '@sinonjs/fake-timers' +import { validateWeekend, isWeekend } from '../../src/validation/market-status' + +test('validateWeekend - success', (t) => { + t.notThrows(() => { + validateWeekend('520-020:America/New_York') + validateWeekend('000-123:UTC') + validateWeekend('123-423:Europe/London') + validateWeekend('600-023:Asia/Tokyo') + }) +}) + +test('validateWeekend - bad format', (t) => { + t.throws(() => { + validateWeekend('520020:America/New_York') + }) + t.throws(() => { + validateWeekend('520-020America/New_York') + }) + t.throws(() => { + validateWeekend('520-020:') + }) + t.throws(() => { + validateWeekend('55-020:UTC') + }) + t.throws(() => { + validateWeekend('55-20:UTC') + }) + t.throws(() => { + validateWeekend('') + }) + t.throws(() => { + validateWeekend() + }) + t.throws(() => { + validateWeekend('520:020-America/New_York') + }) + t.throws(() => { + validateWeekend('520-020: ') + }) +}) + +test('validateWeekend - bad number', (t) => { + t.throws(() => { + validateWeekend('720-020:UTC') + }) + t.throws(() => { + validateWeekend('524-020:UTC') + }) + t.throws(() => { + validateWeekend('525-020:UTC') + }) +}) + +test('validateWeekend - invalid timezone', (t) => { + t.throws(() => { + validateWeekend('520-020:Invalid/Timezone') + validateWeekend('520-020:AmericaNew_York') + }) +}) + +const clock = FakeTimers.install({ toFake: ['Date'] }) + +test.after(() => { + clock.uninstall() +}) + +test('isWeekend - UTC', (t) => { + // Saturday 12:00 -> 612 + clock.setSystemTime(new Date('2024-01-06T12:00:00Z').getTime()) + + t.false(isWeekend('000-123:UTC'), 'Before start day') + t.false(isWeekend('400-500:UTC'), 'After end day') + t.false(isWeekend('613-620:UTC'), 'Before start hour') + t.false(isWeekend('610-612:UTC'), 'After end hour') + + t.true(isWeekend('610-620:UTC'), 'Non-wrapping: middle of weekend should return true') + t.true(isWeekend('600-023:UTC'), 'Non-wrapping: spanning multiple days should return true') + t.true(isWeekend('612-615:UTC'), 'Non-wrapping: same day, at start hour should return true') + + t.true(isWeekend('520-020:UTC'), 'Wrapping: nowDay > startDay should return true') + t.true(isWeekend('400-200:UTC'), 'Wrapping: nowDay > startDay should return true') + + t.true(isWeekend('612-020:UTC'), 'After start hour') + t.true(isWeekend('520-613:UTC'), 'Before end hour') + + t.false(isWeekend('620-610:UTC'), 'Wrapping same day: between end and start should return false') +}) + +test('isWeekend - ET', (t) => { + // Saturday 12:00 UTC = Saturday 07:00 EST -> 607 + clock.setSystemTime(new Date('2024-01-06T12:00:00Z').getTime()) + + t.false(isWeekend('000-123:America/New_York'), 'Before start day') + t.false(isWeekend('400-500:America/New_York'), 'After end day') + t.false(isWeekend('608-620:America/New_York'), 'Before start hour') + t.false(isWeekend('605-607:America/New_York'), 'After end hour') + + t.true(isWeekend('605-620:America/New_York'), 'Non-wrapping: middle of weekend') + t.true(isWeekend('600-023:America/New_York'), 'Non-wrapping: spanning multiple days') + t.true(isWeekend('607-610:America/New_York'), 'Non-wrapping: same day, at start hour ') + + t.true(isWeekend('520-020:America/New_York'), 'Wrapping: nowDay > startDay should return true') + t.true(isWeekend('400-200:America/New_York'), 'Wrapping: nowDay > startDay should return true') + + t.true(isWeekend('607-020:America/New_York'), 'After start hour') + t.true(isWeekend('520-608:America/New_York'), 'Before end hour') + + t.false(isWeekend('620-607:America/New_York'), 'Wrapping same day: at end hour should') +}) + +test('isWeekend - ET - Fri to Sun 8 to 8', (t) => { + // Weekend: Fri 20:00 to Sun 20:00 ET (520-020:America/New_York) + const range = '520-020:America/New_York' + // Thu 21:00 ET + clock.setSystemTime(new Date('2024-01-05T02:00:00Z').getTime()) + t.false(isWeekend(range), 'Before start day') + // Fri 19:00 ET + clock.setSystemTime(new Date('2024-01-06T00:00:00Z').getTime()) + t.false(isWeekend(range), 'On start day, before start hour') + // Fri 20:00 ET + clock.setSystemTime(new Date('2024-01-06T01:00:00Z').getTime()) + t.true(isWeekend(range), 'On start day, at start hour') + // Fri 23:00 ET + clock.setSystemTime(new Date('2024-01-06T04:00:00Z').getTime()) + t.true(isWeekend(range), 'On start day, after start hour') + // Sat 12:00 ET + clock.setSystemTime(new Date('2024-01-06T17:00:00Z').getTime()) + t.true(isWeekend(range), 'Middle day (Saturday)') + // Sun 19:00 ET + clock.setSystemTime(new Date('2024-01-08T00:00:00Z').getTime()) + t.true(isWeekend(range), 'On end day, before end hour') + // Sun 20:00 ET + clock.setSystemTime(new Date('2024-01-08T01:00:00Z').getTime()) + t.false(isWeekend(range), 'On end day, at end hour') + // Sun 21:00 ET + clock.setSystemTime(new Date('2024-01-08T02:00:00Z').getTime()) + t.false(isWeekend(range), 'On end day, after end hour') + // Mon 10:00 ET + clock.setSystemTime(new Date('2024-01-08T15:00:00Z').getTime()) + t.false(isWeekend(range), 'After end day') +}) diff --git a/yarn.lock b/yarn.lock index 31f34b36..954b0774 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14,6 +14,11 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@date-fns/tz@1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@date-fns/tz/-/tz-1.4.1.tgz#2d905f282304630e07bef6d02d2e7dbf3f0cc4e4" + integrity sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA== + "@eslint-community/eslint-utils@^4.7.0": version "4.7.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a" From 537c4aa1c915e9ec79f5b01e48a7522ec91fd42f Mon Sep 17 00:00:00 2001 From: Michael Xiao Date: Wed, 12 Nov 2025 09:42:19 -0500 Subject: [PATCH 2/2] Address comments --- src/adapter/market-status.ts | 11 ++- src/validation/market-status.ts | 34 ++++---- test/adapter/market-status.test.ts | 25 ++++-- test/validation/market-status.test.ts | 121 +++++++++++++------------- 4 files changed, 107 insertions(+), 84 deletions(-) diff --git a/src/adapter/market-status.ts b/src/adapter/market-status.ts index 4cf032dd..4373af94 100644 --- a/src/adapter/market-status.ts +++ b/src/adapter/market-status.ts @@ -1,7 +1,8 @@ import { TransportGenerics } from '../transports' import { AdapterEndpoint } from './endpoint' import { AdapterEndpointParams } from './types' -import { validateWeekend } from '../validation/market-status' +import { parseWeekendString } from '../validation/market-status' +import { AdapterInputError } from '../validation/error' /** * Base input parameter config that any [[MarketStatusEndpoint]] must extend @@ -70,7 +71,13 @@ export class MarketStatusEndpoint< params.customInputValidation = (req, _adapterSettings) => { const data = req.requestContext.data as Record if (data['type'] === '24/5') { - validateWeekend(data['weekend']) + parseWeekendString(data['weekend']) + } + if (data['type'] === 'regular' && data['weekend']) { + throw new AdapterInputError({ + statusCode: 400, + message: '[Param: weekend] must be empty when [Param: type] is regular', + }) } return undefined } diff --git a/src/validation/market-status.ts b/src/validation/market-status.ts index 7a8a8b34..f1dd6d2d 100644 --- a/src/validation/market-status.ts +++ b/src/validation/market-status.ts @@ -1,7 +1,7 @@ import { AdapterInputError } from '../validation/error' import { TZDate } from '@date-fns/tz' -export const validateWeekend = (weekend?: string) => { +export const parseWeekendString = (weekend?: string) => { const dayHour = /[0-6](0\d|1\d|2[0-3])/ const timezonePattern = /[^\s]+/ const regex = new RegExp(`^(${dayHour.source})-(${dayHour.source}):(${timezonePattern.source})$`) @@ -14,31 +14,35 @@ export const validateWeekend = (weekend?: string) => { }) } + const result = { + start: match[1], + end: match[3], + tz: match[5], + } + try { // eslint-disable-next-line new-cap - Intl.DateTimeFormat(undefined, { timeZone: weekend?.split(':')[1] }) + Intl.DateTimeFormat(undefined, { timeZone: result.tz }) } catch (error) { throw new AdapterInputError({ statusCode: 400, - message: `[Param: weekend] is not valid: ${error}`, + message: `timezone ${result.tz} in [Param: weekend] is not valid: ${error}`, }) } -} -export const isWeekend = (weekend?: string) => { - validateWeekend(weekend) + return result +} - // Weekend looks like 520-020:America/New_York - const [range, tz] = (weekend || '').split(':') - const [start, end] = range.split('-') +export const isWeekendNow = (weekend?: string) => { + const parsed = parseWeekendString(weekend) - const startDay = Number(start[0]) - const startHour = Number(start.slice(1)) - const endDay = Number(end[0]) - const endHour = Number(end.slice(1)) + const startDay = Number(parsed.start[0]) + const startHour = Number(parsed.start.slice(1)) + const endDay = Number(parsed.end[0]) + const endHour = Number(parsed.end.slice(1)) - const nowDay = TZDate.tz(tz).getDay() - const nowHour = TZDate.tz(tz).getHours() + const nowDay = TZDate.tz(parsed.tz).getDay() + const nowHour = TZDate.tz(parsed.tz).getHours() // Case 1: weekend does NOT wrap around the week if (startDay < endDay || (startDay === endDay && startHour < endHour)) { diff --git a/test/adapter/market-status.test.ts b/test/adapter/market-status.test.ts index e5aa06d8..bf992157 100644 --- a/test/adapter/market-status.test.ts +++ b/test/adapter/market-status.test.ts @@ -12,7 +12,7 @@ import { Adapter } from '../../src/adapter/basic' import { Transport } from '../../src/transports' import { ResponseCache } from '../../src/cache/response' -test('MarketStatusEndpoint - validates weekend when type is 24/5', async (t) => { +test('MarketStatusEndpoint - validates weekend', async (t) => { class MarketStatusTestTransport implements Transport { name!: string responseCache!: ResponseCache @@ -57,30 +57,39 @@ test('MarketStatusEndpoint - validates weekend when type is 24/5', async (t) => const response1 = await testAdapter.request({ market: 'BTC', type: 'regular', - weekend: '520-020', endpoint: 'test', }) - t.is(response1.statusCode, 200, 'Should succeed with invalid weekend when type is regular') + t.is(response1.statusCode, 200, 'Should succeed with empty weekend when type is regular') const response2 = await testAdapter.request({ market: 'BTC', - type: '24/5', + type: 'regular', weekend: '520-020', endpoint: 'test', }) - t.is(response2.statusCode, 400, 'Should fail with invalid weekend format') + t.is(response2.statusCode, 400, 'Should fail with weekend when type is regular') t.true( - response2.json().error.message.includes('[Param: weekend] does not match format'), - 'Error message should mention weekend format', + response2 + .json() + .error.message.includes('[Param: weekend] must be empty when [Param: type] is regular'), ) const response3 = await testAdapter.request({ + market: 'BTC', + type: '24/5', + weekend: '520-020', + endpoint: 'test', + }) + t.is(response3.statusCode, 400, 'Should fail with invalid weekend format when type is 24/5') + t.true(response3.json().error.message.includes('[Param: weekend] does not match format')) + + const response4 = await testAdapter.request({ market: 'BTC', type: '24/5', weekend: '520-020:America/New_York', endpoint: 'test', }) - t.is(response3.statusCode, 200, 'Should succeed with valid weekend') + t.is(response4.statusCode, 200, 'Should succeed with valid weekend when type is 24/5') await testAdapter.api.close() }) diff --git a/test/validation/market-status.test.ts b/test/validation/market-status.test.ts index 17795cb6..5e9f3adc 100644 --- a/test/validation/market-status.test.ts +++ b/test/validation/market-status.test.ts @@ -1,62 +1,62 @@ import test from 'ava' import FakeTimers from '@sinonjs/fake-timers' -import { validateWeekend, isWeekend } from '../../src/validation/market-status' +import { parseWeekendString, isWeekendNow } from '../../src/validation/market-status' -test('validateWeekend - success', (t) => { +test('parseWeekendString - success', (t) => { t.notThrows(() => { - validateWeekend('520-020:America/New_York') - validateWeekend('000-123:UTC') - validateWeekend('123-423:Europe/London') - validateWeekend('600-023:Asia/Tokyo') + parseWeekendString('520-020:America/New_York') + parseWeekendString('000-123:UTC') + parseWeekendString('123-423:Europe/London') + parseWeekendString('600-023:Asia/Tokyo') }) }) -test('validateWeekend - bad format', (t) => { +test('parseWeekendString - bad format', (t) => { t.throws(() => { - validateWeekend('520020:America/New_York') + parseWeekendString('520020:America/New_York') }) t.throws(() => { - validateWeekend('520-020America/New_York') + parseWeekendString('520-020America/New_York') }) t.throws(() => { - validateWeekend('520-020:') + parseWeekendString('520-020:') }) t.throws(() => { - validateWeekend('55-020:UTC') + parseWeekendString('55-020:UTC') }) t.throws(() => { - validateWeekend('55-20:UTC') + parseWeekendString('55-20:UTC') }) t.throws(() => { - validateWeekend('') + parseWeekendString('') }) t.throws(() => { - validateWeekend() + parseWeekendString() }) t.throws(() => { - validateWeekend('520:020-America/New_York') + parseWeekendString('520:020-America/New_York') }) t.throws(() => { - validateWeekend('520-020: ') + parseWeekendString('520-020: ') }) }) -test('validateWeekend - bad number', (t) => { +test('parseWeekendString - bad number', (t) => { t.throws(() => { - validateWeekend('720-020:UTC') + parseWeekendString('720-020:UTC') }) t.throws(() => { - validateWeekend('524-020:UTC') + parseWeekendString('524-020:UTC') }) t.throws(() => { - validateWeekend('525-020:UTC') + parseWeekendString('525-020:UTC') }) }) -test('validateWeekend - invalid timezone', (t) => { +test('parseWeekendString - invalid timezone', (t) => { t.throws(() => { - validateWeekend('520-020:Invalid/Timezone') - validateWeekend('520-020:AmericaNew_York') + parseWeekendString('520-020:Invalid/Timezone') + parseWeekendString('520-020:AmericaNew_York') }) }) @@ -66,78 +66,81 @@ test.after(() => { clock.uninstall() }) -test('isWeekend - UTC', (t) => { +test('isWeekendNow - UTC', (t) => { // Saturday 12:00 -> 612 clock.setSystemTime(new Date('2024-01-06T12:00:00Z').getTime()) - t.false(isWeekend('000-123:UTC'), 'Before start day') - t.false(isWeekend('400-500:UTC'), 'After end day') - t.false(isWeekend('613-620:UTC'), 'Before start hour') - t.false(isWeekend('610-612:UTC'), 'After end hour') + t.false(isWeekendNow('000-123:UTC'), 'Before start day') + t.false(isWeekendNow('400-500:UTC'), 'After end day') + t.false(isWeekendNow('613-620:UTC'), 'Before start hour') + t.false(isWeekendNow('610-612:UTC'), 'After end hour') - t.true(isWeekend('610-620:UTC'), 'Non-wrapping: middle of weekend should return true') - t.true(isWeekend('600-023:UTC'), 'Non-wrapping: spanning multiple days should return true') - t.true(isWeekend('612-615:UTC'), 'Non-wrapping: same day, at start hour should return true') + t.true(isWeekendNow('610-620:UTC'), 'Non-wrapping: middle of weekend should return true') + t.true(isWeekendNow('600-023:UTC'), 'Non-wrapping: spanning multiple days should return true') + t.true(isWeekendNow('612-615:UTC'), 'Non-wrapping: same day, at start hour should return true') - t.true(isWeekend('520-020:UTC'), 'Wrapping: nowDay > startDay should return true') - t.true(isWeekend('400-200:UTC'), 'Wrapping: nowDay > startDay should return true') + t.true(isWeekendNow('520-020:UTC'), 'Wrapping: nowDay > startDay should return true') + t.true(isWeekendNow('400-200:UTC'), 'Wrapping: nowDay > startDay should return true') - t.true(isWeekend('612-020:UTC'), 'After start hour') - t.true(isWeekend('520-613:UTC'), 'Before end hour') + t.true(isWeekendNow('612-020:UTC'), 'After start hour') + t.true(isWeekendNow('520-613:UTC'), 'Before end hour') - t.false(isWeekend('620-610:UTC'), 'Wrapping same day: between end and start should return false') + t.false( + isWeekendNow('620-610:UTC'), + 'Wrapping same day: between end and start should return false', + ) }) -test('isWeekend - ET', (t) => { +test('isWeekendNow - ET', (t) => { // Saturday 12:00 UTC = Saturday 07:00 EST -> 607 clock.setSystemTime(new Date('2024-01-06T12:00:00Z').getTime()) - t.false(isWeekend('000-123:America/New_York'), 'Before start day') - t.false(isWeekend('400-500:America/New_York'), 'After end day') - t.false(isWeekend('608-620:America/New_York'), 'Before start hour') - t.false(isWeekend('605-607:America/New_York'), 'After end hour') + t.false(isWeekendNow('000-123:America/New_York'), 'Before start day') + t.false(isWeekendNow('400-500:America/New_York'), 'After end day') + t.false(isWeekendNow('608-620:America/New_York'), 'Before start hour') + t.false(isWeekendNow('605-607:America/New_York'), 'After end hour') - t.true(isWeekend('605-620:America/New_York'), 'Non-wrapping: middle of weekend') - t.true(isWeekend('600-023:America/New_York'), 'Non-wrapping: spanning multiple days') - t.true(isWeekend('607-610:America/New_York'), 'Non-wrapping: same day, at start hour ') + t.true(isWeekendNow('605-620:America/New_York'), 'Non-wrapping: middle of weekend') + t.true(isWeekendNow('600-023:America/New_York'), 'Non-wrapping: spanning multiple days') + t.true(isWeekendNow('607-610:America/New_York'), 'Non-wrapping: same day, at start hour ') - t.true(isWeekend('520-020:America/New_York'), 'Wrapping: nowDay > startDay should return true') - t.true(isWeekend('400-200:America/New_York'), 'Wrapping: nowDay > startDay should return true') + t.true(isWeekendNow('520-020:America/New_York'), 'Wrapping: nowDay > startDay should return true') + t.true(isWeekendNow('400-200:America/New_York'), 'Wrapping: nowDay > startDay should return true') - t.true(isWeekend('607-020:America/New_York'), 'After start hour') - t.true(isWeekend('520-608:America/New_York'), 'Before end hour') + t.true(isWeekendNow('607-020:America/New_York'), 'After start hour') + t.true(isWeekendNow('520-608:America/New_York'), 'Before end hour') - t.false(isWeekend('620-607:America/New_York'), 'Wrapping same day: at end hour should') + t.false(isWeekendNow('620-607:America/New_York'), 'Wrapping same day: at end hour should') }) -test('isWeekend - ET - Fri to Sun 8 to 8', (t) => { +test('isWeekendNow - ET - Fri to Sun 8 to 8', (t) => { // Weekend: Fri 20:00 to Sun 20:00 ET (520-020:America/New_York) const range = '520-020:America/New_York' // Thu 21:00 ET clock.setSystemTime(new Date('2024-01-05T02:00:00Z').getTime()) - t.false(isWeekend(range), 'Before start day') + t.false(isWeekendNow(range), 'Before start day') // Fri 19:00 ET clock.setSystemTime(new Date('2024-01-06T00:00:00Z').getTime()) - t.false(isWeekend(range), 'On start day, before start hour') + t.false(isWeekendNow(range), 'On start day, before start hour') // Fri 20:00 ET clock.setSystemTime(new Date('2024-01-06T01:00:00Z').getTime()) - t.true(isWeekend(range), 'On start day, at start hour') + t.true(isWeekendNow(range), 'On start day, at start hour') // Fri 23:00 ET clock.setSystemTime(new Date('2024-01-06T04:00:00Z').getTime()) - t.true(isWeekend(range), 'On start day, after start hour') + t.true(isWeekendNow(range), 'On start day, after start hour') // Sat 12:00 ET clock.setSystemTime(new Date('2024-01-06T17:00:00Z').getTime()) - t.true(isWeekend(range), 'Middle day (Saturday)') + t.true(isWeekendNow(range), 'Middle day (Saturday)') // Sun 19:00 ET clock.setSystemTime(new Date('2024-01-08T00:00:00Z').getTime()) - t.true(isWeekend(range), 'On end day, before end hour') + t.true(isWeekendNow(range), 'On end day, before end hour') // Sun 20:00 ET clock.setSystemTime(new Date('2024-01-08T01:00:00Z').getTime()) - t.false(isWeekend(range), 'On end day, at end hour') + t.false(isWeekendNow(range), 'On end day, at end hour') // Sun 21:00 ET clock.setSystemTime(new Date('2024-01-08T02:00:00Z').getTime()) - t.false(isWeekend(range), 'On end day, after end hour') + t.false(isWeekendNow(range), 'On end day, after end hour') // Mon 10:00 ET clock.setSystemTime(new Date('2024-01-08T15:00:00Z').getTime()) - t.false(isWeekend(range), 'After end day') + t.false(isWeekendNow(range), 'After end day') })