NodeJS: Why is there a two hour difference in new Date()?
Asked Answered
U

3

5

I am doing a

console.log(process.env.TZ);
console.log(new Date());

It outputs

Europe/Amsterdam
2018-09-02T08:07:03.842Z

But the current time is 10:07 not 08:07.

The actual problem is that when I save a model to the db, it somehow gets converted to UTC which is not what I want. Its like order.delivery_date = 2018-08-06 10:00:00; order.save(). when I look in the db, it says 08:00:00. How do I prevent this from happening?

I am using Loopback 3 and MySQL.

Untangle answered 2/9, 2018 at 8:20 Comment(4)
"UTC which is not what I want" yes it is.Surcingle
Many answers have been posted. Could you add details of how your question hasn't been answered in its entirety?Kwangchowan
How about new Date().toLocaleTimeString() and new Date().toLocaleDateString()?Gulch
To overcome timezone differences between servers and browsers I always choose to configure the db-timezone settings to UTC (GMT 0) . Inserting data from the server to the DB by using JS date I use .toISOString(). And presenting the values to the user by using .toLocaleString(). this way, server and db speak the same language, and the browser speaks the user language.Baltic
E
20

It outputs

Europe/Amsterdam
2018-09-02T08:07:03.842Z

But the current time is 10:07 not 08:07.

The Z on the string indicates that the time is in UTC, not local time. That's just the string output by Node.js's console when you pass it a string (it's from toUTCString). JavaScript Date objects work in local time, but also have features to access UTC time instead (getUTCHours, getUTCMinutes, etc.); toUTCString is one of them.

You can use the various local time functions on the Date object (getHours, getMinutes, etc.) and you'll get local time information from it. (For instance, toString will probably give you a local time string.)

Execratory answered 2/9, 2018 at 8:23 Comment(7)
thanks for the clarification. So how do I ensure exactly that when I save a date, its saved exactly as how I input it? customer.login_time = 2018-08-15 10:30:30;customer.save() gets stored as customer.login_time = 2018-08-15 08:30:30;Untangle
@apfz Just store as UTC and display in local time. That ensures that the meaning of the time makes sense to whoever looks at it.Achilles
@apfz - How you treat dates in an application depends on what you're using them for. In many cases, as Patrick said, storing UTC and converting to whatever timezone you need is a good choice. But it really depends on the situation.Execratory
the dates will only be used in my country (only 1 timezone) and are also used across applications. For me it is ideal to store the date exactly as I input it. So I am looking at a solution where I can just store it in my local time zone.Untangle
@apfz - So...do that (though frankly it doesn't sound very future-proof). As I said above, the Date object provides local-time accessors.Execratory
so the problem is that when I save it, it somehow gets converted to UTC which is not what I want. Its like order.delivery_date = 2018-08-06 10:00:00; order.save(). when I look in the db, it says 08:00:00. How do I prevent this? In the production environment it seems that this issue does not occur.Untangle
@apfz - That's up to how you interface with your data layer. You probably need to send the data differently, but it's impossible for us to help you do that, as we have no idea how you're interfacing with your data layer.Execratory
M
4

I don't have the complete picture of your setup, but yes, loopback does convert to UTC from local time when saving dates. If you look in the mysql connector, you'll find the following function that builds up the datetime string from UTC values of the given date:

function dateToMysql(val) {
  return val.getUTCFullYear() + '-' +
    fillZeros(val.getUTCMonth() + 1) + '-' +
    fillZeros(val.getUTCDate()) + ' ' +
    fillZeros(val.getUTCHours()) + ':' +
    fillZeros(val.getUTCMinutes()) + ':' +
    fillZeros(val.getUTCSeconds());

  function fillZeros(v) {
    return v < 10 ? '0' + v : v;
  }
}

It should, however, restore the date to local time on load because it initializes the javascript Date object from the stored string, which will convert to local time:

val = new Date(val.toString().replace(/GMT.*$/, 'GMT'));

Your best option is probably to use operation hooks and manipulate the data as it flows in/out of your model. For example, you could format the date however you want as the data gets loaded from the data store:

Order.observe("loaded", (ctx, next) => {
    if (ctx.data) {
      ctx.data.delivery_date_formatted = tzFormat(ctx.data.delivery_date);
    }

    next();
  });

You can also approach this from the other direction and manipulate the data that's being persisted. You can't really prevent loopback from storing the date as UTC, but you could add or remove the timezone offset so once it gets stripped it by loopback connector, it will persist a string with your local time (VERY hacky, I wouldn't recommend it). Example:

Order.observe("before save", (ctx, next) => {
    if (ctx.instance) {
      ctx.instance.delivery_date = new Date(
        Date.UTC(
          ctx.instance.delivery_date.getFullYear(),
          ctx.instance.delivery_date.getMonth(),
          ctx.instance.delivery_date.getDate(),
          ctx.instance.delivery_date.getHours(),
          ctx.instance.delivery_date.getMinutes(),
          ctx.instance.delivery_date.getSeconds()
        )
      );
    }

    next();
  });
Malloch answered 15/11, 2018 at 16:59 Comment(0)
S
0

Answering the title question: the new Date() will return you the UTC time value, which has Z timezone indicator and timezone value (2 in your case) at the end of the string.

Answering the question about db save() call: It depends on your data access layer and the data column type.

There may be two main approaches:

  • to save UTC and display local time (due to day when you'll have users with 2+ time zones)

so your save() works correctly for that approach, you should just add a local time converter (usually the best way is to add it on the front-end side)

  • save the value in local time as is (not recommended, but may be usefull)

so you should either make converters on write/read or change the type of the column so it should not use UTC format (1 of easy, but hacky ways is to store it as a string value, for example)

If you pick the way with converter, I'll recommend you to use moment.js (both for front or back end). It can save time when you will need any time manipulations, but may be an overhead.

You can also make it manually (to reduce the bundle size, for example), you can use approach from this answer:

const options = {
    timeZone: "America/New_York",
    year: 'numeric', month: 'numeric', day: 'numeric',
    hour: 'numeric', minute: 'numeric', second: 'numeric'
};

const formatter = new Intl.DateTimeFormat([], options);

const UTCTime = "2017-09-03T02:00:00Z";
const localTime = formatter.format(new Date(UTCTime));
const currentTime = formatter.format(new Date()); 
console.log(currentTime, localTime);
Saba answered 15/11, 2018 at 10:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.