Operating in the end user's timezone with momentjs - javascript

I'm in EDT and my end user is in PDT. I'd like my WebApp to operate as if it's running in the end user's timezone (e.g. even if they travel it's to show the time back home). The timestamps coming from the Java server are formatted to include the end user's timezone e.g. "Mon Oct 27 06:57:00 PDT 2014", and I also have the end user TZ string e.g. "America/Vancouver" in a config file. I'm using native Date() and it's displaying the right time for the user, but I can't use it to compare times with times in the client. I'm looking to change to moment.js, but I can't figure out how to get it to do what I want. Basically, given the above timestamp, I want to be able to call m.hour() and get 6 back. I also want to be able to get the day of the year in end user time (both "now" and for a timestamp), and see how far back the last timestamp is from the current time, in minutes. Here is my failed attempt:
var d = "Mon Oct 27 06:57:00 PDT 2014";
var ds = d.split(/ /).slice(1).join(' '); // eat "Mon "
var m = moment.tz(ds, "MMM D HH:mm:ss Z YYYY", "America/Vancouver");
alert("" + m.hour()); // 23 ??? Want "6"!
var n = moment.tz("America/Vancouver");
var df = n.diff(m, 'minute');
alert(m.format() + "\n" + n.format() +
"\n" + df + " minutes ago");
Fiddle: http://jsfiddle.net/up628qbq/
Thanks!

Use a lower-case z in the format string instead of the upper-case Z. That will match the time time zone abbreviation characters.
Note that this won't actually interpret the abbreviation as a particular offset. This is primarily because abbreviations can be ambiguous. (There are 5 different meanings of "CST"). Because of this, some values during a DST fall-back transition may be interpreted incorrectly.
For example, in the Pacific time zone, there are two instances of 1:00 AM on November 2 2014. The first is in PDT (-7) , and second is in PST (-8). Even though you provided an abbreviation, moment won't use it to disambiguate. If you want to be certain of which instance you are working with, you would need a numeric time zone offset to be included and parsed with Z.
Also, you can use ddd for the weekday name instead of splitting and slicing.

Thank you Matt! I don't have enough reputation to "vote up", so I have made the changes you recommend (remove split and add ddd format, change Z to z) in the fiddle: http://jsfiddle.net/up628qbq/1/

Related

Store date as if they were entered in a specific timezone

I am using Moment. I need users to enter a date, and make sure that that's always the time for Australia/Perth, regardless of what the browser is set as.
For example, assume the computer is set as Sydney time (which right now is +3 but in winter it's +2). I want the user to enter a date/time, and make sure that that date/time is stored as Perth's time.
Note that visualising the correct date isn't an issue (with moment.tz). What I am worried about, is the date object creation which would need to happen providing a time, and forcing the browser to pretend that they are in that timezone.
I need this to work regardless of daylight savings etc.
UPDATE: this is what I want to achieve:
// MY CURRENT TIMEZONE IS SYDNEY, CURRENTLY PERTH + 3 BUT +2 IN SUMMER
// IN PERTH IT's 10:11AM, and *THAT* is the time I am interested in
// storing, not 13:11:58
var d = new Date()
// => Wed Nov 28 2018 13:11:58 GMT+1100 (Australian Eastern Daylight Time)
// NOTE: the date 13:11:58 SYDNEY time. I don't want this.
// I MUST pretend that users entered the date with their timezone
// is in Perth
// So...
// Create a date string that exclude original timezone and includes new timezone
perthDateString = moment(new Date()).format('YYYY-MM-DDTHH:MM:ss') + '+0800'
// => "2018-11-28T13:11:58+0800"
// Make a new date object using the tinkered date string
var newD = new Date(perthDateString)
// Display the date. Note: the time is "wrong" (since it displays
// a time that is 3 hours ahead), but it's actually the correct
// answer since 'd' is meant to be Perth time in the first place
newD
// => Wed Nov 28 2018 16:11:58 GMT+1100 (Australian Eastern Daylight Time)
// Display the date as the Perth time
moment.tz(newD, 'Australia/Perth').toString()
// => "Wed Nov 28 2018 13:11:58 GMT+0800"
However:
in perthDateString = moment(new Date()).format('YYYY-MM-DDTHH:MM:ss') + '+0800', I would like to specify Australia/Perth, rather than '+0800'
I feel uneasy with dealing with dates by chopping/concatenating strings
I wonder if EVERY browser will be able to parse the date returned by .format('YYYY-MM-DDTHH:MM:ss') + '+0800' or if I am going to have surprises -- especially when/if I have a solution so that I use Australia/Perth instead
If you are anywhere in the world (say, Sydney or Tokyo), and the local time is "12:30", but you want to store the same time-of-day ("12:30") as if you were in Perth -- you can use the moment-timezone package together with moment.
For example, this snippet will give you a moment for "12:30" in Perth:
let x = moment.tz('2019-01-01T12:30:00', 'Australia/Perth')
console.log(x.format()); // Show that it is 12:30 in Perth time
console.log(new Date(x)); // Generate Date for that time
<script src="https://momentjs.com/downloads/moment.js"></script>
<script src="http://momentjs.com/downloads/moment-timezone-with-data.js"></script>

Why does 'new Date()' parse a string date incorrectly?

When I try to parse a string date from March, new Date() works incorrectly. But when my string date is from April, everything is fine. I don't understand this. Why does it work like that?
var a = '2018-03-07T00:00+03:00';
console.log(a);
console.log(new Date(a).toString());
var b = '2018-04-07T00:00+03:00';
console.log(b);
console.log(new Date(b).toString());
Here's a screenshot from Google Chrome browser:
As others pointed out, it's due to the daylight saving time in your time-zone. Notice the times in your screenshot: the time for a is 23 and for b is midnight 00, which clearly tells you what happened.
If you want to parse the values without the daylight saving time, you can use the GMT+0 time-zone by replacing your time-zone +3:00 with z. However, then you need to correct time manually by adding/subtracting the hours of your time-zone (-3 in your case). Here is an example:
var a = '2018-03-07T00:00+03:00';
a.replace('+03:00', 'z');
a = new Date(a);
a.setHours(a.getHours() - 3); //3 is your time-zone
console.log(a.toUTCString());
var b = '2018-04-07T00:00+03:00';
b.replace('+03:00', 'z');
b = new Date(b);
b.setHours(b.getHours() - 3); //3 is your time-zone
console.log(b.toUTCString());
Obviously, this code will only work for one known time-zone. If your values can be in different time-zone, then instead of replacing, you need to extract the time-zone from the string (the last 6 characters in this format, except for GMT-0 which is z), and then use it to correct the time on the setHours() line.
Because of summer daylight saving time. According to Date documentation, the date is specific moment in time relative to Jan 1st 1970. That moment in time in March belonged to the part of year with daylight saving offset in your specific locale
Your first date is Standard Time... and the second one is Daylight Time.
var x = new Date('2018-03-07T00:00+03:00');
console.log(x);
var y = new Date('2018-04-07T00:00+03:00');
console.log(y);
In your scenario... EET – Eastern European Time versus EEST – Eastern European Summer Time like shown on console.

How do I deal with dates-only in javascript when it keeps appending a time value?

I have a bunch of date fields (not datetime) in SQL Server. When they are fetched by the web server and sent to the client as JSON a time stamp is appended automatically. So instead of receiving just 2016-09-27 I get 2016-09-27T00:00:00.
When the user interacts with the uiBootstrap calendar control it automatically parses that string into a javascript date object and applies a 4 hour offset for the timezone. When this is sent back to the server it's sent as 2016-09-26T20:00:00. Now my date is off by a day. Also the next time it's fetched it will happen again. But this time it will start at 2016-09-26T00:00:00 and will roll back to 2016-09-25T20:00:00. Each cycle between client and server loses a day.
How do I keep my dates from changing? I'm looking at moment.js but so far haven't really figured out how it can help me.
EDIT
I've setup a test function to try different methods of converting datetimes back and forth.
console.log('JSONDate: ' + JSONDate);
var dt = new Date(JSONDate);
console.log('JS Converted Date: ');
console.log(dt);
console.log('Date converted back to string: ' + dt.toISOString());
Here's the output:
JSONDate: 2016-10-02T00:00:00
JS Converted Date: Sun Oct 02 2016 00:00:00 GMT-0400 (Eastern Daylight Time)
Date converted back to string: 2016-10-02T04:00:00.000Z
In this example the date is now 4 hours ahead.
EDIT 2
Web server is running .net, specifically WebAPI 2. I'm using Entity Framework 6 to communicate between web server and SQL Server 2012.
Ideally, your dates would be serialized in the JSON as just dates. Instead of 2016-10-02T00:00:00, you'd have 2016-10-02. The problem is that .NET doesn't have a built in Date type. It only has DateTime. There are alternatives, such as LocalDate in Noda Time, as discussed in this answer.
However, assuming you don't want to change anything on the back-end, the way to handle this is just to make sure the input date/time is treated as local time, and never converted to/from UTC. This should be the default behavior when you parse the string into a Date object when the string is like 2016-10-02T00:00:00, but the behavior has changed a few times over the years, so if you are potentially dealing with older browsers, you may get some that interpret it as UTC instead.
As far as output goes, the toISOString method of the Date object always outputs in UTC - which is the source of your conversion error. If you want an ISO8601 string in local time - you'd have to construct one yourself using the various accessor functions (getFullYear, etc.), handling zero-padding, and ensuring months are incremented to be 1-based instead of 0-based.
The easier solution is to use moment.js, which can handle this for you.
var d = moment('2016-10-02T00:00:00').toDate(); // now you have a `Date` object
var s = moment(d).format("YYYY-MM-DD[T]HH:mm:ss"); // now you have a string again
Of course, if you don't need the time portion, you can omit it from the format string and the rest should still work out ok.
You could try getting the offset and applying it back to the date. Something like this:
var d = new Date('2016-09-27'); //Mon Sep 26 2016 20:00:00 GMT-0400 (EDT)
new Date(d.getTime() + d.getTimezoneOffset() * 60 * 1000) //Tue Sep 27 2016 00:00:00 GMT-0400 (EDT)

Set date() to midnight in users timezone with moment.js

I use moment.js to display a UTC date in the users local timezone:
var date = new Date(Date.UTC(2016,03,30,0,0,0));
var now = new Date();
var diff = (date.getTime()/1000) - (now.getTime()/1000);
var textnode = document.createTextNode(moment(date).format('dddd, DD.MM.YYYY') + ' a las ' + moment(date).format('HH:mm A'));
document.getElementsByClassName("date")[0].appendChild(textnode.cloneNode(true));
I later use the diff variable to show a countdown timer.
I would like to show a different countdown timer to everyone in their local time zone. (Using the difference till its midnight in their time zone, not in UTC)
But I am struggeling to get it work. Instead of using var date = new Date(Date.UTC(2016,03,30,0,0,0)); I probably need to use some function of moment.js that gives me till midnight in the users time zone.
The best example would be new years eve. If I use UTC everyone would have the same counter (9 hours left) but in different parts of the world this wouldn't make sense. For someone in australia it should be 2 hours left, and for someone in the US 14 hours.
I'm not sure that I fully understand your question, but I'll give you some general advice and tips.
When using moment.js, there is very little need to ever use the Date object. Only use it for interacting with other APIs that expect a Date object.
To get a moment in UTC, just use moment.utc(...), passing the appropriate arguments, such as moment.utc([2016,3,30]) or moment.utc('2016-04-30') for midnight April 30th UTC.
If you want to convert that back to the user's local time, use the .local() function. For example, moment.utc('2016-04-30').local() will create a moment with the equivalent local time to the UTC time provided.
If you want a moment in the user's local time, then that would be moment(...), such as moment([2016,3,30]) or moment('2016-04-30') for midnight April 30th local time.
You can difference two moments using the diff function, which can give the answer in specific units, such as m1.diff(m2, 'seconds') where m1 and m2 are moment objects.
You don't need to call format twice. Just encapsulate any text you want outputed with square brackets. .format('dddd, DD.MM.YYYY [a las] HH:mm A')
You might look into moment's locale support. If I'm not mistaken, "a las" indicates Spanish, however it's not always "a las", but sometimes "a la", if the hour is 1. Also, moment only uses those words in its .calendar() function, such as when producing a phrase like "mañana a las 13:17". A regular date formatted with .format('LLLL') in the Spanish locale would be something like: "sábado, 19 de marzo de 2016 13:17". So, you might want to verify that "a las" is exactly what you want in every case.
The title to this question was how to set a date to midnight. For that, I recommend using moment's startOf function. m.startOf('day') will give set the moment m to the start of the day, which is usually midnight. Keep in mind that not every local day actually starts at midnight in every time zone. Due to anomalies like daylight saving time, some days might start at 1:00. For example, this occurs in Brazil on October 16th this year.
Also, if you created the moment in UTC mode, you may wish to convert it back to local mode first before setting it to the start of the day. If you don't want to change the original moment object, be sure to clone it first.
Putting this all together:
var m1 = moment.utc([2016,3,30]);
var m2 = m1.clone().local().startOf('day');
var now = moment();
var diff = m1.diff(now, 'seconds');

How to get hours and minutes in desired timezone without creating new moment object?

I have to display a string on the web page in this format: 16:00 HH:mm
I'm using a moment object to represent a date/time and timezone.
var day = moment().tz('GMT');
day.hours(16).minutes(0).seconds(0).milliseconds(0);
So this is 16:00 in GMT time.
On my web page I want to change the time zone and then collect the hours and minutes.
If I make a new moment object
var day2 = moment().tz('PST); //this is 8 AM since gmt was 16
console.log(day2.get('hours'));
it is 16 not 8!
and try to get the hours and minutes they are in GMT not in PST.
How can I get it in PST? Do I have to keep wrapping it?
// initialize a new moment object to midnight UTC of the current UTC day
var m1 = moment.utc().startOf('day');
// set the time you desire, in UTC
m1.hours(16).minutes(0);
// clone the existing moment object to create a new one
var m2 = moment(m1); // OR var m2 = m1.clone(); (both do the same thing)
// set the time zone of the new object
m2.tz('America/Los_Angeles');
// format the output for display
console.log(m2.format('HH:mm'));
Working jsFiddle here.
If you can't get it to work, then you haven't correctly loaded moment, moment-timezone, and the required time zone data. For the data, you either need to call moment.tz.add with the zone data for the zones you care about, or you need to use one of the moment-timezone-with-data files available on the site.
In the fiddle, you can see the moment-files I'm loading by expanding the External Resources section.
PST can mean different things in different regions. In the moment-timezone docs, I see nothing referring to "PST" or similar abbreviations.
Perhaps try:
var day2 = moment().tz('PST');
// 16 with Error: Moment Timezone has no data for PST. See http://momentjs.com/timezone/docs/#/data-loading/.
var day2 = moment().tz('America/Los_Angeles');
// 15
I don't know about using moment.js, but it's fairly simple using POJS and the same algorithm should work. Just subtract 8 hours from the UTC time of a date object and return a formatted string based on the adjusted UTC time.
Assuming PST is "Pacific Standard Time", also known as "Pacific Time" (PT), and is UTC -8:00:
/* #param {Date} date - input date object
** #returns {string} - time as hh:mm:ss
**
** Subtract 8 hours from date UTC time and return a formatted times string
*/
function getPSTTime(date) {
var d = new Date(+date);
d.setUTCHours(d.getUTCHours() - 8);
return ('0' + d.getUTCHours()).slice(-2) + ':' +
('0' + d.getUTCMinutes()).slice(-2) + ':' +
('0' + d.getUTCSeconds()).slice(-2);
}
document.write('Current PST time: ' + getPSTTime(new Date));
There is moment-timezone which adds functionality to moment.js for IANA time zones. For PST you can use America/Los_Angeles, however it might also automatically adjust for daylight saving so you'll get PDT when that applies. If you want ignore daylight saving, use the above or find a location with the offset you need and use that.

Categories

Resources