Brandon Taylor
Brandon Taylor

Reputation: 34553

Getting incorrect value from moment-timezone when converting Date to Epoch seconds

I have a use case where a user selects a Date and Time in the future in their time zone. In this case, US Central Time. I would like to convert the Date given their timezone into Epoch seconds, which I need to pass to a 3rd party API.

Here's part of my unit test code so far:

moment.tz.add('America/Chicago');

// This value is returned by Mongoose as a Date, which is why I'm using Date() here
const publishAt = new Date('2017-07-26T12:00:00.000Z');

const publishAtEpoch = moment.tz(publishAt, 'America/Chicago').valueOf() / 1000;

Which results in:

1501070400

instead of:

1501088400

which is incorrect according to: https://www.epochconverter.com/timezones?q=1501070400&tz=America%2FChicago

The value 1501070400 is 5 hours too early.

If I pass in a naive string for the Date: 2017-07-26 12:00:00 it works as expected.

If I use a naive Date: new Date('2017-07-26 12:00:00'); the value is 1 hour too early, as my timezone is US Eastern.

What am I doing wrong? I thought moment-timezone was supposed to handle the offset, account for Daylight Savings Time, etc.

Upvotes: 1

Views: 1185

Answers (1)

VincenzoC
VincenzoC

Reputation: 31482

Your publishAt Date is created using an string that ends with Z, so it represents time in UTC. As you can see publishAt (getTime()) value in milliseconds since the Unix Epoch is 1501070400000.

1501070400000 is 26 July 2017 12:00:00 UTC (see records with 0 Offset In seconds in the table in your link).

In my opinion, the problem is that you are expecting to get 1501088400000 instead of 1501070400000 that is the "real" value of new Date('2017-07-26T12:00:00.000Z').

moment.tz('2017-07-26 12:00:00', 'America/Chicago') gives 1501088400000 because you are telling to moment to parse your string as time in Chicago, while new Date('2017-07-26 12:00:00') creates date for your local timezone.

Please note that moment-timezone docs states:

Unix timestamps and Date objects refer to specific points in time, thus it doesn't make sense to use the time zone offset when constructing. Using moment.tz(Number|Date, zone) is functionally equivalent to moment(Number|Date).tz(zone).

Here a snippet showing the described results:

const publishAt = new Date('2017-07-26T12:00:00.000Z');
console.log(publishAt.getTime()); // 1501070400000

const publishAtEpoch = moment.tz(publishAt, 'America/Chicago').valueOf() / 1000;
console.log(publishAtEpoch); // 1501070400

const timeChicago = moment.tz('2017-07-26T12:00:00.000', 'America/Chicago');
console.log(timeChicago.valueOf() / 1000); // 1501088400
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.13/moment-timezone-with-data-2012-2022.min.js"></script>

Here a snippet that parses milliseconds (both 1501070400000 and 1501088400000), then it shows the value in local time, UTC and Chicago timezone. This is something similar to what epochconverter.com does and maybe could help you understand why the site is showing these results.

const m1Local = moment(1501070400000);
console.log(m1Local.format()); // 1501070400000 local time
const m1Utc = moment(1501070400000).utc();
console.log(m1Utc.format()); // 2017-07-26T12:00:00Z
const m1Chicago = moment(1501070400000).tz('America/Chicago');
console.log(m1Chicago.format()); // 2017-07-26T07:00:00-05:00

const m2Local = moment(1501088400000);
console.log(m2Local.format()); // 1501088400000 local time
const m2Utc = moment(1501088400000).utc();
console.log(m2Utc.format()); // 2017-07-26T17:00:00Z
const m2Chicago = moment(1501088400000).tz('America/Chicago');
console.log(m2Chicago.format()); // 2017-07-26T12:00:00-05:00
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.13/moment-timezone-with-data-2012-2022.min.js"></script>

As additional info, note that moment has unix() method that:

moment#unix outputs a Unix timestamp (the number of seconds since the Unix Epoch).

This value is floored to the nearest second, and does not include a milliseconds component.

if you want to avoid to divide by 1000 the value returned by valueOf().

Further readings that could be useful: Local vs UTC vs Offset guide, timezone tag info page.

Upvotes: 2

Related Questions