Javascript - Display date time and ignore locale/timezone - javascript

I am having a bit of a nightmare working with a CMS that saves datetimes without timezones. For a wide variety of infrastructure reasons I am unable to adjust core files, I can only adjust some of the javascript of the CMS field itself, and not include external libraries (or the dayjs UTC plugin).
How it currently works:
CMS Saves datetime string like so: 2020-10-29 05:00 which is missing the timezone
When reloading, the dayjs parses the string 2020-10-29 05:00 as UTC and changes the time based on the browser locale.
If you are using a browser that it not UTC, the time displayed will not correspond to the saved string
My hacky idea:
When loading the string, get the browser's timezone
Modify the string 2020-10-29 05:00 to include the browser's timezone, or offset the date object so that when it is parsed as 'local', it will display correctly
My initial thought was just to add/subtract the offset before displaying but it still didn't seem to work (I think due to getTimezoneOffset not adjusting for daylight savings?):
let date = new Date('2020-10-29 05:00')
console.log(new Date(date.setMinutes(date.getMinutes() + new Date().getTimezoneOffset())))
I suppose an alternate form of this question is: Is there a way to parse a UTC datetime string as though it were local?

If the string is UTC, you should parse it as UTC and then do everything in UTC. I think the easiest way is to parse the string as UTC with your own function or library.
Also, given:
new Date('2020-10-29 05:00')
some browsers will return an invalid date (they treat it as a malformed version of the supported format YYYY-MM-DDTHH:mm:ss).
Generally, using the built–in parser is strongly discouraged because of browser inconsistencies. Also, messing with timezone offsets can also lead to difficult to find issues (like when applying the offset causes the date to cross a DST boundary).
A simple function to do the job:
function parseAsUTC(s) {
let [y, m, d, H, M] = s.split(/\D/);
return new Date(Date.UTC(y, m-1, d, H, M));
}
let s = '2020-10-29 05:00';
console.log(parseAsUTC(s));

Related

When converting date, I get previous day

I want to convert a string into a date "as it is".
const date = "8/16/2019"
console.log(new Date(date))
However, I get:
As you can see I get the prevous day. I was thinking that it might be a timezone issue, even though there is no timezone that I am converting it from.
Any suggestions how to convert is as it is?
I appreciate you replies!
If your format is consistent, you could split on / and use Date.UTC. Creating your new Date from that would ensure it's UTC.
const date = "8/16/2019"
const [month,day,year] = date.split("/");
const utcDate = Date.UTC(year,month-1,day);
console.log(new Date(utcDate));
const date = "8/16/2019"
console.log(new Date(date).toLocaleString("en-US", {timeZone: "Asia/kolkata"}))
Note:- You need to add timezone
You can use toLocaleDateString
console.log(new Date("8/16/2019").toLocaleDateString('en-us', {timeZone: "Asia/Kolkata"}))
new Date("8/16/2019") will create a date object using your current timezone. Add a "Z" at the end if you want your date to be in UTC.
console.log(new Date("8/16/2019Z"))
EDIT
It appears that Firefox is not implementing the parsing of standard date format. Unfortunately until recently how exactly was a date parsed was completeley based on heuristics and intrinsically non portable.
Looking at Firefox bug tracker seems the issue has been discussed but the problem is still present (some toolkit just works around by replacing "Z" with "+00:00" before calling the parser).
The only way to be sure on every browser is to parse the string yourself and build the date from the fields. I didn't notice because I'm using chrome instead (in both chrome and Node works as expected).
EDIT 2
After more investigation seems the standard requires that:
If you use yyyy-mm-ddThh:mm:ssz then you get what ISO format for datetime defines it to be. Also the syntax described in the standard is not very precise and for example is not clear to me if the time zone can be present when no time is present (Chrome says yes, Firefox says no).
If you use another format then anything goes (so for example there is no string that is guaranteed to issue an invalid date response).
In other words new Date("8/16/2019") is not portable Javascript (with the meaning that you don't know what date / time / timezone you will get, if any). Either you parse yourself the date or you just live with what that version of that Javascript engine in that moment decides to give you.

ISO Date Being Sent Incorrectly Because of DST

A component in our app allows the user to input their birthdate and then, later, display it again. It works for most cases, but there is a problem for some use cases, I suspect because of Daylight Savings Time.
I am sending the date to the backend in ISO format (UTC time) and receiving it also in ISO format, but with relative time. Example (8. 8. 2000):
sending: 2000-08-07T22:00:00.000Z
receiving: 2000-08-08T00:00:00+02:00
For some dates, the returned date is one day lower. Example (8. 8. 1977, becomes 7. 8. 1977):
sending: 1977-08-07T23:00:00.000Z
receiving: 1977-08-08T00:00:00+02:00
I have a theory that it's because the client and server use different locales - which have historically observed DST differently. I am based in the Czech Republic, and the country hasn't observed DST in 1977.
How can I fix this problem? If my theory is correct, how do I find out what locales are being used and how do I set the frontend/client one?
Thanks in advance!
You don't want to have to be aware of locales ! We're talking about dates. You need to construct and transmit your dates so that locale never becomes a problem. To do this, your dates need to represent midnight UTC. Looking at your examples, neither your sending nor receiving dates fit this condition, so I think you risk to go insane.
Hopefully this answer will have useful information for you.
If you only want to deal in dates, then do not use the time component, only transmit dates, e.g. 2000-08-07.
Unfortunately, the TC39 made a bad decision in deciding that the ISO 8601 date only format YYYY-MM-DD should be parsed as UTC instead of following ISO 8601 and treating it as local. The decision seems to have been a commercially motivated rather than following common sense.
But regardless, you can send and receive the date in ISO 8601 date–only format by parsing and creating the date yourself. It's only a couple of lines of code and has been answered many time elsewhere, but here are some simple functions:
function parseISODate(s) {
var [y, m, d] = s.split(/\D/);
return new Date(y, m-1, d);
}
function formatISODate(date) {
let z = n => (n<10?'0':'')+n;
return date.getFullYear() + '-' +
z(date.getMonth()+1) + '-' +
date.getDate();
}
let s = '2000-08-31';
let d = parseISODate(s);
console.log(d.toLocaleString());
console.log(formatISODate(d));

Alternative to casting UTC Date in Javascript?

I wish to create a new Date in JS, but have it be cast as UTC time. For example, suppose castAsUTC() produces the following desired effect:
var x = new Date('2019-01-01T00:00:00') // In local time (PST)
castAsUTC(x).toISOString(); // => '2019-01-01T00:00:00Z'
// x.toISOString() gives us '2019-01-01T08:00:00Z', which is undesired
Currently, my function looks like this:
function castAsUTC(date) {
return new Date(x.toLocaleString() + '+00:00');
}
Is there a cleaner/nicer way of producing the same effect? Thanks in advance!
EDIT: To be more specific, I'm interested in transforming the date's timezone, without changing its actual value with as little arithmetic as possible. So calling .toISOString() will produce the same date as it is in local time.
I am currently using the moment-timezone library, but I can't seem to get the desired effect using that, either. I would definitely accept an answer that uses Moment.js
You can switch a Moment instance to UTC using the utc function. Then just use format to get whatever the specific output you want from it.
If indeed the string you have is like the one shown, then the easiest thing to do would be to append a Z to indicate UTC.
var input = '2019-01-01T00:00:00';
var date = new Date(input + 'Z');
var output = date.toISOString();
Or, if you would like to use Moment.js, then do this:
var input = '2019-01-01T00:00:00';
var m = moment.utc(input);
var output = m.format();
You do not need moment-timezone for this.
tl;dr;
You formatted the date wrong. Add the letter "Z" to the end of your date string and it will be treated as UTC.
var x = new Date('2019-01-01T00:00:00Z') // Jan 1, 2019 12 AM UTC
These formatting issues are easier to manage with a library like momentjs (utc and format functions) as described in other answers. If you want to use vanilla javascript, you'll need to subtract out the timezone offset before calling toISOString (see warnings in the longer answer below).
Details
Date in javascript deals with timezones in a somewhat counter intuitive way. Internally, the date is stored as the number of milliseconds since the Unix epoch (Jan 1, 1970). That's the number you get when you call getTime() and it's the number that's used for math and comparisons.
However - when you use the standard string formatting functions (toString, toTimeString, toDateString, etc) javascript automatically applies the timezone offset for the local computers timezone before formatting. In a browser, that means it will apply the offset for the end users computer, not the server. The toISOString and toUTCString functions will not apply the offset - they print the actual UTC value stored in the Date. This will probably still look "wrong" to you because it won't match the value you see in the console or when calling toString.
Here's where things really get interesting. You can create Date's in javascript by specifying the number of milliseconds since the Unix epoch using new Date(milliseconds) or by using a parser with either new Date(dateString). With the milliseconds method, there's no timezone to worry about - it's defined as UTC. The question is, with the parse method, how does javascript determine which timezone you intended? Before ES5 (released 2009) the answer was different depending on the browser! Post ES5, the answer depends on how you format the string! If you use a simplified version of ISO 8601 (with only the date, no time), javascript considers the date to be UTC. Otherwise, if you specify the time in ISO 8601 format, or you use a "human readable" format, it considers the date to be local timezone. Check out MDN for more.
Some examples. I've indicated for each if javascript treats it as a UTC or a local date. In UTC, the value would be Jan 1, 1970 at midnight. In local it depends on the timezone. For OP in pacfic time (UTC-8), the UTC value would be Jan 1, 1970 at 8 AM.
new Date(0) // UTC (milliseconds is always UTC)
new Date("1/1/1970"); // Local - (human readable string)
new Date("1970-1-1"); // Local (invalid ISO 8601 - missing leading zeros on the month and day)
new Date("1970-01-01"); // UTC (valid simplified ISO 8601)
new Date("1970-01-01T00:00"); // Local (valid ISO 8601 with time and no timezone)
new Date("1970-01-01T00:00Z"); // UTC (valid ISO 8601 with UTC specified)
You cannot change this behavior - but you can be pedantic about the formats you use to parse dates. In your case, the problem was you provided an ISO-8601 string with the time component but no timezone. Adding the letter "Z" to the end of your string, or removing the time would both work for you.
Or, always use a library like momentjs to avoid these complexities.
Vanilla JS Workaround
As discussed, the real issue here is knowing whether a date will be treated as local or UTC. You can't "cast" from local to UTC because all Date's are UTC already - it's just formatting. However, if you're sure a date was parsed as local and it should really be UTC, you can work around it by manually adjusting the timezone offset. This is referred to as "epoch shifting" (thanks #MattJohnson for the term!) and it's dangerous. You actually create a brand new Date that refers to a different point in time! If you use it in other parts of your code, you can end up with incorrect values!
Here's a sample epoch shift method (renamed from castAsUtc for clarity). First get the timezone offset from the object, then subtract it and create a new date with the new value. If you combine this with toISOString you'll get a date formatted as you wanted.
function epochShiftToUtc(date) {
var timezoneOffsetMinutes = date.getTimezoneOffset();
var timezoneOffsetMill = timezoneOffsetMinutes * 1000 * 60;
var buffer = new Date(date.getTime() - timezoneOffsetMill);
return buffer;
}
epochShiftToUtc(date).toUTCString();

JS Date() - Leading zero on days

A leading zero for the day within a string seems to break the Javascript Date object in Chrome. There are also some inconsistencies between browsers, since Firefox handles the leading zero correctly, but fails when the zero is not included. See this example: https://jsfiddle.net/3m6ovh1f/3/
Date('2015-11-01'); // works in Firefox, not in Chrome
Date('2015-11-1'); // works in Chrome, not in Firefox
Why? Is there a good way to work around/with the leading zero?
Please note, the strings are coming from MySQL via AJAX and all dates will contain the leading zero, and I can fix this by formating the dates server-side. What format would work the best?
EDIT
Just to specify what my problem was, it looks like Chrome is applying a time zone to the YYYY-MM-DD format, which reverts the Nov. 1st date back to the Oct. 31st date (because of my EDT local time).
According to ECMA-262 (5.1):
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.
The date/time string format as described in 15.9.1.15 is YYYY-MM-DDTHH:mm:ss.sssZ. It can also be a shorter representation of this format, like YYYY-MM-DD.
2015-11-1 is not a valid date/time string for Javascript (note it's YYYY-MM-D and not YYYY-MM-DD). Thus, the implementation (browser) is able to do whatever it wants with that string. It can attempt to parse the string in a different format, or it can simply say that the string is an invalid date. Chrome chooses the former (see DateParser::Parse) and attempts to parse it as a "legacy" date. Firefox seems to choose the latter, and refuses to parse it.
Now, your claim that new Date('2015-11-01') doesn't work in Chrome is incorrect. As the string conforms to the date/time string format, Chrome must parse it to be specification compliant. In fact, I just tried it myself -- it works in Chrome.
So, what are your options here?
Use the correct date/time format (i.e. YYYY-MM-DD or some extension of it).
Use the new Date (year, month, date) constructor, i.e. new Date(2015, 10, 1) (months go from 0-11) in this case.
Whichever option is up to you, but there is a date/time string format that all specification compliant browsers should agree on.
As an alternative, why not use unix timestamps instead? In JavaScript, you would would multiply the timestamp value by 1000,
e.g
var _t = { time: 1446220558 };
var _d = new Date( _t.time*1000 );
Test in your browser console:
new Date( 14462205581000 );
// prints Fri Oct 30 2015 11:55:58 GMT-0400 (EDT)
There's a little benefit in it as well (if data comes via JS) - you'd save 2 bytes on every date element '2015-10-30' VS 1446220558 :)

Taking UTC time from database and displaying local time in browser

I have a table that contains DateTimes in UTC. I'm using PHP to return these date/time strings to an AJAX request in JSON. An example of one of these strings I am receiving on the front end is "2014-12-22 09:36:54". I would like to display this to the user in their local time. For instance I am 8 hours behind UTC time in California, so I would like to see something like "2014-12-22 01:36:54".
In javascript I tried new Date("2014-12-22T09:36:54").toLocaleString() and got "12/22/2014, 9:36:54 AM"---basically an unchanged date/time.
I tried new Date("2014-12-22T09:36:54").toUTCString() and I got "Mon, 22 Dec 2014 17:36:54 GMT", which is pretty much the opposite of what I wanted. But I guess that should have been obvious.
Last thoughts...:
Am I going to have to do some manipulation involving getTimezoneOffset()?
Would this be easier solved on the PHP backend?
One last note is that I included jstz.js thinking it would help but all it does is return the timezone name, and I don't know how that is particularly useful. Does any function take the name of a timezone as an argument which would be helpful in this situation?
To reliably convert a UTC string like "2014-12-22 09:36:54" to local, manually parse it, e.g.:
function parseISOAsLocal(s) {
var b = s.split(/\D/);
return new Date(Date.UTC(b[0], b[1]-1, b[2], b[3], b[4], b[5]));
}
An ISO 8601 compliant string without a timezone must be treated as UTC according to the current ECMA-262 standard (ed5). However, the draft version 6 changes that to treat them as local (per ISO 8601). Most browsers treat it as UTC, however Chrome treats it as local (and IE 8 as NaN). Much better to manually parse date strings and avoid browser vagaries.

Categories

Resources