Moment.js, Moment-timezone: input and display in eastern timezone? - javascript

I've got a javascript app that the user insists that regardless of location of the app user, they want date/times entered by the user in Eastern time. That they will be sent to the database and stored, and later displayed again in the app and displayed in Eastern timezone.
So I was looking to use moment.js and moment-timezone, but can't figure out how to make it work.
When I try const objMoment = moment(), objMoment appears to contain a time offset from the current time.
I was wanting to get a moment object that is whatever time, and then objMoment.tz("America/New_York")
but I ended up with a time that is 5 hours off from what it should be.
const objMoment = moment();
const objEastern = objMoment.clone().tz("America/New_York");
debugger;
formData.SwoDate = objMoment.toDate();
debugger;

A few things:
You don't need to create a local moment and clone it. You can instead construct a moment directly in a specific time zone with:
moment.tz('America/New_York')
Since you call toDate(), the result is always going to be a Date object that represents "now". It doesn't matter if that comes from a Moment that's been set to a different time zone or not. It's the same moment in time.
moment().valueOf() // 1600198416842
moment.utc().valueOf() // 1600198416842
moment.tz('America/New_York').valueOf() // 1600198416842
new Date().valueOf() // 1600198416842
Date.now() // 1600198416842
If you want to see the time in a different time zone, you would need to use a function like format instead.
moment.utc().format() // "2020-09-15T19:33:36Z"
moment.tz('America/New_York').format() // "2020-09-15T15:33:36-04:00"
Moment is a legacy library. You should probably choose a different library, or perhaps no library at all. Please read: Moment Project Status

Related

moment.js mock local() so unit test runs consistently

I want to test the following piece of code. I am wondering if there is a way to mock moment.js or force it to think my current location is America/New_York so that my unit test doesn't fail in gitlab.ci runner which may be in various geographical locations?
const centralTimeStartOfDay = moment.tz('America/Chicago').startOf('day');
const startHour = centralTimeStartOfDay
.hour(7)
.local()
.hour();
Basically I want to hard code my timezone to be America/New_York and want this function to behave consistently.
Edit:
I tried:
Date.now = () => new Date("2020-06-21T12:21:27-04:00")
moment.tz.setDefault('America/New_York')
And still, I get the same result. I want to mock the current time so startHour returns a consistent value.
The problem
So there is no one line answer to this question. The problem is a fundamental one to javascript, where you can see dates in one of two ways:
UTC (getUTCHours(), getUTCMinutes() etc.)
local (i.e. system, getHours(), getMinutes() etc.)
And there is no specified way to set the effective system timezone, or even the UTC offset for that matter.
(Scan through the mdn Date reference or checkout the spec to get a feeling for just how unhelpful this all is.)
"But wait!" we cry, "isn't that why moment-timezone exists??"
Not exactly. moment and moment-timezone give much better / easier control over managing times in javascript, but even they have no way to know what the local timezone Date is using, and use other mechanisms to learn that. And this is a problem as follows.
Once you've got your head round the code you'll see that the moment .local() method (prototype declaration and implementation of setOffsetToLocal) of moment effectively does the following:
sets the UTC offset of the moment to 0
disables "UTC mode" by setting _isUTC to false.
The effect of disabling "UTC mode" is to mean that the majority of accessor methods are forwarded to the underlying Date object. E.g. .hours() eventually calls moment/get-set.js get() which looks like this:
export function get(mom, unit) {
return mom.isValid()
? mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]()
: NaN;
}
_d is the Date object that the moment (mom) is wrapping. So effectively for a non-UTC mode moment, moment.hours() is a passthrough to Date.prototype.getHours(). It doesn't matter what you've set with moment.tz.setDefault(), or if you've overridden Date.now(). Neither of those things are used.
Another thing...
You said:
Basically I want to hard code my time to be America/New_York and want this function behaves consistently
But actually, that is not generally possible. You are using Chicago, which I imagine has offset shifts in sync with New York, but e.g. the UK shifts at a different time from the US, so there are going to be weeks in the year where your test would fail if you were converting from a US timezone to a UK timezone.
The solutions.
But this is still frustrating, because I don't want my devs in Poland and the west coast of America to have breaking local tests because my CI server is running in UTC. So what can we do about it?
The first solution is a not-a-solution: find a different way of doing the thing you're doing! Generally the use cases for using .local() are quite limited, and are to display to a user the time in their current offset. It's not even their timezone because the local Date methods will only look at the current offset. So most of the time you'd only want to use it for the current time, or if you don't mind if it's wrong for half of the Date objects you use it for (for timezones using daylight savings). It could well be better to learn the timezone the user wants through other means, and not use .local() at all.
The second solution is also a not-a-solution: don't worry about your tests so much! The main thing with displaying a local time is that it works, you don't really care what it is exactly. Verify manually that it's displaying the correct time, and in your tests just verify that it returns a reasonable looking thing, without checking the specific time.
If you still want to proceed, this last solution at least makes your case work and a few others, and it's obvious what you need to do if you find you need to extend it. However, it's a complicated area and I make no guarantees that this will not have some unintended side-effects!
In your test setup file:
[
'Date',
'Day',
'FullYear',
'Hours',
'Minutes',
'Month',
'Seconds',
].forEach(
(prop) => {
Date.prototype[`get${prop}`] = function () {
return new Date(
this.getTime()
+ moment(this.getTime()).utcOffset() * 60000
)[`getUTC${prop}`]();
};
}
);
You should now be able to use moment.tz.setDefault() and using .local() should allow you to access the properties of the datetime as though it thought the local timezone was as configured in moment-timezone.
I thought about trying to patch moment instead, but it is a much more complicated beast than Date, and patching Date should be robust since it is the primitive.
try
// package.json
{
"scripts": {
"test": "TZ=EST jest"
}
}
Brilliant daphtdazz - thank you! To clarify for those who follow, this is the full solution I used to control the current date, timezone, and with daphtdazz's help - the local() behavior in moment:
import MockDate from 'mockdate';
const date = new Date('2000-01-01T02:00:00.000+02:00');
MockDate.set(date);
[
'Date',
'Day',
'FullYear',
'Hours',
'Minutes',
'Month',
'Seconds',
].forEach(
(prop) => {
Date.prototype[`get${prop}`] = function () {
return new Date(
this.getTime()
+ moment(this.getTime()).utcOffset() * 60000
)[`getUTC${prop}`]();
};
}
);
const moment = require.requireActual('moment-timezone');
jest.doMock('moment', () => {
moment.tz.setDefault('Africa/Maputo');
return moment;
});

How can i get the timezone value from a moment object?

I have a moment object like below
from which i want to extract the time zone value "PDT".
How can i get this value using moment?
i have tried
moment.tz(this.startDate.toString()).zoneAbbr();
but that returns a value "UTC"
Any suggestions?
I am Afraid you will get this only..
Since this is how the getZoneAbbr function works.
function getZoneAbbr () {
return this._isUTC ? 'UTC' : '';
}
Now, This is a problem. UTC is what one can use with timeOffest to determine which TimeZone you are lying in.
and only way to do it to make a list of Time zone names with their offset values, then compare which offset value you get.
then you can pick your time zone.
Even the normal JavaScript Date function do not have this support as in yet. you will have to create that offset and time zone name map yourself.
You might wan't to add the specific timezone you're in for the date that you parse like following:
moment.tz(moment.now(), 'America/New_York').zoneAbbr();
Moment had the z / zz format parameter to get abbreviated time zone name, but as the doc says:
Note: as of 1.6.0, the z/zz format tokens have been deprecated from plain moment objects. Read more about it here. However, they do work if you are using a specific time zone with the moment-timezone addon.
In the linked github issue, moment developer suggest to use moment.tz.guess() when creating moment object to have good chances of getting enviroment's timezone.
Here a working code example that you can use to get abbreviated time zone name:
var startDate = new Date();
var momObj = moment.tz(startDate, moment.tz.guess());
var zoneAbbr1 = momObj.zoneAbbr();
console.log(zoneAbbr1);
var zoneAbbr2 = momObj.format('z')
console.log(zoneAbbr2);
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.13.0/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.5/moment-timezone-with-data-2010-2020.min.js"></script>
Make sure you have installed moment-timezone with
npm install moment-timezone --save
After that
var moment1 = require('moment-timezone');
var myTime = moment1.tz('America/Denver').format('YYYY-MM-DD HH:mm:ss');
console.log(myTime);

What is the scope of moment.tz.setDefault() when used in Meteor?

TL;DR What is the scope of moment.tz.setDefault()?
I'm sure my problem here stems from my inexperience with both JavaScript and Meteor but I've been struggling with the problem for several straight days now.
I'm working on an app that must take into account the client's timezone but I'm having significant difficulty in forcing the server code to use the client's timezone. Somewhere along the way--that being from the moment the client presses "Submit" to the moment Meteor inserts--my timezone setting is getting lost and local time (of the server) is being used.
The app flow is like this:
(client) user submits form
(client) validation of data is performed
(server) Meteor method is called
(server) validation of data is performed (same code as earlier)
(server) business logic is applied
(server) insert into DB
I capture the timezone at step 1 and try to pass it along through all the steps but I must be missing something because between 4 and 5 the timezone is (seemingly) lost. The fast is, I'm not seeing why. I've checked this 100 times and tried all manner of different permutations but can't figure out where the gap is (I've used soooo many console.log()s it's crazy.)
So instead of trying to set the timezone at every point Moment() is used (because it defaults to calculating in local time) I discovered moment.tz.setDefault() and tried using that at least once on each .js file in my app. But it didn't work.
Reading this it might sound like I'm not doing enough testing but that is not the case. I have spent 10s of hours on this and I'm just not getting it. I'd love to share the code but I think it's just too long and complicated to properly share so I've done my best to explain the problem.
Good news! You're overcomplicating it :-)
Open up a browser console & type time = new Date(). Notice how it's in the correct timezone? That's because the timezone conversion is happening on the client.
Now, type time.valueOf(). As you probably know, you've got the number of milliseconds since 1-1-1970...but in what timezone?? You guessed it, UTC!
So if all you're doing is saving a number, and the client is fully capable of converting that number into the local timezone, why not save the time in UTC on the server? You'll get an ISODate() in your database (which is a fancy int64). Then, when you retrieve it on the client, you can put it in their local time (they might be traveling!) or any other timezone you chose. If it's a meetup in a certain city, simply grab the timezone of that city & apply it to the field. Hint: THIS is the appropriate time to use moment.js, not before!
Edit for time patterns:
Based on the new info, I imagine you have something that accepts an arrivalTime & then makes sure the time is between an earlyArrival and lateArrival say, 7:00 - 8:30AM. So, save the times as dates
timeToDate = function(time) {
return new Date('1970 1 1 ' + time);
};
earlyArrival = timeToDate('7:30 AM');
arrivalTime = timeToDate('8:00 AM');
lateArrival = timeToDate('8:30 AM');
Then, validate via simple math: earlyArrival < arrivalTime.
OR, if you use simple schema (which you should), a validation pattern might look like this:
departureTime: {
type: Date,
min: timeToDate('5:00 PM'),
max: timeToDate('6:30 PM'),
autoValue: function() {
return timeToDate(this.value);
},
custom: function () {
if (this.value < this.field('arrivalTime').value) {
return "lateAfterEarly";
}
}

Save the actual date in a variable (only date, no time)

I want to save the actual date in a variable. only the date, no time
var a = #Date(#Now());
datasource.replaceItemValue("variable", a)`
And
var a = #Date(#Now());
var b = new Date(a.getYear(), a.getMonth(), a.getDay());
datasource.replaceItemValue("variable", b)
Are returning 28.10.14 00:00
var dt:NotesDateTime = #Date(#Now());
datasource.replaceItemValue("variable", dt.getDateOnly());
Is throwing me an error
Isn't there a simple way to get only the actual date without the time?
Use setAnyTime() metohd of NotesDateTime class to remove time component.
If you want to save only the date use a textfield and convert the text to date, if you need it in your code
#Now uses a java.util.Date, which includes time portions. .getDateOnly() is probably throwing an error because that returns a String.
The underlying session.createDateTime() method accepts either text, a java.util.Date or a java.util.Calendar. I think all of them need to include a time element.
If you're only storing the value for reference, I'd agree with brso05 to not worry.
If you're ever likely to use #Adjust (or an equivalent), then not worrying about the time is a recipe for disaster, because every time you try to adjust, you need to remember to take into account Daylight Savings Time.
One option is to set the time to 12:00 midday. That means DST will not be a problem.
java.sql.Date is specifically designed to only include the Date portion, without a time element. Jesse Gallagher talks about java.sql.Date in the context of his frostillic.us framework https://frostillic.us/f.nsf/posts/32A63DD640D868D885257D18006659A9 and he was the one I found out about java.sql.Date from. I'm not sure how he stores those values though.
I'm not sure if the OpenNTF Domino API will allow you to just pass a java.sql.Date to a field and so store just the date portion.

updating a Date object on the client side after getting the correct Date from the server side

I have a UI that needs to have the UTC time updated all the time to be shown to the users.
my original code was this:
(function () {
$http.get('api/getdate').success(function (data) {
current = new Date(data.res + new Date().getTimezoneOffset()*60*1000);
updateDateTimer = setInterval(function(){
$http.get('api/getdate').success(function (data) {
current = new Date(data.res + new Date().getTimezoneOffset()*60*1000);
});
}, 30000);
});
})();
data.res has the correct timestamp i need. this way works fine because i can trust my server with the data that is sent back to the UI. then the only data that i need from the user is its offset from UTC and thus i am sure the date that will be displayed will be correct.
my problem is that a call to the server will be made every 30 seconds, and if i want my clock to change even more often then this can get really nasty.
i thought about making the call to the server only one time and then add 1 second with a timer to the Date object created but this is very not accurate and after a few minutes you can see that the clock is not synchronized any more with the real time.
is there any possibly to not make more calls to the server after the first time?
the problem is that i can't make sure the client won't change its local clock. it seems i need to some how deal with the Date object that is being created with the first .get call, but how?
thanks
Could you please clarify the question a bit? If the only thing you're trying to do is find/show the current UTC time, new Date().getTime() returns the millis since January 1, 1970 UTC so that time is already in UTC (2 calls of getTime() occurring simultaneously in New York and Los Angeles will show the same number). Then you have some UTC functions on the Date object which give you the UTC time, like getUTCHours(), getUTCMinutes and getUTCSeconds().

Categories

Resources