How can I use moment.js to add days, excluding weekends? - javascript

I'm setting a default follow-up date two days from current date, which currently works:
const Notify = moment().add(2, 'days').toDate();
However, I would like to exclude weekends. So I installed moment WeekDay, but I can't seem to get it to work with adding days to the current date. The documentation calls for:
moment().weekday(0)
But I can't get that to work with adding in two days forward. Any ideas?

This solution is simple, easy to follow, and works well for me:
function addBusinessDays(originalDate, numDaysToAdd) {
const Sunday = 0;
const Saturday = 6;
let daysRemaining = numDaysToAdd;
const newDate = originalDate.clone();
while (daysRemaining > 0) {
newDate.add(1, 'days');
if (newDate.day() !== Sunday && newDate.day() !== Saturday) {
daysRemaining--;
}
}
return newDate;
}

Try: moment-business-days
It should help you.
Example:
var momentBusinessDays = require("moment-business-days")
momentBusinessDays('20-09-2018', 'DD-MM-YYYY').businessAdd(3)._d
Result:
Tue Sep 25 2018 00:00:00 GMT+0530 (IST)

You could also not use external lib and do a simple function like one of these two:
const WEEKEND = [moment().day("Saturday").weekday(), moment().day("Sunday").weekday()]
const addBusinessDays1 = (date, daysToAdd) => {
var daysAdded = 0,
momentDate = moment(new Date(date));
while (daysAdded < daysToAdd) {
momentDate = momentDate.add(1, 'days');
if (!WEEKEND.includes(momentDate.weekday())) {
daysAdded++
}
}
return momentDate;
}
console.log(addBusinessDays1(new Date(), 7).format('MM/DD/YYYY'))
console.log(addBusinessDays1('09-20-2018', 3).format('MM/DD/YYYY'))
// This is the somewhat faster version
const addBusinessDays2 = (date, days) => {
var d = moment(new Date(date)).add(Math.floor(days / 5) * 7, 'd');
var remaining = days % 5;
while (remaining) {
d.add(1, 'd');
if (d.day() !== 0 && d.day() !== 6)
remaining--;
}
return d;
};
console.log(addBusinessDays2(new Date(), 7).format('MM/DD/YYYY'))
console.log(addBusinessDays2('09-20-2018', 3).format('MM/DD/YYYY'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
They are slightly modified from this post and I think are a good alternative to external library you have to carry/deal with (assuming this is the only part you need and not other features of that lib).

This will do it based on any starting date, and without a costly loop. You calculate the number of weekend days you need to skip over, then just offset by the number of weekdays and weekends, together.
function addWeekdays(year, month, day, numberOfWeekdays) {
var originalDate = year + '-' + month + '-' + day;
var futureDate = moment(originalDate);
var currentDayOfWeek = futureDate.day(); // 0 = Sunday, 1 = Monday, ..., 6 = Saturday
var numberOfWeekends = Math.floor((currentDayOfWeek + numberOfWeekdays - 1) / 5); // calculate the number of weekends to skip over
futureDate.add(numberOfWeekdays + numberOfWeekends * 2, 'days'); // account for the 2 days per weekend
return futureDate;
}

const addWorkingDays = (date: Moment, days: number) => {
let newDate = date.clone();
for (let i = 0; i < days; i++) {
if (newDate.isoWeekday() !== 6 && newDate.isoWeekday() !== 7) {
newDate = newDate.add(1, "days");
} else {
newDate = newDate.add(1, "days");
i--;
}
}
return newDate.format("YYYY/MM/DD");
};

var moment = require("moment")
function addWorkingDay(date, days){
let daysToAdd = days
const today = moment(date);
const nextWeekStart = today.clone().add(1, 'week').weekday(1);
const weekEnd = today.clone().weekday(5);
const daysTillWeekEnd = Math.max(0, weekEnd.diff(today, 'days'));
if(daysTillWeekEnd >= daysToAdd) return today.clone().add(daysToAdd, 'days');
daysToAdd = daysToAdd - daysTillWeekEnd - 1;
return nextWeekStart.add(Math.floor(daysToAdd/5), 'week').add(daysToAdd % 5, 'days')
}

I think this code will be faster:
var businessDays = 10;
var days = businessDays + Math.floor((Math.min(moment().day(),5)+businessDays)/6)*2;
moment.add(days, 'days');

// using pure JS
function addBusinessDays(originalDate, numDaysToAdd) {
const Sunday = 0;
const Saturday = 6;
let daysRemaining = numDaysToAdd;
const newDate = originalDate;
while (daysRemaining > 0) {
newDate.setDate(newDate.getDate() + 1);
if (newDate.getDay() !== 0 && newDate.getDay() !== 6) {
// skip sunday & saturday
daysRemaining--;
}
}
return newDate;
}
var dt = new Date(); // get date
var business_days = 8;
newDate = addBusinessDays(dt, business_days);
console.log(newDate.toString());

Related

How to find the days of the week in JavaScript

I am trying to find the weekdays from today to Sunday. Since today is Monday I want to display the dates from Monday till Sunday, but tomorrow, I want my programme works from Tuesday to Sunday.
dateSets() {
let firstDayOfWeek = "";
let dates = [];
firstDayOfWeek = new Date();
let sunday = new Date();
sunday.setDate(sunday.getDate() - sunday.getDay() + 7);
const diff = sunday.getDate() - firstDayOfWeek.getDate();
dates.push(new Date());
for (let i = 0; i < diff; i++) {
dates.push(
new Date(firstDayOfWeek.setDate(firstDayOfWeek.getDate() + 1))
);
}
return dates;
},
And here is the other function to find the date of the week:
getDateOfWeek(week, year) {
let simple = new Date(year, 0, 1 + (week - 1) * 7);
let dow = simple.getDay();
let weekStart = simple;
if (dow <= 4) weekStart.setDate(simple.getDate() - simple.getDay() + 1);
else weekStart.setDate(simple.getDate() + 8 - simple.getDay());
return weekStart;
},
But it doesn't work that I expected, in dataset, only Monday is being displayed but not other dates and I don't understand the reason. If you can help me with this, I would be really glad.
Thanks...
function getWeekDates(){
const dates = [new Date()]; // today
const curr = new Date();
const remDaysCount = 7-curr.getDay();
for(let i=1; i<= remDaysCount; i++){
// increase current Date by 1 and set to current Date
const nextDate = curr.setDate(curr.getDate()+1);
dates.push(new Date(nextDate));
}
return dates;
}
console.log(getWeekDates());
This is kind of your choice but if you install NodeJS and open your folder in cmd and type in 'npm init' and 'npm I days' and open your editor and type in
var days = require('days');
console.log(days); // ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
console will reveal all of the days of the week and if you want a specific day you can do the following
var days = require('days');
console.log(days[0]); //Sunday
if you need help with installing NodeJS watch a YouTube video or reply to this comment I will help
let firstDayOfWeek = "";
let dates = [];
firstDayOfWeek = new Date();
let sunday = new Date();
sunday.setDate(sunday.getDate() - sunday.getDay() + 7);
//const diff = sunday.getDate() - firstDayOfWeek.getDate();
//Try this code
var timeDiff = Math.abs(sunday.getTime() - firstDayOfWeek.getTime());
var diff = Math.ceil(timeDiff / (1000 * 3600 * 24));
dates.push(new Date());
for (let i = 0; i < diff; i++) {
dates.push(
new Date(firstDayOfWeek.setDate(firstDayOfWeek.getDate() + 1))
);
}
return dates;
Your issue is here:
const diff = sunday.getDate() - firstDayOfWeek.getDate()
Currently the date is 27 Sep and next Sunday is 3 Oct so diff is -4 and the for loop test i < diff is false from the start. Consider using a loop and increment the date from today until it gets to Sunday.
function getDaysToSunday(date = new Date()) {
let d = new Date(+date);
let result = [];
do {
result.push(new Date(+d));
d.setDate(d.getDate() + 1);
} while (d.getDay() != 1)
return result;
}
console.log(getDaysToSunday().map(d=>d.toDateString()));

MomentJS getting previous dates relative to today

Is there anyway to get the days past the current day using MomentJS?
For example suppose it is January 5, 2018, how would I get the previous dates from January 1, 2018 through to January 5, 2018 ?
My current code looks like this:
const monthArr = [];
const dayArr= [];
const currentDate = moment(new Date()).format("DD");
for (let i = 0; i < +currentDate; i++) {
const month = moment(new Date())
.subtract(i, "day")
.format("MMYYYY");
const day = moment(new Date())
.subtract(i, "day")
.format("MMDDYYYY");
console.log("month" + month);
console.log("day" + day);
let monthObj = {};
let dailyObj = {};
monthArr.push(
(monthObj = {
data: {
[month]: Object.assign({}, document)
}
})
);
day.push(
(dailyObj = {
data: {
[day]: Object.assign({}, document)
}
})
);
monthly(user_id, monthArr[i]) &&
daily(user_id, dayArr[i]);
}
The code in the OP seems very inefficient and far more complex than required.
To generate a series of formatted strings for dates from today to the start of the month only needs one Date and some very simple arithmetic and formatting. It really doesn't need a library nor any date arithmetic, e.g.
// Pad single digit number with leading zero
function pad(n){
return (n < 10? '0' : '') + n;
}
var today = new Date(),
year = today.getFullYear(),
month = pad(today.getMonth() + 1),
day,
i = today.getDate();
do {
day = pad(i);
console.log(`Month: ${month + year}`);
console.log(`Day: ${month + day + year}`);
} while (--i)
There are a number of other issues with your code, but they're not directly related to the question.

How to return the next available date

I'm building a project with express and I have a scheduling calendar. I want to give to my users next available day. Format YYYY-MM-DD.
Rules:
The next available day is usually tomorrow unless:
- After 4pm the next available day is two days from now (i.e. Monday afternoon they can book Wednesday);
- Friday after 4pm the next available day is Monday;
- For Saturday it's Monday;
- For Sunday it's Tuesday;
I also have an array of public holidays, which are also unavailable. If the next day is a public holiday, the app should return the day after.
When there is a public holiday my app goes into a loop and it runs the whole loop. I don't know how to fix this. I thought it would skip the loop when it runs the second time.
const publicHolidays = ['2018-09-28', '2018-12-25']
const availableDay = (nextDay) => {
const d = new Date();
const utc = d.getTime() + (d.getTimezoneOffset() * 60000);
const nd = new Date(utc + (3600000 * 8));
if (nextDay === undefined) {
nextDay = 1;
}
if (nd.getDay() === 5 && nd.getHours() > 15) {
nextDay = 3;
} else if ([0, 6].includes(nd.getDay()) || nd.getHours() > 15) {
nextDay = 2;
}
const day = new Date();
const tomorrow = new Date(day);
tomorrow.setDate(tomorrow.getDate() + nextDay);
const yy = tomorrow.getFullYear();
let mm = tomorrow.getMonth() + 1;
if (mm < 10) {
mm = `0${mm}`;
}
let dd = tomorrow.getDate();
if (dd < 10) {
dd = `0${dd}`;
}
const available = `${yy}-${mm}-${dd}`;
if (publicHolidays.includes(available)) {
const nextDay = 7;
for (let i = 2; i < nextDay; i += 1) {
availableDay(i);
}
} else {
console.log('returning available', available);
return(available);
}
}
availableDay()
I think this logic will work - I've created a function to do the "date string - yyyy-mm-dd" thing because it's used in two places now
I also check for weekends by tomorrow.getDay() % 6 === 0 - you can of course use [0, 6].includes(tomorrow.getDay()) if you prefer
const publicHolidays = ['2018-09-28', '2018-12-25']
const availableDay = () => {
let nextDay = 1; // since we are not recursive any more
const d = new Date();
const utc = d.getTime() + (d.getTimezoneOffset() * 60000);
const nd = new Date(utc + (3600000 * 8));
if (nd.getDay() === 5 && nd.getHours() > 15) {
nextDay = 3;
} else if ([0, 6].includes(nd.getDay()) || nd.getHours() > 15) {
nextDay = 2;
}
const day = new Date();
const tomorrow = new Date(day);
tomorrow.setDate(tomorrow.getDate() + nextDay);
// changes start here
const dateString = d => `${.getFullYear()}-${('0' + (d.getMonth() + 1)).toString(-2)}-${('0' + d.getDate()).toString(-2)}`;
let available = dateString(tomorrow);
while (publicHolidays.includes(available) || (tomorrow.getDay() === 0)) {
tomorrow.setDate(tomorrow.getDate() + 1);
available = dateString(tomorrow);
}
console.log('returning available', available);
return(available);
}
availableDay()
There's probably more you can do to streamline the code - but this should fix the problem at least
I think you should always + 1 to nextDay. so if today is public holiday, try get the next day. the cycle repeat until it is not public holiday.
if (publicHolidays.includes(available)) {
availableDay(nextDay +1 );
} else {
console.log('returning available', available);
return(available);
}
Here is a more generic solution that might be applicable for people searching for something similar:
/**
* #summary Finds the next available date between a range, excluding a list of unavailable dates
* #param {Date} startDate The beginning of the date range.
* #param {Date} endDate The beginning of the date range.
* #param {Array of Date} excludeDates Dates that are not available.
*/
export const findNextAvailableDate = (startDate, endDate, excludeDates) => {
const excludeDatesStrArr = excludeDates.map(date => {
// Make sure dates are in a consistent string format so we can check for equality
excludeDate.setUTCHours(0, 0, 0, 0)
return excludeDate.toISOString()
})
let possibleDate = startDate
possibleDate.setUTCHours(0, 0, 0, 0)
let possibleDateStr = possibleDate.toISOString()
while (possibleDateStr !== endDate) {
if (!excludeDatesStrArr.includes(possibleDateStr)) {
// Date is not in exclude array, return available date
return possibleDate
} else {
// Date is included in exclude array, iterate to the next day
const newDate = possibleDate.setDate(possibleDate.getDate() + 1)
possibleDate = new Date(newDate)
possibleDate.setUTCHours(0, 0, 0, 0)
possibleDateStr = possibleDate.toISOString()
}
}
// Did not find next available date
return false
}

Get a list of dates between two dates using javascript

From JavaScript is there a way to get list of days between two dates from MySQL format. I don't want to use any library for this.
This is what i did.
function generateDateList(from, to) {
var getDate = function(date) { //Mysql Format
var m = date.getMonth(), d = date.getDate();
return date.getFullYear() + '-' + (m < 10 ? '0' + m : m) + '-' + (d < 10 ? '0' + d : d);
}
var fs = from.split('-'), startDate = new Date(fs[0], fs[1], fs[2]), result = [getDate(startDate)], start = startDate.getTime(), ts, end;
if ( typeof to == 'undefined') {
end = new Date().getTime();
} else {
ts = to.split('-');
end = new Date(ts[0], ts[1], ts[2]).getTime();
}
while (start < end) {
start += 86400000;
startDate.setTime(start);
result.push(getDate(startDate));
}
return result;
}
console.log(generateDateList('2014-2-27', '2014-3-2'));
I test it from chrome and nodejs below are the result.
[ '2014-02-27',
'2014-02-28',
'2014-02-29',
'2014-02-30',
'2014-02-31',
'2014-03-01',
'2014-03-02' ]
yeh big leap year:-D..., how can i fix this? or is there any better way.?
const listDate = [];
const startDate ='2017-02-01';
const endDate = '2017-02-10';
const dateMove = new Date(startDate);
let strDate = startDate;
while (strDate < endDate) {
strDate = dateMove.toISOString().slice(0, 10);
listDate.push(strDate);
dateMove.setDate(dateMove.getDate() + 1);
};
Take the start date and increment it by one day until you reach the end date.
Note: MySQL dates are standard format, no need to parse it by hand just pass it to the Date constructor: new Date('2008-06-13').
const addDays = (date, days = 1) => {
const result = new Date(date);
result.setDate(result.getDate() + days);
return result;
};
const dateRange = (start, end, range = []) => {
if (start > end) return range;
const next = addDays(start, 1);
return dateRange(next, end, [...range, start]);
};
const range = dateRange(new Date("2014-02-27"), new Date("2014-03-02"));
console.log(range);
console.log(range.map(date => date.toISOString().slice(0, 10)))
Here I use a recursive function, but you could achieve the same thing using a while (see other answers).
I have used this one from
https://flaviocopes.com/how-to-get-days-between-dates-javascript/
const getDatesBetweenDates = (startDate, endDate) => {
let dates = []
//to avoid modifying the original date
const theDate = new Date(startDate)
while (theDate < new Date(endDate)) {
dates = [...dates, new Date(theDate)]
theDate.setDate(theDate.getDate() + 1)
}
dates = [...dates, new Date(endDate)]
return dates
}
Invoke the function as follows:
getDatesBetweenDates("2021-12-28", "2021-03-01")
Note - I just had to fix issues with the Date object creation (new Date()) in the while loop and in the dates array. Other than that the code is pretty much same as seen on the above link
dateRange(startDate, endDate) {
var start = startDate.split('-');
var end = endDate.split('-');
var startYear = parseInt(start[0]);
var endYear = parseInt(end[0]);
var dates = [];
for(var i = startYear; i <= endYear; i++) {
var endMonth = i != endYear ? 11 : parseInt(end[1]) - 1;
var startMon = i === startYear ? parseInt(start[1])-1 : 0;
for(var j = startMon; j <= endMonth; j = j > 12 ? j % 12 || 11 : j+1) {
var month = j+1;
var displayMonth = month < 10 ? '0'+month : month;
dates.push([i, displayMonth, '01'].join('-'));
}
}
return dates;
}
var oDate1 = oEvent.getParameter("from"),
oDate2 = oEvent.getParameter("to");
var aDates = [];
var currentDate = oDate1;
while (currentDate <= oDate2) {
aDates.push(new Date(currentDate));
currentDate.setDate(currentDate.getDate() + 1);
}
I expanded Công Thắng's great answer to return {years, months, days}, thought it was worth sharing:
function getDates(startDate, endDate) {
const days = [],
months = new Set(),
years = new Set()
const dateMove = new Date(startDate)
let date = startDate
while (date < endDate){
date = dateMove.toISOString().slice(0,10)
months.add(date.slice(0, 7))
years.add(date.slice(0, 4))
days.push(date)
dateMove.setDate(dateMove.getDate()+1) // increment day
}
return {years: [...years], months: [...months], days} // return arrays
}
console.log(getDates('2016-02-28', '2016-03-01')) // leap year
/* =>
{
years: [ '2016' ],
months: [ '2016-02', '2016-03' ],
days: [ '2016-02-28', '2016-02-29', '2016-03-01' ]
}
*/
const {months} = getDates('2016-02-28', '2016-03-01') // get only months
Basically the function just increments the built-in Date object by one day from start to end, while the Sets capture unique months and years.

How to calculate the last friday of the month with momentjs

How could I calculate the last friday of this month using the momentjs api?
Correct answer to this question:
var lastFriday = function () {
var lastDay = moment().endOf('month');
if (lastDay.day() >= 5)
var sub = lastDay.day() - 5;
else
var sub = lastDay.day() + 2;
return lastDay.subtract(sub, 'days');
}
The previous answer returns the next to last friday if the last day of the month is friday.
Given a moment in the month you want the last Friday for:
var lastFridayForMonth = function (monthMoment) {
var lastDay = monthMoment.endOf('month').startOf('day');
switch (lastDay.day()) {
case 6:
return lastDay.subtract(1, 'days');
default:
return lastDay.subtract(lastDay.day() + 2, 'days');
}
},
E.g.
// returns moment('2014-03-28 00:00:00');
lastFridayForMonth(moment('2014-03-14));
Sorry guys but I tested previous answers and all of them are wrong.
Check all of them for
moment([2017,2])
And you will find that the result is incorrect.
I wrote this solution which is working so far:
let date = moment([2017,2]).endOf('month');
while (date.day() !== 5) {
date.subtract(1,'days')
}
return date;
Shorter answer :
var lastFridayForMonth = function (monthMoment) {
let lastDay = monthMoment.endOf('month').endOf('day');
return lastDay.subtract((lastDay.day() + 2) % 7, 'days');
}
I tried testing multiple months and for few months above answer by #afternoon did not work. Below is the test code.(one such test is for Aug 2018)
var moment = require('moment')
var affirm = require("affirm.js")
var cc = moment(Date.now())
for (var i = 0; i < 100000; i++) {
lastFridayForMonth(cc)
var nextWeek = moment(cc).add(7, 'days')
console.log(cc.format("ddd DD MMM YYYY"), nextWeek.format('MMM'))
affirm((cc.month() - nextWeek.month()) === -1 || (cc.month() - nextWeek.month()) === 11, "1 month gap is not found")
affirm(cc.day() === 5, "its not friday")
cc.add(1, "months")
}
I have put another solution
function lastFridayForMonth(monthMoment) {
var month = monthMoment.month()
monthMoment.endOf("month").startOf("isoweek").add(4, "days")
if (monthMoment.month() !== month) monthMoment.subtract(7, "days")
}

Categories

Resources