DateTimeFormatter for Javascript Date.toString output - javascript

Trying to support Javascript's new Date().toString() output format with Java's DateTimeFormatter but can't seem to make it work.
Js output is of the following nature:
Wed Apr 04 2018 09:56:16 GMT-0500 (SA Pacific Standard Time)
Wed Apr 04 2018 16:12:41 GMT+0200 (CEST)
My current formatter:
int defaultOffset = ZonedDateTime.now().getOffset().getTotalSeconds();
DateTimeFormatter dtfJs = new DateTimeFormatterBuilder()
.appendPattern("EE MMM dd yyyy HH:mm:ss [OOOO (zzzz)]")
.parseDefaulting(ChronoField.OFFSET_SECONDS,defaultOffset
.toFormatter();
If i .parse() those date strings from js, I get the following error:
[date] could not be parse at index 25
Index 25 for both the dates mentioned is:
GMT-0500 (SA Pacific Standard Time)
GMT+0200 (CEST)
I know the problem is with the : (colon) because if I print the current date with dtfJs, I get:
Wed Apr 04 2018 10:25:10 GMT-05:00 (Colombia Time)
So the part of the GMT-05:00 is exected as GMT-0500 in the string recieved but I can't find a reserved pattern letter which matches this.
The docs say:
Offset O: This formats the localized offset based on the number of
pattern letters. One letter outputs the short form of the localized
offset, which is localized offset text, such as 'GMT', with hour
without leading zero, optional 2-digit minute and second if non-zero,
and colon, for example 'GMT+8'. Four letters outputs the full form,
which is localized offset text, such as 'GMT, with 2-digit hour and
minute field, optional second field if non-zero, and colon, for
example 'GMT+08:00'. Any other count of letters throws
IllegalArgumentException.
Offset Z: This formats the offset based on the number of pattern
letters. One, two or three letters outputs
the hour and minute, without a colon, such as '+0130'. The output will
be '+0000' when the offset is zero. Four letters outputs the full form
of localized offset, equivalent to four letters of Offset-O. The
output will be the corresponding localized offset text if the offset
is zero. Five letters outputs the hour, minute, with optional second
if non-zero, with colon. It outputs 'Z' if the offset is zero. Six or
more letters throws IllegalArgumentException.
Which means that the four letter will output always with colon ":", thus throwing DateTimeParseException
Help greatly appreciated, thanks
Edit
Thanks to #mszymborski I managed to pass on to validate the parenthesis part "(CEST)", what would be useful here ?
I tried with EE MMM dd yyyy HH:mm:ss 'GMT'Z (zz) but this only works with the second date in the list, not the first
GMT-0500 (SA Pacific Standard Time) ERROR
GMT+0200 (CEST) PASS

Dates in JavaScript is a big mess. toString() is not only browser/implementation dependent, but also locale sensitive. I'm in Brazil, so my browser is set to Portuguese, and new Date().toString() gives this result:
Wed Apr 04 2018 14:14:04 GMT-0300 (Hora oficial do Brasil)
Month and day-of-week names are in English, but the timezone name is in Portuguese. What a mess!
Anyway, to parse those strings, you have to make some decisions.
Do you need to get the timezone or just the offset?
The offset GMT+0200 is used by more than one country (hence, more than one timezone uses it). Although the offset is enough to have a non-ambiguous point in time, it's not enough to know the timezone.
Even short names such as CEST are not enough, because this is also used by more than 1 country.
If you want to parse just the offset, the best way is to simply remove everything after the ( and parse it to an OffsetDateTime:
DateTimeFormatter parser = DateTimeFormatter.ofPattern("EEE MMM dd yyyy HH:mm:ss 'GMT'Z", Locale.US);
// 2018-04-04T16:12:41+02:00
OffsetDateTime.parse("Wed Apr 04 2018 16:12:41 GMT+0200", parser);
Also note that I used a java.util.Locale. That's because the month and day of week are in English, and if you don't set a locale, it'll use the JVM default - and you can't guarantee that it'll always be English. It's better to set a locale if you know in what language the inputs are.
If you need to get the timezones, though, it's more complicated.
Names like "CEST" are ambiguous, and you need to make arbitrary choices for them. With java.time is possible to build a set of preferred timezones to be used in case of ambiguities:
Set<ZoneId> zones = new HashSet<>();
zones.add(ZoneId.of("Europe/Berlin"));
zones.add(ZoneId.of("America/Bogota"));
DateTimeFormatter fmt = new DateTimeFormatterBuilder()
.appendPattern("EEE MMM dd yyyy HH:mm:ss 'GMT'Z (")
// optional long timezone name (such as "Colombia Time" or "Pacific Standard Time")
.optionalStart().appendZoneText(TextStyle.FULL, zones).optionalEnd()
// optional short timezone name (such as CET or CEST)
.optionalStart().appendZoneText(TextStyle.SHORT, zones).optionalEnd()
// close parenthesis
.appendLiteral(')')
// use English locale, for month, timezones and day-of-week names
.toFormatter(Locale.US);
With this, you can parse your inputs to a ZonedDateTime:
// 2018-04-04T16:12:41+02:00[Europe/Berlin]
ZonedDateTime.parse("Wed Apr 04 2018 16:12:41 GMT+0200 (CEST)", fmt);
// 2018-04-04T10:25:10-05:00[America/Bogota]
ZonedDateTime.parse("Wed Apr 04 2018 10:25:10 GMT-0500 (Colombia Time)", fmt);
But unfortunately, this doesn't parse the "SA Pacific Standard Time" case. That's because the timezones names are built-in in the JVM and "SA Pacific Standard Time" is not one of the predefined strings.
A good alternative is to use the mapping suggested by M.Prokhorov in the comments: https://github.com/nfergu/Java-Time-Zone-List/blob/master/TimeZones/src/TimeZoneList.java
Then you manually replace the name in the string and parse it with VV pattern (instead of z), because the mapping uses IANA's names (such as Europe/Berlin, which are parsed by VV).
But the best alternative is to use toISOString(), which produces strings in ISO8601 format, such as 2018-04-04T17:39:17.623Z. The big advantage is that java.time classes can parse it directly (you don't need to create a custom formatter):
OffsetDateTime.parse("2018-04-04T17:39:17.623Z");

Related

Convert date from UK GMT string to local date in JavaScript

I have a situation where I am always returned the date from the server as a UK date time string.
E.g. '2020-07-19 16:40:00'
This would be 4:40PM in UK at +01:00, or 3:40PM UTC.
I want to be able to convert this time from GMT to the local time on the computer;
If I do this when in the UK...
var date = new Date('2020-06-19 16:40:00 GMT');
it returns Fri Jun 19 2020 17:40:00 GMT+0100 (British Summer Time)
Which is an hour out.
If I do the date in winter time (without daylight savings), this is correct.
var date = new Date('2020-01-19 16:40:00 GMT');
returns Sun Jan 19 2020 16:40:00 GMT+0000 (Greenwich Mean Time)
Is there a way I can correctly adjust this to always give the correct time regardless of what timezone the computer is set in, based on UK clock times.
Thanks in advance
As I understand your question, you don't know if the timestamp from the server is GMT or BST as the offset isn't included. You can work it out using plain JS but it's somewhat kludgy and error prone, see Calculate Timezone offset only for one particular timezone.
It would be much better to get the server to use an ISO 8601 format supported by ECMAScript and either send the offset or always use UTC/GMT.
If that isn't an option, you can use a library like Luxon to specify the location (and hence offset rules) to use for parsing, e.g.
let DateTime = luxon.DateTime;
['2020-07-19 16:40:00', // BST +1
'2020-01-19 16:40:00' // GMT +0
].forEach(ts => console.log(
DateTime.fromFormat(ts, 'yyyy-LL-dd HH:mm:ss', {zone: 'Europe/London'}))
);
<script src="https://cdn.jsdelivr.net/npm/luxon#1.24.1/build/global/luxon.min.js"></script>
PS Don't forget to always tell the parser the format to parse.

JavaScript date from string. Unexpected results [duplicate]

This question already has answers here:
Why does Date.parse give incorrect results?
(11 answers)
Closed 2 years ago.
Came across this one tonight and thought I would throw it out there to see if anybody had a rational and clear explanation on what's going on.
I've declared two JavaScript date objects and passed in date strings to the constructor (same date).
In the first variable I have been explicit with the time and added 00:00.
In the second variable I have omitted the time (assuming that it would be midnight).
I've wrapped everything in a Self-Invoking Function but this has no bearing on the outcome.
I'm in GMT so I've used a date that is in BST (GMT+1).
(function() {
'use strict';
let a = new Date("2020-06-05 00:00");
let b = new Date("2020-06-05");
console.log(a);
console.log(b);
console.log(JSON.stringify(a));
console.log(JSON.stringify(b));
})();
I would have expected Fri Jun 05 2020 00:00:00 GMT+0100 (British Summer Time) on both cases but the second variable without the explicit 00:00 has been set to Fri Jun 05 2020 01:00:00 GMT+0100.
Any explanations?
It appears that a date inside BST (+1) without a time assumes it's +1.
Scenario doesn't happen with new Date("2020-01-05"); for example. This is outside BST and therefore +0.
Fri Jun 05 2020 00:00:00 GMT+0100 (British Summer Time)
Fri Jun 05 2020 01:00:00 GMT+0100 (British Summer Time)
"2020-06-04T23:00:00.000Z"
"2020-06-05T00:00:00.000Z"
Due to the ECMA specs:
ECMA-262 Date(value) constructor specs
Date.parse
If the String conforms to the Date Time String Format, substitute values take the place of absent format elements. When the MM or DD elements are absent, "01" is used. When the HH, mm, or ss elements are absent, "00" is used. When the sss element is absent, "000" is used. When the UTC offset representation is absent, date-only forms are interpreted as a UTC time and date-time forms are interpreted as a local time.
According to ECMA specs:
Date only forms without timezone are interpreted as UTC.
Date-Time without timezone are interpreted as local time.
Generally you are better off just using the full ISO-like form with timezone attached. But since these are spec-compliant, if you always keep it under control it's safe to depend on this behavior, albeit it might get confusing sometimes for both you and others, especially when it comes to server-side code and DB storage.

Javascript date object automatically add one day when creating from date string

In my javascript i want to convert date from date string.
i have string like
date = "Thu Sep 03 2015 19:30:00 GMT+0000"
Now i convert string using Date object.
var d = new Date(date);
But this gives me,
Fri Sep 04 2015 01:00:00 GMT+0530 (IST)
It automatically add one day into day. What is wrong?
It automatically add one day into day. What is wrong?
Nothing. The time you input is 19:30 GMT and the timezone on the device you're using is set to GMT+0530. Add 5 hours 30 minutes to 7:30pm and you get 01:00am the following day.
You should not use the Date constructor to parse strings, as it is inconsistent across browsers and until recently, entirely implementation dependent. Manually parse strings, or use a Date library.

Why does js subtract a day from a Date object with a certain format?

I get dates from the database in this format:
yyyy-mm-dd
When I create a javascript Date object using this string, it builds a day before the date.
You can test this in your console:
var d = new Date("2015-02-01");
d
You will get January 31st! I've tested many theories, but none answer the question.
The day is not zero-based, otherwise it would give Feb 00, not Jan 31
It's not performing a math equation, subtracting the day from the month and/or year
Date(2015-02-01) = Wed Dec 31 1969
Date("2015-01") = Wed Dec 31 2014
It is not confusing the day for the month
Date("2015-08-02") = Sat Aug 01 2015
If this were true the date would be Feb 08 2015
If you create a Date using a different format, it works fine
Date("02/01/2015") = Feb 1st, 2015
My conclusion is that js does this purposefully. I have tried researching 'why' but can't find an explanation. Why does js build dates this way, but only with this format? Is there a way around it, or do I have to build the Date, then set it to the next day?
PS: "How to change the format of the date from the db" is not what I'm asking, and that is why I'm not putting any db info here.
Some browsers parse a partial date string as UTC and some as a local time,
so when you read it the localized time may differ from one browser to another
by the time zone offset.
You can force the Date to be UTC and add the local offset if you
want the time to be guaranteed local:
1. set UTC time:
var D= new Date("2015-02-01"+'T00:00:00Z');
2. adjust for local:
D.setMinutes(D.getMinutes()+D.getTimezoneOffset());
value of D: (local Date)
Sun Feb 01 2015 00:00:00 GMT-0500 (Eastern Standard Time)
Offset will be whatever is local time.
Some differences between browsers when time zone is not specified in a parsed string:
(tested on Eastern Standard Time location)
(new Date("2015-02-01T00:00:00")).toUTCString();
Firefox 35: Sun, 01 Feb 2015 05:00:00 GMT
Chrome 40: Sun, 01 Feb 2015 00:00:00 GMT
Opera 27: Sun, 01 Feb 2015 00:00:00 GMT
IE 11: Sun, 01 Feb 2015 05:00:00 GMT
IE and Firefox set the Date as if it was local, Chrome and Opera as if it was UTC.
In javascript, Date objects are internally represented as the number of milliseconds since Jan 1st 1970 00:00:00 UTC. So instead of thinking of it as a "date" in the normal sense, try thinking of a Date object as a "point in time" represented by an integer number (without timezone).
When constructing your Date object using a string, you are actually just calling the parse function. Most date time formats (including ISO 8601) allow you to reduce the precision of a date string.
For reduced precision, 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.
e.g. 2015-02-01 would represent the day February 1st 2015.
This causes a dilemma for javascript because a Date object is always accurate to the millisecond. Javascript cannot store a reduced accuracy date since it is just an integer of milliseconds since 1st Jan 1970. So it does the next best thing which is to assume a time of midnight (00:00:00) if not specified, and a timezone of UTC if not specified.
All valid javascript implementations should give the same result for this:
var d = new Date("2015-02-01");
alert(d.getTime());
1422748800000
The out-by-1-day issue comes when outputting the date either to some (often unclear) debugger or using the getter methods because the local timezone is used. In a browser, that will be your operating systems timezone. Anyone "west" of Greenwich Mean Time may see this problem because they have a negative UTC offset. Please note there are UTC equivalent functions too which use the UTC timezone, if you are really just interested in representing a date rather than a point in time.

Why does javascript interpret these identical dates differently

What's going on here:
> new Date('Apr 15 2013');
Mon Apr 15 2013 00:00:00 GMT+0100 (GMT Daylight Time)
> new Date('04/15/2013');
Mon Apr 15 2013 00:00:00 GMT+0100 (GMT Daylight Time)
> new Date('2013-04-15');
Mon Apr 15 2013 01:00:00 GMT+0100 (GMT Daylight Time)
Obviously, one is being interpreted as UTC time, while the other two are being interpreted as local time. What causes the difference in parsing?
From the specification:
The String may be interpreted as a local time, a UTC time, or a time in some other time zone, depending on the contents of the String. The function first attempts to parse the format of the String according to the rules called out in Date Time String Format (15.9.1.15). If the String does not conform to that format the function may fall back to any implementation-specific heuristics or implementation-specific date formats.
Of all the formats you provided, only '2013-04-15' is officially supported, so the others seem to fall back to implementation-dependent behavior.
Your third example is the only one that is actually explained by the spec. When you call the Date constructor with a single argument, this is what happens (where v is the string passed to the constructor):
Parse v as a date, in exactly the same manner as for the parse method (15.9.4.2); let V be the time value for this date.
The parse method will attempt to parse the string (emphasis added):
The String may be interpreted as a local time, a UTC time, or a time in some other time zone, depending on the contents of the String. The function first attempts to parse the format of the String according to the rules called out in Date Time String Format (15.9.1.15).
If the String does not conform to that format the function may fall back to any implementation-specific heuristics or implementation-specific date formats.
And the "Date Time String Format" is YYYY-MM-DDTHH:mm:ss.sssZ, and also defines a shorter version of the form YYYY-MM-DD, which is what you use in your third example.
For the other examples, the parse will fail and the behaviour is implementation-defined.
The Date constructor delegates to Date.parse, and it appears that Date.parse has two variants:
Given a string representing a time, parse returns the time value. It
accepts the RFC2822 / IETF date syntax (RFC2822 Section 3.3), e.g.
"Mon, 25 Dec 1995 13:30:00 GMT". It understands the continental US
time-zone abbreviations, but for general use, use a time-zone offset,
for example, "Mon, 25 Dec 1995 13:30:00 GMT+0430" (4 hours, 30 minutes
east of the Greenwich meridian). If you do not specify a time zone,
the local time zone is assumed. GMT and UTC are considered equivalent.
Alternatively, the date/time string may be in
ISO 8601 format. Starting with JavaScript 1.8.5 (Firefox 4), a subset
of ISO 8601 is supported. For example, "2011-10-10" (just date) or
"2011-10-10T14:48:00 (date and time) can be passed and parsed.
Clearly, these behave differently with respect to local timezones
Edit: Looks like this is implementation defined

Categories

Resources