Skip to content

Commit

Permalink
fix: fixed dateScheduleDateFilter() timezone usage
Browse files Browse the repository at this point in the history
  • Loading branch information
dereekb committed Aug 5, 2023
1 parent 57ea30a commit 85bf021
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 27 deletions.
14 changes: 12 additions & 2 deletions packages/date/src/lib/date/date.block.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,14 @@ import {
getDateBlockTimingFirstEventDateRange,
dateBlockTimingFromDateRangeAndEvent,
getCurrentDateBlockTimingUtcData,
getCurrentDateBlockTimingOffsetData
getCurrentDateBlockTimingOffsetData,
dateBlockTimingStartForNowInSystemTimezone,
timingIsInExpectedTimezone
} from './date.block';
import { MS_IN_DAY, MINUTES_IN_DAY, range, RangeInput, Hours, Day, TimezoneString } from '@dereekb/util';
import { copyHoursAndMinutesFromDate, roundDownToHour, roundDownToMinute } from './date';
import { dateBlockDurationSpanHasNotEndedFilterFunction } from './date.filter';
import { dateTimezoneUtcNormal, getCurrentSystemOffsetInHours, systemBaseDateToNormalDate, systemNormalDateToBaseDate } from './date.timezone';
import { dateTimezoneUtcNormal, getCurrentSystemOffsetInHours, systemBaseDateToNormalDate, systemNormalDateToBaseDate, SYSTEM_DATE_TIMEZONE_UTC_NORMAL_INSTANCE } from './date.timezone';
import { formatToISO8601DayString } from './date.format';

describe('isValidDateBlockIndex()', () => {
Expand Down Expand Up @@ -209,6 +211,14 @@ describe('getCurrentDateBlockTimingOffset()', () => {
});
});

describe('dateBlockTimingStartForSystemTimezone()', () => {
it('should return the DateBlockTimingStart for the system timezone.', () => {
const result = dateBlockTimingStartForNowInSystemTimezone();
const isInSystemTimezone = timingIsInExpectedTimezone(result, SYSTEM_DATE_TIMEZONE_UTC_NORMAL_INSTANCE);
expect(isInSystemTimezone).toBe(true);
});
});

describe('getCurrentDateBlockTimingStartDate()', () => {
const utcDate = new Date('2022-01-02T00:00:00Z'); // date in utc. Implies there is no offset to consider.

Expand Down
41 changes: 32 additions & 9 deletions packages/date/src/lib/date/date.block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { DateDurationSpan } from './date.duration';
import { differenceInDays, differenceInMilliseconds, isBefore, addDays, addMinutes, getSeconds, getMilliseconds, getMinutes, addMilliseconds, hoursToMilliseconds, addHours, differenceInHours, isAfter, minutesToHours, differenceInMinutes, startOfDay } from 'date-fns';
import { isDate, copyHoursAndMinutesFromDate, roundDownToMinute, copyHoursAndMinutesFromNow } from './date';
import { Expose, Type } from 'class-transformer';
import { DateTimezoneUtcNormalFunctionInput, DateTimezoneUtcNormalInstance, dateTimezoneUtcNormal, getCurrentSystemOffsetInHours, startOfDayInTimezoneDayStringFactory, copyHoursAndMinutesFromDatesWithTimezoneNormal } from './date.timezone';
import { DateTimezoneUtcNormalFunctionInput, DateTimezoneUtcNormalInstance, dateTimezoneUtcNormal, getCurrentSystemOffsetInHours, startOfDayInTimezoneDayStringFactory, copyHoursAndMinutesFromDatesWithTimezoneNormal, SYSTEM_DATE_TIMEZONE_UTC_NORMAL_INSTANCE } from './date.timezone';
import { IsDate, IsNumber, IsOptional, Min } from 'class-validator';
import { parseISO8601DayStringToDate } from './date.format';

Expand Down Expand Up @@ -102,6 +102,13 @@ export type DateBlockArrayRef<B extends DateBlock = DateBlock> = {
blocks: DateBlockArray<B>;
};

/**
* DateBlockTiming with only the start time.
*
* The start time is midnight of what timezone it is in, and can be used to infer the target timezone offset.
*/
export type DateBlockTimingStart = DateRangeStart;

/**
* Is combination of DateRange and DateDurationSpan. The DateRange captures a range of days that a DateBlock takes up, and the DateDurationSpan
* captures the Dates at which the Job occurs at.
Expand All @@ -114,17 +121,12 @@ export type DateBlockArrayRef<B extends DateBlock = DateBlock> = {
* - The startsAt time should be on the same date as normalized start
* - The end time should equal the ending date/time of the final end duration.
*/
export interface DateBlockTiming extends DateRange, DateDurationSpan {}
export interface DateBlockTiming extends DateBlockTimingStart, DateRange, DateDurationSpan {}

/**
* DateBlockTiming with only the start time. The start time infers what timezone it is in.
* The DateRange component for a DateBlockTiming. The start date is a DateBlockTimingStart.
*/
export type DateBlockTimingStart = DateRangeStart;

/**
* Only the start and end of the date block range.
*/
export type DateBlockTimingStartEndRange = Pick<DateBlockTiming, 'start' | 'end'>;
export type DateBlockTimingStartEndRange = DateBlockTimingStart & Pick<DateBlockTiming, 'end'>;

/**
* The start date of a DateBlockTimingStart, along with the endDay which is a normalized day that is at midnight of the last day in the timezone.
Expand Down Expand Up @@ -413,6 +415,27 @@ export function changeTimingToTimezone<T extends DateRangeStart>(timing: T, time
return changeTimingToTimezoneFunction(timezone)(timing);
}

export function changeTimingToSystemTimezone<T extends DateRangeStart>(timing: T): T {
return changeTimingToTimezoneFunction(SYSTEM_DATE_TIMEZONE_UTC_NORMAL_INSTANCE)(timing);
}

export function dateBlockTimingStartForNowInSystemTimezone(): DateBlockTimingStart {
return {
start: startOfDay(new Date())
};
}

/**
* Creates a DateBlockTimingStart for now in the given timezone.
*
* @param timezoneInput
* @returns
*/
export function dateBlockTimingStartForNowInTimezone(timezoneInput: TimingDateTimezoneUtcNormalInput): DateBlockTimingStart {
const dateBlockTimingStartSystemTimezone = dateBlockTimingStartForNowInSystemTimezone();
return changeTimingToTimezone(dateBlockTimingStartSystemTimezone, timezoneInput);
}

/**
* Returns the startsAt date in the current/system timezone for the given date.
*
Expand Down
10 changes: 1 addition & 9 deletions packages/date/src/lib/date/date.schedule.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,11 @@ describe('dateScheduleDateFilter()', () => {
const schedule: DateScheduleDateFilterConfig = { start, w: '89' };
const weekDaysAndWeekends = dateScheduleDateFilter(schedule);

it('should allow every day of the week (indexes)', () => {
const maxIndex = 14;
const dateBlocks: DateBlockIndex[] = range(0, maxIndex);
const results = dateBlocks.filter(weekDaysAndWeekends);

expect(results.length).toBe(maxIndex);
});

it('should allow every day of the week (dates)', () => {
const maxIndex = 14;
const dateBlocks: Date[] = range(0, maxIndex).map((y) => addDays(start, y));
const results = dateBlocks.filter(weekDaysAndWeekends);

const results = dateBlocks.filter(weekDaysAndWeekends);
expect(results.length).toBe(maxIndex);
});

Expand Down
39 changes: 34 additions & 5 deletions packages/date/src/lib/date/date.schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,30 @@ import { Expose } from 'class-transformer';
import { IsString, Matches, IsOptional, Min, IsArray } from 'class-validator';
import { getDay } from 'date-fns';
import { copyHoursAndMinutesFromDate } from './date';
import { DateBlock, dateBlockDayOfWeekFactory, DateBlockDurationSpan, DateBlockIndex, dateBlockIndexRange, DateBlockRange, DateBlockRangeOrDateRange, DateBlockRangeWithRange, DateBlocksExpansionFactory, dateBlocksExpansionFactory, dateBlockTiming, DateBlockTiming, DateBlockTimingStartEndRange, dateTimingRelativeIndexFactory, DateTimingRelativeIndexFactoryInput, getCurrentDateBlockTimingStartDate, groupToDateBlockRanges, safeDateBlockTimingFromDateRangeAndEvent } from './date.block';
import {
changeTimingToSystemTimezone,
changeTimingToTimezone,
DateBlock,
dateBlockDayOfWeekFactory,
DateBlockDurationSpan,
DateBlockIndex,
dateBlockIndexRange,
DateBlockRange,
DateBlockRangeOrDateRange,
DateBlockRangeWithRange,
DateBlocksExpansionFactory,
dateBlocksExpansionFactory,
dateBlockTiming,
DateBlockTiming,
DateBlockTimingStartEndRange,
dateBlockTimingStartForNowInSystemTimezone,
dateBlockTimingStartForNowInTimezone,
dateTimingRelativeIndexFactory,
DateTimingRelativeIndexFactoryInput,
getCurrentDateBlockTimingStartDate,
groupToDateBlockRanges,
safeDateBlockTimingFromDateRangeAndEvent
} from './date.block';
import { dateBlockDurationSpanHasNotStartedFilterFunction, dateBlockDurationSpanHasNotEndedFilterFunction } from './date.filter';
import { DateRange, isSameDateRange } from './date.range';
import { copyHoursAndMinutesFromDatesWithTimezoneNormal } from './date.timezone';
Expand Down Expand Up @@ -389,8 +412,10 @@ export type DateScheduleDateFilter = DecisionFunction<DateScheduleDateFilterInpu

/**
* dateScheduleDateFilter() configuration.
*
* The input date range is a DateBlockTimingStartEndRange, where the start date is expected to be a DateBlockTimingStart.
*/
export interface DateScheduleDateFilterConfig extends DateSchedule, Partial<DateRange> {
export interface DateScheduleDateFilterConfig extends DateSchedule, Partial<DateBlockTimingStartEndRange> {
minMaxDateRange?: Maybe<Partial<DateBlockRangeOrDateRange>>;
/**
* Whether or not to restrict the start as the min date if a min date is not set in minMaxDateRange. True by default.
Expand All @@ -415,14 +440,17 @@ export function copyDateScheduleDateFilterConfig(inputFilter: DateScheduleDateFi
* @returns
*/
export function dateScheduleDateFilter(config: DateScheduleDateFilterConfig): DateScheduleDateFilter {
const { w, start: firstDate = new Date(), setStartAsMinDate = true, end, minMaxDateRange } = config;
const { w, start: inputStart, setStartAsMinDate = true, end, minMaxDateRange } = config;
const timingStart = inputStart != null ? changeTimingToSystemTimezone({ start: inputStart }) : dateBlockTimingStartForNowInSystemTimezone();
const { start: firstDate } = timingStart;

const allowedDays: Set<DayOfWeek> = expandDateScheduleDayCodesToDayOfWeekSet(w);

// Start date is either now or the filter's start date. It is never the minMax's start date, since that is irrelevant to the filter's range.

const firstDateDay = getDay(firstDate);
const dayForIndex = dateBlockDayOfWeekFactory(firstDateDay);
const dateIndexForDate = dateTimingRelativeIndexFactory({ start: firstDate });
const dateIndexForDate = dateTimingRelativeIndexFactory(timingStart);

const indexFloor = setStartAsMinDate ? 0 : Number.MIN_SAFE_INTEGER;
const minAllowedIndex = minMaxDateRange?.start != null ? Math.max(indexFloor, dateIndexForDate(minMaxDateRange.start)) : indexFloor; // start date should be the min inde
Expand All @@ -442,7 +470,8 @@ export function dateScheduleDateFilter(config: DateScheduleDateFilterConfig): Da
}

day = dayForIndex(i);
return (i >= minAllowedIndex && i <= maxAllowedIndex && allowedDays.has(day) && !excludedIndexes.has(i)) || includedIndexes.has(i);
const result = (i >= minAllowedIndex && i <= maxAllowedIndex && allowedDays.has(day) && !excludedIndexes.has(i)) || includedIndexes.has(i);
return result;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export interface CalendarScheduleSelectionState extends PartialCalendarScheduleS
/**
* Filters the days of the schedule to only allow selecting days in the schedule.
*
* If filter.start is provided, then the timezone is ignored, if one is present.
* If filter.start is provided, then the timezone in this normal is ignored, if one is present. Convert to the system timezone first.
*/
filter?: Maybe<DateScheduleDateFilterConfig>;
/**
Expand All @@ -99,7 +99,7 @@ export interface CalendarScheduleSelectionState extends PartialCalendarScheduleS
*/
start: Date;
/**
* Timezone to use. OnlyInfluences the output start date.
* Timezone to use. Only influences the output start date.
*/
timezone?: Maybe<TimezoneString>;
/**
Expand Down

0 comments on commit 85bf021

Please sign in to comment.