I'm supposed to write a code for codewars to find out the number of times a month ends with a Friday within a range of years.
To start off, I did research and found out several solutions but I still couldn't figure out the results in the console.log.
The first solution is from this tutorial:
In this code, the solution is
let LastDay = new Date(1998, 5 + 1, 0).getDate();
I was able to get the date, but it wasn't clear which day the date falls upon.
Then I found another solution at w3schools. This solution also set the date to be the last day of this month:
var d = new Date();
d.setMonth(d.getMonth() +1, 0);
document.getElementById("demo").innerHTML = d;
However, it works if it displays it as innerHTML = Sat Nov 30 2019 00:57:09 GMT-0500 (Eastern Standard Time). However, when I tried to rewrite the code and console.log it like in this example:
let d = new Date();
let month = d.getMonth()+1;
let lastday = d.setMonth(month, 0);
console.log(lastday);
The result I got was 1575093343211. I don't understand how it displays those numbers instead of the dates I was expecting. I thought that if it does display the dates, starting with the day, I can convert the date to string or array and check if the first element is Friday and then add it to the counter in the code I'm writing. How do I get the code to display the way I want it to.
something like this will work...
function LastDayOfMonth(Year, Month) {
return new Date((new Date(Year, Month, 1)) - 1);
}
var d = LastDayOfMonth(new Date().getYear(), new Date().getMonth())
//var d = LastDayOfMonth(2009, 11)
var dayName = d.toString().split(' ')[0];
console.log(dayName)
The result I got was 1575093343211. I don't understand how it displays those numbers instead of the dates I was expecting
Because you console.log the output of the setMonth method, not the date object:
let lastday = d.setMonth(month, 0);
console.log(lastday);
According to the documentation, the setMonth method returns:
The number of milliseconds between 1 January 1970 00:00:00 UTC and the updated date.
Instead you should use that output to create a new instance of the date object:
let lastday = new Date(d.setMonth(month, 0));
console.log(lastday);
Algorithms to get the last day of the month are generally based on setting a date to day 0 of the following month, which ends up being the last day of the required month.
E.g. to get the last day for June, 2019 (noting that 6 is July, not June):
let endOfJune = new Date(2019, 6, 0):
Once you have the date, you can get the day where 0 is Sunday, 1 is Monday, etc. and 5 is Friday:
let endOfJuneDay = endOfJune.getDay();
The set* methods modify the Date they're called on and return the time value for the modified date. So you don't need to assign the result to anything:
let d = new Date();
let month = d.getMonth() + 1;
// Set date to the new month
d.setMonth(month, 0);
console.log(d);
So if you want to loop over the months for a range of years and get the number that end with a Friday (or any particular day), you might loop over the months something like:
/*
** #param {number} startYear - start year of range
** #param {number} endYear - end year of range
** #param {number} dat - day number, 0 = Sunday, 1 = Monday, etc.
** default is 0 (Sunday)
*/
function countEOMDay(startYear, endYear, day = 0) {
// startYear must be <= end year
if (startYear > endYear) return;
// Start on 31 Jan of start year
let start = new Date(startYear, 0, 31);
// End on 31 Dec of end year
let end = new Date(endYear, 11, 31);
let count = 0;
// Loop over months from start to end
while (start <= end) {
// Count matching days
if (start.getDay() == day) {
++count;
}
// Increment month to end of next month
start.setMonth(start.getMonth() + 2, 0);
}
return count;
}
console.log(countEOMDay(2019, 2019, 5)); // 1
console.log(countEOMDay(2018, 2019, 5)); // 3
You can use setMonth() method to set the month of a date object. The return value of setMonth() method is milliseconds between the date object and midnight January 1 1970. That's what you get from console.log(lastday);
Your return value,
1575093343211
is milliseconds between your date object (d) and midnight January 1 1970.
If you want to get the expected date, you have to console log your date object instead the lastday, as follows:
let d = new Date();
let month = d.getMonth()+1;
let lastday = d.setMonth(month, 0);
console.log(d);
output: Sat Nov 30 2019 00:02:47 GMT+0530 (India Standard Time)
This is an alternative solution I wrote to solve your problem. This will return the number of times a month ends with a Friday within a range of years. Hope this will help you :)
var days = [];
var count = 0;
function getLastFridaysCount(startYear, endYear) {
for (var year = startYear; year <= endYear; year++) {
days = [
31,
0 === year % 4 && 0 !== year % 100 || 0 === year % 400 ? 29 : 28,
31, 30, 31, 30, 31, 31, 30, 31, 30, 31
];
for (var month = 0; month <= 11; month++) {
var myDate = new Date();
myDate.setFullYear(year);
myDate.setMonth(month);
myDate.setDate(days[month]);
if(myDate.getDay() == 5)
{
count++;
}
}
}
return count;
}
console.log("count", getLastFridaysCount(2014, 2017));
this is the solution, in the code can find the comments "//" explaining of what happens in each iteration.
function lastDayIsFriday(initialYear, endYear) {
let count = 0;
//according to when the year ends starts the loop
if (endYear !== undefined) {
let start = new Date(initialYear, 0, 31);
let end = new Date(endYear, 11, 31);
while(start <= end) { //check if the start date is < or = to the end
//The getDay() method returns the day of the week (from 0 to 6) for the specified date.
if(start.getDay() === 5) { //if = to FriYAY!!!
count++; //count the day
}
start.setMonth(start.getMonth()+2, 0);// returns the month (from 0 to 11) .getMonth
} //& sets the month of a date object .setMonth
return count;
} else {
let start = new Date(initialYear, 0, 31);
console.log(start.toString());
for(let i = 0; i < 12; i++) {
if(start.getDay() === 5) {
count++;
}
start.setMonth(start.getMonth() + 2, 0);
// console.log(start.toString());
}
return count;
}
}
Related
I am trying to get the date range of the past Wednesday to past Tuesday(7 days) from today's date.
Say the current date is 2022-05-01(May 1st), I am expecting the result to be the past Tuesday(end date) to Past Wednesday (start date = Past Tuesday -7 days)
i.e 20 April 2022 to 26 April 2022
function getStartAndEndDates () {
var now = new Date('2022-05-01'); //May 1st 2022
var day = now.getDay();
var diff = (day <= 2) ? (7 - 2 + day ) : (day - 2);
var PastTuesday = new Date();
var PastWednesday = new Date(PastTuesday.setDate(now.getDate() - diff));
console.log('End date is', PastTuesday.toISOString());
PastWednesday.setDate(PastTuesday.getDate() - 6);
console.log('Start Date is',PastWednesday.toISOString());
return[PastWednesday,PastTuesday];
}
Output obtained is:
End date is 2022-03-27T19:25:35.726Z //here month is set to March
Start Date is 2022-03-21T19:25:35.726Z
Expected Result is
End date is 2022-04-26T19:25:35.726Z // month is supposed to be April
Start Date is 2022-04-20T19:25:35.726Z
How can I change the code to get the expected result?
You should do something like
function getLastWeek(date) {
var today = new Date(date);
var lastWeek = new Date(today.getFullYear(), today.getMonth(), today.getDate() - 7);
return lastWeek;
}
// Your DATE
date = '2022-05-01'
//
var lastWeek = getLastWeek(date);
var lastWeekMonth = lastWeek.getMonth() + 1;
var lastWeekDay = lastWeek.getDate();
var lastWeekYear = lastWeek.getFullYear();
var lastWeekDisplay = lastWeekMonth + "/" + lastWeekDay + "/" + lastWeekYear;
console.log(lastWeekDisplay);
In your code:
var now = new Date('2022-05-01'); //May 1st 2022
Dates in the format YYYY-MM-DD are parsed as UTC, so the above will create a date object representing 2022-05-01T00:00:00Z.
var day = now.getDay();
This will return the local day number. For users with a zero or positive offset, it will return 0 (Sunday) but for users with a negative offset, it will return 6 (Saturday) because their local date is still the previous day.
var diff = (day <= 2) ? (7 - 2 + day ) : (day - 2);
Given day is 0 (for me), the above sets diff to 5.
var PastTuesday = new Date();
This creates a date for "now", which for me is 17 April.
var PastWednesday = new Date(PastTuesday.setDate(now.getDate() - diff));
In the above, now.getDate returns 1, and 1 - 5 is -4, so it sets the date for PastTuesday to -4. Now PastTuesday is in April, so it is set to 4 days prior to the start of April, i.e. 27 March.
Note that this adjusts PastTuesday and creates a copy for PastWednesday at the same time.
console.log('End date is', PastTuesday.toISOString());
Shows the equivalent UTC date and time, with the time representing the time that the code was run.
PastWednesday.setDate(PastTuesday.getDate() - 6);
Sets PastWednesday to 6 days prior to PastTuesday.
Anyhow, what is required is to do everything either as UTC or local, don't mix the two.
Sticking to code as closely as possible to the original and assuming a timestamp in YYYY-MM-DD format is parsed to the function, consider the following, which does everything as local:
// Parse timestamp in YYYY-MM-DD format as local
function parseISOLocal(s = new Date().toLocaleDateString('en-CA')) {
let [y, m, d] = s.split(/\D/);
return new Date(y, m-1, d);
}
// Get week Wed to Tue prior to passed date
function getStartAndEndDates (date) {
// Parse timestamp as local
var pastTuesday = parseISOLocal(date);
// Adjust pastTuesday to previous Tuesday
var day = pastTuesday.getDay();
var diff = (day <= 2) ? (7 - 2 + day ) : (day - 2);
var pastWednesday = new Date(pastTuesday.setDate(pastTuesday.getDate() - diff));
console.log('End date is', pastTuesday.toDateString());
// Adjust pastWednesday to previous Wednesday
pastWednesday.setDate(pastTuesday.getDate() - 6);
console.log('Start Date is',pastWednesday.toDateString());
return [pastWednesday, pastTuesday];
}
// Sunday 1 May 2022
console.log(getStartAndEndDates('2022-05-01').map(d => d.toDateString()));
// Current date
console.log(getStartAndEndDates().map(d => d.toDateString()));
I need to find this month, previous month and the next month of a specific date.
For example, date was set to 31 of every month, what I expect to get the date is
2018-02-28, 2018-03-31 and 2018-04-30. For those dates which has no 31, than it becomes the day before.
And finally generate 2 period, 2018-02-28 to 2018-03-29, 2018-03-30 to 2018-04-31.
I don't know how to handle feb and the month which less than 31.
var d = new Date();
var tyear = d.getFullYear(); //2018
var tmonth = d.getMonth(); //2
new Date(2018, tmonth-1, 31);//output 2018-03-02 not what I wanted
A simple algorithm is to add months to the original date, and if the new date is wrong, set it to the last day of the previous month. Keeping the original date values unmodified helps, e.g.
/* #param {Date} start - date to start
** #param {number} count - number of months to generate dates for
** #returns {Array} monthly Dates from start for count months
*/
function getMonthlyDates(start, count) {
var result = [];
var temp;
var year = start.getFullYear();
var month = start.getMonth();
var startDay = start.getDate();
for (var i=0; i<count; i++) {
temp = new Date(year, month + i, startDay);
if (temp.getDate() != startDay) temp.setDate(0);
result.push(temp);
}
return result;
}
// Start on 31 Jan in leap year
getMonthlyDates(new Date(2016,0,31), 4).forEach(d => console.log(d.toString()));
// Start on 31 Jan not in leap year
getMonthlyDates(new Date(2018,0,31), 4).forEach(d => console.log(d.toString()));
// Start on 30 Jan
getMonthlyDates(new Date(2018,0,30), 4).forEach(d => console.log(d.toString()));
// Start on 5 Jan
getMonthlyDates(new Date(2018,0,5), 4).forEach(d => console.log(d.toString()));
I think you're going to need an array with 12 numbers in it. Each number is the amount of days in each month and the numbers in the array go in order (first number is 31 because January has 31 days, second is 28 or 29 for Feb), etc. Then you'll get the month number from your input date and look in the array at the number corresponding to the month number +/- 1.
You'll then need to construct a date for the previous month and the next month based on the number of days in the current month.
See comments inline:
let daysInMonths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
document.getElementById("date").addEventListener("input", function(){
console.clear();
// Create new Date based on value in date picker
var selectedDate = new Date(this.value + 'T00:00');
var year = selectedDate.getYear();
// Determine if it is a leap year (Feb has 29 days) and update array if so.
if (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)) {
daysInMonths[1] = 29;
}
var selectedDateMonth = selectedDate.getMonth();
// Get previous month number (if current month is January, get December)
let prevMonth = selectedDateMonth > 0 ? selectedDateMonth - 1 : 11;
let prevMonthDate = null;
// If selected date is last day of month...
if(selectedDate.getDate() === daysInMonths[selectedDateMonth]){
// Create new date that takes the selected date and subtracts the correct amount of
// days from it based on a lookup in the array.
var newDate1 = new Date(selectedDate.getTime());
prevMonthDate =
new Date(newDate1.setDate(selectedDate.getDate() - daysInMonths[selectedDateMonth]));
} else {
// Create a new date that is last month and one day earlier
var newDate2 = new Date(selectedDate.getTime());
prevMonthDate =
new Date(new Date(newDate2.setDate(selectedDate.getDate() - 1))
.setMonth(selectedDate.getMonth() - 1));
}
// Get next month (if current month is December, get January
let nextMonth = selectedDateMonth < 11 ? selectedDateMonth + 1 : 0;
let nextMonthDate = null;
// Same idea for next month, but add instead of subtract.
// If selected date is last day of month...
if(selectedDate.getDate() === daysInMonths[selectedDateMonth]){
var newDate3 = new Date(selectedDate.getTime());
nextMonthDate =
new Date(newDate3.setDate(selectedDate.getDate() + daysInMonths[selectedDateMonth + 1]));
} else {
var newDate4 = new Date(selectedDate.getTime());
nextMonthDate = new Date(new Date(newDate4.setDate(selectedDate.getDate() + 1)).setMonth(selectedDate.getMonth() + 1));
}
console.log("Last month date: " + prevMonthDate.toLocaleDateString());
console.log("Next month date: " + nextMonthDate.toLocaleDateString());
});
<p>Pick a date: <input type="date" id="date"></p>
Use this approach:
Javascript Date Object – Adding and Subtracting Months
From the Author
There is a slight problem with the Javascript Date() Object when trying to advance to the next month or go back to the previous month.
For example, if your date is set to October 31, 2018 and you add one month, you'd probably expect the new date to be November 30, 2018 because November 31st doesn't exist. This, however, isn't the case.
Javascript automatically advances your Date object to December 1st. This functionality is very useful in most situations(i.e. adding days to a date, determining the number of days in a month or if it's a leap year), but not for adding/subtracting months. I've put together some functions below that extend the Date() object: nextMonth() and prevMonth().
function prevMonth() {
var thisMonth = this.getMonth();
this.setMonth(thisMonth - 1);
if (this.getMonth() != thisMonth - 1 && (this.getMonth() != 11 || (thisMonth == 11 && this.getDate() == 1)))
this.setDate(0);
}
function nextMonth() {
var thisMonth = this.getMonth();
this.setMonth(thisMonth + 1);
if (this.getMonth() != thisMonth + 1 && this.getMonth() != 0)
this.setDate(0);
}
Date.prototype.nextMonth = nextMonth;
Date.prototype.prevMonth = prevMonth;
var today = new Date(2018, 2, 31); //<----- March 31st, 2018
var prevMonth = new Date(today.getTime());
prevMonth.prevMonth();
console.log("Previous month:", prevMonth);
console.log("This month:", today)
var nextMonth = new Date(today.getTime());
nextMonth.nextMonth();
console.log("Next month:", nextMonth);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Dates and time zones are a real pain in JS, so challenge accepted.
I broke it down in two steps:
- Count the days of prev and next month
- Compare with selected day and pick the lowest number
Testcases included
function createUTCDate(year, month, day) {
return new Date(Date.UTC(year, month, day));
}
function splitDate(date) {
return {
year: date.getUTCFullYear(),
month: date.getUTCMonth(),
day: date.getUTCDate()
};
}
function numberOfDaysInMonth(year, month) {
return new Date(year, month + 1, 0).getDate();
}
function dateNextMonth(dateObj) {
const daysNextMonth = numberOfDaysInMonth(dateObj.year, dateObj.month + 1);
const day = Math.min(daysNextMonth, dateObj.day);
return createUTCDate(dateObj.year, dateObj.month + 1, day);
}
function datePreviousMonth(dateObj) {
const daysPrevMonth = numberOfDaysInMonth(dateObj.year, dateObj.month - 1);
const day = Math.min(daysPrevMonth, dateObj.day);
return createUTCDate(dateObj.year, dateObj.month - 1, day);
}
const log = console.log;
function print(dateString) {
const date = new Date(dateString);
const dateObj = splitDate(date);
log("Previous: ", datePreviousMonth(dateObj).toISOString());
log("Selected: ", date.toISOString());
log("Next: ", dateNextMonth(dateObj).toISOString());
log("--------------");
}
const testCases = [
"2018-03-01 UTC",
"2018-03-31 UTC",
"2018-01-01 UTC",
"2018-12-31 UTC"
];
testCases.forEach(print);
Please note that the hack with new Date(xxx + " UTC") is not according to spec and is just there for testing purposes. Results may vary per browser.
You should choose an input format and construct your dates accordingly.
I handle it in a foolish way by concatenating string
let daysInMonths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
let months = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"];
var target = nexttarget = lasttarget = "29"; //target day
if (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)) {
daysInMonths[1] = 29;
}
function findLastDay(target, month){
if(target > daysInMonths[month]){
target = daysInMonths[month];
}
return target;
}
then
var d = new Date();
var year = d.getFullYear();
var month = d.getMonth();
target = findLastDay(target, month);
var this_month = year+"-"+months[month]+"-"+target;
console.log(this_month);//2018-03-29
// next month
if(month == 11){
nextmonth = 0;
nextyear = year + 1;
}else{
nextmonth = month+1;
nextyear = year;
}
nexttarget = findLastDay(nexttarget, nextmonth);
var next_month = nextyear+"-"+months[nextmonth]+"-"+nexttarget;
console.log(next_month);//2018-04-29
//last month
if(month == 0){
lastmonth = 11;
lastyear = year - 1;
}else{
lastmonth = month - 1;
lastyear = year;
}
lasttarget = findLastDay(lasttarget, lastmonth);
var last_month = lastyear+"-"+months[lastmonth]+"-"+lasttarget;
console.log(last_month);//2018-02-28
Date handling is tricky at the best of times. Don't do this yourself. Use Moment.js.
var target = 31;
var today = moment().date(target).calendar();
// today == '03/31/2018'
var nextMonth = moment().date(target).add(1, 'month').calendar();
// nextMonth == '04/30/2018'
var lastMonth = moment().date(target).subtract(1, 'month').calendar()
// lastMonth == '02/28/2018'
This has been asked (badly) before - I don't think the answer in that post really addressed the issue, and then it went stale. I'm going to attempt to ask it again with a clearer demonstration of the issue.
The implementation of Javascript Date.setMonth() appears not to follow the principle of least surprise. Try this in a browser console:
d = new Date('2017-08-31') // Set to last day of August
d.getMonth() // 7 - months are zero-based
d.setMonth(8) // Try to set the month to 8 (September)
d.getMonth() // 9 - October. WTF Javascript?
Similarly:
d = new Date('2017-10-31')
d.getMonth() // 9
d.setMonth(8)
d.getMonth() // 9 (still?)
Firefox on Linux appears even worse - sometimes returning a date in October, and a result from getMonth() which doesn't match that month!
My question (and I think that of the OP from that linked question) is how to consistently implement a 'next' / 'prev' month function in, e.g. a datepicker? Is there a well known way of doing this which doesn't surprise the user by, for example, skipping September when they start on August 31st and click 'next'? Going from January 31st is even more unpredictable currently - you will end up on either March 2nd or March 3rd, depending on whether it's a leap year or not!
My personal view is that the least surprise would be to move to the last day of the next / previous month. But that requires the setMonth() implementation to care about the number of days in the months in question, not just add / subtract a fixed duration. According to this thread, the moment.js approach is to add / subtract the number of milliseconds in 30 days, which suggests that library would be prone to the same inconsistencies.
It's all simple and logic. Lets take your example and go see what id does.
So the first line
d = new Date('2017-08-31') // Set to last day of August
console.log(d); // "2017-08-31T00:00:00.000Z"
console.log(d.getMonth()); // 7 - months are zero-based
So all good so far. Next step: Your comment says it: // Try to set the month to 8 (September) So it's not done with trying. You either set it to september or you don't. In your example you set it to October. Explanation further down.
d = new Date('2017-08-31') // Set to last day of August
console.log(d); // "2017-08-31T00:00:00.000Z"
console.log(d.getMonth()); // 7 - months are zero-based
d.setMonth(8) // Try to set the month to 8 (September)
console.log(d); // but now I see I was wrong it is (October)
So the good question is WHY? From MDN
Note: Where Date is called as a constructor with more than one
argument, if values are greater than their logical range (e.g. 13 is
provided as the month value or 70 for the minute value), the adjacent
value will be adjusted. E.g. new Date(2013, 13, 1) is equivalent to
new Date(2014, 1, 1), both create a date for 2014-02-01 (note that the
month is 0-based). Similarly for other values: new Date(2013, 2, 1, 0,
70) is equivalent to new Date(2013, 2, 1, 1, 10) which both create a
date for 2013-03-01T01:10:00.
So that sayd September has only 30 Days but the Date Object has 31. This is why it gives you October and not September.
The simplest will be to take the date you have and set it to first day of month. Something like so:
var d = new Date('2017-08-31') // Set to last day of August
// simplest fix take the date you have and set it to first day of month
d = new Date(d.getFullYear(), d.getMonth(), 1);
console.log(d); // "2017-08-31T00:00:00.000Z"
console.log(d.getMonth()); // 7 - months are zero-based
d.setMonth(8) // Set the month to 8 (September)
console.log(d.getMonth()); // get 8 it is (September)
If setMonth is used when adding and subtracting months, then if the date of the start month doesn't exist in the end month, the extra days cause the date to "roll over" to the next month, so 31 March minus 1 month gives 2 or 3 March.
A simple algorithm is to test the start date and end date and if they differ, set the end date to 0 so it goes to the last day of the previous month.
One issue with this is that subtracting 1 month twice may not give the same result as subtracting 2 months once. 31 March 2017 minus one month gives 28 Feb, minus another month gives 28 Jan. Subtract 2 months from 31 March and you get 31 Jan.
C'est la vie.
function addMonths(date, num) {
var d = date.getDate();
date.setMonth(date.getMonth() + num);
if (date.getDate() != d) date.setDate(0);
return date;
}
// Subtract one month from 31 March
var a = new Date(2017,2,31);
console.log(addMonths(a, -1).toString()); // 28 Feb
// Add one month to 31 January
var b = new Date(2017,0,31);
console.log(addMonths(b, 1).toString()); // 28 Feb
// 29 Feb plus 12 months
var c = new Date(2016,1,29)
console.log(addMonths(c, 12).toString()); // 28 Feb
// 29 Feb minus 12 months
var c = new Date(2016,1,29)
console.log(addMonths(c, -12).toString()); // 28 Feb
// 31 Jul minus 1 month
var d = new Date(2016,6,31)
console.log(addMonths(d, -1).toString()); // 30 Jun
Since getMonth() returns an integer number, you can simply implement a generator over the date object, that sets the month + 1 or - 1 so long as your not at month 11 or month 0 respectively.
function nextMonth(dateObj) {
var month = dateObj.getMonth();
if(month != 11) dateObj.setMonth(month + 1);
return dateObj;
}
function prevMonth(dateObj) {
var month = dateObj.getMonth();
if(month != 0) dateObj.setMonth(month - 1);
return dateObj;
}
If you want to match the days in the previous month you can use an object lookup table.
Now, for your last day of the month problem:
function getLastDayofMonth(month) {
var lookUp = {
0:31,
1:28,
2:30,
3:31
};
return lookUp[month];
}
//and then a revised version
function nextMonth(dateObj) {
var month = dateObj.getMonth();
var day = dateObj.getDate();
if(month != 12) dateObj.setMonth(month + 1);
if(getLastDayofMonth(month)<day)dateObj.setDate(getLastDayofMonth(month));
return dateObj;
}
This should work for incrementing the month, you can use a similar strategy to decrement.
// isLeapYear :: Number -> Boolean
const isLeapYear = ((err) => {
return yr => {
// check for the special years, see https://www.wwu.edu/skywise/leapyear.html
if (yr === 0) {
throw err;
}
// after 8 AD, follows 'normal' leap year rules
let passed = true;
// not technically true as there were 13 LY BCE, but hey.
if (yr === 4 || yr < 0 || (yr % 4)) {
passed = false;
} else {
if (yr % 400) {
if (!(yr % 100)) {
passed = false;
}
}
}
return passed;
};
})(new Error('Year zero does not exist, refers to 1 BCE'));
const daysInMonth = [
31,
28,
31,
30,
31,
30,
31,
31,
30,
31,
30,
31
];
// isLastDay :: Number, Number -> Boolean
const isLastDay = (d, m, y) => {
let dm = isLeapYear(y) && m === 1 ? 29 : daysInMonth(m);
return dm === d;
};
// getLastDay :: Number, Number -> Number
const getLastDay = (m, y) => isLeapYear(y) && m === 1 ? 29 : daysInMonth[m];
// incMonth :: Date -> Date
const incMonth = d => {
let dd = new Date(d.getTime());
let day = dd.getDate();
let month = dd.getMonth() + 1;
dd.setDate(5); // should avoid edge-case shenanigans
dd.setMonth(month);
let year = dd.getFullYear();
if (isLastDay(day, month, year)) day = getLastDay(month, year);
dd.setDate(day);
return dd;
};
This was the solution I came up with, which seems small and reliable as far as I can tell. It doesn't need any extra data structures, and relies on setDate(0) to select the last day of the month in the edge cases. Otherwise it leaves the date alone, which is the behaviour I wanted. It also handles wrapping round from one year to the next (in either direction):
function reallySetMonth(dateObj, targetMonth) {
const newDate = new Date(dateObj.setMonth(targetMonth))
if (newDate.getMonth() !== ((targetMonth % 12) + 12) % 12) { // Get the target month modulo 12 (see https://stackoverflow.com/a/4467559/1454454 for details about modulo in Javascript)
newDate.setDate(0)
}
return newDate
}
Note I've only tested this with targetMonth being either one higher or lower than the current month, since I'm using it with 'next' / 'back' buttons. It would need testing further user with arbitrary months.
I have two sets of codes that work. Needed help combining them into one.
This code gets me the difference between two dates. works perfectly:
function test(){
var date1 = new Date(txtbox_1.value);
var date2 = new Date(txtbox_2.value);
var diff = (date2 - date1)/1000;
var diff = Math.abs(Math.floor(diff));
var days = Math.floor(diff/(24*60*60));
var leftSec = diff - days * 24*60*60;
var hrs = Math.floor(leftSec/(60*60));
var leftSec = leftSec - hrs * 60*60;
var min = Math.floor(leftSec/(60));
var leftSec = leftSec - min * 60;
txtbox_3.value = days + "." + hrs; }
source for the above code
The code below by #cyberfly appears to have the answer of excluding sat and sun which is what i needed. source. However, its in jquery and the above code is in JS. Therefore, needed help combining as i lacked that knowledge :(
<script type="text/javascript">
$("#startdate, #enddate").change(function() {
var d1 = $("#startdate").val();
var d2 = $("#enddate").val();
var minutes = 1000*60;
var hours = minutes*60;
var day = hours*24;
var startdate1 = getDateFromFormat(d1, "d-m-y");
var enddate1 = getDateFromFormat(d2, "d-m-y");
var days = calcBusinessDays(new Date(startdate1),new Date(enddate1));
if(days>0)
{ $("#noofdays").val(days);}
else
{ $("#noofdays").val(0);}
});
</script>
EDIT
Made an attempt at combining the codes. here is my sample. getting object expected error.
function test(){
var date1 = new Date(startdate.value);
var date2 = new Date(enddate.value);
var diff = (date2 - date1)/1000;
var diff = Math.abs(Math.floor(diff));
var days = Math.floor(diff/(24*60*60));
var leftSec = diff - days * 24*60*60;
var hrs = Math.floor(leftSec/(60*60));
var leftSec = leftSec - hrs * 60*60;
var min = Math.floor(leftSec/(60));
var leftSec = leftSec - min * 60;
var startdate1 = getDateFromFormat(startdate, "dd/mm/yyyy hh:mm");
var enddate1 = getDateFromFormat(enddate, "dd/mm/yyyy hh:mm");
days = calcBusinessDays(new Date(startdate1),new Date(enddate1));
noofdays.value = days + "." + hrs; }
start: <input type="text" id="startdate" name="startdate" value="02/03/2015 00:00">
end: <input type="text" id="enddate" name="enddate" value="02/03/2015 00:01">
<input type="text" id="noofdays" name="noofdays" value="">
When determining the number of days between two dates, there are lots of decisions to be made about what is a day. For example, the period 1 Feb to 2 Feb is generally one day, so 1 Feb to 1 Feb is zero days.
When adding the complexity of counting only business days, things get a lot tougher. E.g. Monday 2 Feb 2015 to Friday 6 February is 4 elapsed days (Monday to Tuesday is 1, Monday to Wednesday is 2, etc.), however the expression "Monday to Friday" is generally viewed as 5 business days and the duration Mon 2 Feb to Sat 7 Feb should also be 4 business days, but Sunday to Saturday should be 5.
So here's my algorithm:
Get the total number of whole days between the two dates
Divide by 7 to get the number of whole weeks
Multiply the number of weeks by two to get the number of weekend days
Subtract the number of weekend days from the whole to get business days
If the number of total days is not an even number of weeks, add the numbe of weeks * 7 to the start date to get a temp date
While the temp date is less than the end date:
if the temp date is not a Saturday or Sunday, add one the business days
add one to the temp date
That's it.
The stepping part at the end can probably be replaced by some other algorithm, but it will never loop for more than 6 days so it's a simple and reasonably efficient solution to the issue of uneven weeks.
Some consequences of the above:
Monday to Friday is 4 business days
Any day to the same day in a different week is an even number of weeks and therefore an even mutiple of 5, e.g. Monday 2 Feb to Monday 9 Feb and Sunday 1 Feb to Sunday 8 Feb are 5 business days
Friday 6 Feb to Sunday 7 Feb is zero business days
Friday 6 Feb to Monday 9 Feb is one business day
Sunday 8 Feb to: Sunday 15 Feb, Sat 14 Feb and Fri 13 Feb are all 5 business days
Here's the code:
// Expects start date to be before end date
// start and end are Date objects
function dateDifference(start, end) {
// Copy date objects so don't modify originals
var s = new Date(+start);
var e = new Date(+end);
// Set time to midday to avoid dalight saving and browser quirks
s.setHours(12,0,0,0);
e.setHours(12,0,0,0);
// Get the difference in whole days
var totalDays = Math.round((e - s) / 8.64e7);
// Get the difference in whole weeks
var wholeWeeks = totalDays / 7 | 0;
// Estimate business days as number of whole weeks * 5
var days = wholeWeeks * 5;
// If not even number of weeks, calc remaining weekend days
if (totalDays % 7) {
s.setDate(s.getDate() + wholeWeeks * 7);
while (s < e) {
s.setDate(s.getDate() + 1);
// If day isn't a Sunday or Saturday, add to business days
if (s.getDay() != 0 && s.getDay() != 6) {
++days;
}
}
}
return days;
}
I don't know how it compares to jfriend00's answer or the code you referenced, if you want the period to be inclusive, just add one if the start or end date are a business day.
Here's a simple function to calculate the number of business days between two date objects. As designed, it does not count the start day, but does count the end day so if you give it a date on a Tuesday of one week and a Tuesday of the next week, it will return 5 business days. This does not account for holidays, but does work properly across daylight savings changes.
function calcBusinessDays(start, end) {
// This makes no effort to account for holidays
// Counts end day, does not count start day
// make copies we can normalize without changing passed in objects
var start = new Date(start);
var end = new Date(end);
// initial total
var totalBusinessDays = 0;
// normalize both start and end to beginning of the day
start.setHours(0,0,0,0);
end.setHours(0,0,0,0);
var current = new Date(start);
current.setDate(current.getDate() + 1);
var day;
// loop through each day, checking
while (current <= end) {
day = current.getDay();
if (day >= 1 && day <= 5) {
++totalBusinessDays;
}
current.setDate(current.getDate() + 1);
}
return totalBusinessDays;
}
And, the jQuery + jQueryUI code for a demo:
// make both input fields into date pickers
$("#startDate, #endDate").datepicker();
// process click to calculate the difference between the two days
$("#calc").click(function(e) {
var diff = calcBusinessDays(
$("#startDate").datepicker("getDate"),
$("#endDate").datepicker("getDate")
);
$("#diff").html(diff);
});
And, here's a simple demo built with the date picker in jQueryUI: http://jsfiddle.net/jfriend00/z1txs10d/
const firstDate = new Date("December 30, 2020");
const secondDate = new Date("January 4, 2021");
const daysWithOutWeekEnd = [];
for (var currentDate = new Date(firstDate); currentDate <= secondDate; currentDate.setDate(currentDate.getDate() + 1)) {
// console.log(currentDate);
if (currentDate.getDay() != 0 && currentDate.getDay() != 6) {
daysWithOutWeekEnd.push(new Date(currentDate));
}
}
console.log(daysWithOutWeekEnd, daysWithOutWeekEnd.length);
#RobG has given an excellent algorithm to separate business days from weekends.
I think the only problem is if the starting days is a weekend, Saturday or Sunday, then the no of working days/weekends will one less.
Corrected code is below.
function dateDifference(start, end) {
// Copy date objects so don't modify originals
var s = new Date(start);
var e = new Date(end);
var addOneMoreDay = 0;
if( s.getDay() == 0 || s.getDay() == 6 ) {
addOneMoreDay = 1;
}
// Set time to midday to avoid dalight saving and browser quirks
s.setHours(12,0,0,0);
e.setHours(12,0,0,0);
// Get the difference in whole days
var totalDays = Math.round((e - s) / 8.64e7);
// Get the difference in whole weeks
var wholeWeeks = totalDays / 7 | 0;
// Estimate business days as number of whole weeks * 5
var days = wholeWeeks * 5;
// If not even number of weeks, calc remaining weekend days
if (totalDays % 7) {
s.setDate(s.getDate() + wholeWeeks * 7);
while (s < e) {
s.setDate(s.getDate() + 1);
// If day isn't a Sunday or Saturday, add to business days
if (s.getDay() != 0 && s.getDay() != 6) {
++days;
}
//s.setDate(s.getDate() + 1);
}
}
var weekEndDays = totalDays - days + addOneMoreDay;
return weekEndDays;
}
JSFiddle link is https://jsfiddle.net/ykxj4k09/2/
First Get the Number of Days in a month
totalDays(month, year) {
return new Date(year, month, 0).getDate();
}
Then Get No Of Working Days In A Month By removing Saturday and Sunday
totalWorkdays() {
var d = new Date(); // to know present date
var m = d.getMonth() + 1; // to know present month
var y = d.getFullYear(); // to knoow present year
var td = this.totalDays(m, y);// to get no of days in a month
for (var i = 1; i <= td; i++) {
var s = new Date(y, m - 1, i);
if (s.getDay() != 0 && s.getDay() != 6) {
this.workDays.push(s.getDate());// working days
}else {
this.totalWeekDays.push(s.getDate());//week days
}
}
this.totalWorkingDays = this.workDays.length;
}
I thought the above code snippets others shared are lengthy.
I am sharing a concise snippet that gives date after considering the total number of days specified. we can also customize dates other than Saturdays and Sundays.
function getBusinessDays(dateObj, days) {
for (var i = 0; i < days; i++) {
if (days > 0) {
switch (dateObj.getDay()) {
// 6 being Saturday and 0 being Sunday.
case 6, 0:
dateObj.setDate(dateObj.getDate() + 2)
break;
//5 = Friday.
case 5:
dateObj.setDate(dateObj.getDate() + 3)
break;
//handle Monday, Tuesday, Wednesday and Thursday!
default:
dateObj.setDate(dateObj.getDate() + 1)
//console.log(dateObj)
break;
}
}
}
return dateObj;
}
console.log(getBusinessDays(new Date(), 11))
//Mon Dec 20 2021 18:56:01 GMT+0530 (India Standard Time)
I am working with a group that meets the second monday of the month and they want their site to reflect the NEXT meeting date. I have the script to show this months second monday, but i am having trouble with the if else statement. I need it to reflect the next upcoming event and not just this months date. IE. this months event date was Aug 13 2012 which is past the current date (aug 21 2012). I would like it to move to the next available date Sept 10 2012. Below is the code i have so far.
<script type="text/javascript">
Date.prototype.x = function () {
var d = new Date (this.getFullYear(), this.getMonth(), 1, 0, 0, 0)
d.setDate (d.getDate() + 15 - d.getDay())
return d
}
Date.prototype.getSecondMonday = function () {
var d = new Date (this.getFullYear(), 1, 1, 0, 0, 0)
d.setMonth(this.getMonth()+1)
d.setDate (d.getDate() + 15 - d.getDay())
return d
}
var today = new Date()
var todayDate = today.toDateString()
if (Date.prototype.x>todayDate)
{
document.write (new Date().x().toDateString());
}
else
{
document.write (new Date().getSecondMonday().toDateString());
}
</script>
If the date of the second Monday of the current month is less than the current date,
call the function on the first of the next month.
Date.prototype.nextSecondMonday= function(){
var temp= new Date(this), d= temp.getDate(), n= 1;
while(temp.getDay()!= 1) temp.setDate(++n);
temp.setDate(n+7);
if(d>temp.getDate()){
temp.setMonth(temp.getMonth()+1, 1);
return temp.nextSecondMonday();
}
return temp.toLocaleDateString();
}
/* tests
var x= new Date(2012, 7, 22);
x.nextSecondMonday()
Monday, September 10, 2012
var x= new Date(2012, 7, 12);
x.nextSecondMonday()
Monday, August 13, 2012
*/
You're missing () for the x function, so it's not executing it. :) Should be:
if (Date.prototype.x() > todayDate)
UPDATE:
Here is a fixed/working version of the logic cleaned up (and probably overly commented, but I guess it's at least there if anyone needs it).
Date.prototype.nextSecondMonday = function (){
// Load the month.
var target = new Date(this.getFullYear(), this.getMonth(), 1, 0, 0, 0);
var today = new Date();
// Check to see if the 1st is on a Monday.
var isMonday = (target.getDay() == 1);
// Jump ahead two weeks from the 1st, and move back the appropriate number of days to reach the preceding Monday.
// i.e. If the 1st is a Thursday, we would move back three days.
var targetDate = 15 - (target.getDay() - 1);
// Quick adjustment if the 1st is a Monday.
if (isMonday) targetDate -= 7;
// Move to the second Monday in the month.
target.setDate(targetDate);
// Second Monday is before today's date, so find the second Monday next month.
if (today > target) {
//return "<em>" + target.toLocaleDateString() + " is in the past...</em>";
target.setMonth(target.getMonth() + 1);
return target.nextSecondMonday();
}
// Format and return string date of second Monday.
return target.toLocaleDateString();
}
// Working test for the year 2012.
//for (var i = 0; i < 12; i++)
//$("#log").append(new Date(2012, i).nextSecondMonday() + "<br /><br />");