diff --git a/robbery.js b/robbery.js index 4a8309d..1c99814 100644 --- a/robbery.js +++ b/robbery.js @@ -4,18 +4,178 @@ * Сделано задание на звездочку * Реализовано оба метода и tryLater */ -exports.isStar = true; +exports.isStar = false; + +var HOURS_IN_DAY = 24; +var MINUTES_IN_HOUR = 60; +var MINUTES_IN_DAY = HOURS_IN_DAY * 60; +var DAYS_OF_THE_WEEK = ['ПН', 'ВТ', 'СР']; +function DayOfTheWeek(dayText) { + this.days = DAYS_OF_THE_WEEK; + this.getNumber = function () { + return this.days.indexOf(this.text); + }; + this.text = dayText; + this.addDays = function (days) { + this.number += days % 7; + }; + this.getText = function () { + return this.days[this.number]; + }; + + return this; +} +function DateTime(dateTimeString) { + this.day = new DayOfTheWeek(dateTimeString.slice(0, 2)); + this.hours = parseInt(dateTimeString.slice(3, 5)); + this.minutes = parseInt(dateTimeString.slice(6, 8)); + this.timezone = parseInt(dateTimeString.split('+')[1]); + this.addMinutes = function (minutes) { + var daysToAdd = Math.floor(minutes / MINUTES_IN_DAY); + minutes -= MINUTES_IN_DAY * daysToAdd; + var hoursToAdd = Math.floor(minutes / MINUTES_IN_HOUR); + minutes -= MINUTES_IN_HOUR * hoursToAdd; + var minutesToAdd = minutes; + this.minutes += minutesToAdd; + this.hours += hoursToAdd; + this.day.addDays(daysToAdd); + + return; + }; + this.convertToTimezone = function (timezone) { + var delta = timezone - this.timezone; + this.addMinutes(delta * MINUTES_IN_HOUR); + this.timezone = timezone; + }; + this.getMinutesCountFromWeekStart = function () { + return MINUTES_IN_DAY * this.day.getNumber() + MINUTES_IN_HOUR * this.hours + this.minutes; + }; + this.compareTo = function (other) { + return this.getMinutesCountFromWeekStart() - other.getMinutesCountFromWeekStart(); + }; + + return this; +} +function Interval(start, end) { + this.start = start; + this.end = end; + this.intersect = function (otherInterval) { + var intersectionStart = this.start.compareTo(otherInterval.start) > 0 + ? this.start : otherInterval.start; + var intersectionEnd = this.end.compareTo(otherInterval.end) < 0 + ? this.end : otherInterval.end; + if (intersectionEnd.compareTo(intersectionStart) < 0) { + return null; + } + + return new Interval(intersectionStart, intersectionEnd); + }; + this.union = function (otherInterval) { + var intersectionStart = this.start.compareTo(otherInterval.start) < 0 + ? this.start : otherInterval.start; + var intersectionEnd = this.end.compareTo(otherInterval.end) > 0 + ? this.end : otherInterval.end; + + return new Interval(intersectionStart, intersectionEnd); + }; + + return this; +} +function normalizeSchedule(memberSchedule, workingInterval) { + var newSchedule = []; + memberSchedule.forEach(function (busynessInfo) { + var bankTimezone = workingInterval.start.timezone; + var start = new DateTime(busynessInfo.from); + var end = new DateTime(busynessInfo.to); + start.convertToTimezone(bankTimezone); + end.convertToTimezone(bankTimezone); + var busynessInterval = new Interval(start, end); + DAYS_OF_THE_WEEK.forEach(function (dayOfTheWeek) { + workingInterval.start.day = new DayOfTheWeek(dayOfTheWeek); + workingInterval.end.day = new DayOfTheWeek(dayOfTheWeek); + var intersection = busynessInterval.intersect(workingInterval); + if (intersection !== null) { + newSchedule.push(intersection); + } + }); + }); + + return newSchedule; +} +function getNewAllIntervals(allIntervals, lastIntervalEnd, currentInterval) { + var newAllIntervals = []; + for (var i = 0; i < allIntervals.length; i += 1) { + var interval = allIntervals[i]; + if (currentInterval.intersect(interval) === null) { + newAllIntervals.push(interval); + lastIntervalEnd = currentInterval.end.compareTo(lastIntervalEnd) > 0 + ? currentInterval.end : lastIntervalEnd; + } else { + currentInterval = currentInterval.union(interval); + } + } + + return { intervals: newAllIntervals, lastIntervalEnd: lastIntervalEnd }; +} +function findCompatibleInterval(allIntervals, workingInterval, duration) { + var result = null; + var lastIntervalEnd = workingInterval.start; + var currentInterval = allIntervals[0]; + while (allIntervals.length > 0 || result !== null) { + currentInterval = allIntervals[0]; + allIntervals = allIntervals.splice(1); + if (currentInterval.start.compareTo(lastIntervalEnd) >= duration) { + return lastIntervalEnd; + } + var newAllIntervalsResult = getNewAllIntervals(allIntervals, + lastIntervalEnd, currentInterval); + var newAllIntervals = newAllIntervalsResult.intervals; + lastIntervalEnd = newAllIntervalsResult.lastIntervalEnd; + allIntervals = newAllIntervals.sort(function (a, b) { + return a.start.compareTo(b.start); + }); + } + + return result; +} +function getRobberyStart(schedule, workingInterval, duration, day) { + workingInterval.start.day = new DayOfTheWeek(day); + workingInterval.end.day = new DayOfTheWeek(day); + var allIntervals = schedule.Danny.concat(schedule.Rusty).concat(schedule.Linus); + allIntervals = allIntervals.filter(function (interval) { + return interval.start.day.text === day; + }); + allIntervals = allIntervals.sort(function (a, b) { + return a.start.compareTo(b.start); + }); + + return findCompatibleInterval(allIntervals, workingInterval, duration); +} /** * @param {Object} schedule – Расписание Банды * @param {Number} duration - Время на ограбление в минутах * @param {Object} workingHours – Время работы банка - * @param {String} workingHours.from – Время открытия, например, "10:00+5" - * @param {String} workingHours.to – Время закрытия, например, "18:00+5" + * @param {String} workingHours.from – Время открытия, например, '10:00+5' + * @param {String} workingHours.to – Время закрытия, например, '18:00+5' * @returns {Object} */ exports.getAppropriateMoment = function (schedule, duration, workingHours) { console.info(schedule, duration, workingHours); + var bankStart = new DateTime('ПН ' + workingHours.from); + var bankEnd = new DateTime('ПН ' + workingHours.to); + var workingInterval = new Interval(bankStart, bankEnd); + schedule.Danny = normalizeSchedule(schedule.Danny, workingInterval); + schedule.Rusty = normalizeSchedule(schedule.Rusty, workingInterval); + schedule.Linus = normalizeSchedule(schedule.Linus, workingInterval); + var robberyStart = null; + for (var i = 0; i < DAYS_OF_THE_WEEK.length; i += 1) { + var day = DAYS_OF_THE_WEEK[i]; + robberyStart = getRobberyStart(schedule, workingInterval, duration, day); + if (robberyStart !== null) { + break; + } + } return { @@ -24,18 +184,27 @@ exports.getAppropriateMoment = function (schedule, duration, workingHours) { * @returns {Boolean} */ exists: function () { - return false; + return robberyStart !== null; }, /** * Возвращает отформатированную строку с часами для ограбления * Например, - * "Начинаем в %HH:%MM (%DD)" -> "Начинаем в 14:59 (СР)" + * 'Начинаем в %HH:%MM (%DD)' -> 'Начинаем в 14:59 (СР)' * @param {String} template * @returns {String} */ format: function (template) { - return template; + if (robberyStart === null) { + return ''; + } + + return template + .replace('%DD', robberyStart.day.text) + .replace('%HH', robberyStart.hours < 10 + ? '0' + robberyStart.hours : robberyStart.hours) + .replace('%MM', robberyStart.minutes < 10 + ? '0' + robberyStart.minutes : robberyStart.minutes); }, /** diff --git a/test.html b/test.html new file mode 100644 index 0000000..5cc5c0b --- /dev/null +++ b/test.html @@ -0,0 +1,232 @@ + + + + + + + + \ No newline at end of file