How to solve 3-hour accretion problem in Date attribute in Angular?

Asked

Viewed 7,984 times

9

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 the HttpClient Angular adds 3 hours more, then sending to the backend the following: 2019-10-18T11:23:27.756Z. I’ve got the locale configured as follows in AppModule:

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

I’ve already set up the LOCALDE_ID and also did not solve my problem. Any idea how to solve?

  • Use LocalDateTime.now() java 8.

4 answers

16


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 he possesses GMT-0300, indicating 3 hours behind UTC.

That is, both represent at the same moment. This is a somewhat confusing point, but the Date javascript, despite the name, it does not represent a single date and time value (a single day, month, year, time, minute and second specific).

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 right now, in each part of the world the "day of today" and the "current time" may be different depending on the time zone you are in).

Therefore, when printing a Date with console.log, it shows you the value considering the Timezone browser. But when sending Date in a request, it is converting it to UTC. But the value of the Date (your timestamp, the instant it corresponds to) has not been changed.

In this case, the backend take this date and convert to the correct Timezone (and each language has its ways to do this).

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


When changing the value of timestamp, as suggested another answer (which has been deleted), you will be changing the instant at which the date corresponds. It may "work," but it’s not ideal, depending on what you want to 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 misconfigured with the London zone, then it shows 14:20.

For him to show the right time, I can do two things:

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

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

Besides, the difference won’t always be four hours. When London is not in daylight saving time, the difference is 3 hours, except that when Brasilia Daylight Saving Time is in daylight saving time, the difference is 2 hours (but there have been periods when both are in summer time, and the difference is back to 3 hours, since in many years, summer time in Brazil began in early October, and in England ended in late October, then for a few days both were in daylight time).

In the specific case of Brazil, this year we will not have summer time, But since this is something defined by the government, there’s no guarantee that it won’t change in the future. So manipulating the value of timestamp still has this other disadvantage: it won’t be synchronized with real-world changes. You would have to consult Timezone information to know if you use 2 or 3 hours for your calculation.

That is, using fixed values to make this calculation is extremely prone to errors, and the ideal is always to use the correct Timezone.


Unfortunately Javascript does not have a decent way to convert between time zones. The most you can do is get the date and time values in the Timezone browser or in UTC. If you want in the same format above, but using the browser Timezone, you 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 you won’t get the Z at the end because it is no longer in UTC. In that case, you will have to assume that the backend knows which Timezone it is in. You can still use d.getTimezoneOffset(), which returns the difference in minutes with respect to UTC. In the case of Brasília Time, the return is 180 (or 120 when it is in daylight saving time). If you want, you can send this information separately, so that the backend can do the conversion correctly.

Note also that I had to add 1 in the month, because in Javascript months are indexed to zero (January is zero, February is 1, etc).


Moment js.

You can use the Moment js., together 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’s Timezone, and you can manipulate in the way you think best in the backend.

The names of timezones (such as America/Sao_Paulo) are defined by IANA and are always suffering 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 right Timezone and the API does the rest). With 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, he seems to use JSON.stringify to convert the Date, when sending it on an HTTP request lacks sources, 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).

Of course this will change for all instances of Date of your project (which by the comments below seems to be the case).

  • Hello! Thank you for the detailed explanation. But still, the change in the backend is ruled out because the endpoint is called by third parties as well, which would imply an unwanted standardization. In Angular, if I do something like the formatting as in your example of using "pad()" I would have another problem which is in cases where I have an object array that contains the date field, having to go through it all, field by field changing. The question is how to do this generic and global for the project. If I haven’t been clear please let me know.

  • @Matheusmaske What really matters is that the date itself (the instant to which it refers) has not been changed, and the complete information is being sent (date, time and the indication that is in UTC), so whoever consumes this data will know the correct instant that date represents. It is the responsibility of each one to correctly interpret this information (if it is in UTC, it cannot treat as if it were in another time zone, for example). If someone assumes that a date in UTC is in Brasilia time (or another zone qq), the fault is not who sends...

  • @Matheusmaske I’m not sure what I’m going to say, but think that Angular uses JSON.stringifyto transform the Date in string, when sending an HTTP request, then just do something like this: https://stackoverflow.com/a/31104671 - then all dates would be changed to the specific format (you could put the option with pad function, for example). I haven’t tested, but I think it’s worth a try...

  • @Matheusmaske I did a quick test and by the way change the prototype of Date seems to work. I updated the answer and put this option at the end

  • 1

    It really has changed with the override of the Date prototype, and this is the case. Thank you @hkotsubo

  • Where do I put this Date.prototype.toJSON code to change all date requests.

Show 1 more comment

8

I’ll disagree with all the answers, about trying to settle 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 severe 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 have a better sense of the problem, for example if you pass a UTC time so you will still get the adjusted time as per customer:

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 records in the bank with your computer time instead of UTC in the bank, then if you have a user in Rondônia and to read the message he will think that the message was sent one hour in the future, yes it will seem that the message came from the future

de volta do futuro

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 for all users, for example a shared agenda for users to start a video conference, if you record in the database with the time -3 (of most states of Brazil) who is -4 (as Rondônia, Roraima, Amazonas) will arrive an hour late, ie after the conference has started.

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


Do not trust the customer (side)

I say this because simply by entrusting the task to the side where Javascript is, ie the webView or browser you will be relying on the time of the machine/computer/cell phone of the client, this even if the time is wrong for some problem with the person’s operator (a problem like this occurs in the previous 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 hours, so if you can simply avoid receiving schedules from the client and get the time 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 it on the server side, imagining that it has 2 fields (this is an example HTTP POST request, websites 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 submit this along with the request on @angular/common/http telling Timezone local/client and then on the server adjust to UTC this received data.

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 yet it’s complicated to trust the client, because you can’t be sure the client’s computer is "synchronized" with Timezone, you can even choose to work everything like:

America/Sao_paulo

Or:

America/Brasilia

But you have to keep in mind that if you’re going to display this for other timezones, remember Brazil has 4 different zones:

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

Zonas horárias brasil

Now imagine adjusting from local time to other timezones, so ask yourself, "it is easier to count from scratch or from 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 then BRT would be the zone zero, so you would have to work this way:

  • BRT –4: ACT (Time in Acre)
  • BRT –3: AMT (Amazonas Time)
  • BRT 0: BRT (Time of Brasilia)
  • BRT +1: FNT (Time of Fernando de Noronha)

It may work for a long time, but if for some reason the BRT main time changes then you may have some problems, remembering that you will still have to set up database and back-end, and the data sent to the front-end will still have to be adjusted, which will be much more laborious.

  • 1

    Remember that until further notice daylight saving time was revoked throughout the Brazilian territory. If any person of the national executive ever changes his or her mind, do not forget to adapt

  • 1

    @Jeffersonquesado or better, do what I said in the reply, do not trust the client, avoid as much as you can, only in reading (SELECT ... FROM) adjust the time or the same front, has something that does not even need to be recorded. (ps: this is the purpose of the answer ;))

  • I believe I understand. But I have a question: how will I correctly identify the client’s Timezone to display the correct time? For example: I have an online system, my server is in Sao_Paulo. It will be accessed from different parts of the world, so what? How do I adjust the time for display? Because I have to identify where the access is being made to subtract or add or keep time. If I take from the client, it could be wrong, and then I would miscalculate too!

  • @Rbz need not identify at the time reading, you lower in "zone 0" same, or as people call "UTC" and then on the client side you apply, if it is in Javascript, could receive in : var x = new Date; x.setTime(<?php echo $segundosUnixTime; ?>); console.log(x);, but this is a very superficial example of course, if it brings a "timestamp" as January 1, 2020, 10:30:00 UTC in the variable and pass directly new Date("<?php $timestamp; ?>"); will also work, another example would be bring via Xmlhttprequest (if necessary, if already from the environment) ...

  • ... and apply (assuming you bring a JSON that is a series of objects with dates) var req = new XMLHttpRequest();&#xA;req.responseType = 'json';&#xA;req.open('GET', url, true);&#xA;req.onload = function() {&#xA; var resposta = req.response;&#xA; console.log(new Date(resposta[0].date)); /*supondo que exista um objeto no indice zero e que receba um timestamp semelhante January 1, 2020, 10:30:00 UTC*/&#xA;};&#xA;req.send(null); ... @Actually, I explained that right at the beginning of the answer, where I’m talking about var data = new Date('2019-10-18T11:23:27.756Z');, but I realize that I need to edit with a practical example

  • Yes, but how do you know it will fit properly in the customer? You will download UTC but then, you will have to adjust to - or + or keep, how will you recognize which user’s location!? Because the machine could be all wrong... will identify how?

  • @Rbz but the javascript itself, if it is browser, already works with the local defined time zone, passing the value in setTime or as parameter in the new Date (and the parameter is a string understandable as zone "zero") it will already adjust for you with the compensated zone, so there are methods like .getHour() and .getUTCHour(), for you to pick up with the cleared time zone and in "utc"

  • Got it. Would all languages have their way of getting the exact zone of the person, regardless of any kind of machine parameter? It pulls from where then if it identifies nothing of the machine?

  • @Rbz if the language API is decent then yes, it already compensates, even PHP you can set the zone or use default or operating system, assuming it is a script to run based on the person’s hourly zone, maybe a script for CLI, but the database data is zone "zero"then it would only be to set the time in microseconds (Unix time) or to parse an acceptable format (format with suffix UTC or Z) so the API itself has to work at least decent to adjust, I believe until #include <ctime> from [tag:c] so work (ctime() and gmtime()).

  • So, here’s the "problem" of depending on the customer... let’s assume that the customer travels everywhere in the world with his laptop, and accesses the system via the web... where will Timezone come from? the IP would have some reference!? or the last server of the route that brought the package!? The notebook is BR, the guy lives in SP... how will you recognize there in Acre!? And outside the country!?

  • @Rbz ai is not the question of where he is, whether he has moved to a place where the time zone is different and on the server continue to work with UTC (zero zone), even if based on the time zone before him, yet his "hometown" will not have changed from "place" or zone, he can adjust his PC to the zone he wants, what matters is that if it is an INSERT or UPDATE the time must be obtained from the server side, if possible and not from the client

Show 7 more comments

1

Take the Z at the end, or put -00:00 (in case you don’t want any Timezone):

ex.: new Date("2021-05-21T00:00:00.000") or

new Date("2021/05/21 00:00 -00:00")

If you are using only new Date():

const hj = new Date();

new Date( `${hj.getFullYear()}/${("0"+(hj.getMonth())).slice(-2)}/${("0"+(hj.getDate())).slice(-2)} ${("0"+(hj.getHours())).slice(-2)}:${("0"+(hj.getMinutes())).slice(-2)}:${("0"+(hj.getSeconds())).slice(-2)} -00:00`))

0

You don’t have to worry too much about this, since the standard that is being sent to the back end is from ISO 8601. If you take your date and give a console.log(date.toISOString()) you will see that the return is exactly the one that is going to the back-end.
Already when you receive this amount again, just give a new Date(date) which will return to the Scheduled Time, thus the correct time for the user to view.

Some links that may help:

Browser other questions tagged

You are not signed in. Login or sign up in order to post.