Is it okay to send client time zone offset in request? - javascript

I have two dates on the client
openTime: 2020-01-01 00:00:000 (GMT+2)
closeTime: 2020-01-01 06:00:000 (GMT+2)
Before sending it to the server I convert them to ISO
openTime.toISOString() // 2019-12-31T22:00:00.000Z
closeTime.toISOString() // 2020-01-01T04:00:00.000Z
Server receives these dates and applies validation logic
isSameDay(openTime, closeTime) // false
The comparison result is false. It happens because of timezones. Whenever openTime with such time converts to UTC the date "jumps" into the different date.
Before converting to ISO
openTime: 2020-01-01 00:00:000
After converting to ISO
openTime: 2019-12-31T22:00:00.000Z
How to solve this issue?
Is it okay to send client timeZoneOffset beside openTime & closeTime values.
..or, send openTime & closeTime with custom format which adds timezone to the ISO string?

FWIW, I'd go with your concept of formatting the date with the timezone in it. Make the timezone part +HH:MM or -HH:MM (four digits) and the JavaScript Date object will be able to parse it correctly. (That's part of the ISO 8601 subset that JavaScript supports.) So for instance, 2021-08-02T16:03:26.499+02:00.
Then, in your server-side, to see whether the open and close are on the same day, you can just use string comparison: open.substring(0, 10) === close.substring(0, 10).

Related

Wrong date returned by JS when parsing ISO string sent by Dart

In my flutter app, I have a funtion that sends an ISO date string to a node.js rest API. However, when the date is parsed with in the js Date object, it returns a different date. I've also tried to send it in other formats like .toLocal() and .toUtc() with the same result.
Flutter (Dart)
///
/// SEND DATE TO NODE.JS ENDPOINT
///
void sendDate() async {
DateTime date = new DateTime(2020, 1, 1);
http.Response response = await http.post(
Uri.encodeFull('${config.domain}/sendDate'),
headers: { "Content-type" : "application/json"},
body: jsonEncode({"date": date.toIso8601String()})
);
print("Sent date");
print(date.toIso8601String());
print("Received date");
print(response.body);
}
Output
I/flutter (14203): Sent date
I/flutter (14203): "2020-01-01 00:00:00.000"
I/flutter (14203): Received date
I/flutter (14203): "2019-12-31T23:00:00.000Z"
Node.js (Javascript)
api.post('/sendDate',(req,res)=>{
console.log("Sent date")
console.log(req.body.date)
const date = new Date(req.body.date);
console.log("Parsed date");
console.log(date);
res.send(date);
});
Output
Sent date
2020-01-01T00:00:00.000
Parsed date
2019-12-31T23:00:00.000Z
As shown above, the date parsed by Javascript is one day less than the date sent by flutter.
P.S: As stated above, i've sent it in other formates like locale and UTC. Also no timezone configuration has been added.
EDIT: The date returned is actually an hour less. i.e sending DateTime(2020, 1, 1, [2]), will return DateTime(2020, 1, 1, [1]).
According to JavaScript's only in-spec date/time format, the string "2020-01-01 00:00:00.000" is invalid (it should have a T, not a space, between the date and time). (Indeed, according to Wikipedia, a space isn't valid in ISO-8601, either, though it's a common implementation extension.) But V8 (the JavaScript engine used by Node.js) is okay with that, it'll handle a space instead.
Additionally, the string has no timezone information, so this part of the date/time string parsing rules kicks in:
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.
So you appear to be in a timezone that's at GMT+0100. Midnight on Jan 1st in Europe, West Africa, etc. (GMT+0100) is 11 p.m. Dec 31st UTC / GMT.
If you want that date/time interpreted as UTC, you need to add a Z to the end before parsing. To be completely within spec, replace the space with a T as well.

javascript parses date string with browser timezone

var dts = "2019-05-26" // this value came from browser query like "d=1&date=2019-05-26"
var date = new Date(dts)
console.log(JSON.stringify(date))
which prints:
#=> "2019-05-25T19:00:00.0000Z"
Problem
I get this date from user input. Format only contains year, month and day. Problem happens when user browser's timezone applied on parsing. Sometimes, I get correct date in a day but sometimes i get one day before. This causes wrong database querying.
How can I convert this Date object to UTC? Because I need it as a Date object not as a string.
Is there any library that can help me parsing dates at UTC and get back as Date Object?
Use Moment UTC to normalize the time

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();

create javascript Date object from string YYYY-MM-DD in local timezone

I have built an application that is using pure javascript Date objects and date-fns for formatting and manipulating the objects.
The application functions perfectly in GMT where I developed it however I'm now on the West Coast of The States and I've discovered that many of my date objects are thrown out because of the timezone difference.
I am creating date objects from strings, either just YYYY-MM-DD or YYYY-MM-DD HH:mm, for example new Date('2018-01-19') or new Date('2018-01-19 08:00').
The issue is that when I create a date from a string of the format YYYY-MM-DD it creates the object disregarding the local timezone.
const date1 = new Date('2018-01-19');
const date2 = new Date('2018-01-19 00:00');
const dateString1 = format(date1, 'YYYY-MM-DD'); // gives '2018-01-18'
const dateString2 = format(date2, 'YYYY-MM-DD'); // gives '2018-01-19'
The behaviour is not consistent depending on whether you pass a time or not. If you pass a time then the date object is fixed to the local timezone, but if you don't then it is fixed to UTC. What I would like is that if I pass a datestring without the time (just year, month and day) that it would also create a date object assuming that it would be the start of the day in the local timezone.
Why is this? And do I just have to set the time as 00:00 each time I create a Date object?
"Why is this?"
From the documentation: "Support for ISO 8601 formats differs in that date-only strings (e.g. "1970-01-01") are treated as UTC, not local."
Parsing dates from strings isn't really advised; the recommendation is to do it manually by splitting the string and using the individual components as parameters. Or use a date library.

Comparing UTC and Not-UTC formatted dates in Javascript

I'm trying to compare two dates in javascript which are formatted differently. Namely:
2015-09-30T00:00:00 and 9/30/2015 12:00:00 AM
The former is UTC and the latter is not in UTC format. Logically, I am referring to the same date/time here but I can't come up with a way to compare them that will return true. Creating a new Date object with each ends up with different results (due to UTC offset with my local time zone).
What am I missing?
Ended up concluding that tagging on a "UTC" to the second date format before creating the Date object leads to an output consistent with the UTC formatted date.

Categories

Resources