Check if a string is a date or not - javascript

I have the following strings:
a='Check 134', a='2020-01-15T10:47:54Z', a='1234', a= some object
I want to check, whether the string is a date or not.
I tried:
new Date(a) instanceof Date &&
!isNaN(new Date(a).getTime())
, but it returns true for a='Check 123', whenever it has numbers.
What could be a better solution?
Note: I do not want to use momentjs, because of project restrictions

Actually, the question should be: how to determine if a given string can be converted to a real Date object?
Now, more things than you like may be converted to a Date (e.g., try new Date("") or new Date(0)). So you may want to use your own restrictions as to what you want to convert. Here's a method that restricts input to either a real Date object or a String:
const canConvertToDate = trial =>
[String, Date].includes(trial.constructor) &&
!isNaN(new Date(trial));
console.log(`canConvertToDate(\`\`): ${canConvertToDate(``)}`);
console.log(`canConvertToDate(\`no dice\`): ${canConvertToDate(`no dice`)}`);
console.log(`canConvertToDate(new Date): ${canConvertToDate(new Date)}`);
console.log(`canConvertToDate(\`2020-03-03 00:00:00\`): ${
canConvertToDate(`2020-03-03 00:00:00`)}`);
console.log(`canConvertToDate(0): ${canConvertToDate(0)}`);
console.log(`canConvertToDate(\`Wed, 11 Mar 2020 09:27:50 GMT\`): ${
canConvertToDate(`Wed, 11 Mar 2020 09:27:50 GMT`)}`);
console.log(`canConvertToDate(new Date().toUTCString()): ${
canConvertToDate(new Date().toUTCString())}`);
console.log(`canConvertToDate(134): ${canConvertToDate(134)}`);
// Please note the browser difference (Chrome / Firefox)
console.log(`canConvertToDate(\`134\`): ${canConvertToDate(`134`)}`);
.as-console-wrapper { top: 0; max-height: 100% !important; }
The previous snippet will not always give you the desired result (see comments).
Alternatively you can also write a parser of some kind to determine if the given string can be converted to Date, to be more (but not 100%) certain that a string is convertable to Date. Something like:
console.log(`tryParseDate(\`\`): ${tryParseDate(``).date}`);
console.log(`tryParseDate(new Date): ${tryParseDate(new Date).date}`);
console.log(`tryParseDate(\`Wed, 11 Mar 2020 09:27:50 GMT\`): ${
tryParseDate(`Wed, 11 Mar 2020 09:27:50 GMT`).date}`);
console.log(`tryParseDate(\`check 134\`): ${tryParseDate(`check 134`).date}`);
console.log(`tryParseDate(\`3-3-2005\`, \`ddmmyyyy\`): ${
tryParseDate(`03-03-2005`, `ddmmyyyy`).date}`);
console.log(`tryParseDate(\`12-22-1998 22:22:10.345\`, \`mmddyyyy\`): ${
tryParseDate(`12-22-1998 22:22:10.345`, `mmddyyyy`).date}`);
console.log(`tryParseDate(\`29-02-2019 22:22:10.345\`, \`ddmmyyyy\`): ${
tryParseDate(`29-02-2019 22:22:10.345`, `ddmmyyyy`).date}`);
function tryParseDate(someString, format = `yyyymmdd`) {
const invalid = {cando: false, date: new Date(`invalid`)};
if (someString.constructor !== String) { return { ...invalid, date: `Invalid Date: input not a string` }; }
const between = (val, lower, upper) => val >= lower && val <= upper;
const d = someString.split(/[/\- T:.]/g);
if (d.length < 3) { return {...invalid, date: `Invalid Date: can't split to anything useful`}; }
const formats = format.match(/(yyyy)|(mm)|(dd)/gi);
const values = {
year: +d[formats.findIndex(v => v === `yyyy`)],
month: +d[formats.findIndex(v => v === `mm`)],
date: +d[formats.findIndex(v => v === `dd`)] };
const cando = !isNaN(values.year) && values.year > 0 &&
!isNaN(values.month) && between(values.month, 1, 12)
!isNaN(values.date) && between(values.date, 1, 31);
if (!cando) {
return {...invalid, date: `Invalid Date: the given value is not valid`};
}
const date2Parse = `${d[formats.findIndex(v => v=== `yyyy`)]}/${
d[formats.findIndex(v => v=== `mm`)]}/${
d[formats.findIndex(v => v=== `dd`)]} ${
d.slice(3).map((v, i) => !i ? v : i > 2 ? `.${v}` : `:${v}`).join(``)}`;
const parsed = new Date(date2Parse);
const checkLeap = parsed.getMonth() + 1 === values.month;
const error = `Impossible Date: ${
!checkLeap ? `${values.year} not a leap year` : `parsing failed`}`;
return isNaN(parsed) || !checkLeap
? {...invalid, date: error}
: {cando: true, date: parsed};
}
.as-console-wrapper { top: 0; max-height: 100% !important; }

Since you already know the format of the dates that you want to check, use may be momentjs and check against the format
var moment = require("moment");
var res = moment("Check 123", "YYYY-MM-DD", true).isValid();
console.log(res);

It is possible to check whether the string contains year, month, day. In addition, we can add some conditions to have stricter rules to define whether string is Date:
const isDate = str => {
let [y,M,d,h,m,s] = str.split(/[- : T Z]/);
return (y && M <= 12 && d <= 31) ? true : false;
}
An example:
const isDate = str => {
let [y,M,d,h,m,s] = str.split(/[- : T Z]/);
return (y && M <= 12 && d <= 31) ? true : false;
}
console.log('Check 134', isDate('Check 134'))
console.log('2020-01-15T10:47:54Z', isDate('2020-01-15T10:47:54Z'))
console.log('1234', isDate('1234'))
console.log('13-13-13', isDate('13-13-13'))

Here is a simple function that uses Date.parse() internally; when you pass a string with whitespaces to Date.parse(), it will ignore non-digit chars and will return positive; hence you need to remove the space characters before passing it to Date.parse()
const a= 'Check 134';
const b= '2020-01-15T10:47:54Z';
const c= '1234';
const tricky = '1'; // a number from -12 to 13 is valid
function isValidDate (str) {
// optional condition to eliminate the tricky ones
// since chrome will prepend zeros (000...) to the string and then parse it
let noSpace = str.replace(/\s/g, '')
if( noSpace.length < 3) {
return false
}
return Date.parse(noSpace) > 0
}
console.log(a,isValidDate(a))
console.log(b,isValidDate(b))
console.log(c,isValidDate(c))
console.log(tricky,isValidDate(tricky))
// only in chrome
console.log("'1' is ", Date.parse('1') > 1 ," since it can be ", new Date('1').toString())
Edit: there are still some caveats to work Date in Chrome since it has open issue on that, there is lots of discussion around it, check this SO question the tricky corner cases in chrome are a more than that; It seems the best way to run it Chrome is to know your expected input type and validate it according to it; (e.g. RFC 2822/ISO 8601 date format). there are robust regexes for known date formats to use and confirm but trying to validate all available date formats in chrome at the moment have lots corner cases and potential false positives

You can try to use Date.parse() and then check for a positive number (date > 0).
For example:
Date.parse('2020-01-15T10:47:54Z') // 1579085274000
Date.parse('123') // -23225875200000
Date.parse('Check 134') // -57938551324000
https://jsfiddle.net/zmLbh0tu/

You can use Date.parse(). If it returns positive value then it's valid otherwise not. For ex:-
Date.parse("2020-01-15T10:47:54Z")

Just use typeof operator. No need to user any external libraries.
if (typeof date === 'object') {
// date is object
}
if (typeof date === 'string') {
//date is string
}

Related

Convert String to Number / Date or Null if invalid

I have the following TypeScript interface:
export interface Model {
numberValue: number;
dateValue: Date;
}
I created instances of this interface defining the properties from empty strings:
let model1: Model = {
numberValue: +'',
dateValue: new Date('')
};
console.log(model1);
model1 output: { dateValue: Invalid Date; numberValue: 0 }
And from strings containing invalid values:
let model2: Model = {
numberValue: +'A',
dateValue: new Date('A')
};
console.log(model2);
model1 output: { dateValue: Invalid Date; numberValue: NaN }
I need to get for both empty strings and strings with invalid values the following:
model output: { dateValue: NaN; numberValue: NaN }
or
model output: { dateValue: null; numberValue: null }
I am sending model as Json to an API. When the values are empty or converted from invalid values I need to send null.
EDIT: There are libraries like moment.js that can help you a lot when it comes to the fairly tricky topic of Dates. But you can do a basic conversion yourself like this...
parseUTCDate(year: string = "1970", month: string = "01", day: string = "01") {
let tempYear = year;
let tempDay = "";
let tempMonth = "";
// In case a Month shorter than 10 is added, we prepend a 0 to minimize errors
if (month.length < 2) {
tempMonth = "0" + month
} else {
tempMonth = month
}
// What we did to the month, we do for the day as well...
if (day.length < 2) {
tempDay = "0" + day
} else {
tempDay = day
}
// We construct our to-be-parsed date...
let dateToBeParsedS: string = `${tempYear}-${tempMonth}-${tempDay}Z`;
let date: Date = new Date(dateToBeParsedS);
// We check if the Date is even valid. A proper Date value is always equal to itself
if(date.getTime() === date.getTime()) {
// if successful, we return the date
return date;
} else {
// if not... well, we return an undefined or NaN if we like
return undefined;
}
}
However, this method doesn't include all cases that can come up with a date. It's meant as an example of what you can do.
String to numbers is easier... but let's go the extra mile as well.
returnNumberOutOfString(inputNumber: string) {
// The Numberclass can parse numbers... that's what we will use
let tempNumber = new Number(inputNumber)
// Of course, we want to know if the number was valid...
if (isNaN(tempNumber.valueOf())) {
return undefined;
} else {
// If successful, we return the primitive data value of the Number object
return tempNumber.valueOf();
}
}
And with that, you've got basically all you need to convert and validate...
I'm a bit confused in terms of what you're trying to achieve. Also, to remain faithful to semantics, you're NOT creating a new instance of the interface. You've created an object of the TYPE model. It's not the same. But let's keep this going...
You said, IF the value is empty for whatever reason, it should return a null...
"numberValue": numberValue != undefined ? someNum : null
What does this line do? In case the value referenced in the object is not undefined, it should return the number. However, if the case isn't fulfilled, it will return a null.
In case you wish to know, if your date is a valid date, you can handle it in a similar fashion...
"dateValue": dateValue.getTime() === dateValue.getTime() ? dateValue : null
The idea here is, that a proper date will always be equal to itself, otherwise it will NaN

how to safely distinguish ISO 8601 string and number stored as string in queryParams without regex

I have for example a date and amount property stored in the url als query params
...?date=2019-06-12T06:20:39.465Z&amount=20000
I want to be able to parse ALL query params and assign them to their proper type with javascript.
currently I'm using
paramMap.keys.forEach(key => {
let value: any = paramMap.get(key);
if (isFinite(Date.parse(value))) {
// keep as string
} else if (isFinite(parseInt(value, 10))) {
value = isInteger(value) ? parseInt(value, 10) : parseFloat(value);
} else if (value === 'true' || value === 'false') {
value = value === 'true';
}
obj[key] = value;
});
return obj;
In this case the amount=20000 will get recognised by Date.parse.
also
2019-06-12T06:20:39.465Z will be parsed to 2019 with parseInt...
isFinie, parseInt and isInteger are imported from lodash.
If possible I dont want to use regex.
EDIT: Please assume that I don't know the name of the key. Hence the script should work for all properties.
ISO may omit seconds or timezone or milliseconds, but if you ignore those and check everything else you can reliably check against different ISO formats.
console.log(isISO('2019-06-12T06:20:39.234Z'))
console.log(isISO('2019-06-12T06:20:39'))
console.log(isISO('2019-06-12T06:20'))
console.log(isISO("2019-1-1"))
console.log(isISO('2019-06-12T06:20:39+05'))
console.log(isISO('ketchup'))
function isISO(str) {
try {
str = str.split('+')[0];
var iso = new Date(str).toISOString();
var [date, time] = iso.split('T');
var [y, m, d] = date.split('-');
return iso.indexOf(`${y}-${m}-${d}`) === 0;
} catch (e) {
return false;
}
}
Use the + operator, the Number function, or lodash's _.toNumber() which will return NaN for strings with mixed numbers:
console.log(+'2019-06-12T06:20:39.465Z')
console.log(+'20000')
So the logic would be:
const parseParam = value => {
let v = +value
if (!isNaN(v)) return v
v = Date.parse(value)
if (!isNaN(v)) return v
if (value === 'true' || value === 'false') return value === 'true'
return value
}
console.log(parseParam('2019-06-12T06:20:39.465Z'))
console.log(parseParam('20000'))
console.log(parseParam('false'))
console.log(parseParam('cats'))

How to check if value is in preferd format

i have a input field.
Inside this field the user should only enter text in this format
1+1+2+3
maximum 1+1+1+1+1+1+1+1+1+1+1+1 (12x)
How i can check this
only checking for numbers
var isnum = /^\d+$/.test($(this).val());
will be the half work, but for more i dont know how...
You can use a quantifier to say how many to accept, so:
var isnum = /^\d+(?:\+\d+){0,11}$/.test($(this).val());
That says to accept any number of digits at the beginning, optionally followed by 0 to 11 examples of + and any number of digits.
Live Example:
function test(str, expect) {
var result = /^\d+(?:\+\d+){0,11}$/.test(str);
console.log(str, result, !result === !expect ? "Test:Pass" : "Test:FAIL");
}
test("1", true);
test("1+2+3+4+1234", true);
test("1+1+1+1+1+1+1+1+1+1+1+1", true);
test("1+1+1+1+1+1+1+1+1+1+1+1+1", false);
In a comment you've added:
es only digits and plus-sign and the maximum of entered numbers must be = 12, 6+6 or 3+6+3...
That's a completely different thing, and you can't reasonably test for it with a regular expression (you'd need a ridiculous number of alternatives). Instead, use a regular expression (such as the above) to test the format, then do the sum:
if (/*...the format is good...*/) {
sum = str.split("+").reduce((a, b) => Number(a) + Number(b));
if (sum > 12) {
// disallow it
}
}
Live Example:
function test(str, expect) {
var result = /^\d+(?:\+\d+){0,11}$/.test(str);
if (result) {
result = str.split("+").reduce((a, b) => Number(a) + Number(b)) <= 12;
}
console.log(str, result, !result === !expect ? "Test:Pass" : "Test:FAIL");
}
test("1", true);
test("1+2+3+4+1234", false); // sum > 12
test("1+1+1+1+1+1+1+1+1+1+1+1", true);
test("1+1+1+1+1+1+1+1+1+1+1+1+1", false); // too many
test("12345", false); // sum > 12
In that I've used Number(x) to convert from string to number, but you have a lot of options, which I detail in this answer.

typescript 1+1 = 11 not 2

I have a TypeScript class without any import statement at the top. When I new calculateDate() and execute addMonth(new Date(), 1), it adds 11 months to today instead of 2. The m variable is always a result of string concatenation, instead of math addition operation. I even tried parseInt() with the string form of the two operands, it still performs string concatenation. Please help. Thanks.
export class calculateDate {
addMonth(thisDate:Date, monthCount:number){
if (thisDate && monthCount && monthCount != -1) {
let m : number = thisDate.getMonth() + monthCount;
console.log('m=', m);
let newDate: Date = new Date(thisDate.setMonth(m));
return newDate;
}
else
return null;
}
}
You are adding up two strings, try to parse them as int or use this syntax +thisDate.getMonth() + (+monthCount)

Checking if any given string is a valid date

Does anyone know of any way to check if strings are valid dates? I'm trying to block against invalid dates, while not forcing any kind of date format. Basically here's the problem:
!!Date.parse('hello 1') === true
Javascript can figure out a date from that string, therefore, it's a date. I'd rather it not be. Anyone?
How close would stripping out spaces around words get you? It at least weeds out "hello 1" and such.
Date.parse('hello 1'.replace(/\s*([a-z]+)\s*/i, "$1")); // NaN
Date.parse('jan 1'.replace(/\s*([a-z]+)\s*/i, "$1")); // Valid
[update]
Ok, so we'll just replace any non-alphanumerics that fall between a letter and a number:
replace(/([a-z])\W+(\d)/ig, "$1$2")
Since you're using moment.js, try using parsingFlags():
var m = moment("hello 1", ["YYYY/MM/DD"]).parsingFlags();
if (!m.score && !m.empty) {
// valid
}
It's the metrics used for isValid() and you can use them to make a stricter validation function.
Note: You can specify the other formats to support in the second argument's array.
Some other properties returned by parsingFlags() that might be of interest are the following:
m.unusedInput - Ex. ["hello "]
m.unusedTokens - Ex. ["MM", "DD"]
Use this function to check date
function isDate(s)
{
if (s.search(/^\d{1,2}[\/|\-|\.|_]\d{1,2}[\/|\-|\.|_]\d{4}/g) != 0)
return false;
s = s.replace(/[\-|\.|_]/g, "/");
var dt = new Date(Date.parse(s));
var arrDateParts = s.split("/");
return (
dt.getMonth() == arrDateParts[0]-1 &&
dt.getDate() == arrDateParts[1] &&
dt.getFullYear() == arrDateParts[2]
);
}
console.log(isDate("abc 1")); // Will give false
Working Fiddle
It would be ok if you check for several types of dates?
kind of this for narrow the permited dates:
if( givenDate.match(/\d\d\/\d\d\/\d\d\d\d/)
|| givenDate.match(/\w*? \d{1,2} \d{4}/)
|| givenDate.match(anotherFormatToMatch) )
UPDATED
Or, althougt it restrict characters, you coud use something like this:
function myFunction() {
var str = "The rain in SPAIN stays mainly in the plain";
var date = new Date(str);
if (date != "Invalid Date" && !isNaN(new Date(date) && !str.match(/a-z/g) )
alert(date);
}

Categories

Resources