This is not exactly a problem, but more a question of method.
I am working on a project where people are able to type shorthand dates in input field, for example if you simply type "20", the input will automatically display the full date for 20th of this month.
There are many shorthand types possible, so I had to make multiple RegExp and then check each and every one.
My question is, is there a better way to deal with this? I am no javaScript expert,but I have a feeling that this is not exacty "best practise".
Here is the function
function dateParser(date) {
var splitDate = date.split(/[.:\s]/);
var day = new RegExp(/\b\d{1,2}\b/);
var dateHour = new RegExp(/\b\d{1,2}\s\d{1,2}\b/);
var dateHourMin = new RegExp(/\b\d{1,2}\s\d{1,2}[:]\d{1,2}\b/);
var dateMonth = new RegExp(/\b\d{1,2}[\/\-\,\.]\d{1,2}\b/);
var dateMonthHour = new RegExp(/\b\d{1,2}[\/\-\,\.]\d{1,2}\s\d{1,2}\b/);
var dateMonthHourMin = new RegExp(/\b\d{1,2}[\/\-\,\.]\d{1,2}\s\d{1,2}[:]\d{1,2}\b/);
var dateMonthYear = new RegExp(/\b\d{1,2}[\/\-\,\.]\d{1,2}[\/\-\,\.]\d{1,4}\b/);
var dateMonthYearHour = new RegExp(/\b\d{1,2}[\/\-\,\.]\d{1,2}[\/\-\,\.]\d{1,4}\s\d{1,2}\b/);
var dateMonthYearHourMin = new RegExp(/\b\d{1,2}[\/\-\,\.]\d{1,2}[\/\-\,\.]\d{1,4}\s\d{1,2}[:]\d{1,2}\b/);
var month = new Date().getMonth() + 1;
var year = new Date().getFullYear();
var newDate;
if(dateMonthYearHourMin.test(date)) {
newDate = splitDate[0]+"."+splitDate[1]+"."+splitDate[2]+" "+splitDate[3]+":"+splitDate[4];
}
else if(dateMonthYearHour.test(date)) {
newDate = splitDate[0]+"."+splitDate[1]+"."+splitDate[2]+" "+splitDate[3]+":00";
}
else if(dateMonthYear.test(date)) {
newDate = splitDate[0]+"."+splitDate[1]+"."+splitDate[2]+" 12:00";
}
else if(dateMonthHourMin.test(date)) {
newDate = splitDate[0]+"."+splitDate[1]+"."+year+" "+splitDate[2]+":"+splitDate[3];
}
else if(dateMonthHour.test(date)) {
newDate = splitDate[0]+"."+splitDate[1]+"."+year+" "+splitDate[2]+":00";
}
else if(dateMonth.test(date)) {
newDate = splitDate[0]+"."+splitDate[1]+"."+year+" 12:00";
}
else if(dateHourMin.test(date)) {
newDate = splitDate[0]+"."+month+"."+year+" "+splitDate[1]+":"+splitDate[2];
}
else if(dateHour.test(date)) {
newDate = splitDate[0]+"."+month+"."+year+" "+splitDate[1]+":00";
}
else if(day.test(date)) {
newDate = splitDate[0]+"."+month+"."+year+" 12:00";
}
return newDate;
}
I believe you can consolidate some of your regular expressions and make this more compact.
The first thing I notice is that whitespace appears to be an important separator in your input strings. Specifically, the date (or day) is always separated from the hour/minute with a space. So the first thing I would do is split your input on a space:
var parts = date.split( /\s/ );
var datePart = parts[0];
var timePart = parts[1]; // could be undefined
Now we can process the date part and the time part (if it exists) separately. The components of your date part are always separated by a slash, a dash, a comma, or a period, so again we can just split it:
parts = datePart.split( /[\/\-\,\.]/ );
var day = parts[0];
var month = parts[1] // could be undefined
var year = parts[2]; // could be undefined
You can split the time similarly, since hours and minutes are always separated by a colon:
if( timePart ) {
parts = timePart.split( /:/ );
var hour = parts[0];
var minute = parts[1]; // could be undefined
}
This should make this a little more compact and easy to read and maintain. You could go even more compact with a singular regular expression with groups, but I feel that this approach would be better.
The problem you described is tackled by Natural Language Processing, Programming/Query Language Design fields.
One of the approaches for solving those kind of problems is manually written scanner using RegExp/other string scanning and working with the result the way you did. It works for simple languages, doesn't require much knowledge in language design department and usually is intuitive to modify.
If you however have feeling that input is going to grow to something more complicated I recommend replacing RegExp scanning with full-fledged parser/lexer using for example Jison "Your friendly JavaScript parser generator!" or anything else that suits you.
Related
I have a really weird error on IE.
I am using knockout custom validations. And one of my custom validations is to validate date.
function:
function isValidDate(txtDate) {
var currVal = txtDate;
if (currVal == '' || currVal == null)
return false;
//Declare Regex
var rxDatePattern = /^(\d{1,2})(\/|-)(\d{1,2})(\/|-)(\d{4})$/;
var dtArray = currVal.match(rxDatePattern); // is format OK?
if (dtArray == null)
return false;
/*continue of logic*/
}
This works great, when I run it first time. But then I do a redirect to server and return to the same page.
And the validation is called again at that point the problem begins.
I have a two snapshots of memory. They look identical to me. But there has to be some difference that I don't see or the match method is somehow broken.
The difference is not the dtArray == null that is the problem. You can try to run it in console. And it parse the dtArray correctly....
Both snapshot are on the same line ( if (dtArray == null) )
beforeRedirect:
afterRedirect:
Update. I solved my problem.
problem was that I was setting my observable property something like this:
var date = "1990-01-01T00:00:00";
var dob = new Date(date).toLocaleDateString();
masterModel.Dob(dob);
when I do it like this the match works fine now:
var date = "1990-01-01T00:00:00"
var dob = new Date(date);
var dobstring = dob.getDate() + "/" + (dob.getMonth()+1) + "/" + dob.getFullYear();
masterModel.Dob(dobstring);
if you want to see the difference run this on IE in console. My IE version is 11.0.9600
//because I am in UK my locale string is dd/MM/yyyy if you get different one this problem won't work for you!
var date = "1990-01-01T00:00:00"
var dob = new Date(date).toLocaleDateString();
var rxDatePattern = /^(\d{1,2})(\/|-)(\d{1,2})(\/|-)(\d{4})$/;
console.log(dob);
console.log(dob.match(rxDatePattern));
//vs
var date = "1990-01-01T00:00:00"
var dob = new Date(date);
var dobstring = dob.getDate() + "/" + (dob.getMonth()+1) + "/" + dob.getFullYear();
var rxDatePattern = /^(\d{1,2})(\/|-)(\d{1,2})(\/|-)(\d{4})$/;
console.log(dobstring);
console.log(dobstring.match(rxDatePattern));
Try simply checking for falsy values. The empty string, null and undefined are all falsy, there is no need to be more specific than that here.
function isValidDate(txtDate) {
if (!txtDate) return false;
var rxDatePattern = /^(\d{1,2})(\/|-)(\d{1,2})(\/|-)(\d{4})$/;
var dtArray = currVal.match(rxDatePattern);
if (!dtArray) return false;
/*continue of logic*/
}
That being said, I strongly suggest you use a date library (most prominently: moment.js) to do any date parsing, -calculation and -validation work. Don't roll your own regex when a fully functional and properly tested library has been written.
To think one step further, with knockout it's much easier to store an actual date object in an observable, so there is no need to parse any date strings at all, ever. You can also format it for display on screen any way you like, instead of limiting yourself/the user to a single format.
This way you would not need to do any date format validation at all. Either the observable contains a date - or not. For best effect use that together with a date picker widget (for example the one from knockout-jqueryui).
View model:
this.exampleDate = ko.observable();
View, assuming jQueryUI + knockout-jqueryui:
<input type="text" data-bind="datepicker: {
dateFormat: 'dd.mm.yyyy'
}, value: exampleDate" />
I was looking everywhere, but I couldn't find a good code for my problem.
I have some input fields, what the users are using to send online their requests.
And one of the fields is a date field, what I would like somehow to validate in that way, that to be allowed only if the entered date to be somewhere between today and 8 days before.
Example: if today is 29 November , they would be allowed to enter only date between 21st-29th November and nothing else
And to be shown an alert window already when they have entered the wrong date
They will pick up the days from minicalendar but that part is resolved, I need help only with the validating.
If somebody could post a working code, I would be very grateful.
Thank you
Use jQuery UI DatePicker, the script below:
$(function() {
var currentDate = new Date();
var maxAllowedDate = new Date(currentDate);
maxAllowedDate.setDate(currentDate.getDate() + 8);
$( "#datepicker" ).datepicker({
changeYear: true,
minDate: '0',
maxDate: '+7D',
});
$('#datepicker').change(function(){
var enteredVal = new Date(this.value);
if(enteredVal.getTime() < currentDate.getTime() || enteredVal.getTime() > maxAllowedDate.getTime()) {
alert("invalid");
} else {
alert("valid");
}
});
});
and the UI:
<div class="demo">
<p>Date: <input type="text" id="datepicker"></p>
Here is the jsFiddle demo: http://jsfiddle.net/pjkz7k0t/1/
Since you asked for javascript, I will assume you want a javascript answer, not a jQuery answer.
function isValidDate(checkDate) {
if(/\d\d\/\d\d\/\d\d\d\d/.test(checkDate)) {
// split checkDate into three pieces
var strMM = checkDate.split('/')[0];
var strDD = checkDate.split('/')[1];
var strYYYY = checkDate.split('/')[2];
// create new Date() object from split pieces
var strDateCheck = new Date(strYYYY,(strMM - 1),strDD);
// evaluate each piece of resulting date object against each corresponding piece of checkDate
if(((strDateCheck.getMonth() + 1) == strMM) && (strDateCheck.getDate() == strDD) && (strDateCheck.getFullYear() == strYYYY)) {
/* if you wish, add additional validation constraints here */
return true; // all three pieces match exactly
}
}
return false; // did not meet criteria for return true
}
This method uses explicit regex to validate the formats.
Instead of creating elaborate methods of testing each piece, I used the pieces to build a new Date() object, knowing that the result MIGHT not match checkDate, and used pieces from resulting date to test the pieces of checkDate passed into the function. If ALL THREE pieces match, the entered date is valid.
For instance:
'02/29/2014' returns false
'02/29/2012' returns true
'12/36/2014' returns false
'29/06/2014' returns false
The code is pure javascript which improves portability, and this method does not interfere or impede additional validation by any other criteria you choose to use (against year ranges, or evaluating strCheckDate against today(), or any other constraints specific to your specific application).
An added advantage is that this method does not just determine whether what is passed to the function can be used to create a valid date, but confirms that the date entered MATCHES the valid date that can be created (thereby overcoming the problem of javascript rolling "extra days" forward on date creation).
This could easily be expanded to test for various configurations of date, using the exact same logic, simply by creating a different regex test, and splitting checkDate differently.
regex for DD/MM/YYYY would be the same, but the split would look like this:
// split characters into three pieces
var strDD = checkDate.split('/')[0];
var strMM = checkDate.split('/')[1];
var strYYYY = checkDate.split('/')[2];
Or for YYYY/MM/DD you would use regex:
/\d\d\d\d\/\d\d\/\d\d/.test(checkDate)
and the split would look like this:
// split characters into three pieces
var strYYYY = checkDate.split('/')[0];
var strMM = checkDate.split('/')[1];
var strDD = checkDate.split('/')[2];
This is highly morphable (and clean) javascript code to accomplish the purpose of validating a user entered date, and can be quickly modified to expand checks for valid date within range.
function isValidDateRange(checkDate,minDate,maxDate) {
if(/\d\d\/\d\d\/\d\d\d\d/.test(checkDate)) {
// split checkDate into three pieces
var strMM = checkDate.split('/')[0];
var strDD = checkDate.split('/')[1];
var strYYYY = checkDate.split('/')[2];
// create new Date() object from split pieces
var strDateCheck = new Date(strYYYY,(strMM - 1),strDD);
// evaluate each piece of resulting date object against each corresponding piece of checkDate
if(((strDateCheck.getMonth() + 1) == strMM) && (strDateCheck.getDate() == strDD) && (strDateCheck.getFullYear() == strYYYY)) {
// if this code fires, you have a valid date entered, first logic hurdle passed
// If you pass in minDate and maxDate as any format other than a date object, you should
// create new Date(); from them before comparing.
// Example:
// var strMinMM = minDate.split('/')[0];
// var strMinDD = minDate.split('/')[1];
// var strMinYYYY = minDate.split('/')[2];
// minDate = new Date(strMinYYYY,(strMinMM - 1),strMinDD);
// var strMaxMM = maxDate.split('/')[0];
// var strMaxDD = maxDate.split('/')[1];
// var strMaxYYYY = maxDate.split('/')[2];
// maxDate = new Date(strMaxYYYY,(strMaxMM - 1),strMaxDD);
if((!strDateCheck < minDate) && (!strDateCheck > maxDate)) {
return true; // all three pieces match exactly AND date is within specified range
}
}
}
return false; // did not meet criteria for return true
}
This question already has answers here:
How to create a date object from string in javascript [duplicate]
(8 answers)
Closed 9 years ago.
I have two strings:
var string1 = "01/02/2013 22:13";
var string2 = "08/01/2013 02:01";
I want convert to date in JavaScript to compare 2 dates.
Don't prefer Date object to be used to parse your date string. Instead do it yourself like this.
Here below I have written a method to parse your string to Date
function convertToDate(datSt) {
var s1 = datSt.split(" ");
var s1dat = s1[0].split("/");
var s1Tim = s1[1].split(":");
return new Date(s1dat[2], s1dat[1], s1dat[0], s1Tim[0], s1Tim[1])
}
Using Date.getTime() which returns you the millisecond, so that you can perform your Date comparison very easily.
if (d1.getTime() > d2.getTime()) {
//do whatever
} else if (d1.getTime() < d2.getTime()) {
//do whatever
} else {
//do whatever
}
Finally,
var string1 = "01/02/2013 22:13";
var string2 = "08/01/2013 02:01";
var d1 = convertToDate(string1);
var d2 = convertToDate(string2);
//getTime returns milliseconds, which can used for comparison
if (d1.getTime() > d2.getTime()) {
//do whatever
} else if (d1.getTime() < d2.getTime()) {
//do whatever
} else {
//do whatever
}
// Below is the method to split your string to Date object
function convertToDate(datSt) {
var s1 = datSt.split(" ");
var s1dat = s1[0].split("/");
var s1Tim = s1[1].split(":");
return new Date(s1dat[2], s1dat[1], s1dat[0], s1Tim[0], s1Tim[1])
}
JSFiddle
FYI: You can't perform equity(==) operation with Date objects. others(> and <) can be performed, follow this comment to know the reason.
If you wish to use plugin, I would prefer moment.js for a simple parsing.
Parsing date strings in JS is hard. First you need to know what format the string represents - the ones you posted are ambiguous. Libraries like http://momentjs.com/docs/#/parsing/ can help with various unambiguous forms. Definitely use a library because this stuff is tricky and browser's are inconsistent in how they implement 'standard' functions like Date.parse
var date1 = new Date(string1); //Turn it into a date
var date2 = new Date(string2); //Turn the other thing into a date
console.log(Math.abs(date1 - date2)); //Take the absolute value of the subtraction
As #RobertLevy points out, this might not work depending on the culture of the user. (D/M/Y vs M/D/Y), so it is preferred that you deal with all dates server-side, so the browser only gets the (correct) timestamp.
I have an input field that allows a user to enter a date.
I need this date to be in the following format: 10Jan13 (capitalization is not important)
There is a popup calender that if used will format the date correctly for the user.
I'd like to check the value of the input onblur using Javascript to be sure that the user did not either paste or type the date improperly.
I am currently checking number-only fields like this:
var numbers = /^[0-9]+$/;
if (!BIDInput.value.match(numbers))
{
checkedInput.value = "";
alert('Not a number');
}
and I'm checking letters-only fields like this:
var letters = /^[a-z]+$/
if (!nameInput.value.match(letters))
{
nameInput.value = "";
alert('Not a letter');
}
I would like to check the date format in a similar a fashion if possible. But anything that accomplishes the task will do. Can anyone point me in the right direction on how to get this done?
I know that client side validation does not replace server side validation. This is for user experience purposes only.
You're pretty much there with what you have. Basically your format is one or two digits, then one of 12 possible strings, followed by two digits. So for instance:
var shortDateRex = /^\d{1,2}(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\d{2}$/;
Breakdown:
^ Start of string.
\d{1,2} One or two digits.
(:?...) A non-capturing group. Or you could use a capture group if you like.
Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec An alternation, allowing any of those twelve choices. Naturally you can add more if you like. If you have two choices that start the same way (Jan and January, for instance), put the longer one earlier in the alternation.
\d{2} Two digits.
Side note: I'd have to recommend against two-digit dates on principle, and particularly given where in the century we currently are!
Responding to Amberlamps' comment that this doesn't validate the date: Once you've validated the format, it's trivial to then check the date itself if you like (to rule out 30Feb13, for instance):
var validateDateString = (function() {
var monthNames = "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec".toLowerCase().split("|");
var dateValidateRex = /^(\d{1,2})(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)(\d{2})$/i;
var arbitraryCenturyCutoff = 30;
function validateDateString(str) {
var match;
var day, month, year;
var dt;
match = dateValidateRex.exec(str);
if (!match) {
return false;
}
day = parseInt(match[1]);
month = monthNames.indexOf(match[2].toLowerCase()); // You may need a shim on very old browsers for Array#indexOf
year = parseInt(match[3], 10);
year += year > arbitraryCenturyCutoff ? 1900 : 2000;
dt = new Date(year, month, day);
if (dt.getDate() !== day ||
dt.getMonth() !== month ||
dt.getFullYear() !== year) {
// The input was invalid; we know because the date object
// had to adjust something
return false;
}
return true;
}
return validateDateString;
})();
...or something along those lines.
Live Example | Source
Or if (like me) you hate to see a list like that list of month names repeated you can use the RegExp constructor with a string instead, but you have to remember to duplicate your backslashes:
var monthNamesString = "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec";
var monthNames = monthNamesString.toLowerCase().split("|");
var dateValidateRex = new RegExp("^(\\d{1,2})(" + monthNamesString + ")(\\d{2})$", "i");
Live Example | Source
You would use the following regular expression to check for a string starting with 2 numbers, followed by 3 characters followed by 2 numbers
[0-9]{2}[a-zA-Z]{3}[0-9]{2}
EDIT: To anyone making the same mistake I did in asking this question, please look for my amended answer below which demonstrates a MUCH cleaner solution to this problem.
Trying to permit user entry of either 2 digit or 4 digit date format and allow both to pass validation but autocorrect the 2 digit format with the 4 digit equivalent with the assumption that the leading 2 digits should be the same as the current leading 2 digits.
I'm having trouble with the javascript replace() function. This line:
input.replace(enteredDate, year);
is not working as expected. In the context of the spine.js framework I'm using, the input refers to $(event.target) and appears to be correct everywhere else in manipulating the input field where the validation should be occurring. Ultimately, I want to replace the original string found in the input field with the fully concatenated one (which I haven't built out yet), but for now, how can I replace the contents of input (where input is var = $(event.target)) with the var year assigned here:
var year = currentYear.charAt(0) + currentYear.charAt(1) + unparsedYear;
Here is the full code for the function I have so far, which is throwing the following error in reference to the .replace() line - Uncaught TypeError: Object [object Object] has no method 'replace'
validateDate: function(event) {
var input = $(event.target);
var enteredDate = input.val();
input.destroyValidationMessage();
var pattern = /^(\d{1,2})\/(\d{1,2})\/(\d{2})|(\d{4})$/;
var result = enteredDate.match(pattern);
if (result !== null) {
var month = parseInt(result[1], 10);
var day = parseInt(result[2], 10);
var year = parseInt(result[3], 10);
var unparsedYear = parseInt(result[3], 10);
var unparsedYearStrLength = unparsedYear.toString().length;
if ( unparsedYearStrLength < 4) {
var currentYear = new Date().getFullYear().toString();
var year = currentYear.charAt(0) + currentYear.charAt(1) + unparsedYear;
alert('Autocorrected year will be ' + year);
input.replace(enteredDate, year);
}
var date = new Date(year, month - 1, day);
var isValid = date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day;
} else {
input.createValidationMessage('Invalid Date');
input.addClass('invalid');
}
}
As mentioned in my comment above:
input is a DOM element, but you actually want to replace the content of the element vs. the element itself, so use input.val(new_value) and not the actual input element.