Javascript array provide all days/months between two date ranges - javascript

I'm trying to create a function that will accept three input variables (start date, end date, granularity) and will output an array with all dates in between. The output will be either daily or monthly granularity based on the input. For example, if I'm looking at Jan 2015 - Feb 2015:
Monthly will be [2015-01, 2015-02]
Daily will be [2015-01-01, 2015-01-02, 2015-01-03, ....,2015-02-27, 2015-02-28]
I've coded something based off some other posts and logically it seems like it should work to me, but for some reason when looking at daily granularity every month returns with 31 days. The below script only looks at the daily granularity (monthly will be easier to do) - can someone take a look and see what I'm doing wrong, and if there is a more efficient way to do this?
Input format for dates are "yyyy-mm"
I realize this is probably a rookie mistake, but at least I'll learn :)
Thanks
function dateRange2 (startDate,endDate,granularity) {
var dates = [];
var d0 = startDate.split('-');
var d1 = endDate.split('-');
var months31 = [1,3,5,7,8,10,12];
var months30 = [4,6,9,11];
for (var y = d0[0]; y <= d1[0]; y++) {
for (var m = d0[1]; m <= d1[1]; m++) {
if (m in months31) {
for (var d =1;d <=31; d++) {
dates.push(y+"-"+m+"-"+d);
}
} ///// Issue seems to be here - not switching over to next clause
else if (m in months30) {
for (var d =1; d <=30; d++) {
dates.push(y+"-"+m+"-"+d);
}
} else if (m=2 && y=2016) {
for (var d =1; d <=29; d++) {
dates.push(y+"-"+m+"-"+d);
}
} else if (m=2 && y!=2016) {
for (var d =1; d <=28; d++) {
dates.push(y+"-"+m+"-"+d);
}
}
}
}
return dates;
}

instead of in operator, use indexOf as below:
if (months31.indexOf(m) >= 0){
}
else if (months30.index(m) >= 0){
}
in operator will return whether a given value is a property of object or not:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in

Others have shown how to fix your code. To answer "more efficient" is difficult since you haven't provided criteria for that. But if you want robust code with separation of concerns, consider separate functions to parse date strings, format the dates and to calculate the ranges.
There are many small libraries for parsing and formatting dates that are handy if you have many different formats to accommodate, but if not then the following simple functions should suit.
If the documentation and comments are sufficient, please ask and I'll provide updates.
/* Parse date string in ISO 8601 format as local
** #param {string} s - Date string like 2016-04-01
** #returns {Date} If date is invalid, returns an invalid Date
*/
function parseISODate(s) {
var b = s.split(/\D/);
var d = new Date(b[0], b[1]? b[1] - 1 : 0, b[2] || 1);
return d && d.getMonth() == b[1] - 1? d : new Date(NaN);
}
/* Return an ISO 8601 formatted date string based on local time
** Only works for positive years (i.e. doesn't do -ve year)
** #param {Date} date - date object to create date string from
** #returns {string} dates string in yyyy-mm-dd format or default from
** Date.prototype.toString (i.e. "Invalid Date")
*/
function toISODate(date) {
return date.getDate()? ('000' + date.getFullYear()).slice(-4) + '-' +
('0' + (date.getMonth() + 1)).slice(-2) + '-' +
('0' + date.getDate()).slice(-2) : date.toString();
}
/* Generate an array of ISO 8601 formatted date strings for a
** range of dates inclusive of start and end. Either monthly
** or daily intervals (default is daily).
** #param {string} fromDate - start date in ISO 8601 format
** #param {string} toDate - end date in ISO 8601 format
** #param {boolean} monthly - return monthly intervals
** #returns {Array} of date strings. If either fromDate or
** toDate are invalid, returns undefined.
*/
function genDatesInRange(fromDate, toDate, monthly) {
var s = parseISODate(fromDate);
var e = parseISODate(toDate);
var dates = [];
// Check that dates are valid
if (!s.getDate() || !e.getDate()) return;
// If monthly, set start to 1st of start month and e to 1st of end month
if (monthly) {
s.setDate(1);
e.setDate(1);
}
while (s <= e) {
dates.push(monthly? toISODate(s).substr(0,7) : toISODate(s));
if (monthly) {
s.setMonth(s.getMonth() + 1);
} else {
s.setDate(s.getDate() + 1);
}
}
return dates;
}
// Daily
console.log(genDatesInRange('2015-05-23','2015-06-03')); // Daily
console.log(genDatesInRange('2015-09-03','2016-03-13', true)); // Monthly

Figured it out after some trial and error - it may not be the prettiest code but it does the job... ideally I'd have added each commented line as part of a separate function.
I also noticed that each case works using four "if" arguments without requiring "else if".
function dateRangetest (start_month,start_year,end_month,end_year,granularity) {
var dates = [];
var d0 = [start_year,start_month];
var d1 = [end_year,end_month];
// var d0 = [2014,6] FOR TESTING
// var d1 = [2016,4]
switch (granularity) {
case "Daily":
for (var y = d0[0]; y <= d1[0]; y++) {
if ((y == d0[0]) && (d0[0] != d1[0])) { // if year=start_year && year != end year ... start from start_month and loop up to month 12
for (var m = d0[1]; m <= 12; m++) {
for (var d =1;d <= monthday(m,y); d++) {
dates.push(y+"-"+m+"-"+d)
}
}
}
if ((y != d0[0]) && (y!= d1[0])) { // if year != start_year && year != end year .... start from month 1 to month 12 - this would 2015 data in pulling Dec 2014 - April 2016
for (var m = 1; m <= 12; m++) {
for (var d =1;d <= monthday(m,y); d++) {
dates.push(y+"-"+m+"-"+d)
}
}
}
if ((y != d0[0]) && (y == d1[0])) { // if year !=start_year && year = end_year .... start from month 1 up until end_month
for (var m = 1; m <= d1[1]; m++) {
for (var d =1;d <= monthday(m,y); d++) {
dates.push(y+"-"+m+"-"+d)
}
}
}
if ((y == d0[0]) && (y == d1[0])) { /// if year=start_year && year = end_year .... start from start_month to end_month
for (var m = d0[1]; m <= d1[1]; m++) {
for (var d =1;d <= monthday(m,y); d++) {
dates.push(y+"-"+m+"-"+d)
}
}
}
}
break;
case "Monthly":
for (var y = d0[0]; y <= d1[0]; y++) {
if ((y == d0[0]) && (d0[0] != d1[0])) { // if year=start_year && year != end year ... start from start_month and loop up to month 12
for (var m = d0[1]; m <= 12; m++) {
dates.push(y+"-"+m)
}
}
if ((y != d0[0]) && (y!= d1[0])) { // if year != start_year && year != end year .... start from month 1 to month 12 - this would 2015 data in pulling Dec 2014 - April 2016
for (var m = 1; m <= 12; m++) {
dates.push(y+"-"+m)
}
}
if ((y != d0[0]) && (y == d1[0])) { // if year !=start_year && year = end_year .... start from month 1 up until end_month
for (var m = 1; m <= d1[1]; m++) {
dates.push(y+"-"+m)
}
}
if ((y == d0[0]) && (y == d1[0])) { /// if year=start_year && year = end_year .... start from start_month to end_month
for (var m = d0[1]; m <= d1[1]; m++) {
dates.push(y+"-"+m)
}
}
}
}
return dates
}
/// Function for Day Date Range
function monthday (month,year) {
var months31 = [1,3,5,7,8,10,12]
var months30 = [4,6,9,11]
var leapyear = [2016,2020,2024,2028,2032]
if (months31.indexOf(month) >=0){
var result = 31}
else if (months30.indexOf(month) >=0){
var result = 30}
else if (month==2 && leapyear.indexOf(year) >=0){
var result = 29}
else if (month==2 && year != 2016){
var result = 28}
return result
}

Related

In IE date is retrieved as NaN [duplicate]

I'm trying to build a little calendar in JavaScript. I have my dates working great in Firefox and Chrome, but in IE the date functions are returning NaN.
Here is the function :
function buildWeek(dateText){
var headerDates='';
var newDate = new Date(dateText);
for(var d=0;d<7;d++){
headerDates += '<th>' + newDate + '</th>';
newDate.setDate(newDate.getDate()+1);
}
jQuery('div#headerDates').html('<table><tr>'+headerDates+'</tr></table>');
}
dateText is the Monday of the current week which is actually set in php in the format of 'm, d, Y', e.g. "02, 01, 2010".
From a mysql datetime/timestamp format:
var dateStr="2011-08-03 09:15:11"; //returned from mysql timestamp/datetime field
var a=dateStr.split(" ");
var d=a[0].split("-");
var t=a[1].split(":");
var date = new Date(d[0],(d[1]-1),d[2],t[0],t[1],t[2]);
I hope is useful for someone.
Works in IE FF Chrome
The Date constructor accepts any value. If the primitive [[value]] of the argument is number, then the Date that is created has that value. If primitive [[value]] is String, then the specification only guarantees that the Date constructor and the parse method are capable of parsing the result of Date.prototype.toString and Date.prototype.toUTCString()
A reliable way to set a Date is to construct one and use the setFullYear and setTime methods.
An example of that appears here:
http://jibbering.com/faq/#parseDate
ECMA-262 r3 does not define any date formats. Passing string values to the Date constructor or Date.parse has implementation-dependent outcome. It is best avoided.
Edit:
The entry from comp.lang.javascript FAQ is:
An Extended ISO 8601 local date format YYYY-MM-DD can be parsed to a Date with the following:-
/**Parses string formatted as YYYY-MM-DD to a Date object.
* If the supplied string does not match the format, an
* invalid Date (value NaN) is returned.
* #param {string} dateStringInRange format YYYY-MM-DD, with year in
* range of 0000-9999, inclusive.
* #return {Date} Date object representing the string.
*/
function parseISO8601(dateStringInRange) {
var isoExp = /^\s*(\d{4})-(\d\d)-(\d\d)\s*$/,
date = new Date(NaN), month,
parts = isoExp.exec(dateStringInRange);
if(parts) {
month = +parts[2];
date.setFullYear(parts[1], month - 1, parts[3]);
if(month != date.getMonth() + 1) {
date.setTime(NaN);
}
}
return date;
}
Don't use "new Date()", because it takes the input date string as local time:
new Date('11/08/2010').getTime()-new Date('11/07/2010').getTime(); //90000000
new Date('11/07/2010').getTime()-new Date('11/06/2010').getTime(); //86400000
we should use "NewDate()", it takes the input as GMT time:
function NewDate(str)
{str=str.split('-');
var date=new Date();
date.setUTCFullYear(str[0], str[1]-1, str[2]);
date.setUTCHours(0, 0, 0, 0);
return date;
}
NewDate('2010-11-07').toGMTString();
NewDate('2010-11-08').toGMTString();
Here's another approach that adds a method to the Date object
usage: var d = (new Date()).parseISO8601("1971-12-15");
/**
* Parses the ISO 8601 formated date into a date object, ISO 8601 is YYYY-MM-DD
*
* #param {String} date the date as a string eg 1971-12-15
* #returns {Date} Date object representing the date of the supplied string
*/
Date.prototype.parseISO8601 = function(date){
var matches = date.match(/^\s*(\d{4})-(\d{2})-(\d{2})\s*$/);
if(matches){
this.setFullYear(parseInt(matches[1]));
this.setMonth(parseInt(matches[2]) - 1);
this.setDate(parseInt(matches[3]));
}
return this;
};
I always store my date in UTC time.
This is my own function made from the different functions I found in this page.
It takes a STRING as a mysql DATETIME format (example : 2013-06-15 15:21:41). The checking with the regex is optional. You can delete this part to improve performance.
This function return a timestamp.
The DATETIME is considered as a UTC date.
Be carefull : If you expect a local datetime, this function is not for you.
function datetimeToTimestamp(datetime)
{
var regDatetime = /^[0-9]{4}-(?:[0]?[0-9]{1}|10|11|12)-(?:[012]?[0-9]{1}|30|31)(?: (?:[01]?[0-9]{1}|20|21|22|23)(?::[0-5]?[0-9]{1})?(?::[0-5]?[0-9]{1})?)?$/;
if(regDatetime.test(datetime) === false)
throw("Wrong format for the param. `Y-m-d H:i:s` expected.");
var a=datetime.split(" ");
var d=a[0].split("-");
var t=a[1].split(":");
var date = new Date();
date.setUTCFullYear(d[0],(d[1]-1),d[2]);
date.setUTCHours(t[0],t[1],t[2], 0);
return date.getTime();
}
Here's a code snippet that fixes that behavior of IE
(v['date'] is a comma separated date-string, e.g. "2010,4,1"):
if($.browser.msie){
$.lst = v['date'].split(',');
$.tmp = new Date(parseInt($.lst[0]),parseInt($.lst[1])-1,parseInt($.lst[2]));
} else {
$.tmp = new Date(v['date']);
}
The previous approach didn't consider that JS Date month is ZERO based...
Sorry for not explaining too much, I'm at work and just thought this might help.
Here's my approach:
var parseDate = function(dateArg) {
var dateValues = dateArg.split('-');
var date = new Date(dateValues[0],dateValues[1],dateValues[2]);
return date.format("m/d/Y");
}
replace ('-') with the delimeter you're using.
Send the date text and format in which you are sending the datetext in the below method. It will parse and return as date and this is independent of browser.
function cal_parse_internal(val, format) {
val = val + "";
format = format + "";
var i_val = 0;
var i_format = 0;
var x, y;
var now = new Date(dbSysCurrentDate);
var year = now.getYear();
var month = now.getMonth() + 1;
var date = now.getDate();
while (i_format < format.length) {
// Get next token from format string
var c = format.charAt(i_format);
var token = "";
while ((format.charAt(i_format) == c) && (i_format < format.length)) {
token += format.charAt(i_format++);
}
// Extract contents of value based on format token
if (token == "yyyy" || token == "yy" || token == "y") {
if (token == "yyyy") { x = 4; y = 4; }
if (token == "yy") { x = 2; y = 2; }
if (token == "y") { x = 2; y = 4; }
year = _getInt(val, i_val, x, y);
if (year == null) { return 0; }
i_val += year.length;
if (year.length == 2) {
if (year > 70) {
year = 1900 + (year - 0);
} else {
year = 2000 + (year - 0);
}
}
} else if (token == "MMMM") {
month = 0;
for (var i = 0; i < MONTHS_LONG.length; i++) {
var month_name = MONTHS_LONG[i];
if (val.substring(i_val, i_val + month_name.length) == month_name) {
month = i + 1;
i_val += month_name.length;
break;
}
}
if (month < 1 || month > 12) { return 0; }
} else if (token == "MMM") {
month = 0;
for (var i = 0; i < MONTHS_SHORT.length; i++) {
var month_name = MONTHS_SHORT[i];
if (val.substring(i_val, i_val + month_name.length) == month_name) {
month = i + 1;
i_val += month_name.length;
break;
}
}
if (month < 1 || month > 12) { return 0; }
} else if (token == "MM" || token == "M") {
month = _getInt(val, i_val, token.length, 2);
if (month == null || month < 1 || month > 12) { return 0; }
i_val += month.length;
} else if (token == "dd" || token == "d") {
date = _getInt(val, i_val, token.length, 2);
if (date == null || date < 1 || date > 31) { return 0; }
i_val += date.length;
} else {
if (val.substring(i_val, i_val+token.length) != token) {return 0;}
else {i_val += token.length;}
}
}
// If there are any trailing characters left in the value, it doesn't match
if (i_val != val.length) { return 0; }
// Is date valid for month?
if (month == 2) {
// Check for leap year
if ((year%4 == 0 && year%100 != 0) || (year%400 == 0)) { // leap year
if (date > 29) { return false; }
} else {
if (date > 28) { return false; }
}
}
if (month == 4 || month == 6 || month == 9 || month == 11) {
if (date > 30) { return false; }
}
return new Date(year, month - 1, date);
}
The Date constructor in JavaScript needs a string in one of the date formats supported by the parse() method.
Apparently, the format you are specifying isn't supported in IE, so you'll need to either change the PHP code, or parse the string manually in JavaScript.
You can use the following code to parse ISO8601 date string:
function parseISO8601(d) {
var timestamp = d;
if (typeof (d) !== 'number') {
timestamp = Date.parse(d);
}
return new Date(timestamp);
};
Try out using getDate feature of datepicker.
$.datepicker.formatDate('yy-mm-dd',new Date(pField.datepicker("getDate")));
I tried all the above solution but nothing worked for me.
I did some brainstorming and found this and worked fine in IE11 as well.
value="2020-08-10 05:22:44.0";
var date=new Date(value.replace(" ","T")).$format("d/m/yy h:i:s");
console.log(date);
if $format is not working for you use format only.

Creating date list from start_date & end_date input

I'm trying to create a range of dates that can be parsed by daily or weekly granularity and have hit a roadblock. I have a function that allows you to enter a start month,start year, end month, end year, and granularity and returns the date range according to the granularity.
I am able to run this successfully for daily granularity, but when running for weekly granularity, every new month restarts at 1. Looking at the below example:
2015-11-01 2015-11-08 2015-11-15 2015-11-22 2015-11-29 2015-12-01 2015-12-08 2015-12-15 2015-12-22 2015-12-29 2016-01-01 2016-01-08
I would like it to appear as:
2015-11-01 2015-11-08 2015-11-15 2015-11-22 2015-11-29 2015-12-06 2015-12-13 2015-12-20 2015-12-27 2016-01-03
I understand why this is happening - at the end of each for loop the variable "d" is force set to 1. I tried to add additional if statements after the "dates.push(y+"-"+m+"-"+d)" but I know that is really inefficient coding. I added the snippit of what I tried below:
if monthday(m,y) – d < 7 {
d = monthday(m,y) –d
if m = 12 {
m = 1
y = y+1
else {
m = m+1
}
}
The functions used are copied below.
Any insight into how I could do this would be much appreciated!
/// Function to decide how many days in a month
function monthday (month,year) {
var months31 = [1,3,5,7,8,10,12]
var months30 = [4,6,9,11]
var leapyear = [2016,2020,2024,2028,2032] /// if this code is still being used in 2036 I'll eat my hat
if (months31.indexOf(month) >=0){
var result = 31}
else if (months30.indexOf(month) >=0){
var result = 30}
else if (month==2 && leapyear.indexOf(year) >=0){
var result = 29}
else if (month==2 && year != 2016){
var result = 28}
return result
}
////Date Range - calculates # of days/weeks between 2 date ranges
function dateRange (start_month,start_year,end_month,end_year,granularity) {
var dates = [];
var d0 = [start_year,start_month];
var d1 = [end_year,end_month];
switch (granularity) {
case "Daily":
for (var y = d0[0]; y <= d1[0]; y++) {
if ((y == d0[0]) && (d0[0] != d1[0])) { // if year=start_year && year != end year ... start from start_month and loop up to month 12
for (var m = d0[1]; m <= 12; m++) {
for (var d =1;d <= monthday(m,y); d++) {
dates.push(y+"-"+m+"-"+d)
}
}
}
if ((y != d0[0]) && (y!= d1[0])) { // if year != start_year && year != end year .... start from month 1 to month 12 - this would 2015 data in pulling Dec 2014 - April 2016
for (var m = 1; m <= 12; m++) {
for (var d =1;d <= monthday(m,y); d++) {
dates.push(y+"-"+m+"-"+d)
}
}
}
if ((y != d0[0]) && (y == d1[0])) { // if year !=start_year && year = end_year .... start from month 1 up until end_month
for (var m = 1; m <= d1[1]; m++) {
for (var d =1;d <= monthday(m,y); d++) {
dates.push(y+"-"+m+"-"+d)
}
}
}
if ((y == d0[0]) && (y == d1[0])) { /// if year=start_year && year = end_year .... start from start_month to end_month
for (var m = d0[1]; m <= d1[1]; m++) {
for (var d =1;d <= monthday(m,y); d++) {
dates.push(y+"-"+m+"-"+d)
}
}
}
}
break;
case "Weekly":
for (var y = d0[0]; y <= d1[0]; y++) {
if ((y == d0[0]) && (d0[0] != d1[0])) { // if year=start_year && year != end year ... start from start_month and loop up to month 12
for (var m = d0[1]; m <= 12; m++) {
for (var d =1;d <= monthday(m,y); d+=7) {
dates.push(y+"-"+m+"-"+d)
}
}
}
if ((y != d0[0]) && (y!= d1[0])) { // if year != start_year && year != end year .... start from month 1 to month 12 - this would 2015 data in pulling Dec 2014 - April 2016
for (var m = 1; m <= 12; m++) {
for (var d =1;d <= monthday(m,y); d+=7) {
dates.push(y+"-"+m+"-"+d)
}
}
}
if ((y != d0[0]) && (y == d1[0])) { // if year !=start_year && year = end_year .... start from month 1 up until end_month
for (var m = 1; m <= d1[1]; m++) {
for (var d =1;d <= monthday(m,y); d+=7) {
dates.push(y+"-"+m+"-"+d)
}
}
}
if ((y == d0[0]) && (y == d1[0])) { /// if year=start_year && year = end_year .... start from start_month to end_month
for (var m = d0[1]; m <= d1[1]; m++) {
for (var d =1;d <= monthday(m,y); d+=7) {
dates.push(y+"-"+m+"-"+d)
}
}
}
}
break;
}
return dates
}
You don't need to control the month length, let Date object do it for you.
function formatDate(date) {
return date.getFullYear() + '-' + ('0' + (date.getMonth() + 1)).substr(-2) + '-' + ('0' + date.getDate()).substr(-2);
}
function dateRange (start_month,start_year,end_month,end_year,granularity) {
var date = new Date(start_year + '-' + start_month + '-01');
var endDate = new Date(end_year + '-' + end_month + '-01');
var arr = [];
while(date < endDate) {
arr.push(formatDate(date));
switch (granularity) {
case 'Daily':
date.setDate(date.getDate() + 1); // date++
break;
case 'Weekly':
date.setDate(date.getDate() + 7); // week++
break;
case 'Monthly':
date.setMonth(date.getMonth() + 1);
break;
// ...
}
}
return arr;
}
Is it something you were looking for?

Find the number of business days between two dates in Microsoft Dynamics CRM 2011

All, I am trying to use javascript to setValue a integer into a field based on the dates in a start and end field. I'm not even sure what the correct syntax for finding the day of the week with CRM javascript. Here is my code so far.
function netbdays_change() {
var startday = Xrm.Page.getAttribute("new_quotestart").getValue();
var endday = Xrm.Page.getAttribute("new_quoteend").getValue();
cycletime = endday - startday;
Xrm.Page.getAttribute("new_setcycletime").setValue(cycletime);
}
Try this:
function netbdays_change() {
var startday = Xrm.Page.getAttribute("new_quotestart").getValue().getDay();
var endday = Xrm.Page.getAttribute("new_quoteend").getValue().getDay();
cycletime = endday - startday;
Xrm.Page.getAttribute("new_setcycletime").setValue(cycletime);
}
getDay() returns a 0 based representation of the day of the week.
http://www.w3schools.com/jsref/jsref_getday.asp
If you want to calculate the number of days between 2 dates, try this:
function netbdays_change() {
var startday = Xrm.Page.getAttribute("new_quotestart").getValue();
var endday = Xrm.Page.getAttribute("new_quoteend").getValue();
cycletime = Math.abs(endday - startday)
Xrm.Page.getAttribute("new_setcycletime").setValue(cycletime / 86400000);
}
I finally figured out the solution: Feel Free to use everyone. :)
function netbdays_change() {
var startdays = Xrm.Page.getAttribute("new_dateqaassigned").getValue();
var enddays = Xrm.Page.getAttribute("new_quotecomplete").getValue();
var cycletime = Math.abs(enddays - startdays) / 86400000; // This first part now works
startday = Xrm.Page.getAttribute("new_dateqaassigned").getValue().getDay();
endday = Xrm.Page.getAttribute("new_quotecomplete").getValue().getDay();
var x = startday; // day of the week
var y = 0; // number of business days for output
var z = 0; // augment up to the total number of days
while (x <= 7 && z <= cycletime) {
if (x > 0 && x < 6) {
y++;
}
x++;
z++;
}
x = 0;
while (x <= 7 && z <= cycletime) {
if (x > 0 && x < 6) {
y++;
}
x++;
z++;
if (x == 6) {
x = 0;
}
}
Xrm.Page.getAttribute("new_quotetotalcycletime").setValue(y);
}
Here's my code:
function GetBusinessDays(startDate, endDate)
{
if (startDate != null && endDate != null)
{
var cycletime = (Math.abs(endDate - startDate) / 86400000) + 1;
var startday = startDate.getDay();
var x = startday; // day of the week
var y = 0; // number of business days for output
var z = 0; // augment up to the total number of days
while (z < cycletime) {
if (x > 0 && x < 6)
{
y++;
}
x++;
z++;
if (x > 6)
{
x = 0;
}
}
return y;
}
return null;
}

add/subtract business days in Javascript

I need a Date.prototype.addBusDays function
that'll take an integer as the number of working days to add to the date.
However, there are two considerations: 1. Weekends, 2. Holidays (which I imagine would be a preset array to compare against. If beginning date and end date contain 3 holidays, then you push out the end date by 3)
I have come across some scripts online, one dilemma I can think of is, lets say you address all the weekends first, then you do the holidays, what if you +1 day (due to holiday), and your end date is pushed into a weekends again...<
Any ideas?
Thanks!
EDIT:
This is a part of a scheduling tool I am developing, which mean the dates will be tied to tasks which are linked together. Adding 1 day to a task, will trigger a recalculation of everything tied to it, potentially all dates in the database.
Datageek's solution helped me but I needed to augment it. This still doesn't do holidays but does do working days with the option of including Sat and/or Sun, and does support adding negative days:-
function AddWorkingDays(datStartDate, lngNumberOfWorkingDays, blnIncSat, blnIncSun) {
var intWorkingDays = 5;
var intNonWorkingDays = 2;
var intStartDay = datStartDate.getDay(); // 0=Sunday ... 6=Saturday
var intOffset;
var intModifier = 0;
if (blnIncSat) { intWorkingDays++; intNonWorkingDays--; }
if (blnIncSun) { intWorkingDays++; intNonWorkingDays--; }
var newDate = new Date(datStartDate)
if (lngNumberOfWorkingDays >= 0) {
// Moving Forward
if (!blnIncSat && blnIncSun) {
intOffset = intStartDay;
} else {
intOffset = intStartDay - 1;
}
// Special start Saturday rule for 5 day week
if (intStartDay == 6 && !blnIncSat && !blnIncSun) {
intOffset -= 6;
intModifier = 1;
}
} else {
// Moving Backward
if (blnIncSat && !blnIncSun) {
intOffset = intStartDay - 6;
} else {
intOffset = intStartDay - 5;
}
// Special start Sunday rule for 5 day week
if (intStartDay == 0 && !blnIncSat && !blnIncSun) {
intOffset++;
intModifier = 1;
}
}
// ~~ is used to achieve integer division for both positive and negative numbers
newDate.setTime(datStartDate.getTime() + (new Number((~~((lngNumberOfWorkingDays + intOffset) / intWorkingDays) * intNonWorkingDays) + lngNumberOfWorkingDays + intModifier)*86400000));
return newDate;
}
Have a look at the following implementation. Sourced from about.com
addWeekdays = function(date, dd) {
var wks = Math.floor(dd/5);
var dys = dd.mod(5);
var dy = this.getDay();
if (dy === 6 && dys > -1) {
if (dys === 0) {dys-=2; dy+=2;}
dys++; dy -= 6;
}
if (dy === 0 && dys < 1) {
if (dys === 0) {dys+=2; dy-=2;}
dys--; dy += 6;
}
if (dy + dys > 5) dys += 2;
if (dy + dys < 1) dys -= 2;
date.setDate(date.getDate()+wks*7+dys);
}
var date = new Date();
addWeekdays(date, 9);
(Updated) I've put this algorithm through its paces and it seems stable, though it does use recursion for holiday processing:
holidays = [new Date("2/13/2019"), new Date("2/19/2019")];
function addWorkdays(workdays, startDate) {
//Make adjustments if the start date is on a weekend
let dayOfWeek = startDate.getDay();
let adjustedWorkdays = Math.abs(workdays);
if (0 == dayOfWeek || 6 == dayOfWeek) {
adjustedWorkdays += (Math.abs((dayOfWeek % 5) + Math.sign(workdays)) % 2) + 1;
dayOfWeek = (dayOfWeek - 6) * -1;
}
let endDate = new Date(startDate);
endDate.setDate(endDate.getDate() + (((Math.floor(((workdays >= 0 ? dayOfWeek - 1 : 6 - dayOfWeek) + adjustedWorkdays) / 5) * 2) + adjustedWorkdays) * (workdays < 0 ? -1 : 1)));
//If we cross holidays, recompute our end date accordingly
let numHolidays = holidays.reduce(function(total, holiday) { return (holiday >= Math.min(startDate, endDate) && holiday <= Math.max(startDate, endDate)) ? total + 1 : total; }, 0);
if (numHolidays > 0) {
endDate.setDate(endDate.getDate() + Math.sign(workdays));
return addWorkdays((numHolidays - 1) * Math.sign(workdays), endDate);
} else return endDate;
}
I expanded on khellendros74's answer for a project of mine that needed to disable Sundays and mailing holidays in the datepicker and return two dates on press of a button: three business days (i.e. non-holiday and non-Sunday) after the date picked in the datepicker (a field with an id of "calendar") and six business days after the date picked in the datepicker and then put those two results into a couple of disabled input fields (handDelivered and mailed). The button press calls the function calculateDates. Here is that code:
var disabledDates = ['11/11/2015', '11/26/2015', '12/25/2015', '01/01/2016','01/18/2016', '02/15/2016','05/30/2016', '07/04/2016','09/05/2016','10/10/2016','11/11/2016','11/24/2016', '12/26/2016','01/02/2017','01/16/2017', '02/20/2017','05/29/2017', '07/04/2017','09/04/2017','10/09/2017','11/10/2017','11/23/2017', '12/25/2017','01/01/2018','01/15/2018', '02/19/2018','05/28/2018', '07/04/2018','09/03/2018','10/08/2018','11/12/2018','11/22/2018', '12/25/2018','01/01/2019','01/21/2019', '02/18/2019','05/27/2019', '07/04/2019','09/02/2019','10/14/2019','11/11/2019','11/28/2019', '12/25/2019','01/01/2020','01/20/2020', '02/17/2020','05/25/2020', '07/03/2020','09/07/2020','10/11/2020','11/26/2020','11/26/2020', '12/25/2020'];
$(function(){
$('#calendar').datepicker({
dateFormat: 'mm/dd/yy',
beforeShowDay: editDays
});
function editDays(date) {
for (var i = 0; i < disabledDates.length; i++) {
if (new Date(disabledDates[i]).toString() == date.toString() || date.getDay() == 0) {
return [false];
}
}
return [true];
}
});
function calculateDates()
{
if( !$('#calendar').val()){
alert("Please enter a date.");
document.getElementById('calendar').focus();
return false;
}
var dayThreeAdd = 0;
var daySixAdd = 0;
for (var i = 0; i < disabledDates.length; i++) {
var oneDays = AddWorkingDays($('#calendar').val(),1,true,false);
var twoDays = AddWorkingDays($('#calendar').val(),2,true,false);
var threeDays = AddWorkingDays($('#calendar').val(),3,true,false);
var fourDays = AddWorkingDays($('#calendar').val(),4,true,false);
var fiveDays = AddWorkingDays($('#calendar').val(),5,true,false);
var sixDays = AddWorkingDays($('#calendar').val(),6,true,false);
if (new Date(disabledDates[i]).toString() == oneDays.toString()) {
dayThreeAdd++;
daySixAdd++;
}
if (new Date(disabledDates[i]).toString() == twoDays.toString()) {
dayThreeAdd++;
daySixAdd++;
}
if (new Date(disabledDates[i]).toString() == threeDays.toString()) {
dayThreeAdd++;
daySixAdd++;
}
if (new Date(disabledDates[i]).toString() == fourDays.toString()) {
daySixAdd++;
}
if (new Date(disabledDates[i]).toString() == fiveDays.toString()) {
daySixAdd++;
}
if (new Date(disabledDates[i]).toString() == sixDays.toString()) {
daySixAdd++;
}
}
var threeDays = AddWorkingDays($('#calendar').val(),(3 + dayThreeAdd),true,false);
var sixDays = AddWorkingDays($('#calendar').val(),(6 + daySixAdd),true,false);
$('#handDelivered').val((threeDays.getMonth()+1) + '/' + threeDays.getDate() + '/' + (threeDays.getYear()+1900));
$('#mailed').val((sixDays.getMonth()+1) + '/' + sixDays.getDate() + '/' + (sixDays.getYear()+1900));
}
function AddWorkingDays(datStartDate, lngNumberOfWorkingDays, blnIncSat, blnIncSun) {
datStartDate = new Date(datStartDate);
var intWorkingDays = 5;
var intNonWorkingDays = 2;
var intStartDay = datStartDate.getDay(); // 0=Sunday ... 6=Saturday
var intOffset;
var intModifier = 0;
if (blnIncSat) { intWorkingDays++; intNonWorkingDays--; }
if (blnIncSun) { intWorkingDays++; intNonWorkingDays--; }
var newDate = new Date(datStartDate)
if (lngNumberOfWorkingDays >= 0) {
// Moving Forward
if (!blnIncSat && blnIncSun) {
intOffset = intStartDay;
} else {
intOffset = intStartDay - 1;
}
// Special start Saturday rule for 5 day week
if (intStartDay == 6 && !blnIncSat && !blnIncSun) {
intOffset -= 6;
intModifier = 1;
}
} else {
// Moving Backward
if (blnIncSat && !blnIncSun) {
intOffset = intStartDay - 6;
} else {
intOffset = intStartDay - 5;
}
// Special start Sunday rule for 5 day week
if (intStartDay == 0 && !blnIncSat && !blnIncSun) {
intOffset++;
intModifier = 1;
}
}
// ~~ is used to achieve integer division for both positive and negative numbers
newDate.setTime(datStartDate.getTime() + (new Number((~~((lngNumberOfWorkingDays + intOffset) / intWorkingDays) * intNonWorkingDays) + lngNumberOfWorkingDays + intModifier)*86400000));
return newDate;
}
Simple solution to solve the whole problem; you can just loop through the days to skip weekdays and holidays:
Date.prototype.holidays = {
// fill in common holidays
all: [
'0101', // Jan 01
'1225' // Dec 25
],
2016: [
// add year specific holidays
'0104' // Jan 04 2016
],
2017: [
// And so on for other years.
]
};
Date.prototype.addWorkingDays = function(days) {
while (days > 0) {
this.setDate(this.getDate() + 1);
if (!this.isHoliday()) days--;
}
return this;
};
Date.prototype.substractWorkingDays = function(days) {
while (days > 0) {
this.setDate(this.getDate() - 1);
if (!this.isHoliday()) days--;
}
return this;
};
Date.prototype.isHoliday = function() {
function zeroPad(n) {
n |= 0;
return (n < 10 ? '0' : '') + n;
}
// if weekend return true from here it self;
if (this.getDay() == 0 || this.getDay() == 6) {
return true;
}
var day = zeroPad(this.getMonth() + 1) + zeroPad(this.getDate());
// if date is present in the holiday list return true;
return !!~this.holidays.all.indexOf(day) ||
(this.holidays[this.getFullYear()] ?
!!~this.holidays[this.getFullYear()].indexOf(day) : false);
};
// Uasage
var date = new Date('2015-12-31');
date.addWorkingDays(10);
alert(date.toDateString()); // Mon Jan 18 2016
date.substractWorkingDays(10);
alert(date.toDateString()) // Thu Dec 31 2015
This only takes weekends into account and not holidays, but it's a start...
function mod(x, y) {
// https://stackoverflow.com/a/4467559/2173455
return ((x % y) + y) % y;
}
function calculateDateDiff(date, diff) {
let returnDate = new Date(date.getTime());
let daysLeftToAdd = Math.abs(diff);
let weekendDays = 0;
let weekDay = returnDate.getDay();
while(daysLeftToAdd >= 0) {
if(weekDay == 0 || weekDay == 6) {
weekendDays++;
}
else {
daysLeftToAdd--;
}
weekDay = mod(diff > 0 ? weekDay + 1 : weekDay - 1, 7);
}
returnDate.setDate(diff > 0 ?
returnDate.getDate() + diff + weekendDays :
returnDate.getDate() + diff - weekendDays
);
return returnDate;
}

Date constructor returns NaN in IE, but works in Firefox and Chrome

I'm trying to build a little calendar in JavaScript. I have my dates working great in Firefox and Chrome, but in IE the date functions are returning NaN.
Here is the function :
function buildWeek(dateText){
var headerDates='';
var newDate = new Date(dateText);
for(var d=0;d<7;d++){
headerDates += '<th>' + newDate + '</th>';
newDate.setDate(newDate.getDate()+1);
}
jQuery('div#headerDates').html('<table><tr>'+headerDates+'</tr></table>');
}
dateText is the Monday of the current week which is actually set in php in the format of 'm, d, Y', e.g. "02, 01, 2010".
From a mysql datetime/timestamp format:
var dateStr="2011-08-03 09:15:11"; //returned from mysql timestamp/datetime field
var a=dateStr.split(" ");
var d=a[0].split("-");
var t=a[1].split(":");
var date = new Date(d[0],(d[1]-1),d[2],t[0],t[1],t[2]);
I hope is useful for someone.
Works in IE FF Chrome
The Date constructor accepts any value. If the primitive [[value]] of the argument is number, then the Date that is created has that value. If primitive [[value]] is String, then the specification only guarantees that the Date constructor and the parse method are capable of parsing the result of Date.prototype.toString and Date.prototype.toUTCString()
A reliable way to set a Date is to construct one and use the setFullYear and setTime methods.
An example of that appears here:
http://jibbering.com/faq/#parseDate
ECMA-262 r3 does not define any date formats. Passing string values to the Date constructor or Date.parse has implementation-dependent outcome. It is best avoided.
Edit:
The entry from comp.lang.javascript FAQ is:
An Extended ISO 8601 local date format YYYY-MM-DD can be parsed to a Date with the following:-
/**Parses string formatted as YYYY-MM-DD to a Date object.
* If the supplied string does not match the format, an
* invalid Date (value NaN) is returned.
* #param {string} dateStringInRange format YYYY-MM-DD, with year in
* range of 0000-9999, inclusive.
* #return {Date} Date object representing the string.
*/
function parseISO8601(dateStringInRange) {
var isoExp = /^\s*(\d{4})-(\d\d)-(\d\d)\s*$/,
date = new Date(NaN), month,
parts = isoExp.exec(dateStringInRange);
if(parts) {
month = +parts[2];
date.setFullYear(parts[1], month - 1, parts[3]);
if(month != date.getMonth() + 1) {
date.setTime(NaN);
}
}
return date;
}
Don't use "new Date()", because it takes the input date string as local time:
new Date('11/08/2010').getTime()-new Date('11/07/2010').getTime(); //90000000
new Date('11/07/2010').getTime()-new Date('11/06/2010').getTime(); //86400000
we should use "NewDate()", it takes the input as GMT time:
function NewDate(str)
{str=str.split('-');
var date=new Date();
date.setUTCFullYear(str[0], str[1]-1, str[2]);
date.setUTCHours(0, 0, 0, 0);
return date;
}
NewDate('2010-11-07').toGMTString();
NewDate('2010-11-08').toGMTString();
Here's another approach that adds a method to the Date object
usage: var d = (new Date()).parseISO8601("1971-12-15");
/**
* Parses the ISO 8601 formated date into a date object, ISO 8601 is YYYY-MM-DD
*
* #param {String} date the date as a string eg 1971-12-15
* #returns {Date} Date object representing the date of the supplied string
*/
Date.prototype.parseISO8601 = function(date){
var matches = date.match(/^\s*(\d{4})-(\d{2})-(\d{2})\s*$/);
if(matches){
this.setFullYear(parseInt(matches[1]));
this.setMonth(parseInt(matches[2]) - 1);
this.setDate(parseInt(matches[3]));
}
return this;
};
I always store my date in UTC time.
This is my own function made from the different functions I found in this page.
It takes a STRING as a mysql DATETIME format (example : 2013-06-15 15:21:41). The checking with the regex is optional. You can delete this part to improve performance.
This function return a timestamp.
The DATETIME is considered as a UTC date.
Be carefull : If you expect a local datetime, this function is not for you.
function datetimeToTimestamp(datetime)
{
var regDatetime = /^[0-9]{4}-(?:[0]?[0-9]{1}|10|11|12)-(?:[012]?[0-9]{1}|30|31)(?: (?:[01]?[0-9]{1}|20|21|22|23)(?::[0-5]?[0-9]{1})?(?::[0-5]?[0-9]{1})?)?$/;
if(regDatetime.test(datetime) === false)
throw("Wrong format for the param. `Y-m-d H:i:s` expected.");
var a=datetime.split(" ");
var d=a[0].split("-");
var t=a[1].split(":");
var date = new Date();
date.setUTCFullYear(d[0],(d[1]-1),d[2]);
date.setUTCHours(t[0],t[1],t[2], 0);
return date.getTime();
}
Here's a code snippet that fixes that behavior of IE
(v['date'] is a comma separated date-string, e.g. "2010,4,1"):
if($.browser.msie){
$.lst = v['date'].split(',');
$.tmp = new Date(parseInt($.lst[0]),parseInt($.lst[1])-1,parseInt($.lst[2]));
} else {
$.tmp = new Date(v['date']);
}
The previous approach didn't consider that JS Date month is ZERO based...
Sorry for not explaining too much, I'm at work and just thought this might help.
Here's my approach:
var parseDate = function(dateArg) {
var dateValues = dateArg.split('-');
var date = new Date(dateValues[0],dateValues[1],dateValues[2]);
return date.format("m/d/Y");
}
replace ('-') with the delimeter you're using.
Send the date text and format in which you are sending the datetext in the below method. It will parse and return as date and this is independent of browser.
function cal_parse_internal(val, format) {
val = val + "";
format = format + "";
var i_val = 0;
var i_format = 0;
var x, y;
var now = new Date(dbSysCurrentDate);
var year = now.getYear();
var month = now.getMonth() + 1;
var date = now.getDate();
while (i_format < format.length) {
// Get next token from format string
var c = format.charAt(i_format);
var token = "";
while ((format.charAt(i_format) == c) && (i_format < format.length)) {
token += format.charAt(i_format++);
}
// Extract contents of value based on format token
if (token == "yyyy" || token == "yy" || token == "y") {
if (token == "yyyy") { x = 4; y = 4; }
if (token == "yy") { x = 2; y = 2; }
if (token == "y") { x = 2; y = 4; }
year = _getInt(val, i_val, x, y);
if (year == null) { return 0; }
i_val += year.length;
if (year.length == 2) {
if (year > 70) {
year = 1900 + (year - 0);
} else {
year = 2000 + (year - 0);
}
}
} else if (token == "MMMM") {
month = 0;
for (var i = 0; i < MONTHS_LONG.length; i++) {
var month_name = MONTHS_LONG[i];
if (val.substring(i_val, i_val + month_name.length) == month_name) {
month = i + 1;
i_val += month_name.length;
break;
}
}
if (month < 1 || month > 12) { return 0; }
} else if (token == "MMM") {
month = 0;
for (var i = 0; i < MONTHS_SHORT.length; i++) {
var month_name = MONTHS_SHORT[i];
if (val.substring(i_val, i_val + month_name.length) == month_name) {
month = i + 1;
i_val += month_name.length;
break;
}
}
if (month < 1 || month > 12) { return 0; }
} else if (token == "MM" || token == "M") {
month = _getInt(val, i_val, token.length, 2);
if (month == null || month < 1 || month > 12) { return 0; }
i_val += month.length;
} else if (token == "dd" || token == "d") {
date = _getInt(val, i_val, token.length, 2);
if (date == null || date < 1 || date > 31) { return 0; }
i_val += date.length;
} else {
if (val.substring(i_val, i_val+token.length) != token) {return 0;}
else {i_val += token.length;}
}
}
// If there are any trailing characters left in the value, it doesn't match
if (i_val != val.length) { return 0; }
// Is date valid for month?
if (month == 2) {
// Check for leap year
if ((year%4 == 0 && year%100 != 0) || (year%400 == 0)) { // leap year
if (date > 29) { return false; }
} else {
if (date > 28) { return false; }
}
}
if (month == 4 || month == 6 || month == 9 || month == 11) {
if (date > 30) { return false; }
}
return new Date(year, month - 1, date);
}
The Date constructor in JavaScript needs a string in one of the date formats supported by the parse() method.
Apparently, the format you are specifying isn't supported in IE, so you'll need to either change the PHP code, or parse the string manually in JavaScript.
You can use the following code to parse ISO8601 date string:
function parseISO8601(d) {
var timestamp = d;
if (typeof (d) !== 'number') {
timestamp = Date.parse(d);
}
return new Date(timestamp);
};
Try out using getDate feature of datepicker.
$.datepicker.formatDate('yy-mm-dd',new Date(pField.datepicker("getDate")));
I tried all the above solution but nothing worked for me.
I did some brainstorming and found this and worked fine in IE11 as well.
value="2020-08-10 05:22:44.0";
var date=new Date(value.replace(" ","T")).$format("d/m/yy h:i:s");
console.log(date);
if $format is not working for you use format only.

Categories