moment.js - UTC gives wrong date
Asked Answered
T

4

127

Why does moment.js UTC always show the wrong date. For example from chrome's developer console:

moment(('07-18-2013')).utc().format("YYYY-MM-DD").toString()
// or
moment.utc(new Date('07-18-2013')).format("YYYY-MM-DD").toString()

Both of them will return "2013-07-17" why is it returning 17th instead of 18th, that was passed in.

But if I use momentjs without the utc:

moment(new Date('07-18-2013')).format("YYYY-MM-DD").toString()

I get back "2013-07-18" which is what I also expect when using moment.js UTC.

Does this mean we cannot get the correct date when using moment.js UTC?

Throughcomposed answered 25/7, 2013 at 10:44 Comment(1)
I don't think you need toString() after format() (it already returns a string).Invention
C
211

By default, MomentJS parses in local time. If only a date string (with no time) is provided, the time defaults to midnight.

In your code, you create a local date and then convert it to the UTC timezone (in fact, it makes the moment instance switch to UTC mode), so when it is formatted, it is shifted (depending on your local time) forward or backwards.

If the local timezone is UTC+N (N being a positive number), and you parse a date-only string, you will get the previous date.

Here are some examples to illustrate it (my local time offset is UTC+3 during DST):

>>> moment('07-18-2013', 'MM-DD-YYYY').utc().format("YYYY-MM-DD HH:mm")
"2013-07-17 21:00"
>>> moment('07-18-2013 12:00', 'MM-DD-YYYY HH:mm').utc().format("YYYY-MM-DD HH:mm")
"2013-07-18 09:00"
>>> Date()
"Thu Jul 25 2013 14:28:45 GMT+0300 (Jerusalem Daylight Time)"

If you want the date-time string interpreted as UTC, you should be explicit about it:

>>> moment(new Date('07-18-2013 UTC')).utc().format("YYYY-MM-DD HH:mm")
"2013-07-18 00:00"

or, as Matt Johnson mentions in his answer, you can (and probably should) parse it as a UTC date in the first place using moment.utc() and include the format string as a second argument to prevent ambiguity.

>>> moment.utc('07-18-2013', 'MM-DD-YYYY').format("YYYY-MM-DD HH:mm")
"2013-07-18 00:00"

To go the other way around and convert a UTC date to a local date, you can use the local() method, as follows:

>>> moment.utc('07-18-2013', 'MM-DD-YYYY').local().format("YYYY-MM-DD HH:mm")
"2013-07-18 03:00"
Carbine answered 25/7, 2013 at 11:29 Comment(5)
Many thanks. So basically, I should always pass in time when using UTC or pass in UTC as in your second approach.Throughcomposed
Either that or stick to local timezone. If you send times from the server, you can express them as Unix timestamp (X) or as strings at specific timezone. Why use UTC instead of user's local timezone, anyway (except for the purpose of sending normalized data to the server)?Carbine
Be aware that new Date('07-18-2013 UTC') will not work in IE8, if you care.Mayers
I've been struggling with this for so long. They should really have this explained well on their site, as I assume that this is the most common use case of moment.js. Thank you so much! You really saved my skin!Endoderm
this code works for me :[code] moment(strDate, 'DD/MM/YYYY h:mm A').utc(strDate).format("YYYY-MM-DD HH:mm") [/code]Stocktaking
L
41

Both Date and moment will parse the input string in the local time zone of the browser by default. However Date is sometimes inconsistent with this regard. If the string is specifically YYYY-MM-DD, using hyphens, or if it is YYYY-MM-DD HH:mm:ss, it will interpret it as local time. Unlike Date, moment will always be consistent about how it parses.

The correct way to parse an input moment as UTC in the format you provided would be like this:

moment.utc('07-18-2013', 'MM-DD-YYYY')

Refer to this documentation.

If you want to then format it differently for output, you would do this:

moment.utc('07-18-2013', 'MM-DD-YYYY').format('YYYY-MM-DD')

You do not need to call toString explicitly.

Note that it is very important to provide the input format. Without it, a date like 01-04-2013 might get processed as either Jan 4th or Apr 1st, depending on the culture settings of the browser.

Lelalelah answered 25/7, 2013 at 13:24 Comment(6)
Just for learning sake, in the console: moment.utc('2013-07-18 0:00 +0100', 'YYYY-MM-DD HH:mm') gives me "2013-07-18 0:00 +0100" But what dislpays on jsfiddle when ran is different that is: Thu Jul 25 2013 01:00:00 GMT+0100 Note the 01:00:00. thanks.Throughcomposed
Outputting a raw moment on the console isn't very useful. You're probably looking at one of its internal properties. You should format it before checking the results. For example moment.utc().format() or moment().format().Lelalelah
Both Date and moment will parse the input string in the local time zone of the browser by default. I'm on EDT right now. new Date('2010-12-12') gives me Date {Sat Dec 11 2010 19:00:00 GMT-0500 (Eastern Daylight Time)} in FF 38.0.5. Just to contextualize what "in the local time" means, exactly -- in this case, it appears to mean, "Date will assume a time zoneless string is in UTC and will parse to local time." d.getUTCDate() = 12 and d.getDate() = 11Sheepfold
Yes, there are some exceptions. ES5 (most current browsers) will interpret dates with hyphens as UTC, but just about everything else is interpreted as local time. ES6 is changing this behavior to interpret the same string as local time. I updated the answer.Lelalelah
Ha, yep, just ran into this at MDN saying exactly that ('2012-12-12' is UTC b/c it's in an ISO format, but 'December 12, 2012' and even '2012/12/12' are parsed with a local time zone in ES5), but you've beaten me to it. So great that ES6 makes them all local [he said sarcastically]. Dates are a pain, (c) Advent of DatesSheepfold
The thing is, ISO8601 is explicit that unless an offset (or a Z) is provided, the zone is assume to be local. EcmaScript ignored this in the original spec and just assumed all ISO formats should be UTC by default. They're "fixing it" for ES6, so it conforms with the actual ISO spec. Should be interesting to see how many new bugs are created by the "fix". :)Lelalelah
K
2

use this :

return moment.utc(new Date(oData.CreatedAtUtc), 'MM/DD/YYYY h:mm A').local().format("YYYY-MM-DD HH:mm") + ' (' + timezoneAbbr + ')';
Korns answered 6/7, 2021 at 3:35 Comment(0)
S
0

none of the above worked for me❌. I tried converting the date using moment()utc, use parseZone, but none of them worked.

✅Converting the date to ISO string using javascript's toISOString() did though ✅

// input date is "2023-09-28T00:00:00-05:00" 
// timezone is chicago time GMT-5:00
const date_str =  new Date(this.date); 

const isoDate =  date_str.toISOString(); 
// isoDate Result "2023-09-28T05:00:00.000Z"


// Before i used the isoString method,  moment() would
//  would always get the previous date
 
const startTime = moment(isoDate)
    .set('hour', parseInt(momentConfigStartTimeOfDay.hour))
    .set('minute', parseInt(momentConfigStartTimeOfDay.minute))
    .format();
const endTime = moment(isoDate)
    .set('hour', parseInt(momentConfigEndTimeOfDay.hour))
    .set('minute', parseInt(momentConfigEndTimeOfDay.minute))
    .format();
Hopefully this helps someone. I've spent more than a day trying to figure out what would work haha
Sogdian answered 2/9, 2023 at 3:16 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Adigun

© 2022 - 2024 — McMap. All rights reserved.