4

Let say we have a collection like this in Javascript:

[
    { date: 'Fri, 02 May 2014 19:05:00 GMT', value:'abc' },
    { date: 'Fri, 02 May 2014 23:43:00 GMT', value:'jkl' },
    { date: 'Fri, 02 May 2014 19:01:00 GMT', value:'def' },
    { date: 'Fri, 02 May 2014 19:09:00 GMT', value:'ghi' },
    { date: 'Fri, 02 May 2014 23:54:00 GMT', value:'mno' }
]

I would like to to find an elegant algorithm to group by this array by "closest" date. If a date is 15 minutes before or after a previous date, it will be pushed in the same object.

I doesn't really matter how the sub-array will be structured. The result for this entry could be:

[
    [
        { date: 'Fri, 02 May 2014 19:05:00 GMT', value:'abc' },
        { date: 'Fri, 02 May 2014 19:01:00 GMT', value:'def' },
        { date: 'Fri, 02 May 2014 19:09:00 GMT', value:'ghi' }
    ], [
        { date: 'Fri, 02 May 2014 23:43:00 GMT', value:'jkl' },
        { date: 'Fri, 02 May 2014 23:54:00 GMT', value:'mno' }
    ]
]

I have try without real success using underscore.js:

_.map(logs_by_date, function(group, date) {
    return _.reduce(group, function(memo, elem) {
        var moment = get_moment(elem);
        memo[moment].push(elem);
        return memo;
    }, { 'date': date});
});
ekad
  • 14,436
  • 26
  • 44
  • 46
Stéphan Lascar
  • 175
  • 1
  • 5
  • I'm a fan of underscore for work like this. They have a `groupBy` function that should help. http://underscorejs.org/#groupBy – chris vdp May 30 '14 at 22:08
  • The question is a little bit ambiguous. Do the dates `19:00:00`, `19:10:00` and `19:20:00` all belong to the same group, or should the interval between the first and the last date of a group be less than 15 minutes? – Elian Ebbing May 30 '14 at 22:11
  • The first and the last date of a group be less than 15 minutes. – Stéphan Lascar May 30 '14 at 22:13
  • 1
    @StéphanLascar: When does time *start*? Is it every 15 minutes from the earliest time in the list, or 15 minutes from the top of the hour, or something different? – Cᴏʀʏ May 30 '14 at 22:15
  • @Cory: The time start from the earliest time in the list. – Stéphan Lascar May 30 '14 at 22:17
  • 1
    @StéphanLascar In that case, do you realize that the answer is not unique? Again, if you look at the dates `19:00:00`, `19:10:00` and `19:20:00`, how would you group them? Is the order in which the dates are provided relevant? – Elian Ebbing May 30 '14 at 22:18
  • 1
    You should be able to figure this out with a little effort. Here's a really verbose example to get you started -> **http://jsfiddle.net/PM3v9/1/** – adeneo May 30 '14 at 22:20
  • @adeneo: That's pretty nice. – Cᴏʀʏ May 30 '14 at 22:23
  • 1
    It's just an example of how to do this, which is why I didn't post it as an answer, it's not really clear to me what the sorting rules should be, but this will give you the result you wanted for that particular data, but what should happen if you have ten times with five minute intervals, should all of them end up in the same array or should some sort of initial time be used etc. It's all very unclear ! – adeneo May 30 '14 at 22:27
  • possibly relevant, by year instead of by 15 min interval: http://stackoverflow.com/questions/9271710/split-array-of-objects-into-new-arrays-based-on-year-of-objects-date – Paul May 30 '14 at 23:31

2 Answers2

7

Starting with the UnderscoreJS code from tjdett for a group-by-year question:

var dateGroups = _.chain(objarray)
                  .groupBy(function(obj) { return obj.date.getFullYear(); })
                  .sortBy(function(v, k) { return k; })
                  .value();

You could try this same solution with a groupBy function designed for 15 minute intervals instead of years, with return Math.floor(+(obj.date)/(1000*60*15)); This return statement uses + to convert the Date object to a number of milliseconds (since epoch), and then divides by 1000*60*15 for 15 minute intervals with Math.floor() discarding the fraction.

For that to work, obj.date must be type Date. If your dates are just strings, you may first need to parse the year, month, day, hour, minutes out of those strings and then construct a new Date object.

This will create absolute 15 minute clock blocks, i.e. 01:00:00-01:14:59.9999, 01:15:00-01:29:59.9999; not 15 minutes that begin with new activity.

So if you want a group of 15 minutes of data that starts with new data, the groupBy function would need to be created with a closure retaining state of when the current group ends so that new groups could be started, and it would need to be fed from objects sorted by date, so that a sort needs to happen first.

That sounds Rube-Goldbergish, and might be easier to do directly like this (untested):

fixDates(objArray); // a function you'll write to turn your date strings into Date objects
function compareDates(a,b){ return (+a.date)-(+b.date); }
objArray.sort(compareDates); // sorts in place, array is changed
var groups = [], g=[], d=0;
for (var gx=+objArray[0].date+15*60*1000,i=0,l=objArray.length; i<l; ++i){
    d = +(objArray[i].date);
    if (d>gx){ 
       groups.push(g);
       g = [];
       gx = +objArray[i].date+15*60*1000;
    }
    g.push(objArray[i]);
}
groups.push(g);  // include last group otherwise unpushed
// result in groups
Community
  • 1
  • 1
Paul
  • 26,170
  • 12
  • 85
  • 119
0

This method takes startDate,EndDate and interval to break DateTime and return DateTime Collection on specified interval

   const createDateTimeInterval = (startDate, endDate, interval) => {
        const dateArray = [];
        let currentDate = new Date(startDate);
        while (currentDate < new Date(endDate)) {
            var date = new Date(currentDate);
            let dateinString;
            if (date.getHours() < 12) {
                dateinString = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':' + (date.getMinutes() === 0 ? date.getMinutes() + '0' : date.getMinutes()) + ' AM';
            }
            else {
                dateinString = ((date.getHours() - 12) < 10 ? '0' + (date.getHours() - 12) : (date.getHours() - 12)) + ':' + (date.getMinutes() === 0 ? date.getMinutes() + '0' : date.getMinutes()) + ' PM';
            }
            dateArray.push({ label: dateinString, value: JSON.stringify(new Date(currentDate)) });
            currentDate = new Date(currentDate.getTime() + interval);
        }
    
        return dateArray;
    }
    const currentDate = new Date(new Date().getFullYear() + '-' + new Date().getMonth() + '-' + new Date().getDate())
    const nextDate = new Date(new Date(currentDate).getTime() + 60000 * 60 * 24)

This is the 15 minutes interval on a current date.

var dateOptions = createDateTimeInterval(currentDate, nextDate, (60000 * 15));
  • Please provide additional details in your answer. As it's currently written, it's hard to understand your solution. – Community Sep 05 '21 at 20:19