Make local 24h day from utc timestamp, and convert back to utc - javascript

I have utc date 2017-04-22T21:03:54 (local date is 2017-04-23 00:03:54, offset +3)
I would like to get data from server for 1 day. For this example, local day is:
start: 2017-04-23 00:00:00
end: 2017-04-23 23:59:59
And UTC params for server:
start: 2017-04-22 21:00:00
end: 2017-04-23 20:59:59
How I can receive this dates with moment.js? Local offset may be different
I try:
moment.utc(startTime).format() // 2017-04-22T21:03:54Z, but I need 2017-04-22T21:00:00Z
// for example, if local timezone will be +6, I need 2017-04-22T18:00:00Z

(Putting my comments as answer)
The representation of datetimes in a text encoded transport/protocol (as HTTP) should follow ISO-8601. The time portion should include a timezone designator (for UTC time this is the simple Z, see link). These convention removes any ambiguity between client and server about what moment does the time literal represent.
Back ends should store times in UTC:
This removes any ambiguity when the database moves (failover to standby in different timezone, move of server to new location from east coast to west coats etc).
It removes any ambiguity for literals that fall into the DST time overlaps.
It removes any ambiguity from rushed legislative changes modifying timezones (of which several occur every few years)
Now that being said, if you need to convert an UTC time to local time in JavaScript/Node with MomentJS, use Moment's local().

I have utc date 2017-04-22T21:03:54
A date string without a time zone is treated as local (except for ISO 8601 date-only forms, which ECMAScript treats as UTC, contrary to ISO 8601). If you want to treat a string like "2017-04-22 21:00:00" as UTC then you have to tell the parser (in moment.js you can use the utc method).
You should also always tell the parser the format it's trying to parse, otherwise you're hoping it guesses correctly. If utc is used, moment uses offset +0000 by default (its "UTC mode" * ), not the local offset. If you want the host offset, you can use the local method or convert to a Date object and use built-in methods.
var s = '2017-04-22 21:00:00';
var format = 'YYYY-MM-DD HH:mm:ss';
// Tell parser to treat as UTC and the format
var m = moment.utc(s, format);
// Once the string has been parsed, you can output it in any format you like
console.log(
'Original format, offset +0000 : ' + m.format(format)
+'\nBuilt-in toISOString, offset Z: ' + m.toDate().toISOString()
+'\nBuilt-in toString, host offset: ' + m.toDate().toString()
+'\nOriginal format, host offset : ' + m.local().format(format)
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
* I'm not sure this is a good idea as it means the code using the object needs to know it's in UTC mode. If you get a moment.js object from elsewhere, it would be good practice to set it to the "mode" you want. Calling local or utc multiple times has no bad effects.

Related

Converting a date, time and offset into an ISO8601 DateTime using moment.js and moment timezone

I am processing some itinerary data where the times and dates are all provided in the local timezone. I am adding this data to a database where I'd like to store all of the dates in UTC, with the proper timezone offset. I'm trying to process these dates with moment.js.
The input string for date/time is formatted like this 2020-07-12 13:00 and the timezone is in this format Europe/Amsterdam.
I want to end up with a string like:
2020-07-12T11:00:00+02:00
The trouble I'm having, is that moment converts my input time to either local time or utc if I use the .utc() method.
This code is getting me the correct result, but I don't understand why and I'm not sure if I can rely on its accuracy:
var offset = moment.tz(`Europe/Amsterdam`).utcOffset();
var m = moment(`2020-07-12 13:00`, 'YYYY-MM-DD HH:mm').utc().subtract(240 + offset + offset, 'minutes').utcOffset(offset); // (240 is my own UTC offset)
How can I simply input a date, time and timezone and end up with a correct ISO8601 DateTime?
If you are already using Moment and Moment-TimeZone in your app, then you can simply do the following:
const m = moment.tz('2020-07-12 13:00', 'YYYY-MM-DD HH:mm', 'Europe/Amsterdam');
m.format() //=> "2020-07-12T13:00:00+02:00"
However, the Moment team recommends using Luxon for new development. The equivalent is:
const dt = luxon.DateTime.fromFormat('2020-07-12 13:00', 'yyyy-MM-dd HH:mm', { zone: 'Europe/Amsterdam'});
dt.toISO() //=> "2020-07-12T13:00:00.000+02:00"
The only difference being that milliseconds are included. You can use a different formatting function if you prefer a different output.
The main benefit of Luxon is that it uses the built-in time zone functionality provided by the ECMAScript Internationalization API, whereas Moment-Timezone bundles its own time zone data - which can be quite large.
Also, note that in your question by asking for 2020-07-12T11:00:00+02:00 you seem to be misunderstanding the ISO 8601 format. In that format, the time presented is the local time. Thus, it should be 13:00, not 11:00. The +02:00 means, "this was the offset from UTC for this local time". (It doesn't mean that you apply the offset to get the local time.)

Why is JavaScript guessing two different timezones based on strings that are identical in structure?

I am providing dates two to my script in this format: 2016-05-25 12:30:02. However, JavaScript is setting the each date to a different GMT offset. I formatted the dates to match the suggested answer here and added the function provided, but I got the same results as shown below so I reverted back to my original script shown below.
var response = JSON.parse(jsonResponse);
var lastExportedOrderData = response.lastExportedOrder;
/* currentDateTime = '2016-05-25 12:30:02'; */
var currentDateTime = new Date(response.scriptExecutionTime);
/* lastOrderExportedAt = '2016-01-12 16:53:56'; */
var lastOrderExportedAt = new Date(lastExportedOrderData.exported_at);
currentDateTime Results: 2016-05-25 12:30:02 -> Wed May 25 2016 12:30:02 GMT-0400 (EDT)
lastOrderExportedAt Results: 2016-01-12 16:53:56 > Tue Jan 12 2016 16:53:56 GMT-0500 (EST)
I don't really care about the timezone so long as the dates are in the same timezone.
Update
My dates are now being input using the ISO-8601 Standard format but my the problem remains. 2016-05-25T14:04:00-05:00 results as GMT-0400 where 2016-01-12T16:53:56-05:00 results as GMT -0500
They're in the same timezone (U.S. Eastern). The difference is that the one in May is during Daylight Savings Time and thus is Eastern Daylight Time (EDT), and the one in January isn't, and thus is in Eastern Standard Time.
It's important to note that their underlying time values (e.g., as from dt.getTime()) are unaffected by timezone, and as both dates have been parsed (in your example) as local time, they can reliably be compared. You'd only run into an issue comparing them if one were parsed as local time and the other as UTC.
Side note: You're relying on new Date to parse those strings, which means you're relying on unspecified behavior that may vary from JavaScript engine to JavaScript engine. The only string format engines are required to support is a simplified version of ISO-8601, which those strings are not in. In particular, it's entirely possible that one browser's JavaScript engine will decide to parse them as though they were in UTC and another engine's browser will decide (as yours seems to have done) to parse them as local time.
You haven't said what timezone the strings are meant to be interpreted in, but if (for instance) they were meant to be UTC, you can fairly easily change them to be in ISO-8601 format by replacing the space between the date and time with a T, and adding a Z to the end:
str = str.replace(" ", "T") + "Z";
The resulting string, for instance "2016-05-25T12:30:02Z", is valid for new Date on any modern JavaScript engine (so, not IE8).
Alternately, if you know what timezone offset they should be interpreted in (say, GMT-05:00), you can replace the space with T and add a timezone offset to the end:
str = str.replace(" ", "T") + "-05:00";
That string (for instance, "2016-05-25T12:30:02-05:00") is also valid for new Date.
Alternately, get the parts of them:
var parts = /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/.exec(str);
...and build the date/time yourself
// As local time
var dt = new Date(
+parts[1], // Year
+parts[2] - 1, // Month (0 = January)
+parts[3], // Day
+parts[4], // Hour
+parts[5], // Minute
+parts[6] // Second
);
or
// As UTC
var dt = new Date(Date.UTC(
+parts[1], // Year
+parts[2] - 1, // Month (0 = January)
+parts[3], // Day
+parts[4], // Hour
+parts[5], // Minute
+parts[6] // Second
));
The unary + on the above coerces from string to number. (It isn't strictly necessary, though, both new Date and Date.UTC do it for you.)
Just to add to TJ's answer, in implementations consistent with ES5 and later, the string 2016-05-25T14:04:00 will be parsed as a "local" time as it doesn't have a time zone offset. (In an ES2015-compliant browser. Sadly in the previous specification, they got that wrong and made it default to UTC, and currently Chrome implements the ES5 spec [UTC] but Firefox implements the ES2015 spec [local time].) But don't rely on that, parse the string per TJ's answer.
When creating a local time value, the host time zone settings are taking in to consideration. It seems that the host system is set for EST/EDT.
EDT starts at 02:00 on the second Sunday in March, so on 2016-01-12 US Eastern Standard Time (EST) applies which has an offset of -0500. For 2016-05-25, US Eastern Daylight Time (EDT) is used which has an offset of -0400.
Note that the actual time value of the Date created for 2016-05-25T12:30:02-0400 is for 2016-05-25T16:30:02Z (i.e. the equivalent UTC time). When displayed, the date's toString method is called which applies the host system offset for that time and date to adjust the displayed value to "local" time.
Date's use UTC internally so that they can be displayed in any time zone and still represent the same instant in time.

Get local time representation of Date in arbitrary timezone in arbitrary day - considering Daylight Savings Time

For sure, there is a lot of questions about Date objects and timezones but many of them are about converting the current time to another timezone, and others are not very clear about what they want to do.
I want to display the day, hour, minute etc. in an arbitrary timezone, in an arbitrary day. For example, I would like a function f(t, s) that:
given the timestamp 1357041600 (which is 2013/1/1 12:00:00 UTC) and the string "America/Los Angeles", would satisfy the comparison below:
f(1357041600, "America/Los Angeles") == "2013/01/01 04:00:00"
given the timestamp 1372680000 (2013/07/01 12:00:00 UTC), would satisfy the comparison below:
f(1357041600, "America/Los Angeles") == "2013/07/01 05:00:00"
will always behave this way even if the timezone in the browser is, let us say "Europe/London" or "America/São Paulo".
will always behave this way even if the time in the browser is, let us say 2014/02/05 19:32, or 2002/08/04 07:12; and
as a final restriction, will not request anything from the server side (because I'm almost doing it myself :) )
Is it even possible?
given the timestamp 1357041600 (which is 2013/1/1 12:00:00 UTC)
That appears to be seconds since the UNIX epoch (1970-01-01T00:00:00Z). Javascript uses the same epoch, but in milliseconds so to create a suitable date object:
var d = new Date(timestamp * 1000);
That will create a Date object with a suitable time value. You then need to determine the time zone offset using something like the IANA time zone database. That can be applied to the Date object using UTC methods. E.g. resolve the offset to minutes, then use:
d.setUTCMinutes(d.getUTCMinutes() + offset)
UTC methods can then be used to get the adjusted date and time values to create a string in whatever format you require:
var dateString = d.getUTCFullYear() + '/' + pad(d.getUTCMonth() + 1) + '/' ...
where pad is a function to add a leading zero to single digit values. Using UTC methods avoids any impact of local time zone offsets and daylight saving variances.
There are also libraries like timezone.js that can be used to determine the offset, however I have not used them so no endorsement is implied.
For JavaScript runtime environments that support the ECMAScript Internationalization API, and adhere to its recommendation of supporting the IANA time zone database, you can simply do this:
new Date(1357041600000).toLocaleString("en-US", {timeZone: "America/Los_Angeles"})
For other environments, a library is required. There are several listed here.

moment.js - UTC gives wrong date

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?
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"
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.
use this :
return moment.utc(new Date(oData.CreatedAtUtc), 'MM/DD/YYYY h:mm A').local().format("YYYY-MM-DD HH:mm") + ' (' + timezoneAbbr + ')';

Format date in a specific timezone

I'm using Moment.js to parse and format dates in my web app. As part of a JSON object, my backend server sends dates as a number of milliseconds from the UTC epoch (Unix offset).
Parsing dates in a specific timezone is easy -- just append the RFC 822 timezone identifier to the end of the string before parsing:
// response varies according to your timezone
const m1 = moment('3/11/2012 13:00').utc().format("MM/DD HH:mm")
// problem solved, always "03/11 17:00"
const m2 = moment('3/11/2012 13:00 -0400').utc().format("MM/DD HH:mm")
console.log({ m1, m2 })
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
But how do I format a date in a specifc timezone?
I want consistent results regardless of the browser's current time, but I don't want to display dates in UTC.
As pointed out in Manto's answer, .utcOffset() is the preferred method as of Moment 2.9.0. This function uses the real offset from UTC, not the reverse offset (e.g., -240 for New York during DST). Offset strings like "+0400" work the same as before:
// always "2013-05-23 00:55"
moment(1369266934311).utcOffset(60).format('YYYY-MM-DD HH:mm')
moment(1369266934311).utcOffset('+0100').format('YYYY-MM-DD HH:mm')
The older .zone() as a setter was deprecated in Moment.js 2.9.0. It accepted a string containing a timezone identifier (e.g., "-0400" or "-04:00" for -4 hours) or a number representing minutes behind UTC (e.g., 240 for New York during DST).
// always "2013-05-23 00:55"
moment(1369266934311).zone(-60).format('YYYY-MM-DD HH:mm')
moment(1369266934311).zone('+0100').format('YYYY-MM-DD HH:mm')
To work with named timezones instead of numeric offsets, include Moment Timezone and use .tz() instead:
// determines the correct offset for America/Phoenix at the given moment
// always "2013-05-22 16:55"
moment(1369266934311).tz('America/Phoenix').format('YYYY-MM-DD HH:mm')
A couple of answers already mention that moment-timezone is the way to go with named timezone. I just want to clarify something about this library that was pretty confusing to me. There is a difference between these two statements:
moment.tz(date, format, timezone)
moment(date, format).tz(timezone)
Assuming that a timezone is not specified in the date passed in:
The first code takes in the date and assumes the timezone is the one passed in.
The second one will take date, assume the timezone from the browser and then change the time and timezone according to the timezone passed in.
Example:
moment.tz('2018-07-17 19:00:00', 'YYYY-MM-DD HH:mm:ss', 'UTC').format() // "2018-07-17T19:00:00Z"
moment('2018-07-17 19:00:00', 'YYYY-MM-DD HH:mm:ss').tz('UTC').format() // "2018-07-18T00:00:00Z"
My timezone is +5 from utc. So in the first case it does not change and it sets the date and time to have utc timezone.
In the second case, it assumes the date passed in is in -5, then turns it into UTC, and that's why it spits out the date "2018-07-18T00:00:00Z"
NOTE: The format parameter is really important. If omitted moment might fall back to the Date class which can unpredictable behaviors
Assuming the timezone is specified in the date passed in:
In this case they both behave equally
Even though now I understand why it works that way, I thought this was a pretty confusing feature and worth explaining.
Use moment-timezone
moment(date).tz('Europe/Berlin').format(format)
Before being able to access a particular timezone, you will need to load it like so (or using alternative methods described here)
moment.tz.add('Europe/Berlin|CET CEST CEMT|-10 -20 -30')
.zone() has been deprecated, and you should use utcOffset instead:
// for a timezone that is +7 UTC hours
moment(1369266934311).utcOffset(420).format('YYYY-MM-DD HH:mm')
I was having the same issue with Moment.js. I've installed moment-timezone, but the issue wasn't resolved. Then, I did just what here it's exposed, set the timezone and it works like a charm:
moment(new Date({your_date})).zone("+08:00")
Thanks a lot!
Just came acreoss this, and since I had the same issue, I'd just post the results I came up with
when parsing, you could update the offset (ie I am parsing a data (1.1.2014) and I only want the date, 1st Jan 2014. On GMT+1 I'd get 31.12.2013. So I offset the value first.
moment(moment.utc('1.1.2014').format());
Well, came in handy for me to support across timezones
B
If you pass the timestamp as the parameter to moment() (e.g if the timezone is Asia/Hong_kong which is +08:00), what I do is:
const localDateTime = moment((item.createdAt.seconds + 8 * 3600) * 1000).format('YYYY-MM-DD HH:mm:ss');
You can Try this ,
Here you can get the date based on the Client Timezone (Browser).
moment(new Date().getTime()).zone(new Date().toString().match(/([-\+][0-9]+)\s/)[1]).format('YYYY-MM-DD HH:mm:ss')
The regex basically gets you the offset value.
Cheers!!

Categories