I'm working on a graphical listing of Roman emperors and ran into the following problem:
The birth and death dates are stored in a JSON as a string. e.g. Julius Caesar:
"start":"-000100-07-12"
If I use the Date object
console.log(new Date(caesar.start))
... via console.log it works:
//Date Object Thu Jul 12 -0100 00:53:28 GMT+0053 ...
but if I now want to render the object as a string with
console.log(
new Date("-000100-07-12")
.toLocaleDateString("en", {year : "numeric", era: "short"})
);
console.log gives me
"101 BC" instead of "100 BC"
the problem is easily reproducable.
I only found a similar description
for a different technology.
However, the problem seems to be the same.
A fix would be to write a custom toLocaleDateString function, because getFullYear(), getMonth() work as expected.
Has anyone had similar experiences, or a solution to the problem? I guess handling dates before 1582 is a bit hooky.... maybe it has to do with the fact that there is no year 0?
Looks like the problem is in toLocaleDateString.
But separate methods work as expected
For example
date.getDate(); // 12
date.getFullYear(); // -100
date.getMonth(); // 6 (getMonth() starts with 0)
You can get the same format with this methods.
According to the ISO 8601 standard "-000100-07-12" represents 12th July 101 BCE and the date appears to be being parsed correctly.
It's just that the standard doesn't match our expectations as we almost always deal with positive years.
Unfortunately I'm unable to see what the "Cause" of the linked Oracle error is so it may be the same "issue"
Related
I have the following date:
25-JAN-18 01.31.02 AM +00:00
and am trying to convert into a Postgres compatible timestamp with time zone format.
I am trying the following code to convert into the compatible format:
document.getElementById("parsedDate3").innerHTML = moment("25-JAN-18 01.31.02.923526 AM +00:00 ", "d-MMM-YY hh.mm.ss A Z");
But I am getting the output as
Mon Jan 01 2018 02:31:02 GMT+0100
Can anyone please help me with this.
You are using lower-case d which is for the day of the week, 0 (Sunday) through 6 (Saturday).
Use upper-case D instead, which is for the day of the month.
Note that Moment's formatting tokens are slightly different than in other libraries and languages, so check the chart in the docs carefully.
Also:
In your code you have 6 extra digits following the seconds. If you need to account for those, use SSSSSS. Otherwise they are ignored.
You shouldn't assign a Moment object directly to an HTML element's innerHTML property. Instead, call the .format() function on the Moment object first to generate a string. You can optionally pass an argument to this function, to control the output format.
You can simply do it in PostgreSQL:
SELECT to_timestamp('25-JAN-18 01.31.02.923526 AM +00:00', 'DD-MON-YY HH.MI.SS.US AM TZH:TZM');
to_timestamp
-------------------------------
2018-01-25 02:31:02.923526+01
(1 row)
This will work for PostgreSQL v11 or better.
Earlier versions of to_timestamp cannot parse the time zone information yet.
If you need it to work on 9.6, and you know that the time zone is always going to be +00:00, you could simple omit the TZH:TZM in the format string.
V8 Date parser is broken:
> new Date('asd qw 101')
Sat Jan 01 101 00:00:00 GMT+0100 (CET)
I can use fragile regular expression like this:
\d{1,2} (jan|feb|mar|may|jun|jul|aug|sep|oct|nov|dec) \d{1,4}
but it is too fragile. I cannot rely on new Date (issue in V8) and also moment cant help me because moment is getting rid off date detection (github issue-thread).
is there any workaround for broken v8 date parser?
To be clear. We have Gecko and V8, both have Date. V8 has broken Date, Gecko has working one. I need the Date from in Gecko (Firefox).
Update: It’s definitely broken parser https://code.google.com/p/v8/issues/detail?id=2602
nope, Status: WorkingAsIntended
Date objects are based on a time value that is the number of milliseconds since 1 January, 1970 UTC and have the following constructors
new Date();
new Date(value);
new Date(dateString);
new Date(year, month[, day[, hour[, minutes[, seconds[, milliseconds]]]]]);
From the docs,
dateString in new Date(dateString) is a string value representing a date. The string should be in a
format recognized by the Date.parse() method (IETF-compliant RFC 2822
timestamps and also a version of ISO8601).
Now looking at the v8 sourcecode in date.js:
function DateConstructor(year, month, date, hours, minutes, seconds, ms) {
if (!%_IsConstructCall()) {
// ECMA 262 - 15.9.2
return (new $Date()).toString();
}
// ECMA 262 - 15.9.3
var argc = %_ArgumentsLength();
var value;
if (argc == 0) {
value = %DateCurrentTime();
SET_UTC_DATE_VALUE(this, value);
} else if (argc == 1) {
if (IS_NUMBER(year)) {
value = year;
} else if (IS_STRING(year)) {
// Probe the Date cache. If we already have a time value for the
// given time, we re-use that instead of parsing the string again.
var cache = Date_cache;
if (cache.string === year) {
value = cache.time;
} else {
value = DateParse(year); <- DOES NOT RETURN NaN
if (!NUMBER_IS_NAN(value)) {
cache.time = value;
cache.string = year;
}
}
}
...
it looks like DateParse() does not return a NaN for for a string like 'asd qw 101' and hence the error. You can cross-check the same with Date.parse('asd qw 101') in both Chrome(v8) [which returns -58979943000000] and Gecko (Firefox) [which returns a NaN]. Sat Jan 01 101 00:00:00 comes when you seed new Date() with a timestamp of -58979943000000(in both browsers)
is there any workaround for broken v8 date parser?
I wouldnt say V8 date parser is broken. It just tries to satisfy a string against RFC 2822 standard in the best possible way but so does gecko and both break gives different results in certain cases.
Try new Date('Sun Ma 10 2015') in both Chrome(V8) and Firefox(Gecko) for another such anomaly.
Here chrome cannot decide weather 'Ma' stands for 'March' or 'May' and gives an Invalid Date while Firefox doesnt.
Workaround:
You can create your own wrapper around Date() to filter those strings that V8's own parser cannot. However, subclassing built-ins in ECMA-5 is not feasible. In ECMA-6, it will be possible to subclass built-in constructors (Array, Date, and Error) - reference
However you can use a more robust regular expression to validate strings against RFC 2822/ISO 8601
^(?:(?:31(\/|-|\. |\s)(?:0?[13578]|1[02]|(?:Jan|Mar|May|Jul|Aug|Oct|Dec)))\1|(?:(?:29|30)(\/|-|\.|\s)(?:0?[1,3-9]|1[0-2]|(?:Jan|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec))\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.|\s)(?:0?2|(?:Feb))\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.|\s)(?:(?:0?[1-9]|(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep))|(?:1[0-2]|(?:Oct|Nov|Dec)))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$
Image generated from debuggex
So, seems like v8 aint broken, it just works differently.
Hope it helps!
You seem to be asking for a way to parse a string that might be in any particular format and determine what data is represented. There are many reasons why this is a bad idea in general.
You say moment.js is "getting rid of date detection", but actually it never had this feature in the first place. People just made the assumption that it could do that, and in some cases it worked, and in many cases it didn't.
Here's an example that illustrates the problem.
var s = "01.02.03";
Is that a date? Maybe. Maybe not. It could be a section heading in a document. Even if we said it was a date, what date is it? It could be interpreted as any of the following:
January 2nd, 2003
January 2nd, 0003
February 1st, 2003
February 1st, 0003
February 3rd, 2001
February 3rd, 0001
The only way to disambiguate would be with knowledge of the current culture date settings. Javascript's Date object does just that - which means you will get a different value depending on the settings of the machine where the code is running. However, moment.js is about stability across all environments. Cultural settings are explicit, via moment's own locale functionality. Relying on the browser's culture settings leads to errors in interpretation.
The best thing to do is to be explicit about the format you are working with. Don't allow random garbage input. Expect your input in a particular format, and use a regex to validate that format ahead of time, rather then just trying to construct a Date and seeing if it's valid after the fact.
If you can't do that, you'll have to find additional context to help decide. For example, if you are scraping some random bits of the web from a back-end process and you want to extract a date from the text, you'd have to have some knowledge about the language and locale of each particular web page. You could guess, but you'd likely be wrong a fair amount of the time.
See also: Garbage in, garbage out
ES5 15.9.4.2 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. Unrecognizable
Strings or dates containing illegal element values in the format
String shall cause Date.parse to return NaN.
So that's all right and according to the citation above result of v8 date parser:
new Date('asd qw 101') : Sat Jan 01 101 00:00:00 GMT+0100
(CET)
new Date('asd qw') : Invalid Date
Referencing to the accepted answer on this question How do I get the number of days between two dates in JavaScript?. I see, in the function parseDate:
function parseDate(str) {
var mdy = str.split('/')
return new Date(mdy[2], mdy[0]-1, mdy[1]);
}
He is doing this:
var mdy = str.split('/')
return new Date(mdy[2], mdy[0]-1, mdy[1]);
i.e. splitting the passed date into month, day and year and then passing it on to Date like new Date(year, month, day) while he could simply do new Date(str) and it would have returned the same result (Wouldn't it?). Can anyone please explain the difference between both the ways?
Update: Test results:
var str = '1/1/2000'
var mdy = str.split('/')
console.log( new Date(str) ) // Sat Jan 01 2000 00:00:00 GMT+0500 (Pakistan Standard Time)
console.log( new Date(mdy[2], mdy[0]-1, mdy[1]) ); // Sat Jan 01 2000 00:00:00 GMT+0500 (Pakistan Standard Time)
No, they're not the same (even assuming you'll subtract one month later: he's doing mdy[0] - 1) because new Date(str) is required (by standard, see §15.9.4.2) to accept only date in a specific format ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ, see also this post, I won't repeat myself here):
If the String does not conform to that format [ISO 8601] the function may fall back to any implementation-specific heuristics or implementation-specific date formats.
Please note (as pointed out by Royi in comments) that also RFC 2822 should be supported (according to MDN) but it's not mentioned in JavaScript specifications and Internet Explorer doesn't officially support it (see MSDN, it can parse something similar but it's not the same).
In that code they're parsing using a specific locale rules (MM/DD/YYYY, I suppose en-US locale but it's not only one). To be honest I wouldn't even use that code for parsing (because yes, actually it'll be broken for a different locale: even separator used for splitting is not "locale safe"). Let me explain with an example:
You're using a proper configured date time picker (or <input type="date"/> when supported) you'll enter date according to your locale. For example in Italy (but in general in Europe) we write DD/MM/YYYY.
Now let's imagine that user picked 21 December 2014 (formatted as 21/12/2014 according to his locale).
With string splitting that code will fail (because it'll pick 21 as month number, obviously it's not valid). Even worse than that such errors may even go unnoticed (for example if user picks 1/2/2014 code will "think" it's 2nd Jan but user picked 1st Feb). Do you want to make it more complicate? Even new Date(str) may fail because it's browser dependent (and you can't really trust heuristic to be portable and safe).
If you're asking yourself "Then why they used such code?" I'd say that they used a quick workaround to support dates using en-US locale (probably because browser they used didn't support them with heuristic guess) but it's not something you should reuse.
Solution? Do not ever parse date by hand (unless you really and deep know what you're doing), use a good library (for example moment.js) for that because most assumption you may do about date formatting are...wrong.
I tried to enter your test code into jsperf.com, and the results on my machine are clear, and they say that you should not try to split the string.
I tried two tests for the test using a split string, and supprisingly, the split itself was not what was taking up the time.
Try for yourself at http://jsperf.com/date-from-string-or-dateparts
How to format ISO dates BC with Moment.js?
moment("-700-01-01").year(); // 700 (WRONG)
moment("-0700-01-01").year(); // 700 (WRONG)
moment("-000700-01-01").year(); // -700 (RIGHT)
For some reason a year notation with 6 digits works. Is that the "right" way? Why doesn't notation like "-700-01-01" work?
This isn't a Moment.js-specific problem; the same happens if you attempt to initialise a Date() object with the string you're using as well. If you create it as a Date() object first and manually assign the year using setYear() it does accept a date of -700:
var date = new Date();
date.setYear(-700);
moment(date).year();
> -700
However as Niels Keurentjes has pointed out, date calculations this far back get quite complicated and may not be at all reliable.
If you want "-700-01-01" you can configure the year, month and day separately:
date.setYear(-700);
date.setMonth(0);
date.setDate(1);
console.log(date);
> Fri Jan 01 -700 11:53:57 GMT+0000 (GMT Standard Time)
As to whether the 1st day of the 1st month in 700BC was actually a Friday... you'll have to look that one up yourself.
You can also
moment('0000-01-01', 'YYYY-MM-DD').set('y', -700)
in your example the minus sign is also used as separator between years, month and days. As you point out in the comment of the answer of James, using coma as separator helps to distinguish.
moment can display expanded years using the YYYYYY notation. This feature is recorded in the moment.js display documentation and elaborated upon in the ECMAscript documentation.
I am trying to validate following date format through regExp, but still i didn't get ant working solution-
ex.-
OCT-12-2011
FEB-06-1995
how can i do it using regexp.
Thanks in advance!!
Date JS is the first hit on Google.
Comprehensive, yet simple, stealthy and fast. Datejs has passed all trials and is ready to strike. Datejs doesn’t just parse strings, it slices them cleanly in two.
Sample code from their site:
Date.parse('today');
Date.parse('t + 5 d'); // today + 5 days
Date.parse('next thursday');
Date.parse('February 20th 1973');
Date.parse('Thu, 1 July 2004 22:30:00');
The only downside is that it modifies the prototype of build-in Date object. Although it's considered a discouraged practice I doubt that these particular additions will affect you application. There are still plenty of sites using Prototype without any problems.
EDIT: ragarding parsing Date values via RegExps.
Be very careful especially considering different date.toString() implementation in different browsers. Here's what new Date().toString() gave me:
IE9: "Mon Jul 11 14:50:45 UTC+0300 2011"
FF5: "Mon Jul 11 2011 14:51:08 GMT+0300 (FLE Daylight Time)"
If you get those strings from the server and you feel that adding a library just for dates is an overhead you'll be fine with regular expressions.
Don't try to validate it very strictly using a regex. Check the format, using something like this:
[A-Z]{3}-[0-9]{2}-[0-9]{4}
Then, check each part to see whether that part is valid.
... not sure how strictly you want to do this.
var months = [
'JAN','FEB','MAR','APR','MAY','JUN','JUL','AUG','SEP','OCT','NOV','DEC'
],
regValidate = RegExp('^(?:' + months.join('|') + ')-[0-3][0-9]-\\d{4}$');
alert(regValidate.test('OCT-12-2011'));
Depending on you requirements, the following could do:
^[A-Z]{3}-\d{2}-\d{4}$
Or more elaborate:
^(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)-(0[1-9]|[12]\d|3[01])-[12]\d{3}$
Or anything in between :) (You may even elaborate further on the year part.)
If you want to do it with a single regexp, you could use something like:
/^(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)-([012]\d|3[01])-[12]\d{3}$/
This will not match a date before JAN-01-1000 or after DEC-31-2999.
Note: do not forget to check for invalid february dates like FEB-30-2011