Skip to content

Commit

Permalink
feat: added IsDateWithinDateBlockRangeFunction
Browse files Browse the repository at this point in the history
- added IsDateWithinDateBlockRangeFunction
- added isSameDateDay()
- added toggleInSet()
- renamed symmetricDifferenceKeys to symmetricDifferenceArray
  • Loading branch information
dereekb committed Dec 5, 2022
1 parent 168c8b9 commit 994c6b1
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 17 deletions.
135 changes: 124 additions & 11 deletions packages/date/src/lib/date/date.block.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { DayOfWeek, RequiredOnKeys, IndexNumber, IndexRange, indexRangeCheckFunction, IndexRef, MINUTES_IN_DAY, MS_IN_DAY, UniqueModel, lastValue, FactoryWithRequiredInput, FilterFunction, mergeFilterFunctions, range, Milliseconds, Hours, MapFunction, getNextDay, SortCompareFunction, sortAscendingIndexNumberRefFunction, mergeArrayIntoArray, Configurable, ArrayOrValue, asArray, sumOfIntegersBetween, filterMaybeValues, Maybe } from '@dereekb/util';
import { dateRange, DateRange, DateRangeDayDistanceInput, DateRangeStart, DateRangeType, isDateRange } from './date.range';
import { dateRange, DateRange, DateRangeDayDistanceInput, DateRangeStart, DateRangeType, isDateRange, isDateRangeStart } from './date.range';
import { DateDurationSpan } from './date.duration';
import { differenceInDays, differenceInMilliseconds, isBefore, addDays, addMinutes, getSeconds, getMilliseconds, getMinutes, addMilliseconds, hoursToMilliseconds, addHours, differenceInHours, isAfter } from 'date-fns';
import { copyHoursAndMinutesFromDate, roundDownToMinute } from './date';
import { isDate, copyHoursAndMinutesFromDate, roundDownToMinute } from './date';
import { Expose, Type } from 'class-transformer';
import { getCurrentSystemOffsetInHours } from './date.timezone';
import { IsDate, IsNumber, IsOptional, Min } from 'class-validator';
Expand Down Expand Up @@ -561,18 +561,24 @@ export function dateBlockIndexRange(timing: DateBlockTiming, limit?: DateBlockTi
* @returns
*/
export function dateBlocksInDateBlockRange<T extends DateBlock | DateBlockRange>(blocks: T[], range: DateBlockRangeWithRange): T[] {
const dateBlockIsWithinDateBlockRange = dateBlockIsWithinDateBlockRangeFunction(range);
const dateBlockIsWithinDateBlockRange = isDateBlockWithinDateBlockRangeFunction(range);
return blocks.filter(dateBlockIsWithinDateBlockRange);
}

export type IsDateBlockWithinDateBlockRangeInput = DateBlockIndex | DateBlock | DateBlockRange;

/**
* Function that returns true if the input range is equal or falls within the configured DateBlockRange.
*/
export type DateBlockIsWithinDateBlockRangeFunction = (input: DateBlock | DateBlockRange) => boolean;
export type IsDateBlockWithinDateBlockRangeFunction = (input: IsDateBlockWithinDateBlockRangeInput) => boolean;

export function dateBlockIsWithinDateBlockRangeFunction(inputRange: DateBlock | DateBlockRange): DateBlockIsWithinDateBlockRangeFunction {
export function isDateBlockWithinDateBlockRangeFunction(inputRange: IsDateBlockWithinDateBlockRangeInput): IsDateBlockWithinDateBlockRangeFunction {
const range = dateBlockRangeWithRange(inputRange);
return (input: DateBlock | DateBlockRange) => {
return (input: IsDateBlockWithinDateBlockRangeInput) => {
if (typeof input === 'number') {
input = { i: input };
}

if (input.i >= range.i) {
const to = (input as DateBlockRange).to ?? input.i;
return to <= range.to;
Expand All @@ -589,8 +595,94 @@ export function dateBlockIsWithinDateBlockRangeFunction(inputRange: DateBlock |
* @param isContainedWithin
* @returns
*/
export function dateBlockRangeContainsDateBlock(range: DateBlock | DateBlockRange, contains: DateBlock | DateBlockRange) {
return dateBlockIsWithinDateBlockRangeFunction(range)(dateBlockRangeWithRange(contains));
export function isDateBlockWithinDateBlockRange(range: IsDateBlockWithinDateBlockRangeInput, contains: IsDateBlockWithinDateBlockRangeInput) {
return isDateBlockWithinDateBlockRangeFunction(range)(dateBlockRangeWithRange(contains));
}

/**
* Input for a IsDateWithinDateBlockRangeFunction
*/
export type IsDateWithinDateBlockRangeInput = DateOrDateBlockIndex | DateRangeStart | DateRange | DateBlock | DateBlockRange;

/**
* Function that returns true if the input range is equal or falls within the configured DateBlockRange.
*/
export type IsDateWithinDateBlockRangeFunction = (input: IsDateWithinDateBlockRangeInput) => boolean;

export interface IsDateWithinDateBlockRangeConfig {
/**
* Optional date to make the indexes relative to when converting date values.
*
* If not provided, defaults to the index in the range if a date is provided, or throws an exception if a date range is input.
*/
start?: Date;
/**
* Range to compare the input to.
*/
range: IsDateWithinDateBlockRangeInput;
}

export function isDateWithinDateBlockRangeFunction(config: IsDateWithinDateBlockRangeConfig): IsDateWithinDateBlockRangeFunction {
const { start: inputStart, range: inputRange } = config;
let start: Date | undefined = inputStart;

let dateRange: (DateRangeStart & Partial<DateRange>) | undefined;
let rangeInput: DateBlock | DateBlockRange | undefined;

if (typeof inputRange === 'number') {
rangeInput = { i: inputRange };
} else if (isDate(inputRange)) {
dateRange = { start: inputRange };
} else if (isDateRangeStart(inputRange)) {
dateRange = inputRange;
} else {
rangeInput = inputRange as DateBlock | DateBlockRange;
}

if (start == null) {
if (dateRange) {
start = inputRange as Date;
} else {
throw new Error('Invalid isDateWithinDateBlockRangeFunction() config. Start date could not be determined from input.');
}
}

const indexFactory = dateTimingRelativeIndexFactory({ start });

function convertDateRangeToIndexRange(range: DateRangeStart & Partial<DateRange>) {
const i = indexFactory(range.start);
const end: Maybe<Date> = (range as DateRange).end;
const to: Maybe<number> = end != null ? indexFactory(end) : undefined;
return { i, to };
}

if (!rangeInput) {
if (dateRange) {
rangeInput = convertDateRangeToIndexRange(dateRange);
} else {
throw new Error('Invalid isDateWithinDateBlockRangeFunction() config. Range determined from input.'); // shouldn't occur
}
}

const isDateBlockWithinDateBlockRange = isDateBlockWithinDateBlockRangeFunction(rangeInput);

return (input: IsDateWithinDateBlockRangeInput) => {
let range: DateBlockIndex | DateBlock | DateBlockRange;

if (isDate(input)) {
range = indexFactory(input);
} else if (isDateRangeStart(input)) {
range = convertDateRangeToIndexRange(input);
} else {
range = input;
}

if (typeof input === 'number') {
range = { i: input };
}

return isDateBlockWithinDateBlockRange(range);
};
}

// MARK: DateBlockRange
Expand Down Expand Up @@ -632,8 +724,12 @@ export function dateBlockRange(i: number, to?: number): DateBlockRangeWithRange
return { i, to: to ?? i };
}

export function dateBlockRangeWithRange(inputDateBlockRange: DateBlockRange): DateBlockRangeWithRange {
return dateBlockRange(inputDateBlockRange.i, inputDateBlockRange.to);
export function dateBlockRangeWithRange(inputDateBlockRange: DateBlockIndex | DateBlock | DateBlockRange): DateBlockRangeWithRange {
if (typeof inputDateBlockRange === 'number') {
inputDateBlockRange = { i: inputDateBlockRange };
}

return dateBlockRange(inputDateBlockRange.i, (inputDateBlockRange as DateBlockRange).to);
}

/**
Expand Down Expand Up @@ -1206,7 +1302,7 @@ export type ModifyDateBlocksToFitRangeFunction = <B extends DateBlock | DateBloc
*/
export function modifyDateBlocksToFitRangeFunction(range: DateBlockRange): ModifyDateBlocksToFitRangeFunction {
const { i, to } = dateBlockRangeWithRange(range);
const dateBlockIsWithinDateBlockRange = dateBlockIsWithinDateBlockRangeFunction(range);
const dateBlockIsWithinDateBlockRange = isDateBlockWithinDateBlockRangeFunction(range);
return <B extends DateBlock | DateBlockRange | UniqueDateBlock>(input: B[]) =>
filterMaybeValues(
input.map((x) => {
Expand Down Expand Up @@ -1241,3 +1337,20 @@ export function modifyDateBlocksToFitRange<B extends DateBlock | DateBlockRange
export function modifyDateBlockToFitRange<B extends DateBlock | DateBlockRange | UniqueDateBlock>(range: DateBlockRange, input: B): Maybe<B> {
return modifyDateBlocksToFitRange(range, [input])[0];
}

// MARK: Compat

/**
* @deprecated use IsDateBlockWithinDateBlockRangeFunction instead.
*/
export type DateBlockIsWithinDateBlockRangeFunction = IsDateBlockWithinDateBlockRangeFunction;

/**
* @deprecated use isDateBlockWithinDateBlockRangeFunction() instead.
*/
export const dateBlockIsWithinDateBlockRangeFunction = isDateBlockWithinDateBlockRangeFunction;

/**
* @deprecated use isDateBlockWithinDateBlockRange() instead.
*/
export const dateBlockRangeContainsDateBlock = isDateBlockWithinDateBlockRange;
10 changes: 10 additions & 0 deletions packages/date/src/lib/date/date.range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ export interface DateRangeStart {
start: Date;
}

/**
* Returns true if the input is a DateRangeStart.
*
* @param value
* @returns
*/
export function isDateRangeStart(value: unknown): value is DateRangeStart {
return typeof value === 'object' && isDate((value as DateRangeStart).start);
}

/**
* Sorts the input DateRangeStart values in ascending order by start Date.
*
Expand Down
17 changes: 16 additions & 1 deletion packages/date/src/lib/date/date.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isDate as dateFnsIsDate, max as maxDate, min as minDate, parseISO, addDays, isPast, isAfter as isAfterDate, set as setDateValues, isValid, startOfMinute, isEqual as isEqualDate } from 'date-fns';
import { isDate as dateFnsIsDate, max as maxDate, min as minDate, parseISO, addDays, isPast, isAfter as isAfterDate, set as setDateValues, isValid, startOfMinute, isEqual as isEqualDate, isSameDay as isEqualDay } from 'date-fns';
import { DateOrDateString, filterMaybeValues, ISO8601DateString, Maybe, Minutes, MINUTES_IN_DAY, MS_IN_HOUR, MS_IN_MINUTE, Seconds, TimezoneString, ArrayOrValue, asArray } from '@dereekb/util';

export const MAX_FUTURE_DATE = new Date(Date.UTC(9999, 0));
Expand Down Expand Up @@ -107,6 +107,21 @@ export function isSameDate(a: Maybe<Date>, b: Maybe<Date>, defaultValue: Maybe<b
return a != null && b != null ? isEqualDate(a, b) : defaultValue != null ? defaultValue : a == b;
}

/**
* Returns true if both a and b are defined and a is on the same day/month/year as b, otherwise returns the default value.
*
* The default value is true if a and b are null/undefined.
*
* @param a
* @param b
*/
export function isSameDateDay(a: Maybe<Date>, b: Maybe<Date>): boolean;
export function isSameDateDay(a: Maybe<Date>, b: Maybe<Date>, defaultValue: boolean): boolean;
export function isSameDateDay(a: Maybe<Date>, b: Maybe<Date>, defaultValue: Maybe<boolean>): Maybe<boolean>;
export function isSameDateDay(a: Maybe<Date>, b: Maybe<Date>, defaultValue: Maybe<boolean> = null): Maybe<boolean> {
return a != null && b != null ? isEqualDay(a, b) : defaultValue != null ? defaultValue : a == b;
}

// MARK: Unix Date/Time
/**
* Converts the input date to represent the "day" represented in the time.
Expand Down
4 changes: 2 additions & 2 deletions packages/util/src/lib/model/model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { symmetricDifferenceKeys } from '../set/set';
import { symmetricDifferenceArray } from '../set/set';
import { findUnique } from '../array/array.unique';
import { ReadKeyFunction, ReadMultipleKeysFunction } from '../key';
import { Maybe } from '../value/maybe.type';
Expand Down Expand Up @@ -95,7 +95,7 @@ export function readModelKeysFromObjects<T extends UniqueModel>(input: T[], requ
}

export function symmetricDifferenceWithModels<T extends UniqueModel>(a: ModelOrKey<T>[], b: ModelOrKey<T>[], required?: boolean, read?: ReadModelKeyFunction<T>): Maybe<ModelKey>[] {
return symmetricDifferenceKeys(readModelKeys(a, required, read), readModelKeys(b, required, read));
return symmetricDifferenceArray(readModelKeys(a, required, read), readModelKeys(b, required, read));
}

// export function removeModelsWithSameKey<T extends UniqueModel>(input: T[], key: ModelKey, read?: ReadModelKeyFunction<T>): T[];
Expand Down
35 changes: 32 additions & 3 deletions packages/util/src/lib/set/set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,20 @@ export function addToSet<T>(set: Set<T>, values: Maybe<IterableOrValue<T>>) {
useIterableOrValue(values, (x) => set.add(x));
}

export function toggleInSetCopy<T>(set: Set<T>, values: Maybe<IterableOrValue<T>>): Set<T> {
return copySetAndDo(set, (x) => toggleInSet(x, values));
}

export function toggleInSet<T>(set: Set<T>, values: Maybe<IterableOrValue<T>>) {
useIterableOrValue(values, (x) => {
if (set.has(x)) {
set.delete(x);
} else {
set.add(x);
}
});
}

export function removeFromSetCopy<T>(set: Set<T>, values: Maybe<IterableOrValue<T>>): Set<T> {
return copySetAndDo(set, (x) => removeFromSet(x, values));
}
Expand All @@ -44,11 +58,11 @@ export function hasDifferentValues<T>(a: Maybe<Iterable<T>>, b: Maybe<Iterable<T
return a == null || b == null || !setContainsAllValues(setA, b) || setA.size !== new Set(b).size;
}

export function symmetricDifferenceKeys<T>(a: Maybe<Iterable<T>>, b: Maybe<Iterable<T>>): Maybe<T>[] {
return symmetricDifferenceKeysSet(new Set(a), new Set(b));
export function symmetricDifferenceArray<T>(a: Maybe<Iterable<T>>, b: Maybe<Iterable<T>>): Maybe<T>[] {
return symmetricDifferenceArrayBetweenSets(new Set(a), new Set(b));
}

export function symmetricDifferenceKeysSet<T>(a: Set<Maybe<T>>, b: Set<Maybe<T>>): Maybe<T>[] {
export function symmetricDifferenceArrayBetweenSets<T>(a: Set<Maybe<T>>, b: Set<Maybe<T>>): Maybe<T>[] {
return Array.from(symmetricDifference(a, b));
}

Expand All @@ -60,6 +74,10 @@ export function keepValuesFromSet<T>(values: T[], set: Set<T>): T[] {
return filterValuesFromSet(values, set, false);
}

export function excludeValues<T>(valuesToExclude: T[], iterable: Maybe<Iterable<T>>): T[] {
return excludeValuesFromSet(valuesToExclude, new Set(iterable));
}

export function excludeValuesFromSet<T>(values: T[], set: Set<T>): T[] {
return filterValuesFromSet(values, set, true);
}
Expand Down Expand Up @@ -255,3 +273,14 @@ export function containsAllValues<T>(values: Iterable<T>, valuesToFind: Iterable
export function setContainsAllValues<T>(valuesSet: Set<T>, valuesToFind: IterableOrValue<T>): boolean {
return valuesSet ? Array.from(asIterable(valuesToFind)).findIndex((x) => !valuesSet.has(x)) == -1 : false;
}

// MARK: Compat
/**
* @deprecated use symmetricDifferenceArray
*/
export const symmetricDifferenceKeys = symmetricDifferenceArray;

/**
* @deprecated use symmetricDifferenceArrayBetweenSets
*/
export const symmetricDifferenceKeysSet = symmetricDifferenceArrayBetweenSets;

0 comments on commit 994c6b1

Please sign in to comment.