Skip to content

Commit ab4c02f

Browse files
mofojedvbabich
andauthored
fix: Partial holidays range breaks (#2297)
- Partial holidays were not generating valid range breaks, as they did not account for business periods specified on the calendar - Essentially we were adding another "closed" period that overlapped with time that was already outside of regular business hours - Added a ticket to plotly.js to take into account range breaks which may overlap: plotly/plotly.js#7270, as that would be nice to handle on their end, but this should be sufficient to resolve from our end - Needed for DH-16016 - Added a bunch of test cases --------- Co-authored-by: Vlad Babich <vladimir.babich@gmail.com>
1 parent 1309cd2 commit ab4c02f

3 files changed

Lines changed: 486 additions & 110 deletions

File tree

__mocks__/dh-core.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1892,7 +1892,7 @@ class TimeZone {
18921892
// We at least know that '' and undefined, so throw an error.
18931893
throw new Error('Unsupported time zone');
18941894
}
1895-
return { id };
1895+
return { id, standardOffset: 0 };
18961896
}
18971897
}
18981898

packages/chart/src/ChartUtils.test.ts

Lines changed: 239 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import dh from '@deephaven/jsapi-shim';
2+
import { type dh as DhType } from '@deephaven/jsapi-types';
23
import { Formatter } from '@deephaven/jsapi-utils';
4+
import { TestUtils } from '@deephaven/test-utils';
35
import { type Layout } from 'plotly.js';
46
import ChartUtils from './ChartUtils';
57
import ChartTestUtils from './ChartTestUtils';
@@ -519,8 +521,8 @@ it('creates correct bounds from business days', () => {
519521
expect(
520522
chartUtils.createBoundsFromDays(['MONDAY', 'TUESDAY', 'THURSDAY', 'FRIDAY'])
521523
).toStrictEqual([
522-
[6, 1],
523524
[3, 4],
525+
[6, 1],
524526
]);
525527
expect(
526528
chartUtils.createBoundsFromDays([
@@ -533,27 +535,130 @@ it('creates correct bounds from business days', () => {
533535
expect(
534536
chartUtils.createBoundsFromDays(['MONDAY', 'WEDNESDAY', 'FRIDAY'])
535537
).toStrictEqual([
536-
[6, 1],
537538
[2, 3],
538539
[4, 5],
540+
[6, 1],
539541
]);
540542
expect(
541543
chartUtils.createBoundsFromDays(['WEDNESDAY', 'THURSDAY'])
542544
).toStrictEqual([[5, 3]]);
543545
});
544546

545-
it('creates range breaks from holidays correctly', () => {
546-
const holidays = [
547-
new dh.Holiday('2020-06-22', []),
548-
new dh.Holiday('2020-06-22', [new dh.BusinessPeriod('10:00', '14:00')]),
549-
new dh.Holiday('2020-08-23', []),
550-
new dh.Holiday('2020-03-12', [
551-
new dh.BusinessPeriod('07:00', '08:00'),
552-
new dh.BusinessPeriod('21:00', '22:00'),
553-
]),
554-
];
555-
expect(chartUtils.createRangeBreakValuesFromHolidays(holidays)).toStrictEqual(
556-
[
547+
describe('range breaks', () => {
548+
function testCalendar(
549+
calendar: Partial<DhType.calendar.BusinessCalendar>,
550+
expected,
551+
formatter = makeFormatter()
552+
) {
553+
const businessCalendar =
554+
TestUtils.createMockProxy<DhType.calendar.BusinessCalendar>({
555+
businessDays: [],
556+
businessPeriods: [],
557+
holidays: [],
558+
timeZone: {
559+
id: 'UTC',
560+
standardOffset: 0,
561+
},
562+
...calendar,
563+
});
564+
expect(
565+
chartUtils.createRangeBreaksFromBusinessCalendar(
566+
businessCalendar,
567+
formatter
568+
)
569+
).toEqual(expected);
570+
}
571+
572+
describe('closed periods for partial holidays', () => {
573+
function testPeriods(holidayPeriods, calendarPeriods, expected) {
574+
expect(
575+
ChartUtils.createClosedRangesForPartialHoliday(
576+
holidayPeriods,
577+
calendarPeriods
578+
)
579+
).toEqual(expected);
580+
}
581+
582+
const calendarPeriods = [
583+
[],
584+
[new dh.BusinessPeriod('09:00', '17:00')],
585+
[
586+
new dh.BusinessPeriod('09:00', '12:00'),
587+
new dh.BusinessPeriod('13:00', '17:00'),
588+
],
589+
[new dh.BusinessPeriod('06:00', '23:00')],
590+
[new dh.BusinessPeriod('00:00', '24:00')],
591+
];
592+
593+
it('handles shortened day', () => {
594+
const holidayPeriod = [new dh.BusinessPeriod('10:00', '14:00')];
595+
testPeriods(holidayPeriod, calendarPeriods[0], [
596+
[0, 10],
597+
[14, 24],
598+
]);
599+
testPeriods(holidayPeriod, calendarPeriods[1], [
600+
[9, 10],
601+
[14, 17],
602+
]);
603+
testPeriods(holidayPeriod, calendarPeriods[2], [
604+
[9, 10],
605+
[14, 17],
606+
]);
607+
testPeriods(holidayPeriod, calendarPeriods[3], [
608+
[6, 10],
609+
[14, 23],
610+
]);
611+
testPeriods(holidayPeriod, calendarPeriods[4], [
612+
[0, 10],
613+
[14, 24],
614+
]);
615+
});
616+
617+
it('handles split holiday', () => {
618+
const holidayPeriod = [
619+
new dh.BusinessPeriod('09:00', '11:00'),
620+
new dh.BusinessPeriod('13:30', '16:00'),
621+
];
622+
testPeriods(holidayPeriod, calendarPeriods[0], [
623+
[0, 9],
624+
[11, 13.5],
625+
[16, 24],
626+
]);
627+
testPeriods(holidayPeriod, calendarPeriods[1], [
628+
[11, 13.5],
629+
[16, 17],
630+
]);
631+
testPeriods(holidayPeriod, calendarPeriods[2], [
632+
[11, 12],
633+
[13, 13.5],
634+
[16, 17],
635+
]);
636+
testPeriods(holidayPeriod, calendarPeriods[3], [
637+
[6, 9],
638+
[11, 13.5],
639+
[16, 23],
640+
]);
641+
testPeriods(holidayPeriod, calendarPeriods[4], [
642+
[0, 9],
643+
[11, 13.5],
644+
[16, 24],
645+
]);
646+
});
647+
});
648+
649+
it('creates range breaks from holidays correctly', () => {
650+
const holidays = [
651+
new dh.Holiday('2020-06-22', []),
652+
new dh.Holiday('2020-06-22', [new dh.BusinessPeriod('10:00', '14:00')]),
653+
new dh.Holiday('2020-08-23', []),
654+
new dh.Holiday('2020-03-12', [
655+
new dh.BusinessPeriod('07:00', '08:00'),
656+
new dh.BusinessPeriod('21:00', '22:00'),
657+
]),
658+
];
659+
expect(
660+
chartUtils.createRangeBreakValuesFromHolidays(holidays)
661+
).toStrictEqual([
557662
{ values: ['2020-06-22 00:00:00.000000', '2020-08-23 00:00:00.000000'] },
558663
{
559664
dvalue: 36000000,
@@ -575,8 +680,126 @@ it('creates range breaks from holidays correctly', () => {
575680
dvalue: 7200000,
576681
values: ['2020-03-12 22:00:00.000000'],
577682
},
578-
]
579-
);
683+
]);
684+
});
685+
686+
describe('creates range breaks from business periods correctly', () => {
687+
function testPeriods(periods: DhType.calendar.BusinessPeriod[], expected) {
688+
return testCalendar({ businessPeriods: periods }, expected);
689+
}
690+
691+
it('handles empty periods', () => {
692+
testPeriods([], []);
693+
});
694+
695+
it('handles single period', () => {
696+
testPeriods(
697+
[new dh.BusinessPeriod('9:00', '16:30')],
698+
[
699+
{
700+
pattern: 'hour',
701+
bounds: [16.5, 9],
702+
},
703+
]
704+
);
705+
});
706+
707+
it('handles multiple periods', () => {
708+
testPeriods(
709+
[
710+
new dh.BusinessPeriod('9:00', '11:30'),
711+
new dh.BusinessPeriod('13:30', '16:30'),
712+
],
713+
[
714+
{
715+
pattern: 'hour',
716+
bounds: [11.5, 13.5],
717+
},
718+
{
719+
pattern: 'hour',
720+
bounds: [16.5, 9],
721+
},
722+
]
723+
);
724+
});
725+
});
726+
727+
describe('creates range breaks from business days correctly', () => {
728+
function testDays(days: string[], expected) {
729+
return testCalendar({ businessDays: days }, expected);
730+
}
731+
732+
it('handles empty days', () => {
733+
testDays([], []);
734+
});
735+
736+
it('handles single day', () => {
737+
testDays(
738+
['TUESDAY'],
739+
[
740+
{
741+
pattern: 'day of week',
742+
bounds: [3, 2],
743+
},
744+
]
745+
);
746+
});
747+
748+
it('handles a regular business week', () => {
749+
testDays(
750+
['MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY'],
751+
[
752+
{
753+
pattern: 'day of week',
754+
bounds: [6, 1],
755+
},
756+
]
757+
);
758+
});
759+
760+
it('handles every day but Sunday', () => {
761+
testDays(
762+
['MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY'],
763+
[
764+
{
765+
pattern: 'day of week',
766+
bounds: [0, 1],
767+
},
768+
]
769+
);
770+
});
771+
772+
it('handles a full week', () => {
773+
testDays(
774+
[
775+
'SUNDAY',
776+
'MONDAY',
777+
'TUESDAY',
778+
'WEDNESDAY',
779+
'THURSDAY',
780+
'FRIDAY',
781+
'SATURDAY',
782+
],
783+
[]
784+
);
785+
});
786+
787+
it('handles a break in the middle of the week', () => {
788+
testDays(
789+
['MONDAY', 'TUESDAY', 'THURSDAY', 'FRIDAY'],
790+
[
791+
{
792+
pattern: 'day of week',
793+
bounds: [3, 4],
794+
},
795+
{
796+
pattern: 'day of week',
797+
bounds: [6, 1],
798+
},
799+
]
800+
);
801+
});
802+
});
580803
});
581804

582805
describe('axis property name', () => {

0 commit comments

Comments
 (0)