Convert history of dates into date ranges in Javascript - javascript

In my application I have a mysql table where I have maintained the history of price alteration. I mapped the dates out of the history table and it looks like this:
['09-16-2019','09-25-2019','10-10-2019',...n]
Using this history, I want to create an array of date ranges so that I can easily figure out which client in my application registered between what date range. The format I want looks like :
let ranges = [
{start:'09-16-2019',end:'09-24-2019'},
{start:'09-25-2019',end:'10-09-2019'}
.,
.,
..n
]

You can use a simple Array.prototype.reduce() to create an object from the given dates array, like so:
let dates = ['09-16-2019', '09-25-2019', '10-10-2019', '18-10-2019'];
let ranges = dates.reduce((acc, val, i, arr) => {
if (i !== 0) acc.push({
start: arr[i - 1],
end: val
});
return acc;
}, []);
console.log(ranges);

Assuming from your example, what you have is list of start dates, you can iterate and figure out end date.
let x = ['09-16-2019','09-25-2019','10-10-2019']
let res = [];
for(let i = 0; i < x.length - 1; i++) {
let start = x[i];
let end = new Date(Date.parse(x[i+1]) - 86400000).toLocaleDateString("en",{year:"numeric",month:"numeric",day:"numeric"});
res.push({start, end})
}
console.log(res);
// [ { start: '09-16-2019', end: '9/24/2019' },
// { start: '09-25-2019', end: '10/9/2019' } ]

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)

linking two objects, value to value, and filling in any missing values in the sequence

I am trying to merge two objects into 1 Array, and they must be linked by their values. The objects are 'Days' and 'Count' which is the count of the number of events that happen on a day.
Example Data
{"Day":[3,8,9,17,18,21,25,27,30,31],
"Count":[1,3,1,1,1,4,2,2,2,1]}
I can do that with this function:
var data = {{loanDates.data}}; // this query has two objects, 'Day' and 'Count' which is the sum of the number of events that has happened on each day.
return data.Day.map(function(day, key){
var count = test.Count[key];
return {
day,count
}
});
result
[{"day":3,"count":1},
{"day":8,"count":3},
{"day":9,"count":1},
{"day":17,"count":1},
{"day":18,"count":1},
{"day":21,"count":4},
{"day":25,"count":2},
{"day":27,"count":2},
{"day":30,"count":2},
{"day":31,"count":1}]
This returns the correct things, however i want all the days of the month. So for instance say the 10th day of the month doesn't exist in the object, i need a 10th day created and its count set to 0.
Outcome required
[{"day":1,"count":0},
{"day":2,"count":0},
{"day":3,"count":1},
{"day":4,"count":0},
{"day":5,"count":0},
{"day":6,"count":0},
{"day":7,"count":0},
{"day":8,"count":3},
{"day":9,"count":1},
{"day":10,"count":0},
{..................},
{"day":31,"count":0},]
I'd make an object mapping each Day to its Count from the input data, then give it keys 1-31 if it doesn't have them already. Afterwards, you can use Object.entries to map each entry to a value in the array:
const input = {
"Day":[3,8,9,17,18,21,25,27,30,31],
"Count":[1,3,1,1,1,4,2,2,2,1]
};
const countsByDay = {};
input.Day.forEach((day, i) => {
countsByDay[day] = input.Count[i];
});
for (let i = 0; i <= 31; i++) {
countsByDay[i] = countsByDay[i] || 0;
}
const output = Object.entries(countsByDay).map(
([day, count]) => ({ day, count })
);
console.log(output);
Use Array.from() to generate an array of days with count: 0, spread to an array, and override with the array of days that has count values:
const data = {
"Day":[3,8,9,17,18,21,25,27,30,31],
"Count":[1,3,1,1,1,4,2,2,2,1]
}
const result = [
...Array.from({ length: 31 }, (_, i) => ({ day: i + 1, count: 0 })),
...data.Day.map((day, i) => ({ day, count: data.Count[i] }))
]
console.log(result)

How to compare consecutive date/time items in array and filter based on a specific time

I have the following array of objects:
var transactions = [
[
{"id":1,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:33:00.000Z"},
{"id":2,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:33:50.000Z"},
{"id":3,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:34:30.000Z"},
{"id":4,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:36:00.000Z"}
],
[
{"id":5,"sourceAccount":"A","targetAccount":"C","amount":250,"category":"other","time":"2018-03-02T10:33:00.000Z"},
{"id":6,"sourceAccount":"A","targetAccount":"C","amount":250,"category":"other","time":"2018-03-02T10:33:05.000Z"}
]
]
I need to compare time property of each object consecutively, and keep only those properties which time difference between each consecutive transaction is less than 1 minute.
The array format should be stay same, this is what I did try, but no luck, didn't work. What's the problem?
var newArray = transactions.map(g => g.reduce((r, o, i, a) => {
if (!i || new Date(o.time).getTime() - new Date(a[i - 1].time).getTime() >= 60000) {
r.push([o]);
} else {
r[r.length - 1].push(o);
}
return r;
}, []));
The expected output is something like this :
var output = [
[
{"id":1,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:33:00.000Z"},
{"id":2,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:33:50.000Z"},
{"id":3,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:34:30.000Z"}
],
[
{"id":5,"sourceAccount":"A","targetAccount":"C","amount":250,"category":"other","time":"2018-03-02T10:33:00.000Z"},
{"id":6,"sourceAccount":"A","targetAccount":"C","amount":250,"category":"other","time":"2018-03-02T10:33:05.000Z"}
]
]
You can Array#map your source array, and in each iteration, Array#filter the desired elements by comparing the time of current element with the time of previous element.
var transactions = [[{"id":1,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:33:00.000Z"},{"id":2,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:33:50.000Z"},{"id":3,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:34:30.000Z"},{"id":4,"sourceAccount":"A","targetAccount":"B","amount":100,"category":"food","time":"2018-03-02T10:36:00.000Z"}],[{"id":5,"sourceAccount":"A","targetAccount":"C","amount":250,"category":"other","time":"2018-03-02T10:33:00.000Z"},{"id":6,"sourceAccount":"A","targetAccount":"C","amount":250,"category":"other","time":"2018-03-02T10:33:05.000Z"}]];
var result = transactions.map((tr, i) => {
return tr.filter((t, j) => {
if (transactions[i][j - 1]) {
var d1 = new Date(t.time);
var d2 = new Date(transactions[i][j - 1].time);
return (d1.getTime() - d2.getTime()) <= 60000;
}
return true;
});
});
console.log(result);

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

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