Use asterisk * instead of year in object with dates? - javascript

I have this code to calculate days between dates and skip holidays.
var gon = {};
gon["holiday"] = "2015-08-28,2015-09-25,2016-08-31,2016-08-07,2015-08-13,2016-08-29,2016-01-07,2015-10-31".split(",");
// 2 helper functions - moment.js is 35K minified so overkill in my opinion
function pad(num) { return ("0" + num).slice(-2); }
function formatDate(date) { var d = new Date(date), dArr = [d.getFullYear(), pad(d.getMonth() + 1), pad(d.getDate())];return dArr.join('-');}
function calculateDays(first,last) {
var aDay = 24 * 60 * 60 * 1000,
daysDiff = parseInt((last.getTime()-first.getTime())/aDay,10);
if (daysDiff>0) {
for (var i = first.getTime(), lst = last.getTime(); i <= lst; i += aDay) {
var d = new Date(i);
console.log(d.getDay());
if (d.getDay() == 6 || d.getDay() == 0 // weekend
|| gon.holiday.indexOf(formatDate(d)) != -1) {
daysDiff--;
}
}
}
return daysDiff;
}
How can I use asterisk * instead of year to cover all years. I don't want to do like this
gon["holiday"] = "2018-08-28,2018-09-25,2019-08-28,2019-09-25,2020-08-28,2020-09-25,2021-08-28,2021-09-25".split(",");
Can I do something like this
gon["holiday"] = "*-08-28,*-09-25".split(",");

This code could do the work for you:
gon["holiday"]= [...Array(10)].map((_,i) => (2015+i) + "-08-28");
Results:
(10) ["2015-08-28", "2016-08-28", "2017-08-28", "2018-08-28", "2019-08-28", "2020-08-28", "2021-08-28", "2022-08-28", "2023-08-28", "2024-08-28"]

You can use findIndex and provide it a function that would only match the day and month instead of the year, like so:
var gon = {};
gon["holiday"] = "*-08-28,*-09-25".split(",");
function pad(num) { return ("0" + num).slice(-2); }
function formatDate(date) { var d = new Date(date), dArr = [d.getFullYear(), pad(d.getMonth() + 1), pad(d.getDate())];return dArr.join('-');}
function calculateDays(first,last) {
var aDay = 24 * 60 * 60 * 1000,
daysDiff = parseInt((last.getTime()-first.getTime())/aDay,10);
if (daysDiff>0) {
for (var i = first.getTime(), lst = last.getTime(); i <= lst; i += aDay) {
var d = new Date(i);
console.log(d.getDay());
if (d.getDay() == 6 || d.getDay() == 0 // weekend
|| gon.holiday.findIndex((h)=>formatDate(d).replace(/[^-]+-/, '') == h.replace(/[^-]+-/, '')) != -1) {
daysDiff--;
}
}
}
return daysDiff;
}
I'm using a regular expression to remove the year, it removes the first occurrence of a dash and any characters before it.

You could have an array for holidays with fixed anniversaries in MM-DD format:
var fixedHols = ['08-28','09-25'];
another for those that move more or less randomly, such as easter, ramadan, diwali:
var movingHols = ['YYYY-MM-DD',...]
and another for rules–based holidays that is generated for the given year like the first Monday in May or the Tuesday following the first Monday in November or whatever, then test dates against them, e.g.
function isHoliday(date) {
let z = n => (n<10?'0':'')+n;
let fixedHols = ['08-28','09-25'];
let ymd = formatDate(date);
let md = ymd.slice(-5);
let movingHols = [ /* dates as YYY-MM-DD */ ];
let rulesHols = [ /* generate YYY-MM-DD for date.getFullYear() */ ];
// If date is in any array return true, otherwise return false
return [fixedHols, movingHols, rulesHols].some((hols, i) => hols.includes(i? ymd : md));
}
function formatDate(d) {
var z = n => (n<10?'0':'')+n;
return d.getFullYear()+'-'+z(d.getMonth()+1)+'-'+z(d.getDate());
}
[new Date(2018,7,27), // 27 Aug
new Date(2018,7,28), // 28 Aug
new Date(2018,7,29), // 29 Aug
new Date(2021,7,28), // 28 Aug
new Date(2018,8,25), // 25 Sep
new Date(2018,8,26)] // 26 Sep
.forEach(d =>
console.log(`Is ${formatDate(d)} a holiday? ${isHoliday(d)?'Yes':'No'}`)
);

Related

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

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());

functions(partial functions) in javascript

write a function(not a generator) called displayDayNumber(dateString) that satisfies the following rule.
var dayNumber =displayDayNumber("Nov 5,2017"); //309
console.log(dayNumber ()); // 310
dayNumber("Oct 31 2017")); //304
dayNumber();//305
So far My code is working for one time dateString pass value but it is not calculating based when I am passing new value in dayNumber.
var dayNumber =function(dateString=new Date().toDateString())
{
var currentString=dateString;
var no=dateString.substring(8,10);
// console.log(no);
return function () {
// console.log("dateString=",dateString);
var b = calculateNumber(dateString) //helper function to calculate day No
{
var no = dateString.substring(8, 10);
var toNo = parseInt(no) + 1;
var thenString = toNo.toString() + ' ';
var replacecharacter = dateString.replace(no, thenString);
dateString = replacecharacter;
return b;
};
return dateString;
};
}
let genDayNumber=dayNumber ("Sat Nov 05 2017"); //309 //working
genDayNumber(); //working
genDayNumber("Wed Oct 31 2017")); //304 not working
genDayNumber(); //not working
The function you're returning doesn't take an argument, so when you're passing in "Wed Oct 31 2017", it's simply ignored.
You need to modify the returned function to accept a value.
Here is a working example
var dayNumber =displayDayNumber("Nov 5,2017"); //309
console.log(dayNumber); // 309
console.log(dayNumber()); // 310
console.log(dayNumber("Oct 31 2017")); //304
console.log(dayNumber())//305
function displayDayNumber(str) {
let date = new Date(str);
let day = dayOfYear(date);
var result = function(str) {
if (str) {
day = displayDayNumber(str);
return day;
} else {
return ++day;
}
}
result.valueOf = result.toString = function() { return day; }
return result;
}
function dayOfYear(now) {
// Taken from https://stackoverflow.com/questions/8619879/javascript-calculate-the-day-of-the-year-1-366
var start = new Date(now.getFullYear(), 0, 0);
var diff = (now - start) + ((start.getTimezoneOffset() - now.getTimezoneOffset()) * 60 * 1000);
var oneDay = 1000 * 60 * 60 * 24;
var day = Math.floor(diff / oneDay);
return day;
}
const dayNumber = date => {
let days = new Date(date).getDay();
return arg => {
if(arg){
return days = new Date(arg).getDay();
}
return ++days;
};
};

js datepicker - allow non-consecutive days pcm

I would like to limit users to selecting only the first and third Monday of each month. We have a volunteer intake only on these days, so I want to limit incorrect date selections as much as possible.
I'm not a js coder, but have managed to adapt some code I found online to allow the first or third Monday of each month, but I can't work out how to allow both of them.
Here's the code I have for the first Monday:
var firstMonday = new Date(date);
var mondays=0;
firstMonday.setDate(1);
while (mondays < 1) {
firstMonday.setDate(firstMonday.getDate() + 1);
if (firstMonday.getDay() == 1) {
mondays++;
}
}
var result = date.getDate() != firstMonday.getDate();
I think this is what you are asking. Credit to jabclab for the getMondays() function.
// test: first monday of this month
// result: true
//var dates = [new Date(2017,8,4)];
// test: third monday of this month
// result: true
//var dates = [new Date(2017,8,18)];
// test: first and third monday of this month
// result: true
var dates = [new Date(2017,8,4), new Date(2017,8,18)];
// test: first monday, third monday, and random day from this month
// result: false
//var dates = [new Date(2017,8,4), new Date(2017,8,18), new Date(2017,8,22)];
alert(validate(dates));
function validate(dates) {
var valid = true;
var mondays = getMondays();
var firstMonday = mondays[0].setHours(0,0,0,0);
var thirdMonday = mondays[2].setHours(0,0,0,0);
if (dates && dates.length > 0) {
for (var i = 0; i < dates.length; i++) {
// Zero out time so only year, month, and day is compared
var d = dates[i].setHours(0,0,0,0);
if (d != firstMonday && d != thirdMonday) {
return false;
}
}
}
else {
valid = false;
}
return valid;
}
function getMondays() {
var d = new Date(),
month = d.getMonth(),
mondays = [];
d.setDate(1);
// Get the first Monday in the month
while (d.getDay() !== 1) {
d.setDate(d.getDate() + 1);
}
// Get all the other Mondays in the month
while (d.getMonth() === month) {
mondays.push(new Date(d.getTime()));
d.setDate(d.getDate() + 7);
}
return mondays;
}
Thanks, but I'm not sure if the above works or not as I was looking for a js code answer - I'll leave that for someone else to work out.
...which I've found in the meantime. Many thanks to Hugh at Fabrik for the following:
var thisdate = new Date(date);
thisdate.setHours(0,0,0,0);
var day = 1; // monday
var nth = 1; // first
var first = new Date(thisdate.getFullYear(), thisdate.getMonth(), 1),
add = (day - first.getDay() + 7) % 7 + (nth - 1) * 7;
first.setDate(1 + add);
nth = 3; // third
var third = new Date(thisdate.getFullYear(), thisdate.getMonth(), 1),
add = (day - third.getDay() + 7) % 7 + (nth - 1) * 7;
third.setDate(1 + add);
//console.log(thisdate + ', ' + first + ', ' + third);
var result = (first.getTime() !== thisdate.getTime()) && (third.getTime() !== thisdate.getTime());

Do not count given holidays or weekends between two dates

I have included given code in my js file
$( '.dateDisable' ).change(function() {
calculateDays();
});
function calculateDays() {
var first = new Date( $( '#start_date' ).val() ),
last = new Date( $( '#end_date' ).val() ),
daysDiff = leaveCalculation( first,last);
}
function leaveCalculation( d1, d2 ) {
alert(gon.holiday) // 2015-08-28,2015-09-25,2016-08-31,2016-08-07,2015-08-13,2016-08-29,2016-01-07,2015-09-08
var weeks, dateDiff, weekDay1, weekDay2;
if ( d2 < d1 ) return -1;
weekDay1 = d1.getDay(),
weekDay2 = d2.getDay();
weeks = Math.floor( ( d2.getTime() - d1.getTime() ) / 604800000 );
if ( weekDay1 <= weekDay2 )
dateDiff = ( weeks * 5 ) + ( weekDay2 - weekDay1 );
else
dateDiff = ( ( weeks + 1 ) * 5 ) - ( weekDay1 - weekDay2 );
return ( dateDiff + 1 );
}
I want to calculate days.
If I have selected date "27 August, 2015" - "31 August, 2015"
it will calculate daysDiff = 3 but I want to take out daysDiff as if there is holiday on 28-08-2015 as I am fetching holiday disable dates in gon.holiday.
In my case that would be
2015-08-28,2015-09-25,2016-08-31,2016-08-07,2015-08-13,2016-08-29,2016-01-07,2015-09-08
So if I have selected dates "27 August, 2015" - "31 August, 2015" then it will give me daysDiff as 2 because 28, 29 ,30 are already holiday. I want to exclude weekend also
How would I do that?
Like this
var gon = {};
gon["holiday"] = "2015-08-28,2015-09-25,2016-08-31,2016-08-07,2015-08-13,2016-08-29,2016-01-07,2015-09-08".split(",");
// 2 helper functions - moment.js is 35K minified so overkill in my opinion
function pad(num) { return ("0" + num).slice(-2); }
function formatDate(date) { var d = new Date(date), dArr = [d.getFullYear(), pad(d.getMonth() + 1), pad(d.getDate())];return dArr.join('-');}
function calculateDays(first,last) {
var aDay = 24 * 60 * 60 * 1000,
daysDiff = parseInt((last.getTime()-first.getTime())/aDay,10)+1;
if (daysDiff>0) {
for (var i = first.getTime(), lst = last.getTime(); i <= lst; i += aDay) {
var d = new Date(i);
if (d.getDay() == 6 || d.getDay() == 0 // weekend
|| gon.holiday.indexOf(formatDate(d)) != -1) {
daysDiff--;
}
}
}
return daysDiff;
}
// ONLY using jQuery here because OP already used it. I use 1.11 so IE8+
$(function() {
var days = calculateDays(new Date($('#start_date').val()),
new Date($('#end_date').val()));
if (days <= 0) {
alert("Please enter an end date after the begin date");
}
else {
alert(days +" working days found");
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<input id="start_date" value="27 August, 2015" />
<input id="end_date" value="31 August, 2015" />
This can be implemented rather easily with Moment.js:
var startString = "2015-01-01";
var endString = "2015-02-20";
var holidaysString = "2015-01-03, 2015-01-15, 2015-02-05, 2015-03-01";
var start = moment(startString);
var end = moment(endString);
var holidays = holidaysString.split(',').map(function(str) {
return moment(str);
});
var getDuration = function getDuration(start, end, holidays) {
var overlappingDays = holidays.reduce(function(count, holiday) {
return count + ~~(holiday.isAfter(start) && holiday.isBefore(end));
}, 0);
var diff = end.diff(start, 'days');
return diff - overlappingDays;
};
output.innerHTML = getDuration(start, end, holidays);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<span id="output"></span> days.
You can calculate the days between the days
Then split the string of dates that are coming in at gon.holiday into an array
Then loop through this array subtracting days if they are between the two dates
function calculateDays(){
var first = new Date( '27 August, 2015'),
last = new Date('31 August, 2015'),
daysDiff = leaveCalculation( first,last);
}
function leaveCalculation( d1, d2 ) {
var holidayArr = [];
var holiday = gon.holiday; //"2015-08-28,2015-09-25,2016-08-31,2016-08-07,2015-08-13,2016-08-29,2016-01-07,2015-09-08";
if ( d2 < d1 ) return -1;
var oneDay = 24*60*60*1000;
var difDays = Math.round(Math.abs((d1.getTime() - d2.getTime())/(oneDay))); //find the number of days between the two dates
var holidayArr = holiday.split(','); //split at ,
for(var i=0; i<holidayArr.length;i++){ //loop through array
if(new Date(holidayArr[i]) <= d2 && new Date(holidayArr[i]) >= d1){ //check if two dates are between the given dates
difDays-=1;
}
}
return difDays;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

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.

Categories

Resources