Elements in the date array update automatically - javascript

I'm trying to find week days between first date and second date (exclusive first and second). In the array that contains the weekday dates, the values get increment after line 1 in executed, which is unexpected.
private getListOfWeekDaysBetweenTwoDates(
startDate: Date,
endDate: Date
): Date[] {
const weekdaysList = [];
let tempDate = this.addDaystoGivenDate(startDate, 1);
while (tempDate < endDate) {
if (this.isWeekDay(tempDate)) {
weekdaysList.push(tempDate);
}
//line 1
tempDate = this.addDaystoGivenDate(tempDate, 1);
}
return weekdaysList;
}
private addDaystoGivenDate(date: Date, numberOfDays: number): Date {
return new Date(date.setUTCDate(date.getUTCDate() + numberOfDays));
}
private isWeekDay(day: Date): boolean {
if (day.getUTCDay() >= 1 && day.getUTCDay() <= 5) {
return true;
}
return false;
}
How do I stop array values changing?

Isolating the problem, you're changing the date after inserting it to the array, but what you're inserting is a reference to the date:
let d = new Date()
d.setUTCDate(1)
const arr = []
arr.push(d)
console.log(arr)
d.setUTCDate(10)
console.log(arr) //date in arr changes from 1 to 10
That's because you're calling date.setUTCDate() over tempDate.
There is more than one way to solve it, but for example you could store not the reference to tempDate but a completely new Date object:
weekdaysList.push(tempDate); => weekdaysList.push(new Date(tempDate));
In the simplified example:
let d = new Date()
const arr = []
d.setUTCDate(1)
arr.push(new Date(d))
console.log(arr)
d.setUTCDate(10)
console.log(arr) //date in arr keeps 1

Related

JavaScript array of dates to array of ranges of dates

I have a JavaScript array of dates (as strings) like the following:
["2020-07-24T04:00:00.000Z", "2020-07-25T04:00:00.000Z", "2020-07-26T04:00:00.000Z", "2020-07-27T04:00:00.000Z", "2020-07-28T04:00:00.000Z", "2020-07-29T04:00:00.000Z", "2020-07-30T04:00:00.000Z", "2020-07-31T04:00:00.000Z", "2020-08-01T04:00:00.000Z", "2020-11-29T05:00:00.000Z", "2020-12-30T05:00:00.000Z", "2020-12-31T05:00:00.000Z", "2021-01-01T05:00:00.000Z", "2021-01-02T05:00:00.000Z", "2021-02-18T05:00:00.000Z"]
I want to convert this into an array of arrays of [first, last] contiguous date ranges, e.g., as below:
[["2020-07-24T04:00:00.000Z", "2020-08-01T04:00:00.000Z"], ["2020-11-29T05:00:00.000Z"], ["2020-12-30T05:00:00.000Z", "2021-01-02T05:00:00.000Z"], []]
How do I do this? Code attempt below:
var ranges = [];
for (var i = 0; i < popNull.length; i++) {
let currentRange = [];
let current = new Date(popNull[i]);
let tomorrow = new Date(current.getTime() + (24 * 60 * 60 * 1000));
let next = new Date(popNull[i+1]);
if (next === tomorrow) {
}
else {
}
}
I've made a couple of assumptions in the code below
That the dates are pre-sorted in ascending date order
That "contiguous" means less than or equal to 24 hours.
All dates are formatted in a way that can be passed directly to the Date constructor on the platform of choice.
const input = ["2020-07-24T04:00:00.000Z", "2020-07-25T04:00:00.000Z", "2020-07-26T04:00:00.000Z", "2020-07-27T04:00:00.000Z", "2020-07-28T04:00:00.000Z", "2020-07-29T04:00:00.000Z", "2020-07-30T04:00:00.000Z", "2020-07-31T04:00:00.000Z", "2020-08-01T04:00:00.000Z", "2020-11-29T05:00:00.000Z", "2020-12-30T05:00:00.000Z", "2020-12-31T05:00:00.000Z", "2021-01-01T05:00:00.000Z", "2021-01-02T05:00:00.000Z", "2021-02-18T05:00:00.000Z"].map(x => new Date(x));
let aggregation = input.reduce( (acc,i) => {
if(acc.prev){
const diffInHrs = (i - acc.prev)/1000/60/60;
if(diffInHrs <= 24){
acc.result[acc.result.length-1][1] = i;
}
else{
acc.result.push([i])
}
acc.prev = i;
return acc;
}
else{
return {prev:i, result:[[i]]}
}
},{});
console.log(aggregation.result)
You can reduce the dates by keeoing track of the latest and checking the current with the previous. You can diff their epoch valyes and check if they are within a day.
const dates = ["2020-07-24T04:00:00.000Z", "2020-07-25T04:00:00.000Z", "2020-07-26T04:00:00.000Z", "2020-07-27T04:00:00.000Z", "2020-07-28T04:00:00.000Z", "2020-07-29T04:00:00.000Z", "2020-07-30T04:00:00.000Z", "2020-07-31T04:00:00.000Z", "2020-08-01T04:00:00.000Z", "2020-11-29T05:00:00.000Z", "2020-12-30T05:00:00.000Z", "2020-12-31T05:00:00.000Z", "2021-01-01T05:00:00.000Z", "2021-01-02T05:00:00.000Z", "2021-02-18T05:00:00.000Z"];
const DAY_MILLIS = 8.64e7;
const ranges = dates
.reduce((acc, dateStr, index, all) => {
const dateObj = new Date(dateStr);
if (acc.length === 0) {
acc.push({ start: dateObj, prev: dateObj });
} else {
let last = acc[acc.length - 1];
const { start, prev } = last;
if (dateObj.getTime() - prev.getTime() <= DAY_MILLIS) {
last.prev = dateObj;
} else {
last.end = prev;
acc.push({ start: dateObj, prev: dateObj });
}
if (index === all.length - 1) {
last = acc[acc.length - 1];
if (last.end == null) {
last.end = last.prev;
}
}
}
return acc;
}, [])
.map(({ start, prev, end }) =>
((startStr, endStr) =>
startStr !== endStr ? [startStr, endStr] : [startStr])
(start.toISOString(), end.toISOString()));
console.log(ranges);
.as-console-wrapper { top: 0; max-height: 100% !important; }
Output
[
[ "2020-07-24T04:00:00.000Z", "2020-08-01T04:00:00.000Z" ],
[ "2020-11-29T05:00:00.000Z" ],
[ "2020-12-30T05:00:00.000Z", "2021-01-02T05:00:00.000Z" ],
[ "2021-02-18T05:00:00.000Z" ]
]
You can do the following using Array#reduce():
Go through each date.
Check if the current date will extend last range.
if yes, then overwrite the end in the range pair (second element)
if no, start a new range
If it happens that a range only has a single date, then use the start to compare with. The logic still holds - extending the range will add a second date. If the new date is not within the desired time frame, then a new range is created and the previous range is left with a single element in it.
const areDatesWithin = ms => (str1, str2) => {
if (!str1 || !str2)
return false;
const date1 = new Date(str1);
const date2 = new Date(str2);
return (date2 - date1) <= ms;
}
const areDatesWithin1Day = areDatesWithin(1000 * 60 * 60 * 24);
function combineInRanges(dates) {
return dates.reduce((acc, nextDate) => {
const lastDateRange = acc[acc.length-1] ?? [];
//compare with range end (if there) or range start
const lastDate = lastDateRange[1] ?? lastDateRange[0];
//check if the range needs to be extended
const mergeWithRange = areDatesWithin1Day(lastDate, nextDate);
if (mergeWithRange) {
//change the end of the range
lastDateRange[1] = nextDate;
} else {
//start a new range
acc.push([nextDate]);
}
return acc;
}, []);
}
const arr = ["2020-07-24T04:00:00.000Z", "2020-07-25T04:00:00.000Z", "2020-07-26T04:00:00.000Z", "2020-07-27T04:00:00.000Z", "2020-07-28T04:00:00.000Z", "2020-07-29T04:00:00.000Z", "2020-07-30T04:00:00.000Z", "2020-07-31T04:00:00.000Z", "2020-08-01T04:00:00.000Z", "2020-11-29T05:00:00.000Z", "2020-12-30T05:00:00.000Z", "2020-12-31T05:00:00.000Z", "2021-01-01T05:00:00.000Z", "2021-01-02T05:00:00.000Z", "2021-02-18T05:00:00.000Z"];
console.log(combineInRanges(arr));
https://stackoverflow.com/a/67182108/20667780
Jamiec answer is working. If you have a date array with UTC dates correctly offsetted to local timezone, then the daylight save start/end date will have more than 24 hours. You have to change the diffInHrs to 25 instead of 24.
Otherwise, its a perfect answer.
It's a sort of reduction based on the even-ness of the index...
let array = ['a', 'b', 'c', 'd', 'e', 'f'];
let pairs = array.reduce((acc, el, idx) => {
idx % 2 ? acc[acc.length-1].push(el) : acc.push([el]);
return acc;
}, []);
console.log(pairs)

Check if each item in an array is today's date?

I have an array of objects. Each object has a date property. I am trying to create a function where I append every item to a new array if the item's date is equal to today's date.
Also, I am not sure if the for loop is the most efficient approach for this, this list will never be more than a few hundred items though.
My function:
todayListItems() {
const todayItems = [];
const todayDate = moment(new Date()).format('dd-mm-YYYY');
for (let i = 0; i < myArray.length; i++) {
const itemDate = moment(myArray[i].date).format('dd-mm-YYYY');
if (itemDate === todayDate) {
todayItems.push(myArray[i]);
}
}
console.log(todayItems);
return todayItems;
}
This function runs but even if there is an item with today's date nothing will be pushed to the array.
You can use Array.filter() and moment#isSame with the second parameter to limit the granularity to a unit other than milliseconds:
function todayListItems(arr) {
return arr.filter(obj => moment().isSame(obj.date, 'day'));
}
todayListItems(myArray);
Demo:
function todayListItems(arr) {
return arr.filter(obj => moment().isSame(obj.date, 'day'));
}
const myArray = [
{ date: '2019-10-14T12:10:00Z'},
{ date: new Date() },
{ date: moment().minutes(120).format('YYYY-MM-DDTHH:mm:ss') },
{ date: new Date('2019-10-23T01:00:00') },
];
console.log(todayListItems(myArray));
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
You're formatting wrongly. mm is minutes. The formatting should be DD-MM-YYYY, see https://momentjs.com/docs/#/displaying/format/

JS: Get objects from array within a time range

There is an object array like this:
[
{
timestamp: 1318781876
any: 'other fields'
}
]
Of course there are multiple objects in that array. I am using momentJS - if it matters here...
Now I need to split that array into months. That means I need to get all objects for 'july' to display them in a table.
Is this possible at all or should I change the datastructure? I thought using timestamp is the best option, as I could calculate everything from this.
But now I'm thinking if I have to add month and year field to the object...
You could iterate the array and build up a tree, may also write year and month to the objects:
var map={};
array.forEach(function(obj){
var d = new Date(obj.timestamp*1000);
var m = obj.month = d.getMonth() +1;
var y = obj.year = d.getFullYear();
if(!map[y]) map[y]={};
if(!map[y][m]) map[y][m]=[];
map[y][m].push(obj);
});
So now weve got a map like this:
map: {
2017 : {
8 : [
{
timestamp:123456,
month:8,
year:2017,
any:"other value"
}
]
}
}
So you can now get all julys by:
map[2017][7]
It depends if you just do this once, then other answers will be easier, but if you need different timeranges the upper code just needs to iterate once, and you can get the filtered results easily. To get sorted results:
var sorted=Object.keys(map)/*the years*/ .sort().map(function(year){
return { year, months: Object.keys(map[year]).sort().map(function(month){
return {month,results:map[year][month]};
})
};
});
These arrays may already be built up while building the hash table, see ninas way of doing this
This can be done using array.filter
myArr = [
{
timestamp: 1318781876
any: 'other fields'
}
...
];
var filteredArray = myArr.filter(function(item) {
return (item.timestamp > minOfDateRange && item.timestamp < maxOfDateRange);
});
Your structure is quite useful. You can use Array.filter:
const startDate = new Date(2017, 6, 1); // 6 for July
const endDate = new Date(2017, 7, 1);
const selectedData = data.filter(entry => startDate <= entry.timestamp * 1000 && entry.timestamp * 1000 < endDate)
Convert the milisecond to a date using following code and get the month afterward
var date = new Date(milliseconds);
var month = date.getMonth();
then put the july object to another array and display them as you wish
You can use a hashmap approach.
var arr = [
...
{
timestamp: 1318781876
any: 'other fields'
}
...
];
var grouped = {};
var months = ['Jan', 'Feb' ...];
arr.forEach( function(item){
var dateObj = moment.unix(item.timestamp);
var month = months[dateObj.month()];
if(!grouped[month]){
grouped[month] = [];
}
grouped[month].push(item);
});
console.log(grouped);

What would be the best way to sort events according to its date

I have array with a few hundred events where every has date entry (in Date().getTime format in DB) and I have 10 days displayed in simple table. To every day-row I need to display number of events in that particular day. What would be the best way to do that?
Okay here is an update:
I've Firebase db with this structure:
{
events: {
event1: {
date: 144335265211,
title: "sometext",
text: "longer text"
},
event2: {
date: 1444482619766,
title: "sometext",
text: "longer text"
}
}
}
I am pulling it into local Redux store so I have an JS array with the same structure.
This way I am creating the table for particular days:
for (let dc = 0; dc < 10; dc++) {
const date = new Date();
date.setDate(date.getDate() + dc);
const m = date.getMonth();
const d = date.getDay();
const y = date.getUTCFullYear();
rows.push(
<td>"On the " + m + "/" + d + "/" + y + " is SOMENUMBER events"</td>
);
return rows;
}
Thanks a lot!
First you need to convert it to an array:
var events = Object.keys(obj).map((key) => {
return Object.assign({}, obj[key], {eventName: key});
});
Then you need to group them by date. We can do this with lodash.
var groupBy = require('lodash/collection/groupBy');
var eventsWithDay = events.map((event) => {
return Object.assign({}, event, {day: new Date(event.date).setHours(0, 0, 0, 0))
});
var byDay = groupBy(eventsWithDay, 'day');
And byDay will be an object with a key for each date with a value of an array of events for that day.

Counting array elements with specific date in javascript

I have an array of Date() objects in javascript and I want to count the number of events on each day.
Here is an example:
What I have is:
Array [ Date 2014-12-04T10:30:20.000Z, Date 2014-12-05T11:04:58.056Z, Date 2014-12-05T11:04:58.056Z, Date 2014-12-05T11:04:58.056Z ]
What I want is:
Array [{date: '2014-12-04', counts: 1}, {date: '2014-12-05', counts: 3}]
Thanks a lot!
Max
Basic answer:
var arr = [], // fill it with array with your data
results = {}, rarr = [], i, date;
for (i=0; i<arr.length; i++) {
// get the date
date = [arr[i].getFullYear(),arr[i].getMonth(),arr[i].getDate()].join("-");
results[date] = results[date] || 0;
results[date]++;
}
// you can always convert it into an array of objects, if you must
for (i in results) {
if (results.hasOwnProperty(i)) {
rarr.push({date:i,counts:results[i]});
}
}
These can be made much easier with lodash functions, and Array.forEach() in ES5
You much better off having a simple object with the keys as the date and the value as the count. I've added a simple pad function that prefixes a zero where the number is a single digit as per your output requirements.
function pad(n) {
return n.toString().length == 1 ? '0' + n : n;
}
function getCount(arr) {
var obj = {};
for (var i = 0, l = arr.length; i < l; i++) {
var thisDate = arr[i];
var day = pad(thisDate.getDate());
var month = pad(thisDate.getMonth() + 1);
var year = thisDate.getFullYear();
var key = [year, day, month].join('-');
obj[key] = obj[key] || 0;
obj[key]++;
}
return obj;
}
getCount(arr); // Object { 2014-04-12: 1, 2014-05-12: 3 }
DEMO
I came across the same issue and found this solution which uses Map()
`
calc = (obj) => {
const orders = []
const dates_map = new Map()
//iterate through all the objects inside the orders array
orders.forEach(order => {
// format and get the date
const date = new Date(order.created_at).toLocaleDateString('en-GB')
//check if the date key exists in the Map() and save it in a temp
const temp = dates_map.get(date) || false
// if it does not exist
if (temp) {
// clone the object
const previous = {...temp}
// increase counter
previous.count += 1
dates_map.set(date, previous)
}else{
//create new object to avoid overwriting
const result = {}
result.count = 1
dates_map.set(date, result)
}
})
console.log(dates_map)
}
And this is the output
Output: Map(3) {
'08/05/2021' => { count: 2 },
'09/05/2021' => { count: 1 },
'11/05/2021' => { count: 2,}
}
`

Categories