4

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).

EvilDr
  • 8,943
  • 14
  • 73
  • 133

4 Answers4

3

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

Karol Pawlak
  • 440
  • 4
  • 15
  • Hmm interesting, thanks. The challenge here is that if the API returned the date *with `Z` suffix* - a totally valid ISO8601 format, then we have to remember to change the parsing format. I think your solution with multiple `isValid` checks might be the solution... `if (moment(1234, "YYYY-MM-DDTHH:mm:ss", true).isValid() || moment(1234, "YYYY-MM-DDTHH:mm:ssZ", true).isValid()) ` – EvilDr Apr 27 '21 at 09:02
  • You can also strip all incoming dates from the "Z" letter to match the format since "Z" only means that there is zero offset in the timestamp and changes nothing in the matter of parsing. – Karol Pawlak Apr 27 '21 at 09:07
  • According to Wikipedia: https://en.wikipedia.org/wiki/ISO_8601 An offset of zero, in addition to having the special representation "Z", can also be stated numerically as "+00:00", "+0000", or "+00". – Karol Pawlak Apr 27 '21 at 09:07
  • Good answer. Thank you. I'll award the bounty when the time limit allows. – EvilDr Apr 27 '21 at 09:10
  • Please, check above post edits. A simple function, that allows to minimize code needed for validation, also is free from unnecessary repetition – Karol Pawlak Apr 27 '21 at 09:17
1

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>
VincenzoC
  • 30,117
  • 12
  • 90
  • 112
0

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.

Punith Mithra
  • 608
  • 5
  • 9
0

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>
Daniel Alder
  • 5,031
  • 2
  • 45
  • 55