MomentJS and JS Date objects not referring to the same hour - javascript

I've got a server instance (NodeJS) that receives a set of objects, and schedules them for sending push notifications to users.
Some of these objects, are periodic, and this periodicity is handled by a string like this:
90=>Mon&Tue&Thu=>16:00
Which is read as:
offset_minutes=>days_of_the_week=>initial_hour
Then, what I do is to check whether the current day matches one of the given days in the string, and then, modify the date to the given hour in the "initial_hour", and finally, substract the "offset_minutes" amount of minutes from the Date object.
Seems straightforward until now, right? Well, not that much. Let's first see the code:
const isToday = weekDays.split("&")
.map(a => {
switch (a) {
case 'Mon': return 1;
case 'Tue': return 2;
case 'Wed': return 3;
case 'Thu': return 4;
case 'Fri': return 5;
case 'Sat': return 6;
case 'Sun': return 7;
}
})
.some(v => v == currentDay);
if (isToday) {
let finalDate = moment(today)
.set("hour", Number(hour))
.set("minute", Number(mins));
if (offset) {
finalDate.subtract('minutes', Number(offset));
}
return finalDate.toDate();
Everything works well, until I do the MomentJS transformations. When I output a Date object with the ".toDate()" method, this object is always set to 2 hours before the expected time. But if I use the .toISOString() method, I get the proper time for all the occurrencies.
I guess that something is wrong with my Date objects, setting them up at a different timezone than the one I have. A couple of examples:
For the string 90=>Mon&Tue&Thu=>16:00 I get the Date object: 2019-10-14T14:00:11.852Z
For the string 30=>Mon&Tue&Wed&Thu&Fri&Sat&Sun=>18:30 I get the Date object: 2019-10-14T16:30:11.866Z
I would like to know what's the explanation for such a behavior, and if I can do something to change it so the normal Javascript Date object points to the same hour than my momentjs object, or the .toISOString() output.
Thank you!

The posted code is incomplete and doesn't demonstrate the issue described.
I've reimplemented the code without moment.js as best I can and simplified it. It seems to work fine:
function parseThing(s) {
// Parse input string
let b = s.split('=>');
let offset = +b[0];
let days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
let weekDays = b[1].split('&').map(day => days.indexOf(day));
let [hr, min] = b[2].split(':');
// Get a date for today
let date = new Date();
// If today included, return an adjusted date
if (weekDays.includes(date.getDay())) {
date.setHours(hr, min, 0, 0);
if (offset) {
date.setMinutes(date.getMinutes()+ Number(offset));
}
return date;
}
// If today isn't included, return null
return null;
}
let s0 = '90=>Mon&Tue&Thu=>16:00';
let s1 = '0=>Mon&Tue&Wed&Thu&Fri&Sat&Sun=>18:30';
console.log(parseThing(s0).toString());
console.log(parseThing(s1).toString());
Where the local day is one of those in the string (Mon, Tue, Thu) it returns a Date equivalent to a local time of 17:30, which is 90 minutes offset from 16:00, which seems to be correct.
PS I've changed Sunday to 0 as I can't see any rationale for it to be 7. Also seconds and milliseconds are zeroed too.

Related

Dates not incrementing correctly

I have a function that when called, it will add 7 days to the inputted date. The format for the date is YYYY-MM-DD (i.e. 2022-03-02)
for(let i = 0; i < repeatCount; i++)
{
date = addWeek(date);
}
function addWeek(date)
{
let temp = new Date(date);
//console.log("Old Date: ");
//console.log(temp.toISOString().split('T')[0])
//console.log(temp.getDate());
temp.setDate(temp.getDate() + 7);
console.log("New Date: ");
console.log(temp.toISOString().split('T')[0])
//console.log("returning date");
return temp.toISOString().split('T')[0];
}
For some reason, when the function is called as part of a repeat (React Web Application that involves recurring events), the addWeek function will not increment correctly a single time but then increment correctly the rest of the time.
Here's the input from my most recent log when I set repeatCount to 5:
Old Date:
2022-03-04
New Date:
2022-03-11
Old Date:
2022-03-11
New Date:
2022-03-17
Old Date:
2022-03-17
New Date:
2022-03-24
Old Date:
2022-03-24
New Date:
2022-03-31
Old Date:
2022-03-31
New Date:
2022-04-07
As you've probably noticed, it increments the week correctly with the exception of the second repeat. I've tested this multiple times with different dates, and each time, it is only the second iteration that is incremented incorrectly. Everything else works fine.
Please help. I'm losing my mind over this.
I forgot to add earlier: addWeek takes the date as a string input.
This is a Daylight Savings Time problem. Dates in JavaScript are inherently local, so when you use setDate() it tries to keep the same time of day on the new date, accounting for shifts in Daylight Savings Time. But that means the time of day will be different when compared to UTC time (which toISOString() converts to). The actual value of temp on the second output in your example is 2022-03-17T23:00Z, one hour before the date you were looking for. But your code strips off the time element so you end up one day off instead.
Instead of using setDate(), use the Date constructor:
temp = new Date(temp.getFullYear(), temp.getMonth(), temp.getDate() + 7);
var date = new Date('2022-03-02');
const repeatCount = 5;
for(let i = 0; i < repeatCount; i++)
{
date = addWeek(date);
}
function addWeek(date)
{
let temp = new Date(date);
temp = new Date(temp.getFullYear(), temp.getMonth(), temp.getDate() + 7);
console.log("New Date: ", temp);
console.log(temp.toISOString().split('T')[0])
//console.log("returning date");
return temp.toISOString().split('T')[0];
}
https://jsfiddle.net/4wm1vz9d/1/
function addWeek(date)
{
date.setDate(date.getDate()+7)
}
let date = new Date();
console.log(date)
for(let i = 0; i < 5; i++)
{
addWeek(date)
console.log(date)
}
Phew. Javascript is sometimes weird. Seems like the date get's inaccurate because of the conversion to a ISO String. Try returning the temp Date object instead of the string. Then convert it after adding the weeks. It may have something to do with the other values which the Object provides...

How to get k closest dates of a day of the week in JavaScript?

I'm trying to get the list of k closest dates from a day of the week but I don't know how. Can someone please give me a hint or suggestion? Thank you.
For example:
getDates(k, day) { .... } // k is number of dates (1,2,3 etc), day is day of the week ("monday", "tuesday" etc)
// today is 05/19/2020
getDates(1, "monday") // output: [05/25/2020]
getDates(2, "monday") // output: [05/25/2020, 06/01/2020]
I'm convinced that you have already figured out how to solve this question from all the comments. So I'll just post my answer to give my personal approach to this problem.
I'm pretty sure that it's very difficult to solve this without using:
1: day to number converter (sunday => 0, monday => 1, and so on...
This is because Date.prototype.getDay returns a number, not in the
word form)
and
2: date addition function (because there are couple of date addition
that we can't avoid (one for deciding the nearest day of the week, and another for creating new dates for the output array), it's better to just make it a function.)
My approach is (although not fully optimized):
1: First, convert the input day to integer form. This can be done in
few ways, but I simply created an array of dates, and used the
indexOf method to get the day number. But there is another neat way,
which is flexible to any region you live (application of this SOF link).
2: Then, get the nearest date from the current time with day day
(the while loop in the middle). I think it's very easy to read, but we
can use a one liner here. Which will look something like new_day.setDate(new_day.getDate() + (day_num - first_day.getDay() +
(7 * (new_day.getDay() > day_num))));. But I personally feel this is a
bit nasty, so I prefer the loop method.
3: Finally, create the array of dates. In my code, I first created the
empty array of k items (without fill(), the array will be [empty,
empty...] and will be not iterable), then map them to corresponding
dates from calculation.
There is actually a downfall to my method though. When I'm converting
the date object to the form MM/DD/YYYY, i'm using the
toLocaleDateString method. But apparently, using
toLocaleDateString() is risky (see the comment to this SOF
answer), which I'm not really sure why, but if so that would be a
problem.
Also, the output is 5/25/2020 while your expected output is
05/25/2020. It depends on your expectation, but it might be a
problem.
function addDays(date, day) {
let new_date = new Date(date.getTime());
new_date.setDate(new_date.getDate() + day);
return new_date;
}
function getDates(k, day) {
const day_num = ['sunday', 'monday', 'tuesday', 'wednesday' ,'thursday' ,'friday', 'saturday'].indexOf(day.toLowerCase()) // converting day name to number
let new_day = new Date();
while (new_day.getDay() !== day_num) {
new_day = addDays(new_day, 1)
}
return Array(k).fill().map((_, index) => addDays(new_day, index * 7).toLocaleDateString() )
}
// today is 05/19/2020
console.log(getDates(1, "monday")) // output: [05/25/2020]
console.log(getDates(2, "monday")) // output: [05/25/2020, 06/01/2020]
console.log(getDates(10, "monday"))
console.log(getDates(20, "monday")) // also for k > 10
Hope that helped, cheers! :)
As comments suggest, you can move the supplied date to the next instance of the required day, then keep adding 7 days until you get the number of dates you want.
In the following function, if start day is say Monday and the current day is also Monday, then the first date in the results array is the current day. If you want it to be the following week, change:
dateDay > idx?
to
dateDay >= idx?
I think the function should return an array of Dates, then the caller can format them as required.
/**
* Get array of count date stings from next dayName starting on or after today or d
* #param {number} count: number of dates to get (1,2,3 etc.)
* #param {string} dayName: name of day of week, min first 2 letters, any case ("Mon", "TU","we" etc)
* #param {Date} [d]: starting date
* #returns {string[]} Array of k date strings starting with next day, each +7 days from the previous date
*/
function getDates(count, dayName, d = new Date()) {
// Don't modify supplied date
d = new Date(+d);
// Get supplied day name as JS day index
let idx = ['su','mo','tu','we','th','fr','sa'].indexOf(dayName.toLowerCase().slice(0,2));
// Get index of d's day
let dayNum = d.getDay();
// Set to next instance of day, or today if current day == day
d.setDate(d.getDate() + idx - dayNum + (dayNum > idx? 7 : 0));
// Fill result array with required date strings
let result = [];
while (count-- > 0) result.push(d.toDateString()) && d.setDate(d.getDate() + 7);
return result;
}
// Examples
[[1, "Monday"],
[2, "TUES"],
[3, 'wed'],
[1, 'th'],
[3, 'fr'],
[2, 'sa'],
[1, 'su'],
[-20, 'fri'] // -ve count returns empty array
].forEach(args => console.log(getDates(...args)));

Compare two dates using valueOf()

const startDayOfTheWeek: number = moment().startOf('isoweek' as moment.unitOfTime.StartOf).valueOf();
if (this.card.dateScheduled.valueOf() < startDayOfTheWeek) {
this.card.dateScheduled = this.card.dateDue;
}
When using valueOf(), this.card.dateScheduled.valueOf() this gives me a value of the actual date. Not the millisecond relative to 1970 (a.k.a the Unix timestamp/epoch).
Why is that?
In moment.js there are many useful methods for comparing dates like isAfter, isBefore. So in your case use:
if (moment(this.card.dateScheduled).isBefore(startDayOfTheWeek))
I think you can take the benefits of inbuilt functionality.
Here is an example to compare dates in javascript.
const first = +new Date(); // current date
const last = +new Date(2014, 10, 10); // old date
console.log(first);
console.log(last);
// comparing the dates
console.log(first > last);
console.log(first < last);

Moment not adding minutes to object created from javascript Date

I have a method that accepts a javascript date with time as input, and determines if the current date and time is within -30 mins. However, when I debug this at runtime, moment.add doesn't seem to be working with minutes as expected.
function isWithinRange(myDate: Date){
// convert to Moment obj
let myMoment = moment(myDate);
let todayMoment = moment(new Date());
let myMomentOk = myMoment.isValid();
let todayOk = todayMoment.isValid();
// create range values
let preTime = myMoment.subtract('m', 30);
let postTime = myMoment.add('m', 30);
//check values are as expected
let localeTime = myDate.toLocaleString();]
let preLocale = preTime.toLocaleString();
let postLocale = postTime.toLocaleString();
let result = todayMoment.isBetween(preTime, postTime);
return result;
}
But when I inspect the localeTime, preLocale and postLocale times at run time, all three values are the same, "Tue Jun 26 2018 09:58:00 GMT-0400". The add and subtract minutes statements had no impact.
What am I missing or doing wrong here?
Please note that both add() and subtract mutate the original moment.
add():
Mutates the original moment by adding time.
subtract:
Mutates the original moment by subtracting time.
so you have to use clone()
Moreover, in the recent version of moment, the first argument is the amount of time to add/subtract and the second argument is the string that represent the key of what time you want to add
add and subtract takes the amount of time first, and then what type of time, as documented here. Also make sure to create a new moment object for each calculation, as it mutates the moment object.
let preTime = moment(myMoment).subtract(30, 'm');
let postTime = moment(myMoment).add(30, 'm');
You're working on the same moment object all the time, because of this you have the original moment object at the time you're doing let localeTime = myDate.toLocaleString().
You just need to create a new moment object so you don't revert your changes.
...
// create range values
let preTime = moment(myMoment).subtract('m', 30);
let postTime = moment(myMoment).add('m', 30);
...
I think what you need to use is https://momentjs.com/docs/#/query/is-between/ isBetween method from the moment.
const testDate = moment()
testDate.isBetween(moment().subtract(30, 'm'), moment().add(30, 'm'))
// true
const testDate = moment().add(2, 'h');
testDate.isBetween(moment().subtract(30, 'm'), moment().add(30, 'm'))
// false
I think this should help.

Comparing dates in Javascript without Timezones

I'm attempting to get the date based on user's local settings, subtract a day from it and then compare the value with another date value that I'm putting it.
I have tried it with moment.js and even date object of Javascript, using getTime() and then comparing the two dates, but none of them worked for me.
isSame was working when I tested it earlier, but for some reason it isn't working anymore. FYI the date I'm comparing it with(activityDate), I take it from a uploaded xlsx sheet using a upload widget. I need the comparison be timezone independent as user's locale can be anything.
Can anyone point me in the right direction?
Here's the code snippet:
var currentBusinessDate = new Date();
var previousBusinessDate = moment(currentBusinessDate).subtract(1, 'days').toDate();
var previousDate = $scope.getDateCellValueStr(previousBusinessDate, 'DD-MM-YYYY'); //Output: "13-11-2017"
if (moment(activityDate).isSame(previousDate, 'day')) {
uploadData.push(temp);
count = count + 1;
}
//activityDate: "13-11-2017"
//Result: condition returns False
A simple solution could be using setUTCHours: all the dates are going to be set to the same timezone (UTC) and then you will be able to compare them.
const compareWithoutTimezone(date1, date2) => {
const date1UTC = date1.setUTCHours(0, 0, 0, 0);
const date2UTC = date2.setUTCHours(0, 0, 0, 0);
return date1UTC === date2UTC;
}

Categories

Resources