Divide the date range given by moment js by weeks - javascript

I want to divide a given date range into months. And I want to separate these months according to weeks. I want to get the date from the start date to the end of the month at 7-day intervals. For example: (Like 15/12/2020, 22/12/2020, 29/12/2020 -newMonth- 05/01/2020)
It is difficult to understand with this explanation. After all I want to create a json like this.
Starting Date: 15/11/2020 - End Date: 20/01/2021
[
{
"dates": [
15, // Starting date(15/11/2020)
22, // 1 week later
29 // 1 week later
],
"firstDay": "15/11/2020"
},
{
"dates": [
6,
13,
20,
27
],
"firstDay": "06/12/2020"
},
{
"dates": [
3,
10,
17
],
"firstDay": "03/01/2021"
}
]

There are a few tricks that you can use to create the array structure you want:
Use a while loop, which keeps a current date that is constantly incremented by 7 days, to be compared against the end date. The current date should be initialized using the start date in the beginning of the loop.
To perform the "group by month" in your desired structure, you will need to using an object (which serves as a dictionary). The object will use the month + year combination as key, so that if your date ranges span over multiple years you won't collapse months from different years into the same entry.
For each iteration of the while loop, you construct the key from the current date, and then check if the key exists in your dictionary. If not, then we can push this object into your dictionary:
{
dates: [currentDate],
firstDate: currentDate
}
If it already exists, then you simply push the date into the dates array of the sub-object.
Finally, to convert your dictionary into the array strcuture you wanted, using ES6 Object.values() will work. See proof-of-concept below:
function generateRanges(startDate, endDate) {
let current = moment(startDate, 'DD/MM/YYYY');
const end = moment(endDate, 'DD/MM/YYYY');
// A dictionary to track unique month+year combinations
const daysByMonth = {};
while (current < end) {
// Construct key based on month+year
const key = `${current.month()}${current.year()}`;
const date = current.date();
// If key already exists, then we push into the `dates` array
if (key in daysByMonth) {
daysByMonth[key].dates.push(date);
}
// Otherwise we construct a brand new sub-object
else {
daysByMonth[key] = {
dates: [date],
// Since this is the first time we encounter the key,
// We can assume this is the earliest/first date of the month
firstDate: current.format('DD/MM/YYYY')
}
}
// At the end of the while loop, increment by a week, rinse and repeat
current.add(7, 'days');
}
// Once done, we only want the values in the dictionary
// We don't need to keep the unique month+year key
return Object.values(daysByMonth);
}
const range = generateRanges('15/11/2020', '20/01/2021');
console.log(range);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>

The Moment.JS has an API for adding days to a date: the add function.
In order to build your final object, you would start from your startDate, and add 7 days for each loop iteration until reaching the end date. Like this:
let currentDate = moment(startDate);
while(currentDate < endDate) { // Make sure endDate is also a moment object
// Add the current date - you will want to adapt this
dates.push(currentDate.clone()); // Since .add() will mutate the currentDate object, clone the object when adding it to the array so that it's not affected by the add()
// Prepare for the next iteration
currentDate.add({days: 7});
}
As for month changes, remember the last month for every iteration (use month) and create a new object in your final array, then add dates to object instead of the previous one.
Side note: Months are zero-indexed in Javascript, which is not the case for the month day (date)

When dealing with date or datetime is always a good practice to use milliseconds. Also it is space efficient, a long integer = 4kb instead of a Date() object which is 32bytes.
In your case it is very straightforward to convert our dates to milliseconds, find how many weeks are between as integer and then run a loop increment by this iteration.
let from = new Date('2020/11/15').getTime();
let to = new Date('2021/01/20').getTime();
let week = 604800000;
let day = 86400000;
let allWeeks = [];
let current =0;
let weeks = (to-from)/day/7
for (i=0; i<weeks; i++){
allWeeks.push(new Date(from += week).toLocaleDateString())
}
console.log(JSON.stringify(allWeeks))
//["22/11/2020","29/11/2020","06/12/2020","13/12/2020","20/12/2020","27/12/2020","03/01/2021","10/01/2021","17/01/2021","24/01/2021"]
Finally you will have a list applicable for JSON to build any logic you prefer.
I hope to pin point a differed approach to your case!

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)));

Find closest next day from dates array, using Moment.js

I have an array:
dates = [
"2019-10-04T12:38:02.506204+00:00",
"2019-10-04T14:59:39.370487+00:00",
"2019-10-04T14:59:50.897597+00:00",
"2019-10-04T14:59:57.344401+00:00",
"2019-10-04T15:00:04.631711+00:00",
"2019-10-04T15:00:13.460667+00:00",
"2019-10-04T15:00:21.672496+00:00",
"2019-10-04T15:00:29.643194+00:00",
"2019-10-04T15:00:37.022307+00:00",
"2019-10-04T15:01:00.134239+00:00",
"2019-10-04T15:01:08.146224+00:00",
"2019-10-04T15:01:15.205999+00:00",
"2019-10-04T15:01:21.877861+00:00",
"2019-10-04T15:01:28.089368+00:00",
"2019-10-04T16:29:05.438103+00:00",
"2019-10-04T16:29:49.927139+00:00",
"2019-10-05T16:35:00.994699+00:00",
"2019-10-06T08:45:47.289637+00:00",
"2019-10-06T08:46:11.294362+00:00",
"2019-10-06T08:46:23.702585+00:00",
"2019-10-06T08:46:33.374726+00:00",
"2019-10-06T08:46:42.726666+00:00",
"2019-10-06T08:46:52.916887+00:00",
"2019-10-06T08:47:02.659273+00:00",
"2019-10-06T16:08:14.975139+00:00",
"2019-10-06T16:08:29.047735+00:00",
"2019-10-06T16:08:42.831487+00:00",
"2019-10-06T16:08:58.472631+00:00",
"2019-10-07T08:32:43.248706+00:00",
"2019-10-07T08:37:00.512249+00:00",
"2019-10-07T08:38:12.188744+00:00",
"2019-10-07T08:40:08.362515+00:00",
"2019-10-07T08:41:57.151322+00:00",
"2019-10-07T08:46:52.107963+00:00",
"2019-10-07T08:53:38.604697+00:00",
"2019-10-07T08:53:53.449463+00:00",
"2019-10-07T09:15:31.659223+00:00"
]
I need to make a switch with two buttons, the first will switch the dates forward, the second - back.
Dates are used from an array.
Help with JS please.
You can use the isBefore method on Moment.js objects. If you pass in the second argument as "day", then it would consider that:
"2019-10-04T12:38:02.506204+00:00" (4th of October) is before "2019-10-05T16:35:00.994699+00:00" (5th of October)
"2019-10-04T12:38:02.506204+00:00" (4th of October) is not before "2019-10-04T14:59:39.370487+00:00" (4th of October)
let dates = [
"2019-10-04T12:38:02.506204+00:00",
"2019-10-04T14:59:39.370487+00:00",
"2019-10-04T14:59:50.897597+00:00",
"2019-10-04T14:59:57.344401+00:00",
"2019-10-04T15:00:04.631711+00:00",
"2019-10-04T15:00:13.460667+00:00",
"2019-10-04T15:00:21.672496+00:00",
"2019-10-04T15:00:29.643194+00:00",
"2019-10-04T15:00:37.022307+00:00",
"2019-10-04T15:01:00.134239+00:00",
"2019-10-04T15:01:08.146224+00:00",
"2019-10-04T15:01:15.205999+00:00",
"2019-10-04T15:01:21.877861+00:00",
"2019-10-04T15:01:28.089368+00:00",
"2019-10-04T16:29:05.438103+00:00",
"2019-10-04T16:29:49.927139+00:00",
"2019-10-05T16:35:00.994699+00:00",
"2019-10-06T08:45:47.289637+00:00",
"2019-10-06T08:46:11.294362+00:00",
"2019-10-06T08:46:23.702585+00:00",
"2019-10-06T08:46:33.374726+00:00",
"2019-10-06T08:46:42.726666+00:00",
"2019-10-06T08:46:52.916887+00:00",
"2019-10-06T08:47:02.659273+00:00",
"2019-10-06T16:08:14.975139+00:00",
"2019-10-06T16:08:29.047735+00:00",
"2019-10-06T16:08:42.831487+00:00",
"2019-10-06T16:08:58.472631+00:00",
"2019-10-07T08:32:43.248706+00:00",
"2019-10-07T08:37:00.512249+00:00",
"2019-10-07T08:38:12.188744+00:00",
"2019-10-07T08:40:08.362515+00:00",
"2019-10-07T08:41:57.151322+00:00",
"2019-10-07T08:46:52.107963+00:00",
"2019-10-07T08:53:38.604697+00:00",
"2019-10-07T08:53:53.449463+00:00",
"2019-10-07T09:15:31.659223+00:00"
]
let currentDate = moment(dates[0]); //pick a spot for the "current time"
//sort the dates to ensure they are sequential
dates.sort();
let nextDate = dates.
filter(isoDate => currentDate.isBefore(isoDate, "day")) //filter anything before the current
[0]; //take the first (lowest) date
console.log(nextDate);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>

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.

JavaScript Smart Date Filtering

I have an array of some dates that have gaps in them. I want to be able to get the first day of each month in my dataset, given that gaps may fall on the 01 day of the month.
What is the best way to do this?
dates are in MM/DD/YYYY format.
dates = [10/31/2014, 11/03/2014, 11/04/2014, 11/05/2014, ...]
I want the result of the filter to be 11/03/2014, which is the first day of Nov 2014 that is contained in my data set. Weekends are omitted from the data set in this example.
But assuming my data set is large and continues with more gaps, I want to find the first day of every month.
function foo(list, month) {
var monthdates = list.filter(function(e,i,a) {
if(Number(e.split("/")[0]) == month)
return e;
});
var leastdateofmonth = monthdates.reduce(function(p,c,i,a) {
var pd = Number(p.split("/")[1]);
var cd = Number(c.split("/")[1]);
if(pd < cd)
return p;
else
return c;
});
console.log(leastdateofmonth);
}
//call it like this
foo(["10/31/2014", "11/03/2014", "11/04/2014", "11/05/2014"],11);
Test results:
[anupam#localhost ~]$ node sortmonth.js
11/03/2014
Call the function for each month and append it to a "leastdate" list. Here I called it for month 11.
Also consider, using a callback instead of returning, so that nothing gets blocked.

Categories

Resources