@171h/dayjs-business-time
Version:
A [Day.js](https://github.com/iamkun/dayjs) plugin that allows you to work with Business Time.
494 lines (396 loc) • 14.3 kB
text/typescript
import UpdateLocale from 'dayjs/plugin/updateLocale';
import LocaleData from 'dayjs/plugin/localeData';
import IsSameOrBefore from 'dayjs/plugin/isSameOrBefore';
import IsSameOrAfter from 'dayjs/plugin/isSameOrAfter';
import dayjs, {
BusinessHoursMap,
BusinessTimeSegment,
BusinessUnitType,
Dayjs,
} from 'dayjs';
// import '../types'
const DEFAULT_WORKING_HOURS = {
sunday: null,
monday: [{ start: '09:00:00', end: '17:00:00' }],
tuesday: [{ start: '09:00:00', end: '17:00:00' }],
wednesday: [{ start: '09:00:00', end: '17:00:00' }],
thursday: [{ start: '09:00:00', end: '17:00:00' }],
friday: [{ start: '09:00:00', end: '17:00:00' }],
saturday: null,
};
enum DaysNames {
sunday = 0,
monday = 1,
tuesday = 2,
wednesday = 3,
thursday = 4,
friday = 5,
saturday = 6,
}
const businessTime = (
option: any,
DayjsClass: typeof Dayjs,
dayjsFactory: typeof dayjs,
) => {
dayjsFactory.extend(LocaleData);
dayjsFactory.extend(UpdateLocale);
dayjsFactory.extend(IsSameOrBefore);
dayjsFactory.extend(IsSameOrAfter);
setBusinessTime(DEFAULT_WORKING_HOURS);
setHolidays([]);
function getLocale() {
return dayjsFactory.Ls[dayjs().locale()];
}
function updateLocale(newData) {
dayjsFactory.updateLocale(dayjs().locale(), { ...newData });
}
function getHolidays(): string[] {
return getLocale().holidays || [];
}
function setHolidays(holidays) {
updateLocale({ holidays });
}
function getBusinessTime(): BusinessHoursMap {
return getLocale().businessHours;
}
function setBusinessTime(businessHours: BusinessHoursMap) {
updateLocale({ businessHours });
}
function isHoliday() {
const today = this.format('YYYY-MM-DD');
const holidays = getHolidays();
return holidays.includes(today);
}
function isBusinessDay() {
const businessHours = getBusinessTime();
const dayName = DaysNames[this.day()];
const isDefaultWorkingDay = !!businessHours[dayName];
return isDefaultWorkingDay && !this.isHoliday();
}
function addOrsubtractBusinessDays(
date: Dayjs,
numberOfDays: number,
action: 'add' | 'subtract' = 'add',
) {
let daysToIterate = numberOfDays;
let day = date.clone();
while (daysToIterate) {
day = day[action](1, 'day');
if (day.isBusinessDay()) {
daysToIterate = daysToIterate - 1;
}
}
return day;
}
function nextBusinessDay() {
return addOrsubtractBusinessDays(this, 1);
}
function lastBusinessDay() {
return addOrsubtractBusinessDays(this, 1, 'subtract');
}
function addBusinessDays(numberOfDays: number) {
return addOrsubtractBusinessDays(this, numberOfDays);
}
function subtractBusinessDays(numberOfDays: number) {
return addOrsubtractBusinessDays(this, numberOfDays, 'subtract');
}
function timeStringToDayJS(timeString: string, date: Dayjs = dayjs()) {
const [hours, minutes, seconds] = <number[]>(
(timeString.split(':') as unknown)
);
return date
.clone()
.hour(hours)
.minute(minutes)
.second(seconds)
.millisecond(0);
}
function getBusinessTimeSegments(day: Dayjs): BusinessTimeSegment[] {
if (!day.isBusinessDay()) {
return null;
}
let date = day.clone();
const dayName = DaysNames[date.day()];
const businessHours = getBusinessTime()[dayName];
return businessHours.reduce((segments, businessTime, index) => {
let { start, end } = businessTime;
start = timeStringToDayJS(start, date);
end = timeStringToDayJS(end, date);
segments.push({ start, end });
return segments;
}, []);
}
function getCurrentBusinessTimeSegment(date) {
const businessSegments = getBusinessTimeSegments(date);
if (!businessSegments?.length) {
return false;
}
return businessSegments.find((businessSegment) => {
const { start, end } = businessSegment;
return date.isSameOrAfter(start) && date.isSameOrBefore(end);
});
}
function getRealCurrentBusinessTimeSegment(date) {
const businessSegments = getBusinessTimeSegments(date);
if (!businessSegments?.length) {
return false;
}
return businessSegments.find((businessSegment) => {
const { start, end } = businessSegment;
return date.isSameOrAfter(start) && date.isBefore(end);
});
}
function isBusinessTime() {
return !!getCurrentBusinessTimeSegment(this);
}
/**
* The last business time is not included in the business time
*/
function isRealBusinessTime() {
return !!getRealCurrentBusinessTimeSegment(this);
}
function nextBusinessTime() {
if (!this.isBusinessDay()) {
const nextBusinessDay = this.nextBusinessDay();
return getBusinessTimeSegments(nextBusinessDay)[0].start;
}
const segments = getBusinessTimeSegments(this);
for (let index = 0; index < segments.length; index++) {
const { start, end } = segments[index];
const isLastSegment = index === segments.length - 1;
if (this.isBefore(start)) {
return start;
}
if (this.isAfter(end)) {
if (!isLastSegment) {
continue;
}
const nextBusinessDay = this.nextBusinessDay();
return getBusinessTimeSegments(nextBusinessDay)[0].start;
}
return this.clone();
}
}
function lastBusinessTime() {
if (!this.isBusinessDay()) {
const lastBusinessDay = this.lastBusinessDay();
const { end } = getBusinessTimeSegments(lastBusinessDay).pop();
return end;
}
const segments = getBusinessTimeSegments(this).reverse();
for (let index = 0; index < segments.length; index++) {
const { start, end } = segments[index];
const isFirstSegment = index === segments.length - 1;
if (this.isAfter(end)) {
return end;
}
if (this.isBefore(start)) {
if (!isFirstSegment) {
continue;
}
const lastBusinessDay = this.lastBusinessDay();
return getBusinessTimeSegments(lastBusinessDay).pop().end;
}
return this.clone();
}
}
function addBusinessMinutes(minutesToAdd: number, endTimeNotIncluded?: boolean): Dayjs {
return addOrSubtractBusinessMinutes(this, minutesToAdd, 'add', endTimeNotIncluded);
}
function addBusinessHours(hoursToAdd: number): Dayjs {
const minutesToAdd = hoursToAdd * 60;
return this.addBusinessMinutes(minutesToAdd);
}
function addBusinessTime(timeToAdd: number, businessUnit: BusinessUnitType) {
if (businessUnit.match(/^(minute)+s?$/)) {
return this.addBusinessMinutes(timeToAdd);
}
if (businessUnit.match(/^(hour)+s?$/)) {
return this.addBusinessHours(timeToAdd);
}
if (businessUnit.match(/^(day)+s?$/)) {
return this.addBusinessDays(timeToAdd);
}
throw new Error('Invalid Business Time Unit');
}
function addOrSubtractBusinessMinutes(
day: Dayjs,
numberOfMinutes: number,
action: 'add' | 'subtract' = 'add',
endTimeNotIncluded = false,
): Dayjs {
let date =
action === 'add' ? day.nextBusinessTime() : day.lastBusinessTime();
while (numberOfMinutes) {
const segment = getCurrentBusinessTimeSegment(
date) as BusinessTimeSegment;
if (!segment) {
date =
action === 'add' ? date.nextBusinessTime() : date.lastBusinessTime();
continue;
}
let { start, end } = segment;
if (endTimeNotIncluded) {
end = end.subtract(1, 'minute');
}
const compareBaseDate = action === 'add' ? end : date;
const compareDate = action === 'add' ? date : start;
let timeToJump = compareBaseDate.diff(compareDate, 'minute');
if (timeToJump > numberOfMinutes) {
timeToJump = numberOfMinutes;
}
numberOfMinutes -= timeToJump;
if (!timeToJump && numberOfMinutes) {
timeToJump = 1;
}
date = date[action](timeToJump, 'minute');
}
return date;
}
function subtractBusinessMinutes(minutesToSubtract: number, endTimeNotIncluded?: boolean): Dayjs {
return addOrSubtractBusinessMinutes(this, minutesToSubtract, 'subtract', endTimeNotIncluded);
}
function subtractBusinessHours(hoursToSubtract: number): Dayjs {
const minutesToSubtract = hoursToSubtract * 60;
return this.subtractBusinessMinutes(minutesToSubtract);
}
function subtractBusinessTime(
timeToSubtract: number,
businessUnit: BusinessUnitType,
) {
if (businessUnit.match(/^(minute)+s?$/)) {
return this.subtractBusinessMinutes(timeToSubtract);
}
if (businessUnit.match(/^(hour)+s?$/)) {
return this.subtractBusinessHours(timeToSubtract);
}
if (businessUnit.match(/^(day)+s?$/)) {
return this.subtractBusinessDays(timeToSubtract);
}
throw new Error('Invalid Business Time Unit');
}
function fixDatesToCalculateDiff(base, comparator) {
let from: Dayjs = base.clone();
let to: Dayjs = comparator.clone();
let multiplier = 1;
if (base.isAfter(comparator)) {
to = base.clone();
from = comparator.clone();
multiplier = -1;
}
if (!from.isBusinessTime()) {
from = from.lastBusinessTime();
}
if (!to.isBusinessTime()) {
to = to.nextBusinessTime();
}
return { from, to, multiplier };
}
function businessDaysDiff(comparator: Dayjs): number {
let { from, to, multiplier } = fixDatesToCalculateDiff(this, comparator);
let diff = 0;
while (!from.isSame(to, 'day')) {
diff += 1;
from = from.addBusinessDays(1);
}
return diff ? diff * multiplier : 0;
}
function businessMinutesDiff(comparator: Dayjs): number {
let { from, to, multiplier } = fixDatesToCalculateDiff(this, comparator);
let diff = 0;
const isSameDayfromTo = from.isSame(to, 'day');
if (isSameDayfromTo) {
const fromSegments = getBusinessTimeSegments(from);
for (const segment of fromSegments) {
const { start, end } = segment;
if (
to.isSameOrAfter(start) &&
to.isSameOrBefore(end) &&
from.isSameOrAfter(start) &&
from.isSameOrBefore(end)
) {
diff += to.diff(from, 'minutes');
break;
} else if (to.isSameOrAfter(start) && to.isSameOrBefore(end)) {
diff += to.diff(start, 'minutes');
break;
} else if (from.isSameOrAfter(start) && from.isSameOrBefore(end)) {
diff += end.diff(from, 'minutes');
}
}
return diff ? diff * multiplier : 0;
}
let segments = getBusinessTimeSegments(from);
for (const segment of segments) {
const { start, end } = segment;
if (from.isSameOrAfter(start) && from.isSameOrBefore(end)) {
diff += end.diff(from, 'minutes');
} else if (start.isSameOrAfter(from)) {
diff += end.diff(start, 'minutes');
}
}
from = from.addBusinessDays(1);
while (from.isBefore(to, 'day')) {
segments = getBusinessTimeSegments(from);
for (const segment of segments) {
const { start, end } = segment;
diff += end.diff(start, 'minutes');
}
from = from.addBusinessDays(1);
}
const toSegments = getBusinessTimeSegments(to);
for (const segment of toSegments) {
const { start, end } = segment;
if (to.isSameOrAfter(start) && to.isSameOrBefore(end)) {
diff += to.diff(start, 'minutes');
} else if (end.isSameOrBefore(to)) {
diff += end.diff(start, 'minutes');
}
}
return diff ? diff * multiplier : 0;
}
function businessHoursDiff(comparator: Dayjs): number {
const minutesDiff = this.businessMinutesDiff(comparator);
return minutesDiff / 60;
}
function businessTimeDiff(comparator: Dayjs, businessUnit: BusinessUnitType) {
if (businessUnit.match(/^(minute)+s?$/)) {
return this.businessMinutesDiff(comparator);
}
if (businessUnit.match(/^(hour)+s?$/)) {
return this.businessHoursDiff(comparator);
}
if (businessUnit.match(/^(day)+s?$/)) {
return this.businessDaysDiff(comparator);
}
throw new Error('Invalid Business Time Unit');
}
// New functions on dayjs factory
dayjsFactory.getHolidays = getHolidays;
dayjsFactory.setHolidays = setHolidays;
dayjsFactory.getBusinessTime = getBusinessTime;
dayjsFactory.setBusinessTime = setBusinessTime;
// New methods on Dayjs class
DayjsClass.prototype.isHoliday = isHoliday;
DayjsClass.prototype.isBusinessDay = isBusinessDay;
DayjsClass.prototype.nextBusinessDay = nextBusinessDay;
DayjsClass.prototype.lastBusinessDay = lastBusinessDay;
DayjsClass.prototype.addBusinessDays = addBusinessDays;
DayjsClass.prototype.subtractBusinessDays = subtractBusinessDays;
DayjsClass.prototype.isBusinessTime = isBusinessTime;
DayjsClass.prototype.isRealBusinessTime = isRealBusinessTime;
DayjsClass.prototype.nextBusinessTime = nextBusinessTime;
DayjsClass.prototype.lastBusinessTime = lastBusinessTime;
DayjsClass.prototype.addBusinessTime = addBusinessTime;
DayjsClass.prototype.addBusinessHours = addBusinessHours;
DayjsClass.prototype.addBusinessMinutes = addBusinessMinutes;
DayjsClass.prototype.subtractBusinessMinutes = subtractBusinessMinutes;
DayjsClass.prototype.subtractBusinessHours = subtractBusinessHours;
DayjsClass.prototype.subtractBusinessTime = subtractBusinessTime;
DayjsClass.prototype.businessMinutesDiff = businessMinutesDiff;
DayjsClass.prototype.businessHoursDiff = businessHoursDiff;
DayjsClass.prototype.businessDaysDiff = businessDaysDiff;
DayjsClass.prototype.businessTimeDiff = businessTimeDiff;
};
export default businessTime;
exports = module.exports = businessTime;