Skip to content

Instantly share code, notes, and snippets.

@danielmcq
Last active October 6, 2016 07:02
Show Gist options
  • Save danielmcq/38e1ee5d65d9fd7bfb7e22610f619717 to your computer and use it in GitHub Desktop.
Save danielmcq/38e1ee5d65d9fd7bfb7e22610f619717 to your computer and use it in GitHub Desktop.
Find open time blocks

Find open time blocks

This code written in JavaScript for Node.js finds open time blocks, given a set of intervals which are not available.

Node.js 5.x or higher is needed in order to run the code as it takes advantage of ES6 features and uses CommonJS modules. However, it can be run in a modern browser by removing the module.exports line at the end of the file team_availability.js.

To run the tests, save both JavaScript files in the same directory and run node test.js from the directory in a terminal/command prompt. The tests simply show expected and calculated results. In order to keep things simple, no external library for testing was used. Therefore, there are no asserts, expects, shoulds or anything like that. Just simple console output.

"use strict";
// Configurable constants
const ASSUME_DAYTIME = true;
const DAY_START_HOUR = 6; // 6 am
const BLOCK_LENGTH = 0.5; // In hours, 30 minutes = 0.5 hours
const WORK_DAY = [8.5, 17]; // 8:30 to 5:00
const BREAK = [12, 13]; // 12:00 to 1:00
// Constants for program logic
const TIME_PARSE = /^([0-9]{1,2})\:([0-9]{1,2})$/;
const MERIDIAN = 12;
const MINUTES_TO_HOURS = 1/60;
const HOURS_TO_MINUTES = 60;
function timeStringToValue (timeString) {
let parseMatches = TIME_PARSE.exec(timeString);
let hour = parseInt(parseMatches[1]);
if (ASSUME_DAYTIME && hour < DAY_START_HOUR) {
hour += MERIDIAN;
}
let minute = parseInt(parseMatches[2]);
return hour + minute*MINUTES_TO_HOURS;
}
function timeValueToString (timeValue) {
let hour = parseInt(timeValue);
let minute = parseInt( (timeValue - hour)*HOURS_TO_MINUTES );
let minuteString = ["0"].concat(minute.toString().split("")).slice(-2).join("");
if (ASSUME_DAYTIME && hour > MERIDIAN) {
hour -= MERIDIAN;
}
return `${hour}:${minuteString}`;
}
function mergeIntervals (arrayOfIntervals) {
let merged = [arrayOfIntervals[0]];
for (let i = 1; i < arrayOfIntervals.length; i++) {
let lastInterval = merged[merged.length-1];
let currentInterval = arrayOfIntervals[i];
if (lastInterval[1] < currentInterval[0]) {
merged.push(arrayOfIntervals[i]);
} else if (lastInterval[1] < currentInterval[1]) {
lastInterval[1] = currentInterval[1];
merged.pop();
merged.push(lastInterval);
}
}
return merged;
}
function team_availability (timeBlocks) {
let availableTimes = [];
// Clone timeBlocks so original is not modified
let appointments = timeBlocks.slice(0);
// Convert time strings to numeric values
appointments.forEach(appointment => {
appointment[0] = timeStringToValue(appointment[0]);
appointment[1] = timeStringToValue(appointment[1]);
});
// Add lunch hour break to appointments
appointments.push(BREAK.slice(0));
// Sort the appointments to make finding open blocks easier
appointments.sort((blockA, blockB) => blockA[0] > blockB[0] );
// Merge appointment intervals
let mergedAppointments = mergeIntervals(appointments);
// Loop over time blocks, starting with beginning of work day.
let time = WORK_DAY[0];
let apptIdx = 0;
while ( time < WORK_DAY[1] ) {
let appointmentStart, appointmentEnd;
// If the index of the appointments array is still valid, compare with
// the next appointment.
if (apptIdx < mergedAppointments.length) {
appointmentStart = mergedAppointments[apptIdx][0];
appointmentEnd = mergedAppointments[apptIdx][1];
// If there are no more appointments, compare with the end of the work day.
} else {
appointmentStart = WORK_DAY[1];
appointmentEnd = WORK_DAY[1];
}
if (time+BLOCK_LENGTH < appointmentStart) {
availableTimes.push([timeValueToString(time), timeValueToString(time+BLOCK_LENGTH)]);
time += BLOCK_LENGTH;
} else if (time+BLOCK_LENGTH == appointmentStart) {
availableTimes.push([timeValueToString(time), timeValueToString(time+BLOCK_LENGTH)]);
time = appointmentEnd;
apptIdx++;
} else if (time+BLOCK_LENGTH > appointmentStart) {
time = appointmentEnd;
apptIdx++;
} else {
apptIdx++;
}
}
return availableTimes;
}
module.exports = team_availability;
"use strict";
const team_availability = require("./team_availability");
let testData01 = [
["9:00", "11:30"],
["10:00", "11:00"],
["2:30", "3:30"],
["2:30", "3:00"],
["9:00", "12:30"]
];
let expectedResults01 = [
[ "8:30", "9:00" ],
[ "1:00", "1:30" ],
[ "1:30", "2:00" ],
[ "2:00", "2:30" ],
[ "3:30", "4:00" ],
[ "4:00", "4:30" ],
[ "4:30", "5:00" ]
];
console.log( "Test 1\n\tExpected\n", expectedResults01, "\n\tActual\n", team_availability(testData01), "\n\n" );
let testData02 = [
["10:30", "11:30"],
["10:00", "11:00"],
["12:30", "3:30"],
["2:30", "3:00"],
["9:00", "12:30"]
];
let expectedResults02 = [
[ "8:30", "9:00" ],
[ "3:30", "4:00" ],
[ "4:00", "4:30" ],
[ "4:30", "5:00" ]
];
console.log( "Test 2\n\tExpected\n", expectedResults02, "\n\tActual\n", team_availability(testData02), "\n\n" );
let testData03 = [
["8:30", "11:30"],
["10:00", "11:00"],
["12:30", "3:30"],
["2:30", "3:00"],
["4:15", "5:30"]
];
let expectedResults03 = [ [ "11:30", "12:00" ], [ "3:30", "4:00" ] ];
console.log( "Test 3\n\tExpected\n", expectedResults03, "\n\tActual\n", team_availability(testData03), "\n\n" );
let testData04 = [
["9:00", "9:30"],
["9:00", "11:30"],
["10:00", "11:00"],
["2:30", "3:00"],
["2:30", "3:30"]
];
let expectedResults04 = [
[ "8:30", "9:00" ],
[ "11:30", "12:00" ],
[ "1:00", "1:30" ],
[ "1:30", "2:00" ],
[ "2:00", "2:30" ],
[ "3:30", "4:00" ],
[ "4:00", "4:30" ],
[ "4:30", "5:00" ]
];
console.log( "Test 4\n\tExpected\n", expectedResults04, "\n\tActual\n", team_availability(testData04), "\n\n" );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment