How to solve 3-hour increment problem on date attribute on Angular?

I have the following excerpt:

let data = new Date();
console.log(data); // saída: Fri Oct 18 2019 08:23:27 GMT-0300 (Horário Padrão de Brasília)

When making the request with HttpClient Angular adds 3 more hours, so sending the following to the backend: 2019-10-18T11:23:27.756 Z. I already have locale configured as follows in AppModule:

import localePT from '@angular/common/locales/pt';
registerLocaleData(localePT);

I've already set up LOCALDE_ID and it didn't solve my problem either. Any idea how to solve?

Author: hkotsubo, 2019-10-18

3 answers

The " Z " at the end of 2019-10-18T11:23:27.756Z indicates that this date and time is in UTC.

Already when printing the Date, notice that it has GMT-0300, which indicates 3 hours behind UTC.

That is, both represent the same instant. This is a confusing half point, but the Date JavaScript , despite the name, does not represent a single date and time value (a single specific day, month, year, hour, minute, and second).

She actually represents a timestamp : the amount of milliseconds since the Unix Epoch. This value represents a single instant, a specific point in the timeline. Only that the same timestamp can represent a different date and time, depending on the time zone (just think that now, at this exact moment, in each part of the world the "Today" and the "current time" can be different, depending on the time zone you are in).

Thus, when printing a Date with console.log, it shows you the value considering the browser's timezone. But by sending the Date in a request, it is converting it to UTC. But the value of Date (its timestamp, the instant it corresponds to) has not changed.

In this case, it would be enough for the backend to take this date and convert to the correct timezone (and each language has its own ways of doing this).

locale controls the language and location settings, but it does not interfere with the time zone used, it's different things. I can use the locale corresponding to the Portuguese language, but show the date in a time zone of any other country, one thing is not related to another.


By changing the timestamp value, as suggested by another response (which has been deleted), you will be changing the instant the date matches. It may even "work", but it is not ideal, depending on what you want do.

To better understand, an analogy. Now in Brazil it is 10:20 and in London it is 14: 20 (the English are in summer time, hence the difference of 4 hours). Imagine that my computer is incorrectly configured with the London spindle, so it shows 14: 20.

So that it shows the correct time, I can do two things:

  • change your computer setting to Brasilia Time
  • delay the clock by 4 hours

In both cases, the time will now be shown as 10:20, but the second option is worse, because now my clock is indicating an instant in the past. this is what happens when you manipulate the timestamp value , and this is the error of the other answer. Although the value shown is "correct", you have created a Date corresponding to a different instant, and depending on what you are going to do with the date, it may give incorrect results.

In addition, the difference will not always be 4 hours. When in London it is not in summer time the difference is 3 hours, except that, when the gmt is the time in the summer, when the difference is two hours, but there have been periods of time where they're both at the time of the summer, and the difference is around 3 hours, for many years, with the summer time in Brazil, since the end of October, and in England in the end of October, and then in a few days they were at the time of the see).

In the specific case of Brazil, this year we will not have daylight saving time, but since this is something defined by the government, nothing guarantees that this will not change in the future. So manipulating the timestamp value still has this other drawback: it won't be synchronized with real-world changes. You would have to consult the timezone information to know if it uses 2 or 3 hours for its calculation.

That is, using fixed values to do this calculation is extremely error prone, and ideally always use the correct timezone.


Unfortunately JavaScript doesn't have a decent way to convert between time zones. The most you can do is get the date and time values in the timezone of the browser or in UTC. If you want in the same format as above, but using the browser timezone, you will have to do it manually:

function pad(value) {
    return value.toString().padStart(2, 0);
}
let d = new Date();
console.log(`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`);

The difference is that it will not have the Z at the end, since it is no longer in UTC. In this case, you will have to assume that the backend knows which timezone it is on. You can still use d.getTimezoneOffset(), which returns the difference in minutes with respect to UTC. In the case of Brasilia Time, The Return is 180 (or 120 when it is in summer time). If you want, you can send this information separately, so that the backend can do the conversion correctly.

Also notice that I had to add 1 in the month, because in JavaScript the months are indexed at zero (January is zero, February is 1, etc).


Moment.js

If you want, you can use Moment.js , along with the Moment Timezone , to manipulate the dates in the correct timezone:

let d = moment.tz('America/Sao_Paulo');
console.log(d.format()); //2019-10-18T10:32:31-03:00
<script src="https://momentjs.com/downloads/moment.min.js"></script>
<script src="https://momentjs.com/downloads/moment-timezone-with-data.min.js"></script>

Thus, it sends the date in the correct timezone (ex: 2019-10-18T10:32:31-03:00), without relying on the browser timezone, and you can manipulate it the way you see fit in the backend.

The names of timezones (like America/Sao_Paulo) are defined by Iana and are constantly undergoing constant updates (it keeps track of Daylight Saving Time changes around the world, so you don't have to worry about it: just use the correct timezone and the API does the rest). With the Moment.js you can have a list of available timezones using moment.tz.names():

console.log(moment.tz.names());
<script src="https://momentjs.com/downloads/moment.min.js"></script>
<script src="https://momentjs.com/downloads/moment-timezone-with-data.min.js"></script>

Angular

On the Angular, it seems to use JSON.stringify to convert the Date, when sending it in an HTTP request lacks fonts, but I'm looking for.

Anyway, I did a quick test, following the idea of this answer :

// aproveitando a função pad() já vista anteriormente
Date.prototype.toJSON = function() {
    return `${this.getFullYear()}-${pad(this.getMonth() + 1)}-${pad(this.getDate())}T${pad(this.getHours())}:${pad(this.getMinutes())}:${pad(this.getSeconds())}`;
}

By doing this, all dates sent via HttpClient are converted using the above function (which you can adapt to whatever you need).

Evidently this will change for all instances of Date in your project (which from the comments below, seems to be the case).

 16
Author: hkotsubo, 2019-10-22 11:31:31

I will disagree with all answers, about trying to solve on the client side.

Although already said, the time is in UTC, so this is the universal time, you do not need to convert it, this would be better treated in the back-end, but I will tell you, preferably always record in UTC even in the bank, txts, or any log, only in the moment of reading that you will make the adjustment to the user's timezone.

Now for you to get a better sense of the problem, why example if you pass a UTC time so you will still get the time set as per the client:

var data = new Date('2019-10-18T11:23:27.756Z');
data.toString();

Will return something like:

Fri Oct 18 2019 08: 23: 27 GMT-0300 (Brasilia Standard Time)

Imagine that a user is in São Paulo and writes to the bank with your computer time instead of UTC in the bank, so if you have a user in Rondônia and read the message he will think that the message was sent an hour in the future, yes will seem that the message came from the future

back from the future

Now imagine a situation worse than a simple message, imagine that a person schedules something to occur in X time and this has to serve all users, for example a shared schedule for users to start a video conference, if you record in the database with the time -3 (of most states of Brazil) whoever is -4 (Such as Rondônia, Roraima, Amazonas) will arrive an hour late, that is, after the conference has begun.

So if it is to send something send exactly like "this" (with UTC), this if it is necessary to send from the client-side, because most of the time it is not, it is better to rely on the back-end, the moment that would be needed from the client will be Chance the user select a time to schedule something.


Do not trust the client (side)

I say this because simply by entrusting the task for the side where the JavaScript is, ie the webView or browser you will be relying on the schedule of the machine/computer / mobile of the client, this even if the schedule is wrong for some problem with the operator of the person (occurs such a problem in the delayed year with several mobile phones and even servers in Brazil) or battery problem on the computer (a small battery that goes on the motherboard) you will be subject to receiving incorrect schedules, so if you can simply avoiding receiving schedules coming from the client and getting the schedule on the server side will be much more guaranteed.

Even if it is a schedule that the user needs to inform the time you can check this on the server side, imagining that it has 2 fields (this is an example HTTP POST request, sites use HTTP):

POST /agendar HTTP/1.1
Content-Type: application/x-www-form-urlencoded

dia=2019/12/01&hora=09:00

You would preferably have to have a third field or send this along with the request in @angular/common/http telling the local/client timezone and then on the server set this received data to UTC.

POST /agendar HTTP/1.1
Content-Type: application/x-www-form-urlencoded

dia=2019/12/01&hora=09:00&timezone=180

180 is the -3: 00 coming from the function Date.getTimezoneOffset()

And even then it's tricky to trust the client, because you can't be sure the client's computer is "in sync" with timezone, you can even choose to work everything like:

America/Sao_paulo

Or:

America/Brasilia

But you will have to keep in mind that if you are displaying this for other timezones, remember Brazil has 4 zones different:

  • UTC -5: Acre time
  • UTC -4: Amazon time
  • UTC -3: Brasília time
  • UTC -2: Fernando de Noronha Time (this are Fernando de Noronha and the Trindade and Martim Vaz, but of course in this case I doubt that anyone will work there)

Time zones Brazil

Now imagine adjusting from local time to other timezones, then ask yourself, " it's easier to count from scratch or from the 21?"

Image Source: https://commons.wikimedia.org/wiki/File:Standard_Timezones_of_Brazil.svg


Working with BRT

Yes you could work with BRT (Brazil Timezone) instead of UTC, because so the BRT would be the zero zone , so you would have to work this way:

  • BRT –4: Act (Acre time)
  • BRT –3: AMT (Amazon Time)
  • BRT 0: BRT (time of Brasília)
  • BRT +1: fnt (Fernando de Noronha hour)

May work for quite a while, but if for some reason the main BRT schedule changes then you may have some problems, remembering that you will still have to set up bank and backend, and the data sent to the front end will still have to be adjusted, which will be much more laborious.

 8
Author: Guilherme Nascimento, 2020-01-22 12:55:22

You don't have to worry too much about this, since the default being sent to the backend is from ISO 8601. If you take your date and give a console.log(date.toISOString()) you will see that the return is exactly what is going pro back-end.
Already when you receive this value again, just give a new Date(date) that will return to Brasilia Time, then, the correct time for the user to view.

Some links that might help:

 0
Author: Lucas Ayrosa, 2019-10-18 13:25:26