Count the number of anniversaries between two dates? - javascript

What is the easiest way in Javascript to calculate the number of anniversaries between two dates.
My attempt:
module.exports.countAnniversariesBetweenTwoDates = (start_date, end_date, anniversary_date) => {
if (start_date == end_date) {
return 0;
}
let start_date_year = start_date.getFullYear();
let end_date_year = end_date.getFullYear();
let anniversary_year = anniversary_date.getFullYear();
let start_date_month_and_day = Number(('0' + (start_date.getMonth() + 1)).slice(-2) + '' + ('0' + start_date.getDate()).slice(-2));
let end_date_month_and_day = Number(('0' + (end_date.getMonth() + 1)).slice(-2) + '' + ('0' + end_date.getDate()).slice(-2));
let anniversary_date_month_and_day = Number(('0' + (anniversary_date.getMonth() + 1)).slice(-2) + '' + ('0' + anniversary_date.getDate()).slice(-2));
let anniversary_count = 0;
// special case for start year
if (start_date_month_and_day < anniversary_date_month_and_day) {
anniversary_count++;
}
let temp_start_date_year = start_date_year + 1;
while(temp_start_date_year < end_date_year) {
anniversary_count++;
temp_start_date_year++;
}
// special case for end year
if (end_date_month_and_day > anniversary_date_month_and_day) {
anniversary_count++;
}
return anniversary_count;
}

Here's one that accounts for leap years:
function validDate(year, month, day) {
var testDate = new Date(year, month, day);
return testDate.getFullYear() === year && testDate.getMonth() === month && testDate.getDate() === day;
}
function countAnniversaries(begin, end, anniversary) {
var countDate = new Date(anniversary.getTime());
var anniversaries = 0;
var countYear = begin.getFullYear();
var anniversaryMonth = anniversary.getMonth();
var anniversaryDay = anniversary.getDate();
countDate.setFullYear(countYear);
while (countDate.getTime() <= end.getTime()) {
if (validDate(countYear, anniversaryMonth, anniversaryDay) && countDate.getTime() >= begin.getTime()) {
anniversaries++;
}
countYear++;
countDate.setFullYear(countYear);
countDate.setMonth(anniversaryMonth);
countDate.setDate(anniversaryDay);
}
return anniversaries;
}
console.log(countAnniversaries(new Date('Feb 1, 2012'), new Date('Feb 4, 2026'), new Date('Feb 2, 2012')))
console.log(countAnniversaries(new Date('Feb 1, 2012'), new Date('Feb 4, 2026'), new Date('Feb 29, 2012')))

If you use moment.js you can just get the difference between two dates as years, the use Math.floor() to round down to the nearest full number of years:
yearDiff = Math.floor(moment(new Date(endDate)).diff(new Date(startDate),'years'));
Edit: The code above can be simplified a bit more:
yearDiff = moment(endDate).diff(startDate,'years');
You may have to write some additional code to handle time periods of less than a year, or some other special cases.
You could of course loop through the dates similar to how you do it:
var startDate = "03/08/2019";
var endDate = "01/01/2022";
var anniversary = "03/07/2015";
var anniversaryCount = 0;
var counterMoment = moment(startDate);
while (counterMoment.isSameOrBefore(endDate)) {
var anniversaryThisYear = moment(anniversary).year(counterMoment.year());
if (counterMoment.isSame(anniversaryThisYear)) {
anniversaryCount++;
}
counterMoment.add(1,'day');
}
alert("Number of anniversaries: "+anniversaryCount);
It all depends on what the end result is and how you plan to use the value.

Related

Javascript Parse Date Contract_End_Date

I have Contract_End_Date 2 version : 2020-1-1 and 2020-1-11
Result will be like: 2020-01-01 or 2020-01-11
What is solution here?
var updates = {};
var removes = {};
let Contract_End_Date = '2020-1-11';
if (typeof Contract_End_Date !== 'undefined' && Contract_End_Date != null) {
var arr = Contract_End_Date.split(" ");
if (arr.length == 2) {
var firstPart = arr[0];
var dateParts = firstPart.split("-");
if (dateParts.length == 3) {
var day = parseInt(dateParts[2]);
if (day < 10) {
day = "0" + day;
}
var month = parseInt(dateParts[1]);
if (month < 10) {
month = "0" + month;
}
var year = dateParts[0];
updates["Contract_End_Date"] = year + "-" + month + "-" + day;
}
}
}
console.log(updates);
For working with dates in JS, I can just recommend "Moment.js".
It makes your code much more simple and readable.
In your case:
let Contract_End_Date = '2020-1-11';
let formattedDate = moment(Contract_End_Date, 'YYYY-MM-DD').format('YYYY-MM-DD');
console.log(formattedDate);
// 2020-01-11
For more information: https://momentjs.com/
EDIT: Since Moment.js won't get updated with the newest features, it may be better to use other libaries like Luxon

Use asterisk * instead of year in object with dates?

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'}`)
);

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

Why do the months start incrementing after the program begins a new month?

I'm trying to build a weekly calendar for a set number of weeks. I start at the beginning date and loop through the weeks adding each day by the method
nextDay.setDate( start.getDate() + num )
which works fine until after the first day of the next month. Then instead of incrementing each day it increments each month.
var content = $('.content');
function addZeros(int) {
if (parseInt(int) < 10) {
return "0" + int;
} else {
return int;
}
}
var start = new Date("2017-12-01T00:00:00");
var nextDay = new Date(start);
var d = 0;
var dateStr = "";
for (var w = 1; w <= 8; w++) {
dateStr += "week " + w + "<br>";
for (var i = 0; i < 7; i++) {
var factor = (d * 7),
num = i + factor;
nextDay.setDate( start.getDate() + num );
dateStr += nextDay.getFullYear() + "-" + addZeros( nextDay.getMonth() + 1 ) + "-" + addZeros( nextDay.getDate() ) + "<br>";
content.html(dateStr);
}
d++;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="content"></div>
Why does it do this?
Date.prototype.setDate is not idempotent. Once you start giving it values in excess of the length of your Date's month, it calculates the new date based on your current month. Since you reuse the same Date object, every time you call setDate after 31, it jumps further than you want.
let date = new Date('2017-12-29T00:00:00');
date.setDate(30); // 2017-12-30
date.setDate(31); // 2017-12-31
date.setDate(32);
// 2018-01-01 (set date to 32 days after "currentMonth 0th": December 0th + 32 = January 1st)
date.setDate(33); // 2018-02-02 (January 0th + 33 = February 2nd)
date.setDate(34); // 2018-03-06 (February 0th + 34 = March 6th)
Instead of trying to explicitly set the date, let nextDay keep track of its own state. This line of code will advance nextDay by one day:
nextDay.setTime(nextDay.getTime() + 86400000); // 86400000 millis in a day
If you use UTC milliseconds it's easier to do the math, because you can just add the number of milliseconds in a day and not worry how many days are in a month. Then convert it back to a date and you can use .getMonth() etc.
https://jsfiddle.net/z5vkfkLq/
var content = $('.content');
function addZeros(int) {
if (parseInt(int) < 10) {
return "0" + int;
} else {
return int;
}
}
var start = new Date("2017-12-01T00:00:00");
var d = 0;
var dateStr = "";
for (var w = 1; w <= 8; w++) {
dateStr += "week " + w + "<br>";
for (var i = 0; i < 7; i++) {
var factor = (d * 7),
num = i + factor;
var nextDay = new Date(start.getTime() + 86400000*num);
dateStr += nextDay.getFullYear() + "-" + addZeros( nextDay.getMonth() + 1 ) + "-" + addZeros( nextDay.getDate() ) + "<br>";
content.html(dateStr);
}
d++;
}

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