MomentJS is comparing dates differently on the server - javascript

I want to group the events by day. In the development environment (Brazil) it does the group correctly, but on the server (USA) is not. Its's putting the events that take place at the end of the day at the beginning of the next day. I imagine that is related to the timezone.
The dates of the events are in the following format:
2016-11-03T18:00:00-0200
Below is my code, I tried a few things to solve this problem but could not. I'm using MomentJS to handle dates.The events must be grouped by the day regardless of the timezone, offset or anything else.
const events = [];
_.forEach(ungroupedEvents, event => {
const date = moment(event.start_time, moment.ISO_8601);
const period = _.find(events, period => date.isSame(period.date, 'day'));
if (period) {
period.events.push(event);
} else {
events.push({
date: date.format('L'),
events: [event]
});
}
});
How can I achieve this? I am all day and it does not get any progress. :(

You can try with moment-timezone and verify at each step that dates are formatted as UTC and input/output correctly for the right timezone (UTC/GMT).
Or, and I think this is best, use getTime() to get MS since epoch, store and group using that big number (you can use mod % to get day since epoch).

Related

Moment js Date values comparison across time-zones

I am getting 4 dates as inputs mentioned below from an external source.
Dates with time element:
"InitialDate": "2019-02-19T12:03:22.129Z",
"updateDate": "2019-02-28T05:26:57.115Z",
Dates without time element:
"startDate": "2019-02-18",
"endDate": "2020-02-16",
I am coverting InitialDate and updateDate and creating actualInitDatE out of them using a moment format as below, as they are getting time element also in it.
I don't want time element and i only want date elements of all the 4 dates.
const actualInitDatE = moment(InitialDate).format('MM-DD-YYYY') ||
moment(updateDate).format('MM-DD-YYYY');
Now, I am converting the startDate and endDate which are having only date element in it (and no time element) and finally creating actualStartDate and actualEndDateW variables,
const actualStartDateW = moment(startDate).format('MM-DD-YYYY');
const actualEndDateW = moment(endDate).format('MM-DD-YYYY');
Now I am comparing them with the below logic and is working fine in IST,
if (actualInitDatE >= actualStartDateW && actualInitDatE <= actualEndDateW) {
console.log('Compared and True');
}
My Doubt is will this work correctly in UTC and other time zones as well? I am doubtful because some of the dates have time elements and some of them have only the date elements.
I have gone through this and implemented the approach. Is this approach is correct or do we need to use any offset?
javascript Date timezone issue
Can someone help me in this regard and let me know if this code works across timeZones?
I believe the core issue here is that you must specify a timezone for startDate and endDate. If you don't, moment.js will assume local time, for example IST or let's say you were in the US, Pacific time. The problem with this approach is that the code will give inconsistent results (depending on the machine).
You can demonstrate this by running the snippet below in your browser (Chrome is best) and changing your machine timezone. You'll see that parsing the startDate (and endDate) would result in different times depending on your timezone.
So the combination of a timestamp and a timezone give us a clear, unambiguous point in time for the most robust code. If we don't set a timezone when parsing the start and end date, the code could give a different result depending on the machine it is running on.
The best approach is to specify what timezone the startDate and endDate are in, e.g. are they in IST, or in UTC?
This way you can be sure your dates will parse consistently.
I would also suggest creating a function, say, parseDate that accepts a datestring, a format, and a timezone. This is makes all assumptions clear to anyone who reads the code.
There is no issue with InitialDate or updateDate, since they are specified as UTC times (the Z timezone specifier), so they are both clear and unambiguous.
const dates = {
startDate: "2019-02-18",
endDate: "2020-02-16"
}
const startDateNoTimezoneSpecified = moment(dates.startDate);
console.log("StartDate (No Timezone Specified):", startDateNoTimezoneSpecified.toISOString());
function parseDate(dateString, format, timezone) {
return moment.tz(dateString, format, timezone)
}
// Parse start date, assuming it is in IST (I'm assuimg IST refers to India Standard Time , if it's Israel Standard Time replace with Asia/Jerusalem!
console.log("Parse date result (IST):", parseDate(dates.startDate, "YYYY-MM-DD","Asia/Kolkata").toISOString());
console.log("Parse date result (UTC):", parseDate(dates.startDate, "YYYY-MM-DD","UTC").toISOString());
// You can also use moment.utc instead of moment.tz(date, "UTC").. it's simpler!
const startDateUTC = moment.utc(dates.startDate);
console.log("StartDate (UTC (moment.utc)):", startDateUTC.toISOString());
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
<script src="https://momentjs.com/downloads/moment-timezone-with-data-1970-2030.js"></script>
You seem to be over complicating things.
Your conversion of UTC timestamps to local dates is OK, but the format doesn't make sense. MM-DD-YYYY is pretty useless for anything, I'd suggest using ISO 8601 YYYY-MM-DD.
Date-only timestamps should be treated as local, so no conversion is necessary for the second two dates. Using ISO 8601 format, the strings can be compared directly:
let initialDate = '2019-02-19T12:03:22.129Z';
let updateDate = '2019-02-28T05:26:57.115Z';
// Get local date in required format
let actualInitDatE = moment(initialDate || updateDate).format('YYYY-MM-DD');
// Use these as they are
let startDate = '2019-02-18';
let endDate = '2020-02-16';
if (actualInitDatE >= startDate &&
actualInitDatE <= endDate) {
console.log('Compared and True');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
You can also keep the values as moment objects after setting them to the start of the day and use various moment methods for comparison, but I think the string version is pretty simple so why make it harder than it has to be?
Whether "this code works across timeZones" is unknown as you haven't explained what you are actually trying to achieve.

How to make Moment.js ignore the user's timezone?

I've got a form where I input an event that starts at a certain time. Let's say 9am.
To assign a date/time object I'm using MomentJs. The issue comes when displaying it in different time-zones.
In London will show up 9am as intended - in Kiev will show 11am.
How can I make MomentJS and the browser ignore which timezone is relevant for the user, and just displaying the time I'm giving?
Here's my code:
<p>
Start time:
{moment(event.startDate).format("HH:mm")}
</p>
Assuming you have stored the date as utc (which in this case you probably should have), you could use the following:
moment.utc(event.startDate).format("HH:mm")
Let me provide an alternative answer in Vanilla JavaScript. If you want to make it timezone 'neutral', you can first convert it to UTC using toISOString().
const current = new Date();
const utcCurrent = current.toISOString();
console.log(utcCurrent);
If you want to convert it to a specific timezone, such as London, you can use toLocaleString(). Do take note of the browser support for the timezone though.
const londonTime = new Date().toLocaleString('en-US', { timeZone: 'Europe/London' })
console.log(londonTime);
What you want is a normalized Datetime. This can get a little confusing since the concept of timezones is a rather arbitrary construct.
I like to think of Datetime values as "absolute" and "relative". An "absolute" Datetime is one that is true regardless of which timezone you're in. The most common example of these are UTC(+000) and UNIX Time (also known as Unix epoch, POSIX Time or Unix Timestampe).
UTC is pretty obvious. Its the current time at +000 timezone. UNIX time is a bit more interesting. It represents the number of seconds that have elapsed since January 1, 1970.
You should always store data, in both client and backend, as an "absolute" time. My preference is UNIX time since its represented as a single integer (nice and clean).
moment.js does this for you. When you instantiate your moment object, you can use:
var date = moment.utc(utcString)
or for Unix Time
var date = moment.unix(unixInt)
You can then use this object to display the date in any form you wish:
console.log(date.tz.("America/Toronto"))
The only way I could solve this is by removing the timezone and milliseconds info from the string. I used date-fns lib but I imagine moment will work the same way.
import { format } from 'date-fns'
const myDateTimeString = '2022-02-22T19:55:00.000+01:00'
const dateTimeWithoutTimezone = myDateTimeString.slice(0, 16) // <- 2022-02-22T19:55
format(new Date(dateTimeWithoutTimezone), 'HH:mm')

Moment time difference won't use moment timezone but users computer time

I'm attempting to display a duration ticker for something. The start time is always in London time. It works perfectly for people in England/the same timezone, however when people in other time zones look at the duration it displays the wrong value (If you're in a timezone behind England => negative values/too small values, timezone ahead => value too large).
My solution to this was to use moment-timezone. I added the moment timezone data correctly I've attempted to use this timezone data (code simplified and separated into individual lines for easier readability):
let londonTimeNow = moment().tz('Europe/London'),
jobStartTime = moment(job.start, 'DD/MM/YYYY HH:mm:ss'),
diff = londonTimeNow.diff(jobStartTime);
duration = moment.duration(diff).format('HH:mm:ss', {trim: false});
I was hoping this would then get the current time in London and compare to the start time no matter where you are in the world. However, it seems the diff function converts the time to the user's computer time. I tried formatting the londonTimeNow to be a string, but then the diff function doesn't work.
Note, I've debugged and moment().tz() is working correctly, I've tried with other time zones and it gets the correct time in the zone specified.
Any ideas?
EDIT:
It seems I can get it working by manually setting the offset property of 'londonTimeNow' to 0. However this doesn't feel quite right to me. I'd prefer a solution that seems less like a hack.
You should specify the jobstart time in the same way you declare london time using the same timezone:
jobStartTime = moment.tz(jobStart, 'DD/MM/YYYY HH:mm:ss','Europe/London'),
This will set the job start time using the same timezone.
Could you add expected output or specify what you mean by 'diff converts the time'?
The result of diff is a duration and is not in any timezone.
Other than that, the problem seems to be in not using timezone for task start.
Try this:
let londonTimeNow = moment().tz('Europe/London'),
jobStartTime = moment(job.start, 'DD/MM/YYYY HH:mm:ss').tz('Europe/London'),
diff = londonTimeNow.diff(jobStartTime);
duration = moment.duration(diff).format('HH:mm:ss', {trim: false});

how to convert javascript date according to a new timezone

I thought I had a handle on this, but I cant work it out.
scenario:
1) user selects a date widget which passes back a date in local timezone, lets say 10am 'Australia/Sydney'
2) user then selects a timezone that is different, by identifier 'Australia/Brisbane' (this is a different TZ and may have daylight saving etc...) lets assume its +1hr
What I want to do is have a function that takes a Date object that represents [10am 'Australia/Sydney'] and return to me a new Date that represents [10am 'Australia/Brisbane] i.e. the underlying UTC time will have shifted +1hr
function convertToTimezone(date, newTimezone) {
... what goes here? ...
return newDate;
}
Ive been mucking about with moment timezone and I cant get it to do what I want.
The moment-timezone library should make this trivial:
function convertToTimezone(date, newTimezone) {
return moment(date).tz(newTimezone);
}
Or if date is already a moment:
function convertToTimezone(date, newTimezone) {
return date.clone().tz(newTimezone);
}
See the documentation on Converting to Zone for more information.
OK, FWIW, I got an answer myself. moment.tz doesnt work as I imagined.
To summarise, I want to take a javascript Date that has a wallclock time, say '10am on the 15 sep 2018' that has been associated with a certain timezone identifier, say BrisabneOz.
And turn it into a new date that represents that same wallclock time, but in a different timezone. In otherwords, change the underlying UTC time by the amount required by the shift in timezones and/or daylight savings etc...
The way I found to do this was to get the string of the wallclock date, thus stripping any associated timezone from tbe equation, and using moment.tz to make another date object using the new different timezone. Which it can do.
The part that confused me was having to go to a string as a step - thought I could just pass in one date and get moment.tz to magic me up another date ala #Alex Taylor answer, but this doesnt actually work.
function convertDateToTimezone(date, timezone) {
const str = moment(date).format('YYYY-MM-DD HH:mm:ss');
const tzMoment = moment.tz(str, timezone.identifier)
return tzMoment.toDate()
}

fullcalendar confusion with UTC and local date

I do let fullcalendar initialize normally. So it represents current date. (Midnight->midnight, 1day, 1h slots)
From some other datasource I get data with timestamps. The format is "YYYY-MM-DD HH:mm" (transmitted as a string, no timezone information)
So I convert that string to a moment object and test against fullcalendar.start and .end to see if it is within.
moment("2016-04-07 00:00") == $('#calendar').fullCalendar('getView').end
This results in false though the following command
$('#calendar').fullCalendar('getView').end.format("YYYY-MM-DD HH:mm")
returns
"2016-04-07 00:00"
I also tried to compare with diff
moment("2016-04-07 00:00").diff( $('#calendar').fullCalendar('getView').end,"minutes")
which returns
120
Some research on the calendars.end object in Chrome Dev Tools revealed that it internally is represented as
2016-04-07 02:00 GMT+0200
This looks strange to me. I am in timezone 2h ahead of GMT. So it should correctly say 2016-04-07 00:00 GMT+0200, should it not?
This also explains why the diff test above resulted in 120 minutes.
Can someone help? I do not get where the conversion problem comes from. I am using only dates with no timezone information. And as said above, fullcalendar initalizes with no gotodate information and shows a time bar from 00:00 to 00:00. So why does it come that there is this 2h difference?
Thanks a lot. I do understand things a lot better now.
Some of the dates I tried to compare were 'now'. I got 'now' by
var n = moment()
That turned out to be a date time including my timezone.
E.g. moment().format() resulted in '2016-04-07 00:00 GMT+0200' and I now see how this went wrong excepting a comparison against full calendar.end to be true but it was false as '2016-04-07 00:00 GMT+0200' is '2016-04-06 22:00' at UTC.
As
moment.utc()
does not work, I know ended up with using
moment.utc(moment().format('YYYY-MM-DD HH:mm'))
This now seems to work as this treats my local time as it would be the 'numerical same time' at UTC.. thus matching with how fullcalendar handles times internally (ambiguously-zones moments).
Thanks
A few things:
The timezone parameter controls how FullCalendar works with time zones.
By default, FullCalendar uses "ambiguously-zoned moments". These are customizations to moment.js made within fullCalendar. The docs state:
The moment object has also been extended to represent a date with no specified timezone. Under the hood, these moments are represented in UTC-mode.
Thus, to compare dates in this mode, treat them as if they were in UTC.
moment.utc("2016-04-07 00:00")
To compare moments, use the moment query functions, isSame, isBefore, isAfter, isSameOrBefore, isSameOrAfter, and isBetween.
In this case, since FullCalendar's start is inclusive but the end date is exclusive, you probably want to compare like this:
var cal = $('#calendar').fullCalendar('getView');
var start = cal.start;
var end = cal.end;
var m = moment.utc("2016-04-07 00:00"); // your input
var between = m.isSameOrAfter(start) && m.isBefore(end);
Note that there's an pending enhancement to moment's isBetween functionality for a future release that will give you control of exclusivity, but currently isBetween is fully inclusive, so you have to use the combination of functions shown here.

Categories

Resources