125

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?

alex
  • 479,566
  • 201
  • 878
  • 984
brg
  • 3,915
  • 8
  • 37
  • 66
  • 5
    I don't think you need `toString()` after `format()` (it already returns a string). – alex Jul 24 '14 at 05:17

4 Answers4

209

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"
MasterAM
  • 16,283
  • 6
  • 45
  • 66
  • Many thanks. So basically, I should always pass in time when using UTC or pass in UTC as in your second approach. – brg Jul 25 '13 at 12:08
  • 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)? – MasterAM Jul 25 '13 at 12:18
  • 1
    Be aware that `new Date('07-18-2013 UTC')` will not work in IE8, if you care. – Dzmitry Lazerka Oct 29 '13 at 18:38
  • 2
    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! – WebWanderer Jan 21 '16 at 12:44
  • this code works for me :[code] moment(strDate, 'DD/MM/YYYY h:mm A').utc(strDate).format("YYYY-MM-DD HH:mm") [/code] – Omar Isaid Jul 15 '18 at 08:14
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.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • 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. – brg Jul 25 '13 at 20:09
  • 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()`. – Matt Johnson-Pint Jul 25 '13 at 20:47
  • *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()` = `11` – ruffin Jun 15 '15 at 18:44
  • 1
    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. – Matt Johnson-Pint Jun 15 '15 at 18:51
  • Ha, yep, just [ran into this at MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse#Differences_in_assumed_time-zone) 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 Dates – ruffin Jun 15 '15 at 19:19
  • 1
    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". :) – Matt Johnson-Pint Jun 15 '15 at 20:18
2

use this :

return moment.utc(new Date(oData.CreatedAtUtc), 'MM/DD/YYYY h:mm A').local().format("YYYY-MM-DD HH:mm") + ' (' + timezoneAbbr + ')';
I_love_vegetables
  • 1,575
  • 5
  • 12
  • 26
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
Ann lopez
  • 1
  • 1