Why does javaScript allow creation of dates such as
invalidDate = new Date(2015,3,31);
Here I am trying to create a date with April 31st. Instead JavaScript creates a date as Fri May 01 2015 00:00:00. Should we validate the date before creating it?
There are scenarios when we try to parse invalid dates and it does the same thing. How should one parse the date correctly when the given date may not be valid?
var invalidDate = new Date(2015, 3, 31);
alert(invalidDate);
You can use the Date API to create the date and check the result:
function makeDate(year, month, day) {
var d = new Date(year, month, day);
if (d.getFullYear() !== year || d.getMonth() !== month)
throw new Error("Invalid date");
return d;
}
If the day is not a valid day for the given month, then the month will be adjusted; that's a feature of the Date API that allows easy "date math". If the above code notices that the input values have been corrected, it throws an exception.
Because there is no 31st April, so it's giving you the nearest valid date...
For clarity, as below, any integer above a valid date will roll over to the following month.
i.e. Date(2016,0,1) is 1st Jan 2016.
Date(2016,0,61) adds 60 days on to that date, which rolls past the 29th Feb 2016 and into March, thus..
Date(2016,0,61) = 1st March 2016
As a result, Date(2015,3,31) = 30th April 2015 plus one day = 1st May 2015
You can validate the date with a function like this:
function isValidDate(year, month, day) {
var m = month - 1; // Note: the month is 0-based
var d = new Date(year, m, day);
/*
For invalid dates, either the day or month gets changed:
2015-04-31 -> 2015-05-01
2015-02-29 -> 2015-03-01
2015-13-10 -> 2016-01-10
2016-00-10 -> 2015-12-10
*/
return d.getDate() == day && d.getMonth() == m;
}
In general, you should never trust user input. As a result, you should try to put them in a situation where they can not enter bad data. This can be done through validation or thought a User interface that only enables them to select valid dates.
You can look around SO for some examples on validating dates: Detecting an "invalid date" Date instance in JavaScript. If you are using a javascript framework they might already have something built into it as well.
As for why it works and "lets you do it", I would suspect that is because javascript's parse function calculates the "milliseconds elapsed since January 1, 1970, 00:00:00 UTC". The resulting milliseconds is then used and javascript represents that as the correct date.
Related
Both the above new date formats give different results.
new Date(2015,2,30) : Date 2015-03-29T18:30:00.000Z
new Date('2015-2-30') : Invalid Date
Why is this different ?
EDITS
Points :
1. Month index in the first format start with 0.
2. The first format handles the overflow of the dates and hence is not a prefered way to test for invalid dates.
For eg : new Date(2015, 1, 30) {when the user is looking for 30,Feb,2015} will be converted to 01,March,2015. That is the extra date is carry forwarded to the month. Pretty indecent a convert according to me. However, if you write new Date("2015-2-30"){when the user is looking for 30,Feb,2015} , this will be an invalid date.
You're calling the Date constructor with different types of parameters :
in the first case you're providing integers which it uses to populate its fields (the first three being the year, the month and the day of the month) ; note that the monthes are 0-indexed : a value of 1 in that field will correspond to February rather than January
in the second case you're providing a String which will be parsed as if passed to Date.parse, that is as an ISO 8601 Extended Format date (YYYY‐MM‐DDTHH:mm:ss.sssZ)
Check docs.
It's because by first example you pass arguments
new Date(year, month[, day[, hour[, minutes[, seconds[, milliseconds]]]]]);
And by second one you pass date string that is not valid. Missing 0 before 2
new Date(dateString);
You can create new Date in these ways:
var d = new Date();
var d = new Date(milliseconds);
var d = new Date(dateString);
var d = new Date(year, month, day, hours, minutes, seconds, milliseconds);
For dateString it must be provided by one of these input:
ISO Date "2015-03-25" (The International Standard)
Short Date "03/25/2015" or "2015/03/25"
Long Date "Mar 25 2015" or "25 Mar 2015"
Full Date "Wednesday March 25 2015"
There is a fundamental rule with ECMAScript dates: never use the Date constructor or Date.parse (they are equivalent for parsing) to parse strings.
Given new Date(2015,2,30) the values are treated as "local" for 30 March 2015, so the UTC time value is adjusted for the timezone offset on the host machine for that date. The outcome will be identical in all implementations consistent with all versions of ECMAScript from ed 3 onward at least.
Given the string new Date('2015-3-30'), the browser parsing algorithm is used. That string it might be treated as:
An invalid date, which is the result in Safari since it's ISO–like but not the correct format and the day is invalid for February
ISO 8601 and hence UTC for 30 March 2015, which is the result in Firefox and is also consistent with the standard since it's not valid ISO 8601 and hence can be parsed any way the implementation wishes to
A local date for 30 March 2015 which is the result in Chrome.
Also, given new Date('2015-2-30'):
Safari and Firefox give an invalid date
Chrome gives 2 March 2015 as a local date (i.e. 30 February rolls over to 2 March, which is the same behaviour as new Date(2015,1,30))
All the above outcomes are consistent with ECMAScript 2016. Hence the opening statement.
Edit
The quickest way to validate the values of a date while parsing is to test the month, in the simplest case of parsing a date in d/m/y format:
function parseDMY(s) {
var b = s.split(/\D/);
var d = new Date(b[2], --b[1], b[0]);
// If month is wrong, return invalid date
return d && d.getMonth() == b[1]? d : new Date(NaN);
}
// Dates in d/m/y format
['23/10/2015','32/10/2015','12/13/2015'].forEach(function(s){
var d = parseDMY(s);
console.log(s + ' : ' + (isNaN(d)? 'Invalid' : 'Valid'));
});
The output will be
console.log(new Date(2015,2,30)); // here 2 represents the march, ,month starts from 0 where 0 represents first month
console.log(new Date('2015-3-30'));
Mon Mar 30 2015 00:00:00 GMT+0530 (India Standard Time)
Mon Mar 30 2015 00:00:00 GMT+0530 (India Standard Time)
new Date('2015-2-30') // it means 30th day in February; which will convert it to second march
I need one help.I am unable to compare the selected date with today's date using Angular.js or JavaScript.I am explaining my code below.
var today=new Date();
console.log('2 dates',today,$scope.date);
From the above console i am getting the output like below.
2 dates Fri Jun 03 2016 18:29:16 GMT+0530 (India Standard Time) 03-06-2016
if(today > new Date($scope.date.split("-").reverse().join(","))){
alert('Please select future delivery date');
}
Here i need suppose my selected date is 03-06-2016 and today's date is 03-06-2016 the alert prompt should not come.But in my case its not happening like this.This alert message is coming also if selected date is 03-06-2016. Here i need the alert message should come if selected date is more than today's date only.Please help me.
Months are zero-indexed when constructing Dates:
console.log(new Date(2016,06,03));
"2016-07-03T04:00:00.000Z" // you expected June, but got July.
Note also that that is set to midnight: any new Date() run during the same day will be greater than that value.
String manipulation of dates is risky and error-prone, which is why everyone's knee-jerking momentjs at you (it is in fact a very good library and worth using if you're doing much date manipulation).
At the least, you may want to consider using a less ambiguous date format in $scope.date, but if you're stuck with that, in this case, you need to subtract 1 from the month when constructing a Date from it:
// this sample code will only work correctly on June 3 2016 :)
var $scope = {
date: "03-06-2016", // I am assuming DD-MM-YYYY format here
tomorrow: "04-06-2016"
};
var today = new Date();
// test against today:
var dArr = $scope.date.split('-');
var testDate = new Date(dArr[2],dArr[1]-1,dArr[0]);
if (today > testDate) {
console.log("today is greater than testDate.");
/* Note that anytime during today will be greater than the zero
o'clock constructed from $scope.date. If what you really want
is to alert only on the following day, then add 1 to the day
of $scope.date when converting it to a Date(). */
}
// now test against tomorrow:
dArr = $scope.tomorrow.split('-');
testDate = new Date(dArr[2],dArr[1]-1,dArr[0]);
if (today < testDate) {
console.log("today is less than (tomorrow's) testDate.");
}
Don't compare JavaScript dates using native < or > operators; install and use moment.js - it will handle all the difficulties for you:
if(moment(new Date()).isAfter(moment($scope.date, "MM-DD-YYYY"), 'day')) { ...
Convert to timestamp to compare it:
$scope.date = '2016-06-01';
var currentDate = new Date().valueOf();
var date = new Date($scope.date).valueOf();
if (currentDate > date) {
// something
}
It you use multi timezone, please convert both to a timezone.
I know with a java calendar, you can go back to a previous date, but how do I do that with a javascript date? Lets say I want to go backwards three months, how do I do that? I'm assuming that there has to be some logic to do that, and not just do a setMonth(), since rolling back 3 months may take you back to the previous year, and so the year needs to be updated too.
Why don't you use one of the date libraries such as date.js
http://datejs.com
Date.add(-3).month();
Any rollover date manipulation to a new period (ie new year, month, week, day, etc) is handled automatically
var d = new Date();
-> Tue Oct 01 2013 14:12:21 GMT+1000 (EST)
d.setMonth(d.getMonth() - 3);
-> Mon Jul 01 2013 14:13:43 GMT+1000 (EST)
d.setMonth(d.getMonth() - 10);
-> Sat Sep 01 2012 14:14:31 GMT+1000 (EST)
Using get month you can get the value from 0-11,so if you want to go back to 3 months just chekc if month no. is greater than 3 ,if it is greater than 3 no need to change the year,else decrement the year by 1
MomentJS is a JavaScript library that is great for date manipulation. Using MomentJS, you could do something like:
// Subtract 3 months from the current moment (now)
moment().subtract('months', 3);
If you must use the JavaScript Date object, you can use the setMonth() but it can be unreliable (as you mentioned). As an example:
// Get the next and previous month from now (first day of the month)
var now = new Date();
var futureMonth = now.setMonth(now.getMonth() + 1, 1);
var pastMonth = now.setMonth(now.getMonth() - 1, 1);
Note that you need to specify the second parameter to set the day to 1. This will prevent the "next" month from skipping a month (e.g. adding a month to January 31, 2014 will result in March 3rd, 2014 if you omit the second parameter).
No, you don't need to worry about roll backing the year. Just set the month and remaining it with JS rendering machine.
var date = new Date();
date.setMonth(-10); //+ increase the month or - decrease
alert(date);
JSFiddle
I have an HTML form where I am allowing users to enter a date to print out a report. However, if that date is within 3 days of the current date, I have it set to tell the user that they must wait 3 days. For some reason the code works when I enter something like "09/30/2012" but when I enter "10/01/2012", the error check skips. It seems at though, if it's a double digit month (10, 11, and 12), it complete skips the error check. Please let me know if you have any ideas. Thanks
JS Code:
var date = myForm.SC_date.value;
var d = new Date(date);
var varBegin = (d.getMonth()+1) + "-" + (d.getDate()-3) + "-" + d.getFullYear()
re = /^\d{1,2}\/\d{1,2}\/\d{4}$/;
if (myForm.SC_date.value == "")
window.alert("Please enter the requested date of variance. NOTE: Date must be 3 days prior to today's date.")
//Here is where I am having issues
/*else if(new Date(date) > new Date(varBegin))
window.alert("Invalid date. You must wait at least 3 days before you can request a report.")*/
else if(!myForm.SC_date.value.match(re))
window.alert("Invalid date. Please enter the date as follows: mm/dd/yyyy.")
HTML Code:
<td>Date of Variance </td>
<td colspan="2"><input name="SC_date:*" id="SC_date" type="text" tabindex="06">
</textarea><b><span class="style3">*</span> </b><span class="style2">(mm/dd/yyyy)</span>
</td>
I don't think you want to construct your "3 days ago" date by manipulating a string. I.e., this snippet here:
var varBegin = (d.getMonth()+1) + "-" + (d.getDate()-3) + "-" + d.getFullYear()
First, I'm not sure why you're using hyphens as delimiters here, when you are using forward-slashes as delimiters in your input field?
In any case, that's not a reliable way to construct the date. When you feed a string into the constructor of a Date object, you are effectively calling Date.parse(). That behaves differently on different browsers.
Check this out:
> new Date('1-1-2012');
Sun Jan 01 2012 00:00:00 GMT-0800 (PST)
> new Date('01-01-2012');
Sun Jan 01 2012 00:00:00 GMT-0800 (PST)
> new Date('2012-1-1');
Sun Jan 01 2012 00:00:00 GMT-0800 (PST)
Looks pretty good, right? But that's on Chrome.
Now check out what happens in an up-to-date version of Firefox, with the exact same calls:
> new Date('1-1-2012');
Date {Invalid Date}
> new Date('01-01-2012');
Date {Invalid Date}
> new Date('2012-1-1');
Date {Invalid Date}
> new Date('2012-01-01');
Date {Sat Dec 31 2011 16:00:00 GMT-0800 (PST)}
Furthermore, look at this behavior, in both browsers:
> new Date('2012-01-01');
Sat Dec 31 2011 16:00:00 GMT-0800 (PST)
Simply prepending zeroes to the month and date digits causes a time warp! You have to set the time and a timezone (for me, PST) to make that go away:
> new Date('2012-01-01T00:00:00-08:00')
Sun Jan 01 2012 00:00:00 GMT-0800 (PST)
Basically, dealing with date string parsing is a headache. You don't want to have to digest and account for specs like this, this, and this.
So, here's a better alternative -- pass the year, month, and date values (in that order) to the constructor of the Date object. That will reliably create the date for you, so your comparisons are valid.
Like this, for your specific example:
var WARNING_PERIOD_IN_DAYS = 3;
// Extract month, day, year from form input, 'trimming' whitespace.
var re = /^\s*(\d{1,2})\/(\d{1,2})\/(\d{4})\s*$/;
var match = re.exec(inputVal); // from "myForm.SC_date.value".
if (match) {
var month = parseInt(match[1]) - 1; // Zero-indexed months.
var date = parseInt(match[2]);
var year = parseInt(match[3]);
var inputDate = new Date(year, month, date);
var currentDate = new Date();
var threeDaysAgo = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDay() - WARNING_PERIOD_IN_DAYS);
console.log((inputDate > threeDaysAgo) ? 'Within warning period' : 'No warning needed');
}
Speaking of specs, there's one cool thing to note here, which is that in JavaScript, you can "wrap" the date value (it can be too large, or negative), and the resulting Date will still be valid and correct. Here's why:
From the ECMAScript 262 spec, here's what happens when you call setDate():
**15.9.5.36 Date.prototype.setDate (date)**
1. Let t be the result of LocalTime(this time value).
2. Let dt be ToNumber(date).
3. Let newDate be MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), dt), TimeWithinDay(t)).
4. Let u be TimeClip(UTC(newDate)).
5. Set the [[PrimitiveValue]] internal property of this Date object to u.
6. Return u.
This is the key bit: MakeDay(YearFromTime(t), MonthFromTime(t), dt)
MakeDay gets the year and the month from the current time value of the Date object (in milliseconds of epoch time), and does this:
**15.9.1.12 MakeDay (year, month, date)**
The operator MakeDay calculates a number of days from its three arguments, which must be ECMAScript Number values. This operator functions as follows:
1. If year is not finite or month is not finite or date is not finite, return NaN.
2. Let y be ToInteger(year).
3. Let m be ToInteger(month).
4. Let dt be ToInteger(date).
5. Let ym be y + floor(m /12).
6. Let mn be m modulo 12.
7. Find a value t such that YearFromTime(t) == ym and MonthFromTime(t) == mn and DateFromTime(t) == 1;
but if this is not possible (because some argument is out of range), return NaN.
8. Return Day(t) + dt - 1.
This looks rather involved, but basically it's:
The floor, modulo, and date==1 bits handle month rollovers (months that are negative or greater than 12).
The resulting instant in epoch time is converted to a number of days.
Your date value is added to that number of days. If your date value is negative, that's fine, it will just be subtracted.
The result is passed back to setDate().
setDate calls MakeDate(), which converts the number of days plus the intra-day time into milliseconds in epoch time.
The Date object's internal time is set to this new epoch time.
That's why you can do stuff like this (comments taken from the MakeDay() function in the V8 JS engine project):
// MakeDay(2007, -4, 20) --> MakeDay(2006, 8, 20)
// MakeDay(2007, -33, 1) --> MakeDay(2004, 3, 1)
// MakeDay(2007, 14, -50) --> MakeDay(2007, 8, 11)
Ok, so that was almost certainly too much detail for this particular problem... but I just wanted to make clear what's really going on behind the scenes. Thanks for your patience.
And... just one last thing...
You have a random </textarea> hanging out in that HTML snippet. If there is an opening <textarea> somewhere before it, then it's incorrectly enclosing some of your other elements. If there is no opening <textarea>, then delete it.
If you don't care about the time of the day, I recommend you do the following:
var dUser = new Date(date); //just like you did before
var dVarBegin = new Date("10/05/2012"); //here you do whatever is the date you are setting.
var diff = dVarBegin.getTime() - dUser.getTime();
//now diff is the difference in milliseconds!
I don't fully understand your requirements in relation to the 3 days. However, if what you needed was to compare dates, now you can! I hope this works for you. If you need something more, please ellaborate a little bit on the 3 days thing.
Use a library that lets you control the date formats accepted, e.g. Globalize.js or Date.js. Then define the exact test you wish to carry out especially whether time of the day is significant and whether the test should be relative to current time in user’s system (which is what you get with new Date() without arguments). You can then e.g. calculate a time difference as outlined by #Mamsaac and convert milliseconds to days with simple arithmetic.
It is illogical to use Date() and then, without checking the result, start doing pattern matching on the input. Moreover, Date() is by definition system-dependent and should seldom be used. There is no guarantee that it will accept a format like mm/dd/yyyy at all.
I'm in javascript, running this in the console
d = new Date();
d.setMonth(1);
d.setFullYear(2009);
d.setDate(15);
d.toString();
outputs this:
"Sun Mar 15 2009 18:05:46 GMT-0400 (EDT)"
Why would this be happening? It seems like a browser bug.
That's because when you initialize a new Date, it comes with today's date, so today is Oct 30 2008, then you set the month to February, so there is no February 30, so set first the day, then the month, and then the year:
d = new Date();
d.setDate(15);
d.setMonth(1);
d.setFullYear(2009);
But as #Jason W, says it's better to use the Date constructor:
new Date(year, month, date [, hour, minute, second, millisecond ]);
It's probably best to construct a Date object in one step to avoid the Date object being in an ambiguous or invalid state:
d = new Date(2009, 1, 15);
d = new Date();
d.setDate(15);
d.setMonth(1);
d.setFullYear(2009);
d.toString();
This works.
After a bunch of testing in FF3 on XP with Firebug, here are the things I can tell you
Calling Date.setDate() after calling Date.setMonth() will generate this odd behavior.
Date.setMonth() forces the timezone to be CST (or, some non DST-aware zone)
Date.setDate() forces the timezone to be CDT (or, some DST-aware zone)
So, there's definitely something wonky going on with setMonth() and setDate() in respect to the timezone.
The only solution I can offer is this: Set the date before you set the month.
This will work generally to avoid the rollover behavior of the javascript Date API:
d.setDate(1);
d.setFullYear(year);
d.setMonth(month);
d.setDate(day);
Given that year + month + day are in a "valid" combination, e.g. taken from another Date object using getFullYear(), getMonth(), getDate().
The important parts are:
starting with setDate(1) to avoid possible rollover when the current date value is 29, 30 or 31
call setMonth(month) before setDate(day) to avoid the same rollover in case the current month value is "problematic" (because then the initial setDate(1) would be without effect)