How to find overlap between time intervals without date part - javascript

I have to find out the overlap between 2-time ranges and it does not include date part in comparison but time only e.g range_1 is 9AM-6PM and range_2 is 5PM-8AM. The actual times are in 24-hour format. I have written a solution that finds the overlap but it does not work when any of the time is in cross-day/after-midnight time for example 10PM-2AM
Both the times are of the same day but my current solution does not work correctly when the comparison has to make for any time after midnight. For example, it gives correct output in case of range_1: 9AM-6PM and range_2: 5PM-8PM, gives output 5PM-6PM overlap that's correct but cannot find overlap in case range_1: 10PM-2AM and range_2: 1AM-3AM same day.
Following is the link to my codepen:
https://codepen.io/anon/pen/NZOqJm?editors=0010
function overlap(t1,t2)
{
var timeFormat = "hh:mm";
let t1from = moment(t1.timeFrom,timeFormat);
let t1to = moment(t1.timeTo,timeFormat);
let t2from = moment(t2.timeFrom,timeFormat);
let t2to = moment(t2.timeTo,timeFormat);
let overlapFrom = null;
let overlapTo = null;
if (t2from.isBetween(t1from, t1to) && t2to.isBetween(t1from, t1to)) {
//complete overlap
overlapFrom = t2from;
overlapTo = t2to;
}
else if (t1from.isBetween(t2from, t2to) && t1to.isBetween(t2from, t2to))
{
overlapFrom = t1from;
overlapTo = t1to;
}
else if (t2from.isBetween(t1from, t1to)) {
overlapFrom = t2from;
overlapTo = t1to;
}
else if (t2to.isBetween(t1from, t1to)) {
overlapFrom = t1from;
overlapTo = t2to;
}
let doesOverlap = overlapFrom !== null && overlapTo !== null;
let response = {
doesOverlap: doesOverlap,
overlapingMinutes: doesOverlap ? Math.abs(moment.duration(overlapFrom.diff(overlapTo)).asMinutes()): 0,
overlapFrom,
overlapTo
}
return response;
}
/*let t1 = {
timeFrom:'22:00',
timeTo:'02:00'
}
let t2 = {
timeFrom:'01:00',
timeTo:'03:00'
}*/
let t1 = {
timeFrom:'09:00',
timeTo:'18:00'
}
let t2 = {
timeFrom:'17:00',
timeTo:'20:00'
}
console.log(overlap(t1,t2));
range_1: 10PM-2AM
range_2: 1AM-3AM
Correct Output Should be: 1 AM to 2 AM
As both of the times overlap, all the times are within the day and does not include any date. Consider it as 2 students study at these times daily, then what times of both if these overlaps

I'd just work with hours / minutes then:
const smaller = (a, b) => a.hours < b.hours || (a.hours === b.hours && a.mins < b.mins);
const equals = (a, b) => a.hours === b.hours && a.mins === b.mins;
function overlap(a, b) {
if(smaller(a.to, a.from)) {
return [
...overlap({ from: { hours: 0, mins: 0}, to: a.to }, b),
...overlap({ from: a.from, to: { hours: 24, mins: 0 }}, b),
];
}
if(smaller(b.to, b.from)) {
return [
...overlap(a, { from: { hours: 0, mins: 0}, to: a.to }),
...overlap(a, { from: a.from, to: { hours: 24, mins: 0 }}),
];
}
const result = {
from: smaller(b.from, a.from) ? a.from : b.from,
to: smaller(a.to, b.to) ? a.to : b.to,
};
return equals(result.from, result.to) ? [] : [result];
}
console.log(...[
[
{ from: { hours: 6, mins: 10 }, to: { hours: 8, mins: 10 }},
{ from: { hours: 6, mins: 10 }, to: { hours: 8, mins: 10 }}
], [
{ from: { hours: 7, mins: 10 }, to: { hours: 10, mins: 10 }},
{ from: { hours: 6, mins: 10 }, to: { hours: 8, mins: 10 }}
], [
{ from: { hours: 23, mins: 10 }, to: { hours: 8, mins: 10 }},
{ from: { hours: 6, mins: 10 }, to: { hours: 8, mins: 10 }}
]
].map(([a, b]) => `${JSON.stringify(a)}\n overlaps with ${JSON.stringify(b)}\n in ${JSON.stringify(overlap(a, b))}\n\n`));

There are a number of corner cases that need to be sorted out, like do
"10:00, 11:00" and "11:00, 12:00" overlap? And is "10:00, 10:00" zero minutes or 24 hours?
In any case, the way I'd go about doing this is to convert the times to minutes past midnight and compare that. What this code does is that. The comparison is "is the start time of one duration 'between' the start/end times of the other", and, if not, swap the times and do the check again.
function overlap(t0, t1) {
// convert time to minutes past midnight
var minutes = (d) => {return Object.values(d).map(d => {
d=d.split(':').map(d=>parseInt(d));
return d[0]*60+d[1];
})};
// If the end time is before the start then add 24 hours to the end
// time to wrap it into next day. start==end gets 24 hrs added.
var nextday = (d) => {if(d[0]>=d[1])d[1]+=24*60;return d}
t0 = nextday(minutes(t0));
t1 = nextday(minutes(t1));
var olap = (t0, t1) => {
// beginning of t0 between begin/end of t1
// or
// end of t0 between begin/end of t1
return (t0[0]>=t1[0] && t0[0]<=t1[1])
|| (t1[0]>=t0[0] && t1[0]<=t0[1]);
}
return olap(t0, t1) || olap(t1, t0)
}

Related

Javascript make calculations and filter list

I have below array -
const data=[
{
month:"nov",
veryLate:3,
Late:5,
onTime:2
},
{
month:"dec",
veryLate:1,
Late:3,
onTime:16
},
{
month:"jan",
veryLate:28,
Late:1,
onTime:1
},
}
I want to filter and make calculations on this array such that percentage can be obtained.
Eg. veryLate + Late+ onTime = (3+5+2) = 10
So percentage wise it is -
const data= [
{
month:"nov",
veryLate:30,
Late:50,
onTime:20
},
{
month:"dec",
veryLate:5,
Late:15,
onTime:80
},
,
{
month:"jan",
veryLate:98.33,
Late:3.33,
onTime:3.33
},]
To calculate this I had performed below , but getting syntax error over brackets -
var filteredData=data.map(x=>{
x.month,
x.veryLate/(x.veryLate+x.Late+x.onTime)*100,
x.Late/(x.veryLate+x.Late+x.onTime)*100,
x.onTime/(x.veryLate+x.Late+x.onTime)*100,
});
How can I obtained calculated results?
x.veryLate wont work in x it should be veryLate itself same for the others
const data=[
{
month:"nov",
veryLate:3,
Late:5,
onTime:2
},
{
month:"dec",
veryLate:1,
Late:3,
onTime:16
},
{
month:"jan",
veryLate:28,
Late:1,
onTime:1
},
]
var filteredData= data.map(x => (
{
...x,
veryLate: x.veryLate/(x.veryLate+x.Late+x.onTime)*100,
Late: x.Late/(x.veryLate+x.Late+x.onTime)*100,
onTime: x.onTime/(x.veryLate+x.Late+x.onTime)*100
})
)
console.log(filteredData)
You also must wrap the returning object literal into parentheses. Currently the curly braces is being denoted as the function body.
The map() method needs to return a value.
Try this
var filteredData=data.map(x => {
// get the original values to avoid repeating x.
const {month, veryLate, Late, onTime} = x;
// do your calculations
const newVeryLate = veryLate / ( veryLate + Late + onTime) * 100;
const newLate = Late / (veryLate + Late + onTime) * 100;
const newOnTime = onTime / (veryLate + Late + onTime) * 100;
// return the new object
return {month, veryLate: newVeryLate, Late: newLate, onTime: newOnTime}
});
var filteredData=data.reduce((a, v)=>{
let obj = {};
let sum = v.veryLate+v.Late+v.onTime;
obj.month = v.month;
obj.veryLate = v.veryLate/sum*100;
obj.Late = v.Late/sum*100;
obj.onTime = v.onTime/sum*100;
a.push(obj)
return a;
}, []);
Throwing error because:
The data array has no closing bracket
map method is not returning anything. if you want to return an object from it use return keyword.
The problem is that the callback passed to Array.prototype.map needs to return something for every iteration and in your current implementation you aren't returning anything.
You can use map as shown below:
const data = [
{ month: "nov", veryLate: 3, Late: 5, onTime: 2 },
{ month: "dec", veryLate: 1, Late: 3, onTime: 16 },
{ month: "jan", veryLate: 28, Late: 1, onTime: 1 },
];
const filteredData = data.map(({ month, veryLate, Late, onTime }) => {
const total = veryLate + Late + onTime;
return {
month,
veryLate: (veryLate / total) * 100,
Late: (Late / total) * 100,
onTime: (onTime / total) * 100,
};
});
console.log(filteredData);
If you're finding map confusing, you can also do it with a regular for loop, as shown below:
const data = [
{ month: "nov", veryLate: 3, Late: 5, onTime: 2 },
{ month: "dec", veryLate: 1, Late: 3, onTime: 16 },
{ month: "jan", veryLate: 28, Late: 1, onTime: 1 },
];
const filteredData = [];
for (let i = 0; i < data.length; i++) {
const { month, veryLate, Late, onTime } = data[i];
const total = veryLate + Late + onTime;
result.push({
month,
veryLate: (veryLate / total) * 100,
Late: (Late / total) * 100,
onTime: (onTime / total) * 100,
});
}
console.log(filteredData);
Additional Documentation:
Object Destructuring

How to map array elements in JavaScript

Only elements that have a value greater than or equal to the threshold must be kept in the array. Then a new array will have to be created which will contain several objects. Each of these objects will have two properties, the start and the end.
If there are several elements in a row (which have a timestamp 10 minutes apart), they will be grouped in the same object. Where the start value will be the timestamp of the first element and the end value will be the timestamp value of the last element of the group plus 10 min.
If there are not several elements followed, the start value will be the timestamp and the end will be the timestamp plus 10 minutes.
const data = [{
timestamp: '2021-11-23T14:00:00+0000',
amount: 21
},
{
timestamp: '2021-11-23T14:10:00+0000',
amount: 27
},
{
timestamp: '2021-11-23T14:20:00+0000',
amount: 31
},
{
timestamp: '2021-11-23T14:30:00+0000',
amount: 29
},
{
timestamp: '2021-11-23T14:40:00+0000',
amount: 18
},
{
timestamp: '2021-11-23T14:50:00+0000',
amount: 17
},
{
timestamp: '2021-11-23T15:00:00+0000',
amount: 25
},
{
timestamp: '2021-11-23T15:10:00+0000',
amount: 21
}
]
const threshold = 25
const aboveThreshold = data.filter(element => element.amount >= threshold)
const workSchedule = []
for (let i = 0; i < aboveThreshold.length; i++) {
if (i === 0) {
workSchedule.push({
start: aboveThreshold[i].timestamp,
end: aboveThreshold[i + 1].timestamp
})
}
if (i > 0 && i < aboveThreshold.length - 1) {
if (aboveThreshold[i].timestamp.slice(11, 13) === aboveThreshold[i + 1].timestamp.slice(11, 13)) {
workSchedule.push({
start: aboveThreshold[i].timestamp,
end: aboveThreshold[i + 1].timestamp
})
}
}
if (i === aboveThreshold.length - 1) {
workSchedule.push({
start: aboveThreshold[i].timestamp,
end: aboveThreshold[i].timestamp
})
}
}
console.log(workSchedule)
But the end result I want is the following:
[
{
start: '2021-11-23T14:10:00+0000',
end: '2021-11-23T14:40:00+0000'
},
{
start: '2021-11-23T15:00:00+0000',
end: '2021-11-23T15:10:00+0000'
}
]
I hope I was clear 😬 and is there a simpler and easier to understand/read approach than what I've done so far?
You can apply a simple reduce function to get the result you want with a little bit of help from Date object. Here is a solution:
const aboveThreshold = data.filter(element => element.amount >= threshold);
const nws = aboveThreshold.reduce((acc, v) => {
const end = new Date(Date.parse(v.timestamp) + 600000);
if (acc.length === 0) return [{ start: v.timestamp, end: end.toISOString() }];
let diff = Date.parse(v.timestamp) - Date.parse(acc[acc.length - 1].end);
// checks if the difference is less than 10 minutes
if (diff <= 10 * 60 * 1000) {
acc[acc.length - 1].end = end.toISOString();
} else {
acc.push({ start: v.timestamp, end: end.toISOString() });
}
return acc
}, []);
Check out Reduce Documentation.
This is the result it gives with your data
[{
end: "2021-11-23T14:40:00.000Z",
start: "2021-11-23T14:10:00+0000"
}, {
end: "2021-11-23T15:10:00.000Z",
start: "2021-11-23T15:00:00+0000"
}]

Find pair of employees that have worked together the longest on common project

Kind of a duplicate but not at the same time because it's in python so I'm reposting it. The outputs are different as well for the same inputs.
Got a complicated assignment that I finally managed to solve but the algorithm I came up with is quite slow.
I'm pretty sure its around n^2 + n in worst case scenario.
It's supposed to go through a list of employees and return a list of employee pairs that have accumulated the most days worked together on common projectS. (the s is important)
Format for input:
EmployeeID, ProjectID, StartDate, EndDate(NULL === today)
Example input:
1,1,2019-7-4,2020-8-14
1,2,2019-12-25,2020-12-28
1,3,2018-10-12,NULL
1,4,2019-11-16,NULL
1,5,2020-1-5,2020-12-21
2,1,2018-10-3,NULL
2,2,2019-1-16,2020-3-24
2,3,2019-5-22,2019-12-26
2,4,2020-3-7,NULL
2,5,2018-1-24,2019-1-15
3,1,2019-3-21,2020-11-26
3,5,2019-9-28,2020-12-25
4,2,2018-10-22,NULL
4,3,2018-1-27,2020-8-28
5,3,2018-2-3,2020-10-14
5,5,2018-8-4,NULL
Format for output:
Employee#1, Employee#2, CommonProjectID, DaysWorked
Example output:
1,2,1,407
1,2,2,90
1,2,3,219
1,2,4,513
Here's my take on it but as I said it's quite slow and I was asked to try to optimize it. Been working on this for 5 hours now and can't come up with anything better.
export default function getHighestPair(empl) {
console.log(empl);
let pairs = {};
let daysTogether = {};
if (empl)
empl.forEach((el1) => {
/*
.slice() is used to exclude the current employee and employees before him
from the search which slightly reduces complexity. This is because
employee 5 + employee 13 is the same as employee 13 + employee 5
*/
empl.slice(empl.indexOf(el1) + 1, empl.length).forEach((el2) => {
// get start and end date of each of employee
if (el1[0] !== el2[0]) {
const startDate1 = new Date(el1[2]);
const endDate1 = el1[3] === "NULL" ? new Date() : new Date(el1[3]);
const startDate2 = new Date(el2[2]);
const endDate2 = el2[3] === "NULL" ? new Date() : new Date(el2[3]);
// check if they are in the same team (working on the same project)
if (el1[1] === el2[1]) {
if (startDate1 <= endDate2 && startDate2 <= endDate1) {
// calculate the start and end day that we need
const start = startDate1 <= startDate2 ? startDate2 : startDate1;
const end = endDate1 <= endDate2 ? endDate1 : endDate2;
if (end >= startDate2) {
// put them inside this formula and we get the time they have worked together in days
const diffTime = Math.abs(end - start);
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
const x = `${el1[0]}${el2[0]}`;
if (!daysTogether[x]) Object.assign(daysTogether, { [x]: 0 });
daysTogether[x] = 1 * daysTogether[x] + diffDays;
if (!pairs[x]) Object.assign(pairs, { [x]: [] });
pairs[x] = [...pairs[x], [el1[0], el2[0], el1[1], diffDays]];
}
}
}
}
});
});
/*
gets the index of the pair that have worked together the longest toghether from
"daysTogether" which keeps count of the days for each project
*/
return pairs[
Object.keys(daysTogether).reduce((a, b) =>
daysTogether[a] > daysTogether[b] ? a : b
)
];
}
This solution should be O(n log n).
for each record
group record by project id
for each stored record in this group, compare to current record
compare the worked together time to the longest for this group, if it's greater update longest
add current record to stored records in this group
The deObjectify cleanup is not strictly necessary
const data = [
[1,2,"2019-12-25","2020-12-28"],
[1,3,"2018-10-12",null],
[1,4,"2019-11-16",null],
[1,5,"2020-1-5","2020-12-21"],
[2,1,"2018-10-3",null],
[2,2,"2019-1-16","2020-3-24"],
[2,3,"2019-5-22","2019-12-26"],
[2,4,"2020-3-7",null],
[2,5,"2018-1-24","2019-1-15"],
[3,1,"2019-3-21","2020-11-26"],
[3,5,"2019-9-28","2020-12-25"],
[4,2,"2018-10-22",null],
[4,3,"2018-1-27","2020-8-28"],
[5,3,"2018-2-3","2020-10-14"],
[5,5,"2018-8-4",null]
];
const overlap = (e1d1, e1d2, e2d1, e2d2) => {
const startDate1 = new Date(e1d1);
const endDate1 = e1d2 === null ? new Date() : new Date(e1d2);
const startDate2 = new Date(e2d1);
const endDate2 = e2d2 === null ? new Date() : new Date(e2d2);
const start = startDate1 < startDate2 ? startDate2 : startDate1;
const end = endDate1 < endDate2 ? endDate1 : endDate2;
if (end >= start) {
const diffTime = Math.abs(end - start);
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays;
}
return 0;
};
const result = data.reduce((acc, el) => {
let c = acc[el[1]];
if (!c) {
c = acc[el[1]] = {
overlap: 0,
e1: 0,
e2: 0,
data: []
};
};
c.data.forEach(d => {
const o = overlap(d[2], d[3], el[2], el[3]);
if (o > c.overlap) {
c.overlap = o;
c.e1 = d[0];
c.e2 = el[0];
}
});
c.data.push(el);
return acc;
}, {});
const deObjectify = Object.entries(result).map(([projectId, {e1, e2, overlap}]) => ({e1, e2, projectId, overlap}));
console.log(deObjectify);
console.log("inner workings");
console.log(result);
So this is my final code...
Result:
[ { emA: 1, emB: 2, sum: 1230, details: [{ proj: 1, days: 407 }, { proj: 2, days: 90 }, { proj: 3, days: 219 }, { proj: 4, days: 514 }]}
, { emA: 1, emB: 5, sum: 1084, details: [{ proj: 3, days: 733 }, { proj: 5, days: 351 }]}
, { emA: 1, emB: 4, sum: 1055, details: [{ proj: 2, days: 369 }, { proj: 3, days: 686 }]}
, { emA: 4, emB: 5, sum: 937, details: [{ proj: 3, days: 937 } ]}
, { emA: 1, emB: 3, sum: 758, details: [{ proj: 1, days: 407 }, { proj: 5, days: 351 }]}
, { emA: 2, emB: 4, sum: 652, details: [{ proj: 2, days: 433 }, { proj: 3, days: 219 }]}
, { emA: 2, emB: 3, sum: 616, details: [{ proj: 1, days: 616 } ]}
, { emA: 3, emB: 5, sum: 455, details: [{ proj: 5, days: 455 } ]}
, { emA: 2, emB: 5, sum: 384, details: [{ proj: 3, days: 219 }, { proj: 5, days: 165 }]}
]
const data =
[ [ 1, 1, '2019-7-4', '2020-8-14' ]
, [ 1, 2, '2019-12-25', '2020-12-28' ] // EmployeeID, ProjectID, StartDate, EndDate(null === today)
, [ 1, 3, '2018-10-12', null ]
, [ 1, 4, '2019-11-16', null ]
, [ 1, 5, '2020-1-5', '2020-12-21' ]
, [ 2, 1, '2018-10-3', null ]
, [ 2, 2, '2019-1-16', '2020-3-24' ]
, [ 2, 3, '2019-5-22', '2019-12-26' ]
, [ 2, 4, '2020-3-7', null ]
, [ 2, 5, '2018-1-24', '2019-1-15' ]
, [ 3, 1, '2019-3-21', '2020-11-26' ]
, [ 3, 5, '2019-9-28', '2020-12-25' ]
, [ 4, 2, '2018-10-22', null ]
, [ 4, 3, '2018-1-27', '2020-8-28' ]
, [ 5, 3, '2018-2-3', '2020-10-14' ]
, [ 5, 5, '2018-8-4', null ]
]
const
oneDay = 24 * 60 * 60 * 1000 // hours*minutes*seconds*milliseconds
, setDate = YMD =>
{
let [Y,M,D] = YMD.split('-').map(Number)
return new Date(Y,--M,D)
}
// group Employees by project id , change date string to JS newDate
const Proj_Emps = data.reduce( (r,[EmployeeID, ProjectID, StartDate, EndDate])=>
{
let stD = setDate(StartDate)
, enD = EndDate ? setDate(EndDate) : new Date()
r[ProjectID] = r[ProjectID] ?? []
r[ProjectID].push({EmployeeID,stD,enD})
return r
}, {})
// combination of pairs of employees per project
let combination = {}
for (let proj in Proj_Emps)
for (let i = 0; i < Proj_Emps[proj].length - 1; i++)
for (let j = i + 1; j < Proj_Emps[proj].length; j++)
{
let emA = Proj_Emps[proj][i]
let emB = Proj_Emps[proj][j]
if (( emA.enD <= emB.enD && emA.enD > emB.stD )
||( emB.enD <= emA.enD && emB.enD > emA.stD )
){
let
D1 = emA.stD > emB.stD ? emA.stD : emB.stD
, D2 = emA.enD < emB.enD ? emA.enD : emB.enD
, days = Math.ceil((D2 - D1) / oneDay)
, key = `${emA.EmployeeID}-${emB.EmployeeID}`
;
combination[key] = combination[key] ?? { emA: emA.EmployeeID, emB: emB.EmployeeID, sum:0, details:[] }
combination[key].details.push({proj: Number(proj), days })
combination[key].sum += days
}
}
let Result =
Object.entries(combination)
.sort((a,b)=> b[1].sum - a[1].sum )
.map(([k,v])=>v)
Result.forEach(el => console.log( JSON.stringify(el).replaceAll('"','')))
.as-console-wrapper { max-height: 100% !important; top: 0 }
.as-console-row::after { display: none !important; }
I also fixed a bugg (typo) on the calculation of dates

How to get regular interval count

So I have the data in below format
const data = [
{ date: '01-07-2019' },
{ date: '02-07-2019' },
{ date: '03-07-2019' },
{ date: '04-07-2019' },
{ date: '05-07-2019' },
{ date: '06-07-2019' },
{ date: '07-07-2019' },
{ date: '08-07-2019' },
{ date: '09-07-2019' },
{ date: '10-07-2019' },
{ date: '15-07-2019' },
{ date: '16-07-2019' },
{ date: '20-07-2019' },
{ date: '21-07-2019' },
{ date: '22-07-2019' },
{ date: '23-07-2019' }
]
So I have to count the regular interval dates. For example on date { date: '10-07-2019' }, { date: '20-07-2019' } and on { date: '23-07-2019' } it breaks so count should be again started with 1.
const ouput = [{
startDate: '01-07-2019',
endDate: '10-07-2019',
count: 10
}, {
startDate: '15-07-2019',
endDate: '16-07-2019',
count: 2
}, {
startDate: '20-07-2019',
endDate: '23-07-2019',
count: 4
}]
I did that
const output = Object.values(data.reduce((a, { startDate, endDate }, i) => {
const startTime = moment(data[i].date)
const endTime = moment(data[i + 1] && data[i + 1].date)
if (moment.duration(endTime.diff(startTime)).asDays === 1) {
a.startDate = startDate
a.startDate = endDate
}
a.count++;
return a;
}, {}));
But it is not giving what I expect. Please help.
I would do that with a function generator to handle the desired aggregation.
The below code will loop the dates, take a pair, check whether the start date exists, update the end date and automatically yield the value if necessary.
Comments are directly in the code below, the code assumes the initial array is already sorted as the example you mentioned.
As a side note, you're actually including the last date in the count, while, effectively, it should be one day less than your count. Further comments about that are available below in the function generator code.
const data = [
{ date: '01-07-2019' },
{ date: '02-07-2019' },
{ date: '03-07-2019' },
{ date: '04-07-2019' },
{ date: '05-07-2019' },
{ date: '06-07-2019' },
{ date: '07-07-2019' },
{ date: '08-07-2019' },
{ date: '09-07-2019' },
{ date: '10-07-2019' },
{ date: '15-07-2019' },
{ date: '16-07-2019' },
{ date: '20-07-2019' },
{ date: '21-07-2019' },
{ date: '22-07-2019' },
{ date: '23-07-2019' }
];
// Counts intervals of consecutive dates.
function* countIntervals(dates) {
// declare an initial accumulator.
let acc = {
count: 0
};
for (let i = 0; i < dates.length; i++) {
// get the currently looped value and the next one.
const [curr, next] = [moment(dates[i].date, 'DD-MM-YYYY'), dates[i+1] ? moment(dates[i+1].date, 'DD-MM-YYYY') : null];
// if the current date and next days are valid and if the difference in days between them is 1..
if (curr && next && (next.diff(curr, "days") === 1)) {
// Then keep track of the start date if not set, update the end date and increase the count of days.
acc.startDate = acc.startDate || dates[i].date, acc.endDate = dates[i+1].date, acc.count++;
}
else {
// otherwise, if the accumulator has a start date, yield the value.
if (acc && acc.startDate) {
acc.count++; // <-- comment this if you don't want the last date to be included.
yield Object.assign({}, acc);
// and init again the accumulator.
acc = {
count: 0
};
}
}
}
// if the loop is finished and the progression continued, yield the current accumulator.
if (acc.startDate) yield acc;
}
// usage...
const intervals = [...countIntervals(data)];
console.log(intervals);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
Here you go, Try this
const data = [
{ date: "01-07-2019" },
{ date: "02-07-2019" },
{ date: "03-07-2019" },
{ date: "04-07-2019" },
{ date: "05-07-2019" },
{ date: "06-07-2019" },
{ date: "07-07-2019" },
{ date: "08-07-2019" },
{ date: "09-07-2019" },
{ date: "10-07-2019" },
{ date: "15-07-2019" },
{ date: "16-07-2019" },
{ date: "20-07-2019" },
{ date: "21-07-2019" },
{ date: "22-07-2019" },
{ date: "23-07-2019" }
];
function to parse date
function parseDate(input) {
var parts = input.split("-");
// new Date(year, month [, day [, hours[, minutes[, seconds[, ms]]]]])
return new Date(parts[2], parts[1] - 1, parts[0]); // Note: months are 0-based
}
function to get date difference
function dateDiff(date1, date2) {
date1 = parseDate(date1);
date2 = parseDate(date2);
let diffTime = Math.abs(date2.getTime() - date1.getTime());
let diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays;
}
Required output
const output = data.reduce(function(resultSet, currentValue, currentIndex, arr) {
if (resultSet.length == 0) {
resultSet.push({
startDate: currentValue.date,
endDate: currentValue.date,
count: 1
});
}
else{
let dateDiffrence = dateDiff(resultSet[resultSet.length-1].endDate, currentValue.date);
console.log(dateDiffrence);
if(dateDiffrence == 1){
resultSet[resultSet.length-1].endDate = currentValue.date;
resultSet[resultSet.length-1].count++;
}else{
resultSet.push({
startDate: currentValue.date,
endDate: currentValue.date,
count: 1
});
}
}
return resultSet;
}, []);
Yet another possible solution.
const parseDate = (str) => {
const [d, m, y] = str.split('-');
return +new Date(y, m - 1, d)
}
const output = data.reduce((a, {
date
}, i) => {
const cur = parseDate(date);
const lastDate = data[i - 1] && data[i - 1].date || date;
const last = parseDate(lastDate || date);
if (cur - last > 1000 * 60 * 60 * 24) a.push({count: 0});
const {
startDate = date,
count
} = a.pop();
a.push({
startDate,
endDate: date,
count: count + 1
})
return a;
}, [{
count: 0
}])
console.log (output)
<script>
const data = [
{ date: '01-07-2019' },
{ date: '02-07-2019' },
{ date: '03-07-2019' },
{ date: '04-07-2019' },
{ date: '05-07-2019' },
{ date: '06-07-2019' },
{ date: '07-07-2019' },
{ date: '08-07-2019' },
{ date: '09-07-2019' },
{ date: '10-07-2019' },
{ date: '15-07-2019' },
{ date: '16-07-2019' },
{ date: '20-07-2019' },
{ date: '21-07-2019' },
{ date: '22-07-2019' },
{ date: '23-07-2019' }
]</script>
If you construct UTC dates there will be no need to use moment.js. With UTC every day is 24 hours and DST does not apply. This solution features a boilerplate function to handle the creation of the UTC date from your date string format.
const data = [
{ date: '01-07-2019' },
{ date: '02-07-2019' },
{ date: '03-07-2019' },
{ date: '04-07-2019' },
{ date: '05-07-2019' },
{ date: '06-07-2019' },
{ date: '07-07-2019' },
{ date: '08-07-2019' },
{ date: '09-07-2019' },
{ date: '10-07-2019' },
{ date: '15-07-2019' },
{ date: '16-07-2019' },
{ date: '20-07-2019' },
{ date: '21-07-2019' },
{ date: '22-07-2019' },
{ date: '23-07-2019' }
];
const ONE_DAY = 1000 * 60 * 60 * 24;
function dateStrToUTC(dateStr) {
const dateParts = dateStr.split('-');
const utcDate = new Date();
utcDate.setUTCFullYear(dateParts[2]);
utcDate.setUTCMonth(dateParts[1] - 1);
utcDate.setUTCDate(dateParts[0]);
utcDate.setUTCHours(0);
utcDate.setUTCMinutes(0);
utcDate.setUTCSeconds(0);
utcDate.setUTCMilliseconds(0);
return utcDate;
}
function getRegularIntervals(accumulator, currentValue) {
const index = accumulator.length - 1;
let daysPassed = 0;
if (index > -1) {
daysPassed = (dateStrToUTC(currentValue.date) - dateStrToUTC(accumulator[index].endDate)) / ONE_DAY;
}
if (index > -1 && 1 == daysPassed) {
accumulator[index].endDate = currentValue.date;
accumulator[index].count++;
} else {
accumulator.push({
startDate: currentValue.date,
endDate: currentValue.date,
count: 1
});
}
return accumulator;
}
const output = data.reduce(getRegularIntervals, []);
console.log(output);
Output as expected:
[
{
"startDate": "01-07-2019",
"endDate": "10-07-2019",
"count": 10
},
{
"startDate": "15-07-2019",
"endDate": "16-07-2019",
"count": 2
},
{
"startDate": "20-07-2019",
"endDate": "23-07-2019",
"count": 4
}
]
I liked your approach of using reduce function.
Going with the same, I just added some more logic in there and here is the final code.
// initially lets assume first date is the start as well as end date
var dateIntervalObject = {
startDate: data[0].date,
endDate: data[0].date,
count: 1
};
var result = data.reduce((resultArray, obj, i) => {
if(i > 0) {
var startTime = moment(dateIntervalObject.endDate, "DD-MM-YYYY");
var endTime = moment(obj.date, "DD-MM-YYYY");
if (endTime.diff(startTime, 'days') === 1) {
dateIntervalObject.endDate = obj.date;
dateIntervalObject.count += 1;
// remove the latest object in array, to replace with new
resultArray.pop();
} else {
dateIntervalObject = {
startDate: obj.date,
endDate: obj.date,
count: 1
};
}
// push the date Interval object in the array
resultArray.push(dateIntervalObject);
}
return resultArray;
}, [dateIntervalObject]);
console.log('result: ',result);
Note:
When initialState of the accumulator is passed to reduce function it starts iterating from 0th index, which in our case have already been initialized in the dateIntervalObject and therefore the first iteration with index value 0 is skipped.
Also, if the interval is not changing, we don't need to add another object to our result array but instead update the end date of the last element of our result array. Therefore, first pop and then push to just update the end date and count value.
Hope this helps!

How to add count for the same week using reduce JavaScript?

I am trying to implement a function using reduce that allows me to group an array of objects { date: '2019-03-11', count: 8 } by weeks. So far, I was able to group dates by weeks but I am having trouble to add together count if the date falls in the same week.
const dates = [
{ date: '2019-03-11', count: 8 },
{ date: '2019-03-12', count: 7 },
{ date: '2019-03-09', count: 6 },
{ date: '2019-02-27', count: 10 },
{ date: '2019-02-26', count: 11 },
{ date: '2019-02-22', count: 12 },
{ date: '2019-04-21', count: 3 },
{ date: '2019-04-18', count: 2 },
{ date: '2019-04-17', count: 4 },
{ date: '2019-04-19', count: 5 }
];
Date.prototype.getWeek = function() {
const onejan = new Date(this.getFullYear(), 0, 1);
return Math.ceil(((this - onejan) / 86400000 + onejan.getDay() + 1) / 7);
};
const groups = dates.reduce(function(acc, item) {
const today = new Date(item.date);
const weekNumber = today.getWeek(today);
// check if the week number exists
if (typeof acc[weekNumber] === 'undefined') {
acc[weekNumber] = [];
}
acc[weekNumber].push(item.date, item.count);
return acc;
}, {});
console.log(groups);
Current Result
Desired Result
[
{ weekStart: '2019-02-17', count: 12 },
{ weekStart: '2019-02-24', count: 21 },
{ weekStart: '2019-03-03', count: 6 },
{ weekStart: '2019-03-10', count: 15 },
{ weekStart: '2019-04-14', count: 11 },
{ weekStart: '2019-04-21', count: 21 }
]
where weekStart is the first date of the week (Sunday) by which it was grouped
SOLUTION
const dates = [
{ date: '2019-02-24', count: 10 },
{ date: '2019-02-25', count: 11 },
{ date: '2019-02-26', count: 12 },
{ date: '2019-03-09', count: 8 },
{ date: '2019-03-10', count: 7 },
{ date: '2019-03-11', count: 6 },
{ date: '2019-04-14', count: 3 },
{ date: '2019-04-15', count: 2 },
{ date: '2019-04-16', count: 4 },
{ date: '2019-04-22', count: 5 }
];
/**
* Returns the week number for this date. dowOffset is the day of week the week
* "starts" on for your locale - it can be from 0 to 6. If dowOffset is 1 (Monday),
* the week returned is the ISO 8601 week number.
* #param int dowOffset
* #return int
*/
Date.prototype.getWeek = function(dowOffset) {
/*getWeek() was developed by Nick Baicoianu at MeanFreePath: http://www.epoch-calendar.com */
dowOffset = typeof dowOffset == 'int' ? dowOffset : 0; //default dowOffset to zero
var newYear = new Date(this.getFullYear(), 0, 1);
var day = newYear.getDay() - dowOffset; //the day of week the year begins on
day = day >= 0 ? day : day + 7;
var daynum =
Math.floor(
(this.getTime() -
newYear.getTime() -
(this.getTimezoneOffset() - newYear.getTimezoneOffset()) * 60000) /
86400000
) + 1;
var weeknum;
//if the year starts before the middle of a week
if (day < 4) {
weeknum = Math.floor((daynum + day - 1) / 7) + 1;
if (weeknum > 52) {
nYear = new Date(this.getFullYear() + 1, 0, 1);
nday = nYear.getDay() - dowOffset;
nday = nday >= 0 ? nday : nday + 7;
/*if the next year starts before the middle of
the week, it is week #1 of that year*/
weeknum = nday < 4 ? 1 : 53;
}
} else {
weeknum = Math.floor((daynum + day - 1) / 7);
}
return weeknum;
};
function getWeekStart(date) {
var offset = new Date(date).getDay();
return new Date(new Date(date) - offset * 24 * 60 * 60 * 1000)
.toISOString()
.slice(0, 10);
}
function groupWeeks(dates) {
const groupsByWeekNumber = dates.reduce(function(acc, item) {
const today = new Date(item.date);
const weekNumber = today.getWeek();
// check if the week number exists
if (typeof acc[weekNumber] === 'undefined') {
acc[weekNumber] = [];
}
acc[weekNumber].push(item);
return acc;
}, []);
return groupsByWeekNumber.map(function(group) {
return {
weekStart: getWeekStart(group[0].date),
count: group.reduce(function(acc, item) {
return acc + item.count;
}, 0)
};
});
}
console.log(groupWeeks(dates));
You could take the weekday as offset and subtract the milliseconds from the given date.
Reducing works with a callback and a clsoure over the key with the week start date.
(m, { date, count }) => // outer callback with map and object
(k => m.set(k, (m.get(k) || 0) + count)) // closure over k and updating the count in map
(getWeekStart(date)) // get value for k
function getWeekStart(date) {
var offset = new Date(date).getDay();
return new Date(new Date(date) - offset * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
}
const
dates = [{ date: '2019-03-11', count: 8 }, { date: '2019-03-12', count: 7 }, { date: '2019-03-09', count: 6 }, { date: '2019-02-27', count: 10 }, { date: '2019-02-26', count: 11 }, { date: '2019-02-22', count: 12 }, { date: '2019-04-21', count: 3 }, { date: '2019-04-18', count: 2 }, { date: '2019-04-17', count: 4 }, { date: '2019-04-19', count: 5 }],
result = Array.from(
dates.reduce((m, { date, count }) =>
(k => m.set(k, (m.get(k) || 0) + count))(getWeekStart(date)),
new Map),
([date, count]) => ({ date, count })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories