diff --git a/README.md b/README.md
index d5a5517..08542e9 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,8 @@
# business-hours.js
-Handle business hours of a restaurant, office or any other business. Highly customizable with lots of features.
+Handle business hours of a restaurant, office or any other business. Highly customizable with lots of features, based on moment.js.
+
+[Demo](https://codesandbox.io/s/github/littletower/business-hours.js/tree/master/example)
-[Demo](https://business-hours-example.herokuapp.com/)
[![Travis](https://img.shields.io/travis/littletower/business-hours.js.svg?style=flat-square)]()
[![Codecov](https://img.shields.io/codecov/c/github/littletower/business-hours.js.svg?style=flat-square)]()
@@ -20,80 +21,82 @@ yarn add business-hours.js
# Configuration
-To get started, setup a JSON file where you define the business hours.
-
-`0` stands for `Sunday`
-`1` stands for `Monday` and so on.
+To get started, you'll need to define your business hours in JSON format for every weekday.
You can have 1 to N `from/to` pairs per weekday. If on a given day you are closed, instead of a `from/to` pair, just put `closed`.
+In `holidays` you can define on which day the business is closed for holidays. It can be one day (YYYY/MM/DD) or a range (YYYY/MM/DD-YYYY/MM/DD).
+
+Set the desired timezone in `timeZone`, use any `tz` timezone.
+
```json
-{
- "0": [
- {
- "from": "10:00",
- "to": "13:30"
- },
- {
- "from": "17:00",
- "to": "20:00"
- },
- {
- "from": "21:00",
- "to": "24:00"
- }
- ],
- "1": [
- {
- "from": "10:00",
- "to": "13:30"
- },
- {
- "from": "18:00",
- "to": "22:00"
- }
- ],
- "2": "closed",
- "3": [
- {
- "from": "10:00",
- "to": "13:30"
- },
- {
- "from": "18:00",
- "to": "22:00"
- }
- ],
- "4": [
- {
- "from": "10:00",
- "to": "13:30"
- },
- {
- "from": "18:00",
- "to": "22:00"
- }
- ],
- "5": [
- {
- "from": "10:00",
- "to": "13:30"
- },
- {
- "from": "18:00",
- "to": "22:00"
- }
- ],
- "6": [
- {
- "from": "10:00",
- "to": "13:30"
- },
- {
- "from": "18:00",
- "to": "22:00"
- }
- ]
+"Monday": [
+ {
+ "from": "10:00",
+ "to": "13:30"
+ },
+ {
+ "from": "18:00",
+ "to": "22:00"
+ }
+],
+"Tuesday": "closed",
+"Wednesday": [
+ {
+ "from": "10:00",
+ "to": "13:30"
+ },
+ {
+ "from": "18:00",
+ "to": "22:00"
+ }
+],
+"Thursday": [
+ {
+ "from": "10:00",
+ "to": "13:30"
+ },
+ {
+ "from": "18:00",
+ "to": "22:00"
+ }
+],
+"Friday": [
+ {
+ "from": "10:00",
+ "to": "13:30"
+ },
+ {
+ "from": "18:00",
+ "to": "22:00"
+ }
+],
+"Saturday": [
+ {
+ "from": "10:00",
+ "to": "13:30"
+ },
+ {
+ "from": "18:00",
+ "to": "22:00"
+ }
+],
+"Sunday": [
+ {
+ "from": "10:00",
+ "to": "13:30"
+ },
+ {
+ "from": "17:00",
+ "to": "20:00"
+ },
+ {
+ "from": "21:00",
+ "to": "24:00"
+ }
+],
+"holidays": ["2017/12/11", "2017/12/23-2018/01/02"],
+"timeZone":"Europe/Amsterdam"
}
```
@@ -114,9 +117,33 @@ import hoursJson from "./hours.json";
or it could come from any other endpoint (DB, GraphQL, Firebase...), as long as it's in JSON.
# Example
+To check if your business is currently open:
+```
+let isBusinessOpenNow = businessHours.isOpenNow(); //returns boolean value
+```
+
Find a whole example in React here [here](example/)
+# Doc
+
+Method | Argument | Description
+------ | -------- | -----------
+`isOpenNow` | optional : date | Returns if your business is open or not. If an argument is provided, the method will be executed for the given date.
+`isClosedNow` | optional : date | Returns if your business is closed or not. If an argument is provided, the method will be executed for the given date.
+`willBeOpenOn` | date | Returns if your business will be open on given date.
+`isOpenTomorrow` | | Returns if your business is open tomorrow.
+`isOpenAfterTomorrow` | | Returns if your business is open after tomorrow.
+`nextOpeningDate` | optional : boolean | Returns the next opening date. If argument is set to `true`, the next opening date could be today.
+`nextOpeningHour` | | Returns the next opening hour.
+`isOnHoliday` | optional : date | Returns if your business is closed for holidays.
+`isOnHolidayInDays` | integer | Returns if your business will be closed for holidays in `x` days.
+
+
# TODOs
-- [ ] use ISO formate for weekday, meaning, starting the week on Monday instead of Sunday
-- [ ] add holidays (single day or range) in ISO (ISO 8601) format YYYY-MM-DD
+- [ ] support after midnight hours, (eg. open from 18:00 to 03:00)
+- [x] Time zones support
+- [x] use ISO format for weekdays, meaning, starting the week on Monday instead of Sunday
+- [x] add holidays (single day or range) in ISO (ISO 8601) format YYYY-MM-DD
+- [ ] support hourly holidays, like business opens from 20:00 instead of 18:00 on a given date.
- [ ] add always closed on public holidays (country specific)
+- [ ] add localized formatter to display all the business hours
diff --git a/dist/index.js b/dist/index.js
index 3753878..b3165a6 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -2,41 +2,13 @@
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
-var _set_minutes = require("date-fns/set_minutes");
+var _momentTimezone = require("moment-timezone");
-var _set_minutes2 = _interopRequireDefault(_set_minutes);
+var _momentTimezone2 = _interopRequireDefault(_momentTimezone);
-var _set_hours = require("date-fns/set_hours");
+var _utils = require("./utils");
-var _set_hours2 = _interopRequireDefault(_set_hours);
-
-var _get_day = require("date-fns/get_day");
-
-var _get_day2 = _interopRequireDefault(_get_day);
-
-var _format = require("date-fns/format");
-
-var _format2 = _interopRequireDefault(_format);
-
-var _is_within_range = require("date-fns/is_within_range");
-
-var _is_within_range2 = _interopRequireDefault(_is_within_range);
-
-var _is_future = require("date-fns/is_future");
-
-var _is_future2 = _interopRequireDefault(_is_future);
-
-var _add_days = require("date-fns/add_days");
-
-var _add_days2 = _interopRequireDefault(_add_days);
-
-var _is_equal = require("date-fns/is_equal");
-
-var _is_equal2 = _interopRequireDefault(_is_equal);
-
-var _is_before = require("date-fns/is_before");
-
-var _is_before2 = _interopRequireDefault(_is_before);
+var _utils2 = _interopRequireDefault(_utils);
var _lodash = require("lodash");
@@ -46,7 +18,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
-var weekdays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
+var weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
var BusinessHours = function () {
function BusinessHours() {
@@ -66,6 +38,11 @@ var BusinessHours = function () {
if (isNaN(hour)) return false;
return true;
}
+ }, {
+ key: "_getISOWeekDayName",
+ value: function _getISOWeekDayName(isoDay) {
+ return weekdays[isoDay - 1];
+ }
}, {
key: "init",
value: function init(hours) {
@@ -76,17 +53,17 @@ var BusinessHours = function () {
}
weekdays.forEach(function (day, index) {
- if (!hours.hasOwnProperty(index.toString())) {
+ if (!hours.hasOwnProperty(day)) {
throw new Error(day + " is missing from config");
} else {
- if (hours[index.toString()] !== "closed") {
- if (!hours[index.toString()][0].hasOwnProperty("from")) {
+ if (hours[day] !== "closed") {
+ if (!hours[day][0].hasOwnProperty("from")) {
console.error(day + " is missing 'from' in config");
- } else if (!hours[index.toString()][0].hasOwnProperty("to")) {
+ } else if (!hours[day][0].hasOwnProperty("to")) {
console.error(day + " is missing 'to' in config");
- } else if (!_this._isHourValid(hours[index.toString()][0].from)) {
+ } else if (!_this._isHourValid(hours[day][0].from)) {
console.error(day + "'s 'from' has not the right format. Should be ##:##");
- } else if (!_this._isHourValid(hours[index.toString()][0].to)) {
+ } else if (!_this._isHourValid(hours[day][0].to)) {
console.error(day + "'s 'to' has not the right format. Should be ##:##");
}
}
@@ -98,16 +75,19 @@ var BusinessHours = function () {
}, {
key: "isClosedNow",
value: function isClosedNow() {
- var now = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Date();
+ var now = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _utils2.default.now(this.hours.timeZone);
return !this.isOpenNow(now);
}
}, {
key: "isOpenNow",
value: function isOpenNow() {
- var now = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Date();
+ var now = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _utils2.default.now(this.hours.timeZone);
- var day = (0, _get_day2.default)(now);
+ var day = this._getISOWeekDayName(now.isoWeekday());
+ if (this.isOnHoliday(now)) {
+ return false;
+ }
// console.log("now: ", format(now, "DD/MM/YYYY HH:mm"));
var isOpenNow = false;
if (this.hours[day.toString()] === "closed") return isOpenNow;
@@ -118,9 +98,17 @@ var BusinessHours = function () {
var fromMinutes = from.substr(3, 2);
var toHours = to.substr(0, 2);
var toMinutes = to.substr(3, 2);
- var fromDate = (0, _set_hours2.default)((0, _set_minutes2.default)(now, fromMinutes), fromHours);
- var toDate = (0, _set_hours2.default)((0, _set_minutes2.default)(now, toMinutes), toHours);
- isOpenNow = (0, _is_within_range2.default)(now, fromDate, toDate);
+ var fromDate = now.clone();
+ fromDate.set({
+ hour: fromHours,
+ minute: fromMinutes
+ });
+ var toDate = now.clone();
+ toDate.set({
+ hour: toHours,
+ minute: toMinutes
+ });
+ isOpenNow = now.isBetween(fromDate, toDate);
return isOpenNow;
});
@@ -129,26 +117,28 @@ var BusinessHours = function () {
}, {
key: "willBeOpenOn",
value: function willBeOpenOn(date) {
- var day = (0, _get_day2.default)(date);
- if ((0, _is_future2.default)(date) || (0, _is_equal2.default)(new Date(), date)) {
- if (this.hours[day.toString()] !== "closed") {
+ var day = this._getISOWeekDayName(date.isoWeekday());
+ var now = _utils2.default.now(this.hours.timeZone);
+ if (now.isBefore(date) || now.isSame(date)) {
+ if (this.hours[day] !== "closed" && !this.isOnHoliday(date)) {
return true;
} else {
return false;
}
}
+
return false;
}
}, {
key: "isOpenTomorrow",
value: function isOpenTomorrow() {
- var tomorrow = (0, _add_days2.default)(new Date(), 1);
+ var tomorrow = _utils2.default.now(this.hours.timeZone).add(1, "days");
return this.willBeOpenOn(tomorrow);
}
}, {
key: "isOpenAfterTomorrow",
value: function isOpenAfterTomorrow() {
- var afterTomorrow = (0, _add_days2.default)(new Date(), 2);
+ var afterTomorrow = _utils2.default.now(this.hours.timeZone).add(2, "days");
return this.willBeOpenOn(afterTomorrow);
}
}, {
@@ -156,9 +146,10 @@ var BusinessHours = function () {
value: function nextOpeningDate() {
var includeToday = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
- var date = (0, _add_days2.default)(new Date(), 1);
- if (includeToday) {
- date = new Date();
+ var date = _utils2.default.now(this.hours.timeZone);
+
+ if (!includeToday) {
+ date.add(1, "days");
}
var nextOpeningDate = null;
@@ -166,10 +157,10 @@ var BusinessHours = function () {
if (this.willBeOpenOn(date)) {
nextOpeningDate = date;
} else {
- date = (0, _add_days2.default)(date, 1);
+ date.add(1, "days");
}
}
- return nextOpeningDate;
+ return nextOpeningDate.hours(0).minutes(0).seconds(0);
}
}, {
key: "nextOpeningHour",
@@ -183,18 +174,21 @@ var BusinessHours = function () {
}, {
key: "_nextOpeningHour",
value: function _nextOpeningHour(nextOpeningDate) {
- var day = (0, _get_day2.default)(nextOpeningDate);
+ var _this2 = this;
+
+ var day = this._getISOWeekDayName(nextOpeningDate.isoWeekday());
var firstDate = null;
- this.hours[day.toString()].some(function (fromTo, index) {
+ this.hours[day].some(function (fromTo, index) {
var from = fromTo.from;
var to = fromTo.to;
var fromHours = from.substr(0, 2);
var fromMinutes = from.substr(3, 2);
var toHours = to.substr(0, 2);
var toMinutes = to.substr(3, 2);
- var fromDate = (0, _set_hours2.default)((0, _set_minutes2.default)(nextOpeningDate, fromMinutes), fromHours);
- if ((0, _is_before2.default)(new Date(), fromDate)) {
+ var fromDate = nextOpeningDate.hours(fromHours).minutes(fromMinutes).seconds(0);
+
+ if (_utils2.default.now(_this2.hours.timeZone).isBefore(fromDate)) {
firstDate = fromDate;
return true;
}
@@ -205,6 +199,49 @@ var BusinessHours = function () {
//nextOpeningDateText
//nextOpeningHourText
+ }, {
+ key: "isOnHoliday",
+ value: function isOnHoliday() {
+ var now = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _utils2.default.now(this.hours.timeZone);
+ var callback = arguments[1];
+
+ if (_lodash2.default.isEmpty(this.hours)) {
+ throw new Error("Hours are not set. Check your init() function or configuration.");
+ }
+ if (_lodash2.default.isEmpty(this.hours.holidays)) {
+ this.hours.holidays = [];
+ }
+ for (var i = 0; i < this.hours.holidays.length; i++) {
+ if (this.hours.holidays[i].indexOf("-") > -1) {
+ var dates = this.hours.holidays[i].split("-");
+ var beginDate = (0, _momentTimezone2.default)(dates[0]);
+ var endDate = (0, _momentTimezone2.default)(dates[1]);
+ if (now.isBetween(beginDate, endDate)) {
+ typeof callback === "function" && callback();
+ return true;
+ }
+ } else {
+ var holidayDate = (0, _momentTimezone2.default)(this.hours.holidays[i]);
+ if (now.isSame(holidayDate, "day")) {
+ typeof callback === "function" && callback();
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }, {
+ key: "isOnHolidayInDays",
+ value: function isOnHolidayInDays() {
+ var x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
+
+ if (!_lodash2.default.isInteger(x)) {
+ throw new Error("isOnHolidayInDays(:int) only accepts integers.");
+ }
+ var futureDate = _utils2.default.now(this.hours.timeZone).add(x, "days");
+
+ return this.isOnHoliday(futureDate);
+ }
}]);
return BusinessHours;
diff --git a/dist/utils/index.js b/dist/utils/index.js
new file mode 100644
index 0000000..63e808f
--- /dev/null
+++ b/dist/utils/index.js
@@ -0,0 +1,33 @@
+"use strict";
+
+var _momentTimezone = require("moment-timezone");
+
+var _momentTimezone2 = _interopRequireDefault(_momentTimezone);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+// hours and month start from 0
+// date starts at 1
+var utils = {
+ createDate: function createDate(year, month, date, hour, minute) {
+ var timeZone = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : "Europe/Amsterdam";
+
+ var newDate = _momentTimezone2.default.tz(timeZone);
+ newDate.set({
+ year: year,
+ month: month,
+ date: date,
+ hour: hour,
+ minute: minute
+ });
+
+ return newDate;
+ },
+ now: function now() {
+ var timeZone = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "Europe/Amsterdam";
+
+ return _momentTimezone2.default.tz(timeZone);
+ }
+};
+
+module.exports = utils;
\ No newline at end of file
diff --git a/example/package.json b/example/package.json
index 4b68b5a..4f89d2c 100644
--- a/example/package.json
+++ b/example/package.json
@@ -6,10 +6,11 @@
"react-scripts": "1.0.17"
},
"dependencies": {
- "business-hours.js": "1.0.3",
+ "business-hours.js": "latest",
+ "moment-timezone": "0.5.14",
"react": "16.1.0",
"react-dom": "16.1.0",
- "react-live-clock": "2.0.1"
+ "react-live-clock": "2.0.2"
},
"scripts": {
"start": "react-scripts start",
diff --git a/example/src/App.css b/example/src/App.css
index c5c6e8a..b0514fa 100644
--- a/example/src/App.css
+++ b/example/src/App.css
@@ -9,7 +9,7 @@
.App-header {
background-color: #222;
- height: 150px;
+ height: 200px;
padding: 20px;
color: white;
}
@@ -23,6 +23,17 @@
}
@keyframes App-logo-spin {
- from { transform: rotate(0deg); }
- to { transform: rotate(360deg); }
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+.closed {
+ color: red;
+}
+.open {
+ color: green;
}
diff --git a/example/src/App.js b/example/src/App.js
index 541ad96..9342822 100644
--- a/example/src/App.js
+++ b/example/src/App.js
@@ -13,20 +13,36 @@ class App extends Component {
return (
- Now it is:{" "}
-
+ business-hours.js Example
+
+ {" "}
+ Browser timezone:{" "}
+
+
+
+ {" "}
+ Business timezone:{" "}
+
+
+
- Is open now? {businessHours.isOpenNow().toString()}
+ The business is currently:{" "}
+ {businessHours.isOpenNow() ? (
+ OPEN
+ ) : (
+ CLOSED
+ )}
- Next opening hour: {businessHours.nextOpeningHour().toString()}
- Is it open tomorrow? {businessHours.isOpenTomorrow().toString()}
+ Next opening hour:{" "}
+ {businessHours.nextOpeningHour().format("YYYY-MM-DD HH:mm z")}
-
business-hours.js Example
-
- Sunday:
10:00-13:30 and 17:00-20:00 and 21:00-24:00
-
+
Defined business hours:
Monday:
10:00-13:30 and 18:00-22:00
@@ -45,6 +61,9 @@ class App extends Component {
Satuday:
10:00-13:30 and 18:00-22:00
+
+ Sunday:
10:00-13:30 and 17:00-20:00 and 21:00-24:00
+
);
}
diff --git a/example/src/hours.json b/example/src/hours.json
index c0b4aa3..52cd942 100644
--- a/example/src/hours.json
+++ b/example/src/hours.json
@@ -1,19 +1,16 @@
{
- "0": [
+ "Monday": [
{
"from": "10:00",
"to": "13:30"
},
{
- "from": "17:00",
- "to": "20:00"
- },
- {
- "from": "21:00",
- "to": "24:00"
+ "from": "18:00",
+ "to": "22:00"
}
],
- "1": [
+ "Tuesday": "closed",
+ "Wednesday": [
{
"from": "10:00",
"to": "13:30"
@@ -23,8 +20,7 @@
"to": "22:00"
}
],
- "2": "closed",
- "3": [
+ "Thursday": [
{
"from": "10:00",
"to": "13:30"
@@ -34,7 +30,7 @@
"to": "22:00"
}
],
- "4": [
+ "Friday": [
{
"from": "10:00",
"to": "13:30"
@@ -44,7 +40,7 @@
"to": "22:00"
}
],
- "5": [
+ "Saturday": [
{
"from": "10:00",
"to": "13:30"
@@ -54,14 +50,20 @@
"to": "22:00"
}
],
- "6": [
+ "Sunday": [
{
"from": "10:00",
"to": "13:30"
},
{
- "from": "18:00",
- "to": "22:00"
+ "from": "17:00",
+ "to": "20:00"
+ },
+ {
+ "from": "21:00",
+ "to": "24:00"
}
- ]
+ ],
+ "holidays": ["2017/12/11", "2017/12/23-2018/01/02"],
+ "timeZone": "Europe/Amsterdam"
}
diff --git a/example/yarn.lock b/example/yarn.lock
index a8481ea..e61712d 100644
--- a/example/yarn.lock
+++ b/example/yarn.lock
@@ -4163,6 +4163,12 @@ moment-timezone@0.5.13:
dependencies:
moment ">= 2.9.0"
+moment-timezone@0.5.14:
+ version "0.5.14"
+ resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.14.tgz#4eb38ff9538b80108ba467a458f3ed4268ccfcb1"
+ dependencies:
+ moment ">= 2.9.0"
+
moment@2.18.1:
version "2.18.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
@@ -5155,9 +5161,9 @@ react-error-overlay@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-3.0.0.tgz#c2bc8f4d91f1375b3dad6d75265d51cd5eeaf655"
-react-live-clock@2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/react-live-clock/-/react-live-clock-2.0.1.tgz#f50c47059b6ab7f66979430803695a3bf2946174"
+react-live-clock@2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/react-live-clock/-/react-live-clock-2.0.2.tgz#c9e7a75b525db9c897f9a7b7f266cd3d671acc5d"
dependencies:
moment "2.18.1"
moment-timezone "0.5.13"
diff --git a/package.json b/package.json
index 48a717c..184c8fd 100644
--- a/package.json
+++ b/package.json
@@ -6,19 +6,19 @@
"scripts": {
"commit": "git-cz",
"precommit": "npm run build && npm run test:single",
- "report-coverage":
- "nyc report --reporter=lcov | codecov -t c1cea4d6-9aec-445c-8179-bc5543358876",
+ "report-coverage": "nyc report --reporter=lcov | codecov -t c1cea4d6-9aec-445c-8179-bc5543358876",
"prebuild": "rimraf dist",
- "build-with-files":
- "npm run prebuild && babel --copy-files --out-dir dist --ignore *.test.js src",
+ "build-with-files": "npm run prebuild && babel --copy-files --out-dir dist --ignore *.test.js src",
"build": "npm run prebuild && babel --out-dir dist --ignore *.test.js src",
"test": "mocha --compilers js:babel-register src/index.test.js -w",
"test:single": "nyc mocha --compilers js:babel-register src/index.test.js",
- "semantic-release":
- "semantic-release pre && npm publish && semantic-release post"
+ "semantic-release": "semantic-release pre && npm publish && semantic-release post"
},
"babel": {
- "presets": ["es2015", "stage-2"]
+ "presets": [
+ "es2015",
+ "stage-2"
+ ]
},
"repository": {
"type": "git",
@@ -55,8 +55,8 @@
}
},
"dependencies": {
- "date-fns": "1.28.5",
"lodash": "4.17.4",
- "mockdate": "2.0.2"
+ "mockdate": "2.0.2",
+ "moment-timezone": "0.5.14"
}
}
diff --git a/src/hours.json b/src/hours.json
index c0b4aa3..52cd942 100644
--- a/src/hours.json
+++ b/src/hours.json
@@ -1,19 +1,16 @@
{
- "0": [
+ "Monday": [
{
"from": "10:00",
"to": "13:30"
},
{
- "from": "17:00",
- "to": "20:00"
- },
- {
- "from": "21:00",
- "to": "24:00"
+ "from": "18:00",
+ "to": "22:00"
}
],
- "1": [
+ "Tuesday": "closed",
+ "Wednesday": [
{
"from": "10:00",
"to": "13:30"
@@ -23,8 +20,7 @@
"to": "22:00"
}
],
- "2": "closed",
- "3": [
+ "Thursday": [
{
"from": "10:00",
"to": "13:30"
@@ -34,7 +30,7 @@
"to": "22:00"
}
],
- "4": [
+ "Friday": [
{
"from": "10:00",
"to": "13:30"
@@ -44,7 +40,7 @@
"to": "22:00"
}
],
- "5": [
+ "Saturday": [
{
"from": "10:00",
"to": "13:30"
@@ -54,14 +50,20 @@
"to": "22:00"
}
],
- "6": [
+ "Sunday": [
{
"from": "10:00",
"to": "13:30"
},
{
- "from": "18:00",
- "to": "22:00"
+ "from": "17:00",
+ "to": "20:00"
+ },
+ {
+ "from": "21:00",
+ "to": "24:00"
}
- ]
+ ],
+ "holidays": ["2017/12/11", "2017/12/23-2018/01/02"],
+ "timeZone": "Europe/Amsterdam"
}
diff --git a/src/hoursMissingHolidays.json b/src/hoursMissingHolidays.json
new file mode 100644
index 0000000..9668327
--- /dev/null
+++ b/src/hoursMissingHolidays.json
@@ -0,0 +1,67 @@
+{
+ "Monday": [
+ {
+ "from": "10:00",
+ "to": "13:30"
+ },
+ {
+ "from": "18:00",
+ "to": "22:00"
+ }
+ ],
+ "Tuesday": "closed",
+ "Wednesday": [
+ {
+ "from": "10:00",
+ "to": "13:30"
+ },
+ {
+ "from": "18:00",
+ "to": "22:00"
+ }
+ ],
+ "Thursday": [
+ {
+ "from": "10:00",
+ "to": "13:30"
+ },
+ {
+ "from": "18:00",
+ "to": "22:00"
+ }
+ ],
+ "Friday": [
+ {
+ "from": "10:00",
+ "to": "13:30"
+ },
+ {
+ "from": "18:00",
+ "to": "22:00"
+ }
+ ],
+ "Saturday": [
+ {
+ "from": "10:00",
+ "to": "13:30"
+ },
+ {
+ "from": "18:00",
+ "to": "22:00"
+ }
+ ],
+ "Sunday": [
+ {
+ "from": "10:00",
+ "to": "13:30"
+ },
+ {
+ "from": "17:00",
+ "to": "20:00"
+ },
+ {
+ "from": "21:00",
+ "to": "24:00"
+ }
+ ]
+}
diff --git a/src/index.js b/src/index.js
index a029c73..3abb217 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,21 +1,14 @@
-import setMinutes from "date-fns/set_minutes";
-import setHours from "date-fns/set_hours";
-import getDay from "date-fns/get_day";
-import format from "date-fns/format";
-import isWithinRange from "date-fns/is_within_range";
-import isFuture from "date-fns/is_future";
-import addDays from "date-fns/add_days";
-import isEqual from "date-fns/is_equal";
-import isBefore from "date-fns/is_before";
+import moment from "moment-timezone";
+import utils from "./utils";
import _ from "lodash";
const weekdays = [
- "Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
- "Saturday"
+ "Saturday",
+ "Sunday"
];
class BusinessHours {
@@ -32,6 +25,9 @@ class BusinessHours {
if (isNaN(hour)) return false;
return true;
}
+ _getISOWeekDayName(isoDay) {
+ return weekdays[isoDay - 1];
+ }
init(hours) {
if (_.isEmpty(hours)) {
@@ -39,19 +35,19 @@ class BusinessHours {
}
weekdays.forEach((day, index) => {
- if (!hours.hasOwnProperty(index.toString())) {
+ if (!hours.hasOwnProperty(day)) {
throw new Error(day + " is missing from config");
} else {
- if (hours[index.toString()] !== "closed") {
- if (!hours[index.toString()][0].hasOwnProperty("from")) {
+ if (hours[day] !== "closed") {
+ if (!hours[day][0].hasOwnProperty("from")) {
console.error(day + " is missing 'from' in config");
- } else if (!hours[index.toString()][0].hasOwnProperty("to")) {
+ } else if (!hours[day][0].hasOwnProperty("to")) {
console.error(day + " is missing 'to' in config");
- } else if (!this._isHourValid(hours[index.toString()][0].from)) {
+ } else if (!this._isHourValid(hours[day][0].from)) {
console.error(
day + "'s 'from' has not the right format. Should be ##:##"
);
- } else if (!this._isHourValid(hours[index.toString()][0].to)) {
+ } else if (!this._isHourValid(hours[day][0].to)) {
console.error(
day + "'s 'to' has not the right format. Should be ##:##"
);
@@ -63,12 +59,15 @@ class BusinessHours {
this.hours = hours;
}
- isClosedNow(now = new Date()) {
+ isClosedNow(now = utils.now(this.hours.timeZone)) {
return !this.isOpenNow(now);
}
- isOpenNow(now = new Date()) {
- const day = getDay(now);
+ isOpenNow(now = utils.now(this.hours.timeZone)) {
+ const day = this._getISOWeekDayName(now.isoWeekday());
+ if (this.isOnHoliday(now)) {
+ return false;
+ }
// console.log("now: ", format(now, "DD/MM/YYYY HH:mm"));
let isOpenNow = false;
if (this.hours[day.toString()] === "closed") return isOpenNow;
@@ -79,9 +78,17 @@ class BusinessHours {
const fromMinutes = from.substr(3, 2);
const toHours = to.substr(0, 2);
const toMinutes = to.substr(3, 2);
- const fromDate = setHours(setMinutes(now, fromMinutes), fromHours);
- const toDate = setHours(setMinutes(now, toMinutes), toHours);
- isOpenNow = isWithinRange(now, fromDate, toDate);
+ let fromDate = now.clone();
+ fromDate.set({
+ hour: fromHours,
+ minute: fromMinutes
+ });
+ let toDate = now.clone();
+ toDate.set({
+ hour: toHours,
+ minute: toMinutes
+ });
+ isOpenNow = now.isBetween(fromDate, toDate);
return isOpenNow;
});
@@ -89,29 +96,34 @@ class BusinessHours {
}
willBeOpenOn(date) {
- const day = getDay(date);
- if (isFuture(date) || isEqual(new Date(), date)) {
- if (this.hours[day.toString()] !== "closed") {
+ const day = this._getISOWeekDayName(date.isoWeekday());
+ const now = utils.now(this.hours.timeZone);
+ if (now.isBefore(date) || now.isSame(date)) {
+ if (this.hours[day] !== "closed" && !this.isOnHoliday(date)) {
return true;
} else {
return false;
}
}
+
return false;
}
+
isOpenTomorrow() {
- const tomorrow = addDays(new Date(), 1);
+ const tomorrow = utils.now(this.hours.timeZone).add(1, "days");
return this.willBeOpenOn(tomorrow);
}
+
isOpenAfterTomorrow() {
- const afterTomorrow = addDays(new Date(), 2);
+ const afterTomorrow = utils.now(this.hours.timeZone).add(2, "days");
return this.willBeOpenOn(afterTomorrow);
}
nextOpeningDate(includeToday = false) {
- let date = addDays(new Date(), 1);
- if (includeToday) {
- date = new Date();
+ let date = utils.now(this.hours.timeZone);
+
+ if (!includeToday) {
+ date.add(1, "days");
}
let nextOpeningDate = null;
@@ -119,10 +131,13 @@ class BusinessHours {
if (this.willBeOpenOn(date)) {
nextOpeningDate = date;
} else {
- date = addDays(date, 1);
+ date.add(1, "days");
}
}
- return nextOpeningDate;
+ return nextOpeningDate
+ .hours(0)
+ .minutes(0)
+ .seconds(0);
}
nextOpeningHour() {
@@ -133,21 +148,22 @@ class BusinessHours {
return nextOpeningHour;
}
_nextOpeningHour(nextOpeningDate) {
- const day = getDay(nextOpeningDate);
+ const day = this._getISOWeekDayName(nextOpeningDate.isoWeekday());
let firstDate = null;
- this.hours[day.toString()].some((fromTo, index) => {
+ this.hours[day].some((fromTo, index) => {
const from = fromTo.from;
const to = fromTo.to;
const fromHours = from.substr(0, 2);
const fromMinutes = from.substr(3, 2);
const toHours = to.substr(0, 2);
const toMinutes = to.substr(3, 2);
- const fromDate = setHours(
- setMinutes(nextOpeningDate, fromMinutes),
- fromHours
- );
- if (isBefore(new Date(), fromDate)) {
+ let fromDate = nextOpeningDate
+ .hours(fromHours)
+ .minutes(fromMinutes)
+ .seconds(0);
+
+ if (utils.now(this.hours.timeZone).isBefore(fromDate)) {
firstDate = fromDate;
return true;
}
@@ -157,6 +173,44 @@ class BusinessHours {
}
//nextOpeningDateText
//nextOpeningHourText
+
+ isOnHoliday(now = utils.now(this.hours.timeZone), callback) {
+ if (_.isEmpty(this.hours)) {
+ throw new Error(
+ "Hours are not set. Check your init() function or configuration."
+ );
+ }
+ if (_.isEmpty(this.hours.holidays)) {
+ this.hours.holidays = [];
+ }
+ for (let i = 0; i < this.hours.holidays.length; i++) {
+ if (this.hours.holidays[i].indexOf("-") > -1) {
+ let dates = this.hours.holidays[i].split("-");
+ let beginDate = moment(dates[0]);
+ let endDate = moment(dates[1]);
+ if (now.isBetween(beginDate, endDate)) {
+ typeof callback === "function" && callback();
+ return true;
+ }
+ } else {
+ let holidayDate = moment(this.hours.holidays[i]);
+ if (now.isSame(holidayDate, "day")) {
+ typeof callback === "function" && callback();
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ isOnHolidayInDays(x = 1) {
+ if (!_.isInteger(x)) {
+ throw new Error("isOnHolidayInDays(:int) only accepts integers.");
+ }
+ let futureDate = utils.now(this.hours.timeZone).add(x, "days");
+
+ return this.isOnHoliday(futureDate);
+ }
}
module.exports = new BusinessHours();
diff --git a/src/index.test.js b/src/index.test.js
index 85ded12..4ecc3ad 100644
--- a/src/index.test.js
+++ b/src/index.test.js
@@ -2,36 +2,73 @@ var expect = require("chai").expect;
var bh = require("./index.js");
var hoursJson = require("./hours.json");
var hoursJson2 = require("./hours2.json");
+var hoursJsonMissingHolidays = require("./hoursMissingHolidays.json");
+var utils = require("./utils/index.js");
var MockDate = require("mockdate");
-import format from "date-fns/format";
-bh.init(hoursJson);
+const TIMEZONE = "Europe/Luxembourg";
describe("business-hours-js", function() {
- after(function() {
+ beforeEach(function() {
+ bh.init(hoursJson);
+ });
+ afterEach(function() {
MockDate.reset();
});
+
describe("isOpenNow", function() {
it("outside business hours, should equal false", function() {
- expect(bh.isOpenNow(new Date(2017, 9, 1, 16, 0))).to.equal(false);
+ expect(
+ bh.isOpenNow(utils.createDate(2017, 9, 1, 14, 0, TIMEZONE))
+ ).to.equal(false);
});
it("inside business hours, should equal true", function() {
- expect(bh.isOpenNow(new Date(2017, 9, 1, 18, 0))).to.equal(true);
+ expect(
+ bh.isOpenNow(utils.createDate(2017, 9, 1, 18, 0, TIMEZONE))
+ ).to.equal(true);
+ });
+ it("closed day, should equal false", function() {
+ expect(
+ bh.isOpenNow(utils.createDate(2017, 9, 3, 18, 0, TIMEZONE))
+ ).to.equal(false);
+ });
+ it("on holiday day, should equal false", function() {
+ expect(
+ bh.isOpenNow(utils.createDate(2017, 11, 9, 0, 0, TIMEZONE))
+ ).to.equal(false);
});
- it("cloded day, should equal false", function() {
- expect(bh.isOpenNow(new Date(2017, 9, 3, 18, 0))).to.equal(false);
+ it("on holiday day (range), should equal false", function() {
+ expect(
+ bh.isOpenNow(utils.createDate(2017, 11, 24, 0, 0, TIMEZONE))
+ ).to.equal(false);
});
});
describe("isClosedNow", function() {
it("outside business hours, should equal true", function() {
- expect(bh.isClosedNow(new Date(2017, 9, 1, 16, 0))).to.equal(true);
+ expect(
+ bh.isClosedNow(utils.createDate(2017, 9, 1, 14, 0, TIMEZONE))
+ ).to.equal(true);
});
it("inside business hours, should equal false", function() {
- expect(bh.isClosedNow(new Date(2017, 9, 1, 18, 0))).to.equal(false);
+ expect(
+ bh.isClosedNow(utils.createDate(2017, 9, 1, 18, 0, TIMEZONE))
+ ).to.equal(false);
});
- it("cloded day, should equal true", function() {
- expect(bh.isClosedNow(new Date(2017, 9, 3, 18, 0))).to.equal(true);
+ it("closed day, should equal true", function() {
+ expect(
+ bh.isClosedNow(utils.createDate(2017, 9, 3, 18, 0, TIMEZONE))
+ ).to.equal(true);
+ });
+ it("on holiday day, should equal true", function() {
+ expect(
+ bh.isClosedNow(utils.createDate(2017, 11, 9, 0, 0, TIMEZONE))
+ ).to.equal(true);
+ });
+ it("on holiday day (range), should equal true", function() {
+ expect(
+ bh.isClosedNow(utils.createDate(2017, 11, 24, 0, 0, TIMEZONE))
+ ).to.equal(true);
});
});
@@ -58,13 +95,19 @@ describe("business-hours-js", function() {
describe("willBeOpenOn", function() {
it("with future date, should be true", function() {
- expect(bh.willBeOpenOn(new Date(2019, 9, 3))).to.equal(true);
+ expect(
+ bh.willBeOpenOn(utils.createDate(2018, 0, 3, 0, 0, TIMEZONE))
+ ).to.equal(true);
});
it("with future date, on closed day, should be false", function() {
- expect(bh.willBeOpenOn(new Date(2019, 9, 1))).to.equal(false);
+ expect(
+ bh.willBeOpenOn(utils.createDate(2019, 0, 1, 0, 0, TIMEZONE))
+ ).to.equal(false);
});
it("with past date, should be false", function() {
- expect(bh.willBeOpenOn(new Date(2015, 9, 1))).to.equal(false);
+ expect(
+ bh.willBeOpenOn(utils.createDate(2015, 0, 1, 0, 0, TIMEZONE))
+ ).to.equal(false);
});
});
describe("isOpenTomorrow", function() {
@@ -96,74 +139,140 @@ describe("business-hours-js", function() {
describe("nextOpeningDate", function() {
it("monday 2/10/2017, should return 4/10/2017", function() {
MockDate.set("10/2/2017");
- var expectedDate = new Date(2017, 9, 4, 0, 0);
- expect(bh.nextOpeningDate().getTime()).to.equal(expectedDate.getTime());
+ var expectedDate = utils.createDate(2017, 9, 4, 0, 0, TIMEZONE);
+ expect(bh.nextOpeningDate().isSame(expectedDate, "day")).to.equal(true);
MockDate.reset();
});
it("tuesday 3/10/2017, should return 4/10/2017", function() {
MockDate.set("10/3/2017");
- var expectedDate = new Date(2017, 9, 4, 0, 0);
- expect(bh.nextOpeningDate().getTime()).to.equal(expectedDate.getTime());
+ var expectedDate = utils.createDate(2017, 9, 4, 0, 0, TIMEZONE);
+ expect(bh.nextOpeningDate().isSame(expectedDate, "day")).to.equal(true);
MockDate.reset();
});
it("wednesday 4/10/2017, should return 5/10/2017", function() {
MockDate.set("10/4/2017");
- var expectedDate = new Date(2017, 9, 5, 0, 0);
- expect(bh.nextOpeningDate().getTime()).to.equal(expectedDate.getTime());
+ var expectedDate = utils.createDate(2017, 9, 5, 0, 0, TIMEZONE);
+ expect(bh.nextOpeningDate().isSame(expectedDate, "day")).to.equal(true);
MockDate.reset();
});
it("sunday 8/10/2017, should return monday 9/10/2017", function() {
MockDate.set("10/8/2017");
- var expectedDate = new Date(2017, 9, 9, 0, 0);
- expect(bh.nextOpeningDate().getTime()).to.equal(expectedDate.getTime());
+ var expectedDate = utils.createDate(2017, 9, 9, 0, 0, TIMEZONE);
+ expect(bh.nextOpeningDate().isSame(expectedDate, "day")).to.equal(true);
MockDate.reset();
});
it("monday 2/10/2017, should return monday 2/10/2017", function() {
MockDate.set("10/2/2017");
- var expectedDate = new Date(2017, 9, 2, 0, 0);
- expect(bh.nextOpeningDate(true).getTime()).to.equal(
- expectedDate.getTime()
+ var expectedDate = utils.createDate(2017, 9, 2, 0, 0, TIMEZONE);
+ expect(bh.nextOpeningDate(true).isSame(expectedDate, "day")).to.equal(
+ true
);
MockDate.reset();
});
});
describe("nextOpeningHour", function() {
it("monday 2/10/2017 6:00, should return 2/10/2017 10:00", function() {
- MockDate.set(new Date(2017, 9, 2, 6, 0));
- var expectedDate = new Date(2017, 9, 2, 10, 0);
- expect(bh.nextOpeningHour().getTime()).to.equal(expectedDate.getTime());
+ MockDate.set(utils.createDate(2017, 9, 2, 6, 0, TIMEZONE));
+ var expectedDate = utils.createDate(2017, 9, 2, 10, 0);
+
+ expect(bh.nextOpeningHour().isSame(expectedDate, "minute")).to.equal(
+ true
+ );
MockDate.reset();
});
it("monday 2/10/2017 16:00, should return 2/10/2017 18:00", function() {
- MockDate.set(new Date(2017, 9, 2, 16, 0));
- var expectedDate = new Date(2017, 9, 2, 18, 0);
- expect(bh.nextOpeningHour().getTime()).to.equal(expectedDate.getTime());
+ MockDate.set(utils.createDate(2017, 9, 2, 16, 0, TIMEZONE));
+ var expectedDate = utils.createDate(2017, 9, 2, 18, 0, TIMEZONE);
+ expect(bh.nextOpeningHour().isSame(expectedDate, "minute")).to.equal(
+ true
+ );
MockDate.reset();
});
it("sunday 1/10/2017 20:30, should return 1/10/2017 21:00", function() {
- MockDate.set(new Date(2017, 9, 1, 20, 30));
- var expectedDate = new Date(2017, 9, 1, 21, 0);
- expect(bh.nextOpeningHour().getTime()).to.equal(expectedDate.getTime());
+ MockDate.set(utils.createDate(2017, 9, 1, 20, 30, TIMEZONE));
+ var expectedDate = utils.createDate(2017, 9, 1, 21, 0, TIMEZONE);
+ expect(bh.nextOpeningHour().isSame(expectedDate, "minute")).to.equal(
+ true
+ );
MockDate.reset();
});
it("sunday 1/10/2017 21:30, should return 2/10/2017 10:00", function() {
- MockDate.set(new Date(2017, 9, 1, 21, 30));
- var expectedDate = new Date(2017, 9, 2, 10, 0);
- expect(bh.nextOpeningHour().getTime()).to.equal(expectedDate.getTime());
+ MockDate.set(utils.createDate(2017, 9, 1, 21, 30, TIMEZONE));
+ var expectedDate = utils.createDate(2017, 9, 2, 10, 0, TIMEZONE);
+ expect(bh.nextOpeningHour().isSame(expectedDate, "minute")).to.equal(
+ true
+ );
MockDate.reset();
});
it("tuesday(closed) 3/10/2017 18:30, should return 4/10/2017 10:00", function() {
- MockDate.set(new Date(2017, 9, 3, 18, 30));
- var expectedDate = new Date(2017, 9, 4, 10, 0);
- expect(bh.nextOpeningHour().getTime()).to.equal(expectedDate.getTime());
+ MockDate.set(utils.createDate(2017, 9, 3, 18, 30, TIMEZONE));
+ var expectedDate = utils.createDate(2017, 9, 4, 10, 0, TIMEZONE);
+ expect(bh.nextOpeningHour().isSame(expectedDate, "minute")).to.equal(
+ true
+ );
MockDate.reset();
});
});
+ describe("isOnHoliday", function() {
+ it("on holiday 2017/12/11", function() {
+ MockDate.set(utils.createDate(2017, 11, 11, 0, 0, TIMEZONE));
+ expect(bh.isOnHoliday()).to.equal(true);
+ });
+ it("after a holiday 2017/12/12", function() {
+ MockDate.set(utils.createDate(2017, 11, 12, 0, 0, TIMEZONE));
+ expect(bh.isOnHoliday()).to.equal(false);
+ });
+ it("before a holiday 2017/12/9", function() {
+ MockDate.set(utils.createDate(2017, 11, 9, 0, 0, TIMEZONE));
+ expect(bh.isOnHoliday()).to.equal(false);
+ });
+ it("in range 2017/12/24", function() {
+ MockDate.set(utils.createDate(2017, 11, 24, 0, 0, TIMEZONE));
+ expect(bh.isOnHoliday()).to.equal(true);
+ });
+ it("within range, with year change 2018/01/01", function() {
+ MockDate.set(utils.createDate(2018, 0, 1, 0, 0, TIMEZONE));
+ expect(bh.isOnHoliday()).to.equal(true);
+ });
+ it("outside range, with year change 2018/01/04", function() {
+ MockDate.set(utils.createDate(2018, 0, 4, 0, 0, TIMEZONE));
+ expect(bh.isOnHoliday()).to.equal(false);
+ });
+ it("missing holidays in config", function() {
+ bh.init(hoursJsonMissingHolidays);
+ expect(bh.isOnHoliday()).to.equal(false);
+ });
+ });
+ describe("isOnHolidayInDays", function() {
+ it("on holiday 3 days from 2017/12/08", function() {
+ MockDate.set(utils.createDate(2017, 11, 8, 0, 0, TIMEZONE));
+ expect(bh.isOnHolidayInDays(3)).to.equal(true);
+ });
+ it('Nan param "3 days"', function() {
+ expect(bh.isOnHolidayInDays.bind(bh, "3 days")).to.throw(
+ "isOnHolidayInDays(:int) only accepts integers."
+ );
+ });
+ it('NaN param "3"', function() {
+ expect(bh.isOnHolidayInDays.bind(bh, "3")).to.throw(
+ "isOnHolidayInDays(:int) only accepts integers."
+ );
+ });
+ it('NaN param "undefined", x defaults to 1', function() {
+ MockDate.set(utils.createDate(2017, 11, 8, 0, 0, TIMEZONE));
+ expect(bh.isOnHolidayInDays(undefined)).to.equal(false);
+ });
+ it('NaN param ""', function() {
+ expect(bh.isOnHolidayInDays.bind(bh, "")).to.throw(
+ "isOnHolidayInDays(:int) only accepts integers."
+ );
+ });
+ });
//MockDate.set('1/1/2000');
describe("init", function() {
- it("missing sunday", function() {
+ it("missing monday", function() {
expect(bh.init.bind(bh, hoursJson2)).to.throw(
- "Sunday is missing from config"
+ "Monday is missing from config"
);
});
it("missing config, empty object", function() {
@@ -183,7 +292,7 @@ describe("business-hours-js", function() {
});
it("missing config, {'a':'test'} ", function() {
expect(bh.init.bind(bh, { a: "test" })).to.throw(
- "Sunday is missing from config"
+ "Monday is missing from config"
);
});
});
diff --git a/src/utils/index.js b/src/utils/index.js
new file mode 100644
index 0000000..8e3abee
--- /dev/null
+++ b/src/utils/index.js
@@ -0,0 +1,24 @@
+import moment from "moment-timezone";
+
+// hours and month start from 0
+// date starts at 1
+var utils = {
+ createDate(year, month, date, hour, minute, timeZone = "Europe/Amsterdam") {
+ let newDate = moment.tz(timeZone);
+ newDate.set({
+ year: year,
+ month: month,
+ date: date,
+ hour: hour,
+ minute: minute
+ });
+
+ return newDate;
+ },
+
+ now(timeZone = "Europe/Amsterdam") {
+ return moment.tz(timeZone);
+ }
+};
+
+module.exports = utils;