Skip to content

Commit

Permalink
feat(core): add outsideOfficeHours
Browse files Browse the repository at this point in the history
  • Loading branch information
JamieMason committed Mar 11, 2019
1 parent 16bf961 commit e4900d5
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 13 deletions.
37 changes: 35 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ npm install --save is-office-hours

## :memo: Usage

Always `false` on Saturday and Sunday, `true` from 9:00am until 4:59pm weekdays.
Does not take into account public holidays.
### isOfficeHours

Returns `true` if the provided `Date` falls within Monday to Friday 9:00am to
4:59pm.

```js
import { isOfficeHours } from 'is-office-hours';

isOfficeHours(new Date('2019-03-04T08:59:59.000Z'));
// false
isOfficeHours(new Date('2019-03-04T09:00:00.000Z'));
Expand All @@ -39,3 +43,32 @@ isOfficeHours(new Date('2019-03-04T17:00:00.000Z'));
isOfficeHours(new Date('2019-03-04T17:01:00.000Z'));
// false
```

### outsideOfficeHours

If the provided date falls within Office Hours, a new date is returned with the
time adjusted to fall outside Office Hours. If the provided date falls outside
Office Hours, it is returned unchanged.

The provided date is never mutated.

```js
import { outsideOfficeHours } from 'is-office-hours';

outsideOfficeHours(new Date('2019-03-04T09:00:00.000Z'));
// Date(2019-03-04T18:00:00.000Z)
outsideOfficeHours(new Date('2019-03-04T10:00:00.000Z'));
// Date(2019-03-04T18:37:29.000Z)
outsideOfficeHours(new Date('2019-03-04T11:00:00.000Z'));
// Date(2019-03-04T19:14:59.000Z)
outsideOfficeHours(new Date('2019-03-04T12:00:00.000Z'));
// Date(2019-03-04T19:52:29.000Z)
outsideOfficeHours(new Date('2019-03-04T13:00:00.000Z'));
// Date(2019-03-04T20:29:59.000Z)
outsideOfficeHours(new Date('2019-03-04T14:00:00.000Z'));
// Date(2019-03-04T21:07:29.000Z)
outsideOfficeHours(new Date('2019-03-04T15:00:00.000Z'));
// Date(2019-03-04T21:44:59.000Z)
outsideOfficeHours(new Date('2019-03-04T16:00:00.000Z'));
// Date(2019-03-04T22:22:29.000Z)
```
6 changes: 2 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
const isWorkingDay = (date: Date) => date.getDay() >= 1 && date.getDay() <= 5;

export const isOfficeHours = (date: Date): boolean =>
isWorkingDay(date) && date.getHours() >= 9 && date.getHours() < 17;
export { isOfficeHours } from './is-office-hours';
export { outsideOfficeHours } from './outside-office-hours';
8 changes: 1 addition & 7 deletions src/index.spec.ts → src/is-office-hours.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,11 @@ const checkDate = (isoDate: string, isoTime: string) =>

[['Saturday', '2019-03-09'], ['Sunday', '2019-03-10']].forEach(
([restDay, isoDate]) => {
it(`Returns false until 9:00am ${restDay}`, () => {
it(`Returns false on ${restDay}`, () => {
expect(checkDate(isoDate, '08:59:59')).toBeFalse();
});

it(`Returns false between 9:00am and 4:59pm ${restDay}`, () => {
expect(checkDate(isoDate, '09:00:00')).toBeFalse();
expect(checkDate(isoDate, '09:01:00')).toBeFalse();
expect(checkDate(isoDate, '16:59:59')).toBeFalse();
});

it(`Returns false from 5:00pm ${restDay}`, () => {
expect(checkDate(isoDate, '17:00:00')).toBeFalse();
expect(checkDate(isoDate, '17:01:00')).toBeFalse();
});
Expand Down
16 changes: 16 additions & 0 deletions src/is-office-hours.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const isWorkingDay = (day: number) => day >= 1 && day <= 5;

/** @member hour @member minute @member second */
export type Time = [number, number, number];

export const stintStart: Time = [9, 0, 0];
export const stintEnd: Time = [16, 59, 59];

/**
* Returns `true` if the provided `Date` falls within Monday to Friday 9:00am to
* 4:59pm.
*/
export const isOfficeHours = (date: Date): boolean =>
isWorkingDay(date.getDay()) &&
date.getHours() >= stintStart[0] &&
date.getHours() <= stintEnd[0];
1 change: 1 addition & 0 deletions src/lib/pad.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const pad = (n: number) => (n < 10 ? `0${n}` : `${n}`);
7 changes: 7 additions & 0 deletions src/lib/range.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const range = (floor: number, ceiling: number) => {
const array = [];
while (floor <= ceiling) {
array.push(floor++);
}
return array;
};
41 changes: 41 additions & 0 deletions src/outside-office-hours.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import 'expect-more-jest';
import { outsideOfficeHours } from './';
import { pad } from './lib/pad';
import { range } from './lib/range';

describe('Dates during office hours', () => {
range(9, 16).forEach((hour) => {
range(0, 59).forEach((minute) => {
const eveStart = new Date('2019-03-04T18:00:00.000Z');
const eveEnd = new Date('2019-03-04T22:59:59.000Z');
const iso8601 = `2019-03-04T${pad(hour)}:${pad(minute)}:00.000Z`;
const date = new Date(iso8601);
const epoch = date.getTime();
const nextDate = outsideOfficeHours(date);

it(`Moves ${iso8601}`, () => {
expect(nextDate.getTime()).toBeLessThanOrEqual(eveEnd.getTime());
expect(nextDate.getTime()).toBeGreaterThanOrEqual(eveStart.getTime());
});

it('Does not mutate the original Date', () => {
expect(date.getTime()).toEqual(epoch);
});
});
});
});

describe('Dates outside office hours', () => {
range(0, 8)
.concat(range(17, 23))
.forEach((hour) => {
range(0, 59).forEach((minute) => {
const iso8601 = `2019-03-04T${pad(hour)}:${pad(minute)}:00.000Z`;
const date = new Date(iso8601);

it(`Leaves ${iso8601} untouched`, () => {
expect(outsideOfficeHours(date)).toBe(date);
});
});
});
});
61 changes: 61 additions & 0 deletions src/outside-office-hours.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { isOfficeHours, stintEnd, stintStart, Time } from './is-office-hours';

const getPercentOfPart = (part: number, whole: number) => (part / whole) * 100;

const getPartFromPercent = (percent: number, whole: number) =>
whole * (percent / 100);

const getMsSinceMidnight = ([hours, minutes, seconds]: Time) => {
const date = new Date(0);
date.setHours(hours);
date.setMinutes(minutes);
date.setSeconds(seconds);
return date.getTime();
};

const getTimeFromDate = (date: Date): Time => [
date.getHours(),
date.getMinutes(),
date.getSeconds()
];

const getDateWithTime = (date: Date, [hours, minutes, seconds]: Time) => {
const nextDate = new Date(date.getTime());
nextDate.setHours(hours);
nextDate.setMinutes(minutes);
nextDate.setSeconds(seconds);
return nextDate;
};

const getMsBetween = (start: Time, end: Time) =>
getMsSinceMidnight(end) - getMsSinceMidnight(start);

const eveningStart: Time = [18, 0, 0];
const eveningEnd: Time = [22, 59, 59];
const eveningLength = getMsBetween(eveningStart, eveningEnd);
const stintLength = getMsBetween(stintStart, stintEnd);
const eveningStartMs = getMsSinceMidnight(eveningStart);
const stintStartMs = getMsSinceMidnight(stintStart);

/**
* If the provided `Date` falls within Office Hours, a new `Date` is returned
* with the time adjusted to fall outside Office Hours. If the provided `Date`
* falls outside Office Hours, it is returned unchanged.
*
* The provided `Date` is never mutated.
*/
export const outsideOfficeHours = (date: Date): Date => {
if (isOfficeHours(date)) {
const eventTime = getTimeFromDate(date);
const eventStartMs = getMsSinceMidnight(eventTime);
const eventDistance = eventStartMs - stintStartMs;
if (eventDistance === 0) {
return getDateWithTime(date, eveningStart);
}
const eventPercent = getPercentOfPart(eventDistance, stintLength);
const eveningDistance = getPartFromPercent(eventPercent, eveningLength);
const nextDate = new Date(eveningDistance + eveningStartMs);
return getDateWithTime(date, getTimeFromDate(nextDate));
}
return date;
};

0 comments on commit e4900d5

Please sign in to comment.