Skip to content

Commit

Permalink
Release 0.10.0
Browse files Browse the repository at this point in the history
- Resolves #9
- Resolves #9
- Resolves #9
  • Loading branch information
ClickerMonkey committed Jun 27, 2018
1 parent 941b2e1 commit 2d26602
Show file tree
Hide file tree
Showing 14 changed files with 638 additions and 47 deletions.
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "dayspan",
"version": "0.9.0",
"version": "0.10.0",
"description": "A date & schedule library to use for advanced calendars in TypeScript and JS",
"main": "umd/dayspan.js",
"mainName": "ds",
Expand Down
17 changes: 17 additions & 0 deletions src/Functions.ts
Expand Up @@ -163,6 +163,23 @@ export class Functions
return this.isDefined( a ) ? a : (this.isDefined( b ) ? b : c);
}

/**
* Copies values from `from` object and sets them to the `target` object.
*
* @param target The object to set values to.
* @param from The object to copy value references from.
* @returns The reference to `target`.
*/
public static extend(target: any, from: any): any
{
for (let prop in from)
{
target[ prop ] = from[ prop ];
}

return target;
}

/**
* Pads the string `x` up to `length` characters with the given `padding`
* optionally placing the `padding` `before` `x`.
Expand Down
5 changes: 2 additions & 3 deletions src/Iterator.ts
Expand Up @@ -14,8 +14,7 @@ export type IteratorCallback<T, R> = (item: T, iterator: Iterator<T>) => R;

/**
* An [[Iterator]] source which handles iterating over items and calls
* `callback` for each item, checking [[Iterator.iterating]] after each
* invokation to stop iteration as early as possible.
* [[Iterator.act]] for each item, taking the requested action when possible.
*
* @param callback The function to invoke for each item.
* @param iterator The iterator to check for early exists.
Expand Down Expand Up @@ -108,7 +107,7 @@ export class Iterator<T>
/**
* A result of the iteration passed to [[Iterator.stop]].
*/
public result: any = undefined;
public result: any = null;

/**
* The last action (if any) called on this iterator.
Expand Down
2 changes: 1 addition & 1 deletion src/Parse.ts
Expand Up @@ -56,7 +56,7 @@ export class Parse
check.given = true;
}

check.input = input;
check.input = fn.coalesce( input, null );
check.property = property;

return check;
Expand Down
90 changes: 71 additions & 19 deletions src/Pattern.ts
Expand Up @@ -3,8 +3,8 @@ import { Functions as fn } from './Functions';
import { Day, DayProperty } from './Day';
import { Suffix } from './Suffix';
import { Weekday } from './Weekday';
import { FrequencyValueEvery } from './Frequency';
import { ScheduleInput } from './Schedule';
import { FrequencyValueEvery, FrequencyValue } from './Frequency';
import { Schedule, ScheduleInput } from './Schedule';


/**
Expand Down Expand Up @@ -120,15 +120,47 @@ export class Pattern
}

/**
* Applies this pattern to schedule input removing and adding any necessary
* properties from the input to match this pattern - based around the day
* provided.
* Applies this pattern to a [[Schedule]] or [[ScheduleInput]] removing and
* adding any necessary properties from the input to match this pattern -
* based around the day provided.
*
* @param input The input to update to match this pattern.
* @param schedule The schedule to update to match this pattern.
* @param day The day to base the schedule on.
* @returns The reference to the input passed in.
*/
public apply<M>(input: ScheduleInput<M>, day: Day): ScheduleInput<M>
public apply<M, I extends ScheduleInput<M> | Schedule<M>>(schedule: I, day: Day): I
{
if (schedule instanceof Schedule)
{
this.applyGeneric(day,
(prop, frequency) => schedule.setFrequency( prop, frequency ),
(prop) => schedule.setFrequency( prop )
);

schedule.updateChecks();
}
else
{
this.applyGeneric(day,
(prop, frequency) => schedule[ prop ] = frequency,
(prop) => delete schedule[ prop ]
);
}

return schedule;
}

/**
* Applies this pattern to any object provided they implement the
* `setFrequency` and `removeFrequency` functions.
*
* @param day The day to base the schedule on.
* @param setFrequency The function which sets the frequency on the object.
* @param removeFrequency The function to remove a frequency from the object.
*/
public applyGeneric(day: Day,
setFrequency: (property: DayProperty, frequency: any) => any,
removeFrequency: (property: DayProperty) => any): void
{
for (let prop of Pattern.PROPS)
{
Expand All @@ -137,23 +169,43 @@ export class Pattern
// Should have one value
if (rule === 1)
{
input[ prop ] = [day[ prop ]];
setFrequency( prop, [day[ prop ]] );
}

// Can be any of the values in the array
if (fn.isArray(rule))
{
input[ prop ] = rule;
setFrequency( prop, rule );
}

// Must not be present
if (!fn.isDefined(rule))
{
delete input[ prop ];
removeFrequency( prop );
}
}
}

return input;
/**
* Determines whether the given [[Schedule]] or [[ScheduleInput]] matches this
* pattern. Optionally a day can be provided to make sure the day matches the
* schedule and pattern together.
*
* @param schedule The schedule input to test.
* @param exactlyWith A day to further validate against for matching.
* @returns `true` if the schedule was a match to this pattern with the
* day if one was provided, otherwise `false`.
*/
public isMatch<M, I extends ScheduleInput<M> | Schedule<M>>(schedule: I, exactlyWith?: Day): boolean
{
if (schedule instanceof Schedule)
{
return this.isMatchGeneric((prop) => schedule[ prop ].input, exactlyWith);
}
else
{
return this.isMatchGeneric((prop) => schedule[ prop ], exactlyWith);
}
}

/**
Expand All @@ -166,14 +218,14 @@ export class Pattern
* @returns `true` if the schedule input was a match to this pattern with the
* day if one was provided, otherwise `false`.
*/
public isMatch<M>(input: ScheduleInput<M>, exactlyWith?: Day): boolean
public isMatchGeneric(getFrequency: (property: DayProperty) => FrequencyValue, exactlyWith?: Day): boolean
{
let exactly: boolean = fn.isDefined( exactlyWith );

for (let prop of Pattern.PROPS)
{
let rule = this.rules[ prop ];
let curr = input[ prop ];
let curr = getFrequency( prop );

// Optional, skip it
if (rule === false)
Expand All @@ -196,9 +248,9 @@ export class Pattern
// Must be an array of the same size
if (fn.isNumber(rule))
{
if (fn.isArray(curr) && curr.length === rule)
if (fn.isArray(curr) && (<number[]>curr).length === rule)
{
if (exactly && curr.indexOf( exactlyWith[ prop ] ) === -1)
if (exactly && (<number[]>curr).indexOf( <number>exactlyWith[ prop ] ) === -1)
{
return false;
}
Expand All @@ -217,7 +269,7 @@ export class Pattern
return false;
}

if (rule.length !== curr.length)
if (rule.length !== (<number[]>curr).length)
{
return false;
}
Expand Down Expand Up @@ -245,7 +297,7 @@ export class Pattern
}

var ruleOffset = rule.offset || 0;
var currOffset = curr.offset || 0;
var currOffset = (<FrequencyValueEvery>curr).offset || 0;

if (currOffset !== ruleOffset || curr.every !== rule.every)
{
Expand Down Expand Up @@ -284,11 +336,11 @@ export class Pattern
* @param exactlyWith A day to further validate against for matching.
* @see [[Pattern.isMatch]]
*/
public static findMatch<M>(input: ScheduleInput<M>, listedOnly: boolean = true, exactlyWith?: Day): Pattern
public static findMatch<M, I extends ScheduleInput<M> | Schedule<M>>(input: I, listedOnly: boolean = true, exactlyWith?: Day): Pattern
{
for (let pattern of Patterns)
{
if ((pattern.listed || !listedOnly) && pattern.isMatch( input, exactlyWith ))
if ((pattern.listed || !listedOnly) && pattern.isMatch<M, I>( input, exactlyWith ))
{
return pattern;
}
Expand Down
118 changes: 115 additions & 3 deletions src/Schedule.ts
Expand Up @@ -562,6 +562,70 @@ export class Schedule<M>
return this.times.length === 0;
}

/**
* Sets whether this schedule is a full day event if it is not already. If
* this schedule is a full day event and `false` is passed to this function
* a single timed event will be added based on `defaultTime`. If this schedule
* has timed events and `true` is passed to make the schedule full day, the
* timed events are removed from this schedule. If the durationUnit is not the
* expected unit based on the new full day flag - the duration is reset to 1
* and the duration unit is set to the expected unit.
*
* @param fullDay Whether this schedule should represent a full day event or
* timed events.
* @param defaultTime If `fullDay` is `false` and this schedule is currently
* a full day event - this time will be used as the time of the first event.
*/
public setFullDay(fullDay: boolean = true, defaultTime: TimeInput = '08:00'): this
{
if (fullDay !== this.isFullDay())
{
if (fullDay)
{
this.times = [];

if (this.durationUnit !== 'days' && this.durationUnit !== 'day')
{
this.duration = 1;
this.durationUnit = 'days';
}
}
else
{
this.times = [Parse.time( defaultTime )];

if (this.durationUnit !== 'hours' && this.durationUnit !== 'hour')
{
this.duration = 1;
this.durationUnit = 'hours';
}
}
}

return this;
}

/**
* Adjusts the [[Schedule.start]] and [[Schedule.end]] dates specified on this
* schedule if this schedule represents a single event and the `start` and
* `end` are already set or `addSpan` is `true`.
*
* @param addSpan If `true`, the `start` and `end` dates will always be
* adjusted if this schedule is a single event.
*/
public adjustDefinedSpan(addSpan: boolean = false): this
{
let single: DaySpan = this.getSingleEventSpan();

if (single && (addSpan || (this.start && this.end)))
{
this.start = single.start.start();
this.end = single.end.end();
}

return this;
}

/**
* Returns a span of time for a schedule with full day events starting on the
* start of the given day with the desired duration in days or weeks.
Expand Down Expand Up @@ -918,6 +982,21 @@ export class Schedule<M>
return !!this.iterateSpans( day, true ).first( span => span.contains( day ) );
}

/**
* Sets the frequency for the given property. This does not update the
* [[Schedule.checks]] array, the [[Schedule.updateChecks]] function needs
* to be called.
*
* @param property The frequency to update.
* @param frequency The new frequency.
*/
public setFrequency(property: DayProperty, frequency?: FrequencyValue): this
{
this[ property ] = Parse.frequency( frequency, property );

return this;
}

/**
* Changes the exclusion status of the event at the given time. By default
* this excludes this event - but `false` may be passed to undo an exclusion.
Expand Down Expand Up @@ -972,6 +1051,40 @@ export class Schedule<M>
return false;
}

/**
* Moves a time specified in this schedule to the given time, adjusting
* any cancelled event instances, metadata, and any excluded and included
* event instances.
*
* @param fromTime The time to move.
* @param toTime The new time in the schedule.
* @returns `true` if time was moved, otherwise `false`.
*/
public moveTime(fromTime: Time, toTime: Time): boolean
{
let found: boolean = false;

for (let i = 0; i < this.times.length && !found; i++)
{
if (found = fromTime.matches( this.times[ i ] ))
{
this.times.splice( i, 1, toTime );
}
}

if (found)
{
this.include.moveTime( fromTime, toTime );
this.exclude.moveTime( fromTime, toTime );
this.cancel.moveTime( fromTime, toTime );
this.meta.moveTime( fromTime, toTime );

this.adjustDefinedSpan( false );
}

return found;
}

/**
* Moves the event instance starting at `fromTime` to `toTime` optionally
* placing `meta` in the schedules metadata for the new time `toTime`. A move
Expand Down Expand Up @@ -1033,7 +1146,7 @@ export class Schedule<M>

if (this.times.length === 1 && takeTime)
{
this.times[ 0 ] = toTime.asTime();
this.times = [toTime.asTime()];
}

this.updateChecks();
Expand Down Expand Up @@ -1364,7 +1477,6 @@ export class Schedule<M>
* @param alwaysDuration If the duration values (`duration` and
* `durationUnit`) should always be returned in the input.
* @returns The input that describes this schedule.
* @see [[Schedule.getExclusions]]
* @see [[Time.format]]
*/
public toInput(returnDays: boolean = false, returnTimes: boolean = false, timeFormat: string = '', alwaysDuration: boolean = false): ScheduleInput<M>
Expand All @@ -1390,7 +1502,7 @@ export class Schedule<M>
if (exclusions.length) out.exclude = exclusions;
if (inclusions.length) out.include = inclusions;
if (cancels.length) out.cancel = cancels;
if (hasMeta) out.meta = this.meta.map;
if (hasMeta) out.meta = fn.extend( {}, this.meta.map );
if (this.dayOfWeek.input) out.dayOfWeek = this.dayOfWeek.input;
if (this.dayOfMonth.input) out.dayOfMonth = this.dayOfMonth.input;
if (this.lastDayOfMonth.input) out.lastDayOfMonth = this.lastDayOfMonth.input;
Expand Down

0 comments on commit 2d26602

Please sign in to comment.