Moment-timezone: Wrong behavior when creating a moment with timezone

Created on 30 Jan 2014  路  19Comments  路  Source: moment/moment-timezone

According to the documents:

moment.tz("2013-11-18 11:55", "America/Toronto").format(); // "2013-11-18T11:55:00-05:00"

But running this code in my browser with locale UTC +0100 gives me this

moment.tz("2013-11-18 11:55", "America/Toronto").format();
"2013-11-18T05:55:00-05:00"

seems to me that it first creates a new Moment with my systems locale and then change the timezone which does not provide the expected behavior.

If however I use my own timezone it works as expected and I think that's what the author of the docs did.

bug

Most helpful comment

@andymcfee - There's no bug, and you don't need the hack. You simply need to provide a format string. Moment only recognizes ISO8601 unless you tell it otherwise. You'll notice your code throws a deprecation warning onto the console telling you it's reverting to the date constructor.

You should do:

moment.tz('30/09/2015 10:00 am', 'DD/MM/YYYY h:mm a', 'Europe/Dublin')

Also note that the "output" at this state is a moment object, not a string. You need to then call format, and provide the output format you'd like.

All 19 comments

Can confirm, it seems constructor does the same thing as mutator is supposed to.

moment.tz('1.1.2014 14:55', 'Europe/Berlin').toString()
"Wed Jan 01 2014 15:55:00 GMT+0100"

That means there is no way to use moment-timezone to parse date in a non-local timezone. Which makes tz useless.

Yes, except if you're not passing an ISO string, you should supply a format or your browser will be the one doing the parsing. Still, even as an ISO string, it doesn't behave as expected. I'll look at this closer.

OK, maybe I'm misunderstanding something. My understanding was that moment.tz('1.1.2014 14:55', 'Europe/Berlin').toString() should take the date, parse it as if it were an ISO string "2014-01-01T14:55:00+0100" (or +0200 in summer) and then output it in the browser local time zone. Is that wrong?

EDIT: To clarify - essentially the only reason I was trying to use moment-timezone is that I'm getting the date and timezone separately - ie. dealing with an evil API that doesn't give me ISO strings - and want to get something meaningful.

Ok after reading some more in the docs I see the issue with moment(string) and why it should be an ISO string.
However the docs should probably be more clear on what to expect since the statement
moment.tz("2013-11-18 11:55", "America/Toronto").format(); // "2013-11-18T11:55:00-05:00"
clearly is not accurate.

For now I'm doing a bit of a hack where I create a moment with the input I have and use format() on that.
Then I use format('Z') on another moment with the timezone specified.
I then replace the offset of the first format() and replace the offset with the timezone offset using string manipulation (ewww).
Then I create a new Moment with the new formated ISO string.

And I would really want to do this more elagant :)

+1
Having a library to convert between the local timezone and another one is incredibly useful so I'd like to thank everyone for the hard work put into this. The next step is to allow for arbitrarily created datetimes in a specified timezone. I'd like to be able to create a proper moment object for 2014-02-05 at 4pm in Asia/Tokyo by specifying "2014-02-05 16:00" and the timezone identifier.

It is also quite important for this to support formats without dates.
I have been struggling to find a solution for the following:
when on a machine in Phoenix, create a moment in another zone from the display on the screen, then persist it in UTC.
This provides totally unexpected results as it first creates a Local Time then converts it to the tz before finally converting to UTC.
moment.tz("9:00AM", "h:mmA", "Pacific/Honolulu").utc().format("h:mmA") => 4:00PM when it should be 7:00PM.
Attaching the current offset does work since there is no date, but if the format changes in the future, all code relying on this just broke.

Is there a milestone for when this is projected to be included?

This functionality works correctly in v0.0.1 and breaks in v0.0.3.
v0.0.1

moment('28.02.2014 10:20', 'DD.MM.YYYY HH:mm').tz('Europe/Moscow').format()

result:

"2014-02-28T10:20:00+04:00"

v0.0.3

moment('28.02.2014 10:20', 'DD.MM.YYYY HH:mm').tz('Europe/Moscow').format()

result:

"2014-02-28T06:20:00+04:00"

momentjs version is 2.5.1

My tests show that it seems to work correctly in Firefox and incorrectly in Chrome.

I use the following code to get the desired behavior:

function setTimezone(moment, timezone) {

    var a = moment.toArray(); // year,month,date,hours,minutes,seconds as an array

    moment = moment.tz(timezone);

    moment.year(a[0])
        .month(a[1])
        .date(a[2])
        .hours(a[3])
        .minutes(a[4])
        .seconds(a[5])
        .milliseconds(a[6]);

    return moment; // for chaining
};

var m = setTimezone(moment('2014-03-10T10:00'), "America/New_York");
console.log(m.format());

Outputs 2014-03-10T10:00:00-04:00

This is a use-case I require, as well. (I am using it in Node.JS, so naturally it's broken there.)

@dhilgarth's hack works beautifully in my tests, so until this is fixed upstream, that's what I'll be using, too.

function moment_tz(iso8601, timezone) {
  var m = moment(iso8601),
      a = m.toArray();

  return m.tz(timezone).year(a[0]).month(a[1]).date(a[2]).hours(a[3]).minutes(a[4]).seconds(a[5]).milliseconds(a[6]);
}

This has been fixed in 0.1.0.

moment.tz("2013-11-18 11:55", "America/Toronto").format(); // 2013-11-18T11:55:00-05:00

I also added more comprehensive documentation for how parsing works. http://momentjs.com/timezone/docs/#/using-timezones/parsing-in-zone/

Hey @timrwood - I have come across this thread having experienced a similar issue to that originally described here. I am running moment 2.9.0 and moment-timezone 0.2.5. When I parse times greater than 10.00hrs the the timezone I am applying to that moment is unexpectedly added/subtracted from the moment I am parsing. Here are my test cases;

var assert = require("assert");
var dateUtils = require("../../app/util/dateUtils.js");

describe("dateUtils", function() {

        describe("#successfulTimeZoneAddition", function() {
            // Given.
            var testTime = "2014-09-11 11:44 PM EDT   4.92 feet  High Tide";
            var testTimeZoneId = "America/New_York";

            // When.
            var dateTime = dateUtils.convertDate(testTime, testTimeZoneId);

            // Then.
            it("The provided time should have a time zone added.", function(done) {
                 assert.deepEqual("2014-09-11T23:44:00-04:00", dateTime.format());

                 done();
            })
        })

        describe("#failingTimeZoneAddition", function() {
            // Given.
            var testTime = "2014-09-15  9:36 AM EDT   Sunrise";
            var testTimeZoneId = "America/New_York";

            // When.
            var dateTime = dateUtils.convertDate(testTime, testTimeZoneId);

            // Then.
            it("The provided time should have a time zone added.", function(done) {
                 assert.deepEqual("2014-09-15T09:36:00-04:00", dateTime.format());

                 done();
            })
        })
})

The dateUtils function is shown below - you can see it's a relatively simple moment("ISO Date", "tz") usage.

var moment = require("moment-timezone");

var dateUtils = function dateUtils() {

    /**
     * Format <code>YYYY-MM-DD hh:mm aa z</code> date format,
     * for example, '2014-09-11  7:11 PM EDT'.
     */
    var convertDate = function(dateString, tzId) {
        // 2011-07-21  7:36 PM
        var twentyFourHourTime = convertTo24Hour(dateString.substring(11, 19));
        var dateTime = dateString.substring(0, 10) + " " + twentyFourHourTime;
        var eventWithTz = moment.tz(dateTime, tzId);
        return eventWithTz;
    };


    function convertTo24Hour(time) {
        var hours = parseInt(time.substr(0, 2));
        if (time.indexOf("AM") != -1 && hours == 12) {
            time = time.replace("12", "0");
        }

        if (time.indexOf("PM") != -1 && hours < 12) {
            time = time.replace(hours, (hours + 12));
        }

        // Remove any white space.
        time = time.replace(" ", "");

        return time.replace(/(AM|PM)/, "");
    }


    return {
        convertDate: convertDate
    };
};

module.exports = dateUtils();

It appears that all times below 10.00am are affected by this unexpected behaviour. Should I raise a separate tracker for this?

@BrantApps, I think this is probably a bug with convertTo24Hour returning 9:00 instead of 09:00.

You could just use moment's parser to parse the time with something like this instead.

var convertDate = function(dateString, tzId) {
    return moment.tz(dateString.substring(0, 19), 'YYYY-MM-DD h:mm A', tzId);
};

I am still running into this bug and I believe I'm using all the latest versions of the moment libraries:

    "moment": "~2.10.6",
    "moment-timezone": "~0.4.0"

Essentially, I was creating a moment from a string var m = moment.tz('30/09/2015 10:00 am', 'Europe/Dublin') and the output was "30/09/2015 11:00 am IST" instead of the expected output ""30/09/2015 10:00 am IST".

As @dhilgarth pointed out, in Firefox, I got the expected output of "30/09/2015 10:00 am IST", but had the bug in Chrome.

I had to use the hack provided by @dhilgarth.

@andymcfee - There's no bug, and you don't need the hack. You simply need to provide a format string. Moment only recognizes ISO8601 unless you tell it otherwise. You'll notice your code throws a deprecation warning onto the console telling you it's reverting to the date constructor.

You should do:

moment.tz('30/09/2015 10:00 am', 'DD/MM/YYYY h:mm a', 'Europe/Dublin')

Also note that the "output" at this state is a moment object, not a string. You need to then call format, and provide the output format you'd like.

@mj1856 ah! That works. I can't believe I didn't try that at any point in my battle with timezones. Thanks.

Using Moment v2.9.0 with Moment-Timezone v0.3.1, I'm noticing that when providing an array of format strings, the moment object is first created with UTC as the timezone and then shifted by the timezone provided. So given this array of formats:

validTimeFormats:             [
            'YYYY-MM-DD HH:mm a', 'YYYY-MM-DD hh:mm a', 'YYYY-MM-DD H:mm a', 'YYYY-MM-DD h:mm a',
            'YYYY-MM-DD HH:m a', 'YYYY-MM-DD H:m a', 'YYYY-MM-DD hh:m a', 'YYYY-MM-DD h:m a', 'YYYY-MM-DD HH:mm A',
            'YYYY-MM-DD hh:mm A', 'YYYY-MM-DD H:mm A', 'YYYY-MM-DD h:mm A', 'YYYY-MM-DD HH:m A', 'YYYY-MM-DD H:m A',
            'YYYY-MM-DD hh:m A', 'YYYY-MM-DD h:m A', 'YYYY-MM-DD HH:mm', 'YYYY-MM-DD hh:mm', 'YYYY-MM-DD H:mm',
            'YYYY-MM-DD h:mm', 'YYYY-MM-DD HH:m', 'YYYY-MM-DD H:m', 'YYYY-MM-DD hh:m', 'YYYY-MM-DD h:m'
        ]

And this code:

moment.tz('2016-10-24 12:00 PM', _this.validTimeFormats, true, 'America/New_York');

results in this moment object:

Mon Oct 24 2016 04:00:00 GMT-0400 (EDT)

@CareerFairPlus - This doesn't belong on this thread. Commenting on old, closed, unrelated issues is a sure way to get ignored. I'll be happy to answer your question if you create a new issue. Thanks.

Sorry! This is where Google led me. I upgraded to the latest version of moment and moment-timezone and am not seeing the issue anymore.

If a date string passed to moment, moment assume it as locale date so convert date into local timezone and then create moment object. To avoid conversion into local timezone, valid ISO date string should have 'z' as suffix.
e.g. '2014-09-11T23:44:00z'

Was this page helpful?
0 / 5 - 0 ratings

Related issues

palindrom615 picture palindrom615  路  4Comments

guidsen picture guidsen  路  4Comments

garryyao picture garryyao  路  4Comments

casper5822 picture casper5822  路  5Comments

Liero picture Liero  路  4Comments