Why moment(date).isValid() returns wrong result - javascript

when I check the following date it returns true result, Why?
const value = "3";
if (moment(new Date(value), "DD-MM-YYYY HH:mm", true).isValid()) // true
{ }
or
const value = "3";
if (moment(new Date(value)).isValid()) // true
{ }

That is because new Date("3") is valid date and
console.log(new Date("3"))

This is one of those cases that shows you need to sanitize your date strings and should not depend on the native parser, unless you are sure your strings have already been validated and are conformant.
ECMA-262 Date(value) constructor specs
Date.parse
If the String does not conform to that format the function may fall back to any implementation-specific heuristics or implementation-specific date formats.
So it's not conformant to "Date Time String Format", which requires the string to start with "YYYY", so it goes to an implementation specific parsing that is similar to the rules for above, but using the form: "MM-DD-YYYY".
The purpose of using strict mode (setting third argument of moment() to true) is to let moment do the string parsing and determine if it fits the formats that you provide (to for example avoid unexpected parsing behavior like this). If you use Date() to parse, you are no longer using moment's strict mode to validate the string fits your required format.
let value = "3";
function checkDate(value){
console.log(value,
moment(value, "DD-MM-YYYY HH:mm", true).isValid() ? 'valid' : 'invalid')
}
value = "01-01-2011 11:22"
checkDate(value)
value = "01-01-2011 11:22Z"
checkDate(value)
value = "3"
checkDate(value)
value = new Date("3").toString()
checkDate(value)
value = new Date('INVALID DATE')
checkDate(value)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.25.3/moment-with-locales.min.js" integrity="sha256-8d6kI5cQEwofkZmaPTRbKgyD70GN5mDpTYNP9YWhTlI=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.28/moment-timezone-with-data.js" integrity="sha256-O1PdKrSbpAYWSBteb7yX/CMmHhu3US31mtCbsryGwaY=" crossorigin="anonymous"></script>
If you don't need to validate the date string (for example to prevent unexpected parsing behavior), don't need to worry about non-modern browsers, and really just need to parse conforming Date strings and format to basic string formats, you could just use native Date() with Intl.DateTimeFormat or Date.prototype.toLocaleString.
TL;DR the way you are using it right now implies that you don't actually need moment

Because 3 is a valid Date, the Date also supports only the year or only month format.
If you try new Date("YYYY") --> It's a valid date
If you try new Date("MM") --> It's a valid format
So, for new Date("{1 to 12}") It will accept
For new Date("{13 to 31}") It's invalid
Also, from new Date("{32 to 49}") It considers as year, for two digits it will add the current century that is (2000 + number you specified), for e.g.
new Date("32") is the year 2032 and for new Date("{50 to 99}") it adds in the previous century i.e. (1900 + number you specified), for e.g. new Date("99") is the year "1999"
For three-digit, till "100000" on-wards it's pretty straightforward what input it is will be a year, for e.g. new Date("100") is year 100 and new Date("100000") is the year 100000. The rest are invalid.
The above is valid as of today, instead of taking care of the above information it's always advisable to use Null, NaN, and Undefined values where the date field is not present

Related

Prevent moment.js accepting an integer as a valid ISO8601 date

I have some API's for creating dashboard widgets. Those API's return basic name/value data pairs that are passed to Google Charts. Moment.js checks whether the value is an ISO8601 date, and if so passes to Google Charts as a date instance.
However, the ISO_8601 isValid check is currently returning true if the date is a simple integer, e.g. 1234:
var myInt = 1234;
if (moment(myInt, moment.ISO_8601, true).isValid()) {
console.log("Valid!");
}
I couldn't locate the necessary functionality to force a date format in the moment.js code, so this brutal hack works for now:
var myInt = 1234;
if (JSON.stringify(myInt).includes("T") && moment(myInt, moment.ISO_8601, true).isValid()) {
console.log("Valid!");
}
Is there a correct way to use moment.js to configure the isValid() check?
The date format from my API is yyyy-mm-ddThh:mm:ss (without Z on the end).
According to THIS answer, when using strict parsing (last parameter set to true) you should also specify parse format, to avoid situations like you discribed. As many users notice, specyfying string format instead of using moment.ISO_8601 works as expected.
alert(isISODateValid(123)); //false
alert(isISODateValid("2011-10-10T14:48:00")); //true
alert(isISODateValid("2011-10-10T14:48:00Z")); //true
function isISODateValid(date) {
return moment(date.toString().replaceAll("Z",""), "YYYY-MM-DDTHH:mm:ss", true).isValid();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
EDITS: Updated snippet - if date contains "Z" suffix, remove it before parsing validating source date format
Since you stated that: "The date format from my API is yyyy-mm-ddThh:mm:ss (without Z on the end)", the best way to parse it is explictly pass the format you are expecting to moment, using the right moment format tokens instead of using moment.ISO_8601.
So, in your case, simply use moment(myInt, "YYYY-MM-DDTHH:mm:ss", true), as shown in the snipppet:
function checkValid(input) {
if (moment(input, "YYYY-MM-DDTHH:mm:ss", true).isValid()) {
console.log(input + " is valid!");
}
}
checkValid(1234);
checkValid("2021-04-27T20:40:15");
checkValid("2021-04-27T20:40:15Z");
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
Please note that the Z at the end stands for the timezone offset UTC+0, if you have it moment takes it into account, while without it, moment parses the input as local time (see Local vs UTC vs Offset guide)
As a side note, moment.ISO_8601 works as you were expecting in moment versions prior to 2.25.0:
function checkValid(input) {
if (moment(input, moment.ISO_8601, true).isValid()) {
console.log(input + " is valid!");
}
}
checkValid(1234);
checkValid("2021-04-27T20:40:15");
checkValid("2021-04-27T20:40:15Z");
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
You could testify the sting before you pass it to the moment.? I have taken an example from this post
/**
* RegExp to test a string for a full ISO 8601 Date
* Does not do any sort of date validation, only checks if the string is according to the ISO 8601 spec.
* YYYY-MM-DDThh:mm:ss
* YYYY-MM-DDThh:mm:ssTZD
* YYYY-MM-DDThh:mm:ss.sTZD
* #see: https://www.w3.org/TR/NOTE-datetime
* #type {RegExp}
*/
var ISO_8601_FULL = /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?(([+-]\d\d:\d\d)|Z)?$/i
// Usage:
ISO_8601_FULL.test( "2016-05-24T15:54:14.876Z" ) // true
ISO_8601_FULL.test( "2002-12-31T23:00:00+01:00" ) // true
ISO_8601_FULL.test( "2016-02-01" ) // false
ISO_8601_FULL.test( "2016" ) // false
if (ISO_8601_FULL.test(myDate) && moment(myDate, moment.ISO_8601, true).isValid()) {
console.log("Valid!");
}
I suppose the date should not be an integer.
Interesting is the difference between string and number. If it's a number, it is interpreted as the number of milliseconds since epoc, which is quite helpful in computer languages, but obviously not always what was requested and also not obvious to every developer. This can easily avoided with a type check (typeof input != 'string').
The other variant is more confusing: "12345" is not valid. good. But "1234" is interpreted as a year, and at the same time "34" seems to be interpreted as a time offset in minutes (Sun Jan 01 1234 00:00:00 GMT+0034). To me, this clearly looks like a bug in the library, since it's quite useless to parse the same digits multiple times for different purposes. But also after this is fixed, "1234" stays a valid date (year only) as defined in the standard ISO 8601
https://en.wikipedia.org/wiki/ISO_8601
For reduced precision,[17] any number of values may be dropped from any of the date and time representations, but in the order from the least to the most significant. For example, "2004-05" is a valid ISO 8601 date, which indicates May (the fifth month) 2004. This format will never represent the 5th day of an unspecified month in 2004, nor will it represent a time-span extending from 2004 into 2005.
Btw: "543210" is also valid and means "5432-10", or October of the year 5432
function checkValid(input) {
m = moment(input, true);
console.log(input + " type: " + typeof input + " valid: " + m.isValid() + " result:" + m.toString());
}
checkValid(1234);
checkValid("1234");
checkValid(12345);
checkValid("12345");
checkValid("2021-04-27T20:40:15");
checkValid("2021-04-27T20:40:15Z");
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>

Create JavaScript Date object from date string + time string + timezone offset string

I have total 4 different input i.e.:
Date string (2020-05-05)
Time string (15:30)
Timezone offset (-09:00)
I want to combine these strings into one datetime object like (2020-05-05T15:30:00-09:00) no-matter what my local browser timezone is. The issue is when I combine these strings and I try to make date object using new Date() function, my datetime gets converted into UTC timestamp.
I tried this:
const date =
moment(this.actualDateOfSurgeryDate).format(YYYYMMDD) +
'T' +
moment(this.actualDateOfSurgeryTimeDropDown + ' ' + this.actualDateOfSurgeryTimeAM_PMDropDown, ['h:mm A']).format('HH:mm:ss') +
offsetTime;
this.caseDetail.actualDateOfSurgery = new Date(date);
This gives me output something like: 2020-05-05T04:30:00.000Z
How can I get my desired output: 2020-05-05T15:30:00-09:00 ??
I have moment js available in my project
I want to combine these strings into one datetime object like (2020-05-05T15:30:00-09:00)
Date objects are extremely simple, they're just a time value that is an offset in milliseconds since 1970-01-01T00:00:00Z, so are inherently UTC. The built–in parser is unreliable and lacks any functionality such as format tokens.
So if you have separate values like:
Date string (2020-05-05)
Time string (15:30)
Timezone offset (-09:00)
then you can create a string that is compliant with the format defined in ECMA-262 and that should be parsed correctly by the built–in parser, e.g.
new Date('2020-05-05T15:30:00.000-09:00')
However, general advice is to avoid the built–in parser due to differences in implementations. Also, the format must be exact (e.g. including seconds and milliseconds in the timestamp, colon (:) in the offset) or some implementations will reject it as malformed and return an invalid date.
Once you have a Date object, getting a "local" timestamp with offset is an issue of formatting, which has been answered many times before (e.g. How to format a JavaScript date). There aren't any decent built–in formatting functions (toLocaleString with options is OK for some purposes but generally lacking in functionality), so you'll have to either write your own function, or use a library.
The following examples use Luxon, which is suggested as the upgrade path from moment.js.
With Luxon, if you specify a representative location, you'll get the offset for that location at the date's date and time. Alternatively, you can fix the offset to a set value, essentially setting it for a timezone without a representative location, so it doesn't have any reference to daylight saving or historic offset changes:
let DateTime = luxon.DateTime;
// Offset per the specified location
let d0 = DateTime.fromISO('2020-01-01', {zone: 'America/Yakutat'});
let d1 = DateTime.fromISO('2020-06-30', {zone: 'America/Yakutat'});
console.log(d0.toString());
console.log(d1.toString());
// Fixed offset per the supplied string
let d2 = DateTime.fromISO('2020-05-05T15:30:00.000-09:00', { setZone: true});
let d3 = DateTime.fromISO('2020-01-01T15:30:00.000-09:00', { setZone: true});
console.log(d2.toString());
console.log(d3.toString());
<script src="https://cdn.jsdelivr.net/npm/luxon#1.24.1/build/global/luxon.min.js"></script>
I get 16:30 due to DST
A date before March or after October will give 15:30
let dateString = "2020-05-05"+"T"+"15:30"+":00"+"-09:00"
console.log(dateString)
const date = new Date(dateString)
console.log(date)
const Anchorage = date.toLocaleString('en-US', {timeZone: 'America/Anchorage', hour12: false})
console.log(Anchorage)
let options = {}
options.timeZone = 'America/Anchorage';
options.timeZoneName = 'short';
console.log(date.toLocaleDateString('en-US'), date.toLocaleTimeString('en-US', options));

convert iso time strings to valid momentjs format and compare them

I want to compare some times with momentjs. These times come from time pickers and use the ISO 8601 format, 24hr hh:mm (more info here).
I want to compare the examples "01:45" and "13:36". Using the function isSameOrBefore should return me true. Unfortunately
const valid = moment("01:45").isSameOrBefore("13:36")
does not work, because the iso strings use a wrong format. I get the warning
Deprecation warning: value provided is not in a recognized RFC2822 or
ISO format. moment construction falls back to js Date()
I tried to go for this
const isoFormat = 'hh:mm'
const first = "01:45"
const second = "13:36"
const firstBeforeSecond = moment(first, isoFormat).isBefore(second, isoFormat)
console.log(firstBeforeSecond)
<script src="https://cdn.jsdelivr.net/npm/moment#2.24.0/moment.min.js"></script>
but this still fails. The value I get returned is false and this is not correct. What is the correct way to compare these time strings?
Using moment for this is huge overkill
Just compare strings
"01:45" < "13:36"
is true
"13:36" === "13:36"
is also true
the reason is that in ISO 8601 the strings have the same length and hence will
"00:00" always be the lowest value and "23:59" the highest.
This is safe because they stay string and have ":" in them, so will not be cast to number or lose the leading 0s or such
The correct syntax is:
moment(first, isoFormat).isBefore(moment(second, isoFormat))
try this
var first = moment('01:45', 'hh:mm');
var second = moment('13:36', 'hh:mm');
console.log(first. isSameOrBefore(second));
specifying format will not give any warnings :)

How to do a date format in Javascript?

I need to understand how to do date formattting in javascript.
i have date as,
var date="12/02/1994";// dd/mm/yyy
var date1=new Date(date);
date1.getDate();// this gives me Month which is 02
date1.getMonth();// this gives me date which is 12.
How do i get the exact date i have in var date in get date and getmonth function? Please help
The answer is pretty simple: JavaScript uses mm/dd/yyyy data format.
It doesn't support dd/mm/yyyy format, so, if you need to parse this format, then you will have to do this manually like this:
function parseDdmmyyyy(str)
{
var spl = str.split('/');
return new Date(spl[2], spl[1] - 1, spl[0]);
}
or you will have to use external libraries like Moment.js.
Javascript date() expects date in mm/dd/yy and not in dd/mm/yy. And months start from 0 and not 1.
var from = "12/02/1994".split("/");
var date1 = new Date(from[2], from[1] - 1, from[0]);
date1.getDate();
date1.getMonth();
Use new Date('02/12/1994'), new Date('1994-02-12') or new Date(1994, 02-1, 12), because in js months start from 0 and american date format is used where month goes first
you can use the simple JS file DateFormat.js which has some very good example through the URL mattkruse (Date Funtion)
from this JS file you can validate the incoming date is a true format even you can add format date within a several ways.
Presumably you want to know how to format strings so they are consistently parsed by browsers. The short answer, is there is no guarantee that any particular string will be correctly parsed by all browsers in use (or perhaps even most).
So the bottom line is: don't parse strings with the Date constructor, ever. It's largely implementation dependent and even the one format specified in ES5 and ECMAScript 2015 is poorly and inconsistently supported.
How browsers treat a string like "12/02/1994" is entirely implementation dependent, however most will treat it as the peculiar US month/day/year format, i.e. 2 December and getMonth will return 11, since months are zero indexed.
So you should always manually parse strings (a library can help, but a simple parsing function is only 2 lines, 3 if validation is required), e.g.
// Parse a date string as d/m/y
// If s is not a valid date, return a Date object with its
// time value set to NaN.
function parseDMY(s) {
var b = s.split(/\D/);
var d = new Date(b[2], --b[1], b[0]);
return d && b[1] == d.getMonth()? d : new Date(NaN);
}
document.write(parseDMY('12/02/1994'));

getDay() errors with Invalid Date in some browsers

I have the following string: "2012-12-10T23:40:41Z"
My goal is to get the day as a number (0-6)... sun-sat
I pass this string as follows:
var input = "2012-12-10T23:40:41Z";
var day = new Date(input).getDay();
alert(day);
This works just fine in Chrome, but in the adobe air webkit view, it errors with, "Invalid Date"
Any suggestions on finding a way to get the day that is supported by older browsers?
Prior to ES5, there was no standard way of parsing a date (each host implemented their own way of parsing). Since ES5, dates must be in the ISO8601 format in order to be parseable in strict mode.
For maximum portability, you should parse the date manually.
More of a comment.
To manually parse a javascript ISO8601 date string, split it into parts and pass it to the date constructor. Note that javascript date strings should always be UTC (Z), so:
function parseISOdateString(s) {
s = s.split(/\D/);
return new Date(Date.UTC(s[0], --s[1], s[2], s[3], s[4], s[5]))
}
You can add bells and whistles to the above to validate the string and the resultant date object if you like.
Note that according to ES5, invalid date strings given to Date.parse (e.g. 2012-12-00) should return NaN, but some browsers will create a date object for 2012-11-30. The above will return a date object for invalid strings, so if consistency with the spec is required:
function parseISOdateString(s) {
s = s.split(/\D/);
var d = new Date(Date.UTC(s[0], --s[1], s[2], s[3], s[4], s[5]))
return (d.getUTCFullYear() == s[0] && d.getUTCDate() == s[2])? d : NaN;
}
That will return NaN for invalid strings like "2012-12-00"
This page should prove helpful, even though it is a bit old. The issue is that ISO 8601 style dates only got support in Javascript 1.8.5.

Categories