JSON group array of objects by intersection JSON group array of objects by intersection JSON group array of objects by intersection JSON group array of objects by intersection For now, here is the code
const data = [ { "from":"05/01/2021", "to":"05/06/2021" }, { "from":"05/10/2021", "to":"05/15/2021" }, { "from":"05/20/2021", "to":"05/25/2021" } ]
const monthToString = month => {
const months = {
'01': 'Jan',
'02': 'Feb',
'03': 'Mar',
'04': 'Apr',
'05': 'May',
'06': 'Jun',
'07': 'Jul',
'08': 'Aug',
'09': 'Sep',
'10': 'Oct',
'11': 'Nov',
'12': 'Dec',
}
return months[month] || month
}
const result = Object.entries(data.reduce((res, {from, to}) => {
const [monthFrom, day, year] = from.split('/')
const [m, dayTo, y] = to.split('/')
const month = monthToString(m)
return {
...res,
[month]: [...(res[month] || []), [day, dayTo]]
}
}, {})).map(([month, days]) => `${month} ${days.map(([from, to]) => `${parseInt(from)}-${parseInt(to)}`).join(', ')}`).join('\n')
console.log(result)
How to Combine dates when they intersect?
CodePudding user response:
Use the Date object instead of parsing the string. To compare the dates, it is convenient for you to use the Date object in logic. The following example verifies that the two date ranges intersect.
/**
* @param {Object} a
* @param {string} a.from
* @param {string} a.to
* @param {Object} b
* @param {string} b.from
* @param {string} b.to
* @returns {bool}
*/
function isDateIntersect(a, b) {
const aFrom = new Date(a.from);
const aTo = new Date(a.to);
const bFrom = new Date(b.from);
const bTo = new Date(b.to);
return aFrom <= bFrom && aTo > bFrom || bFrom <= aFrom && bTo > aFrom;
}
Furthermore, if the date ranges intersect or are included, you can create a function that merges the dates.
/**
* @param {Object} a
* @param {string} a.from
* @param {string} a.to
* @param {Object} b
* @param {string} b.from
* @param {string} b.to
* @returns {bool|null}
*/
function mergeIntersectDate(a, b) {
const aFrom = new Date(a.from);
const aTo = new Date(a.to);
const bFrom = new Date(b.from);
const bTo = new Date(b.to);
if (aFrom <= bFrom && aTo > bFrom || bFrom <= aFrom && bTo > aFrom) {
return [
new Date(Math.min(aFrom, bFrom)),
new Date(Math.max(aTo, bTo)),
];
}
return null;
}
After that, print out your format based on the merged date.
const dateRange = mergeIntersectDate(
{
"from":"03/15/2021",
"to":"03/18/2021"
},
{
"from":"03/16/2021",
"to":"03/28/2021"
}
);
if (dateRange) {
const [from, to] = dateRange;
const fromMonth = months[(from.getMonth() 1 '').padStart(2, '0')];
const fromDay = from.getDay();
const toMonth = months[(to.getMonth() 1 '').padStart(2, '0')];
const toDay = to.getDay();
console.log(`${fromMonth} ${fromDay} - ${toMonth} ${toDay}`);
}
CodePudding user response:
I would suggest creating a function parseDateRanges()
to parse and map the inputs to an array of { from, to } objects where from and to are Dates.
Once we have this, we'll create a mergeDateRanges()
function to merge overlapping date ranges. This function will sort and merge overlapping ranges based on the required logic.
Finally we'll apply a formatter formatDateRange()
to each range and join to get our output.
I've added the mentioned inputs, along with the required outputs as test cases, each should output the correct value.
// Define our tests...
const testCases = [
{
input: [ { "from":"03/01/2021", "to":"03/06/2021" }, { "from":"03/10/2021", "to":"03/15/2021" }, { "from":"03/20/2021", "to":"03/25/2021" } ],
expectedOutput: 'Mar 1-6, 10-15, 20-25',
},
{
input: [ { "from":"03/01/2021", "to":"03/05/2021" }, { "from":"03/08/2021", "to":"03/10/2021" }, { "from":"03/07/2021", "to":"03/20/2021" } ],
expectedOutput: 'Mar 1-5, 7-20'
},
{
input: [ { "from":"03/01/2021", "to":"03/05/2021" }, { "from":"03/04/2021", "to":"03/10/2021" }, { "from":"03/07/2021", "to":"03/20/2021" }, { "from":"03/20/2021", "to":"03/30/2021" } ],
expectedOutput: 'Mar 1-30'
},
{
input: [ { "from":"03/01/2021", "to":"03/05/2021" }, { "from":"03/06/2021", "to":"03/08/2021" }, { "from":"03/15/2021", "to":"03/18/2021" }, { "from":"03/16/2021", "to":"03/28/2021" } ],
expectedOutput: 'Mar 1-8, 15-28'
}
];
// Convert a monthIndex (0 - 11) to string ('Jan', 'Feb' etc.)
function monthToString(monthIndex) {
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
return months[monthIndex];
}
// Parse a date in MM/dd/yyyy format to a Date object.
function parseDate(str) {
const [ month, day, year ] = str.split('/').map(Number);
return new Date(year, month - 1, day);
}
// Parse input ranges to from and to Dates
function parseDateRanges(input) {
return input.map(({ from, to }) => ({ from: parseDate(from), to: parseDate(to) } ));
}
// Merge any overlapping ranges
function mergeDateRanges(ranges) {
let sortedRanges = [ ...ranges].sort((a, b) => a.from - b.from);
return sortedRanges.reduce((acc, range) => {
let prevRange = acc[acc.length - 1];
if (prevRange && (range.from <= (prevRange.to.getTime() 86400000))) {
prevRange.to = (range.to > prevRange.to) ? range.to : prevRange.to;
} else {
acc.push(range);
}
return acc;
}, [])
}
// Format a date range to 'Mar 1-30' for example..
function formatDateRange(range, idx) {
const fromMonth = (idx === 0) ? (monthToString(range.from.getMonth()) ' '): '';
const toMonth = (range.from.getMonth() === range.to.getMonth()) ? '': (monthToString(range.to.getMonth()) ' ');
return fromMonth range.from.getDate() "-" toMonth range.to.getDate();
}
// Format a group of ranges
function formatRanges(ranges) {
let parsedRanges = parseDateRanges(ranges);
return mergeDateRanges(parsedRanges).map(formatDateRange).join(', ')
}
function outputArr(a) {
console.log(...a.map(s => s.padEnd(25, ' ')));
}
outputArr(['Expected', 'Actual', 'Test passed'])
for(let testCase of testCases) {
let output = formatRanges(testCase.input);
outputArr([testCase.expectedOutput, output, (output === testCase.expectedOutput) ? 'Yes': 'No'])
}
.as-console-wrapper { max-height: 100% !important; }