Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 243 additions & 4 deletions robbery.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,161 @@
* Сделано задание на звездочку
* Реализовано оба метода и tryLater
*/
const isStar = true;
const isStar = false;

const WEEK = ['ПН', 'ВТ', 'СР', 'ЧТ', 'ПТ', 'СБ', 'ВС'];

class WeekDay {
constructor(weekDay) {
this.day = weekDay;
this.number = WEEK.indexOf(this.day);
}

addDays(daysCount) {
this.number += daysCount;
this.number %= 7;
this.day = WEEK[this.number];
}
}

class TimeStamp {
constructor(weekDay, hours, minutes, timeZone) {
this.weekDay = weekDay ? new WeekDay(weekDay) : weekDay;
this.hours = hours;
this.minutes = minutes;
this.timeZone = timeZone;
}

shift(timeShift) {
this.timeZone += timeShift;
this.hours += timeShift;
if (this.hours >= 24 || this.hours < 0) {
this.weekDay.addDays(this.hours < 0 ? -1 : 1);
this.hours %= 24;
}

return this;
}

between(time1, time2) {
if (!(time1 instanceof TimeStamp && time2 instanceof TimeStamp)) {
throw new TypeError();
}

return TimeStamp.compare(this, time1) >= 0 && TimeStamp.compare(this, time2) <= 0;
}

static compare(time1, time2) {
if (!(time1 instanceof TimeStamp && time2 instanceof TimeStamp)) {
throw new TypeError();
}
let comparator = time1.weekDay.number - time2.weekDay.number;
comparator = comparator !== 0 ? comparator : time1.hours - time2.hours;
comparator = comparator !== 0 ? comparator : time1.minutes - time2.minutes;

return comparator;
}

static max(time1, time2) {
if (!(time1 instanceof TimeStamp && time2 instanceof TimeStamp)) {
throw new TypeError();
}
if (TimeStamp.compare(time1, time2) >= 0) {
return time1;
}

return time2;
}

static min(time1, time2) {
if (!(time1 instanceof TimeStamp && time2 instanceof TimeStamp)) {
throw new TypeError();
}
if (TimeStamp.compare(time1, time2) <= 0) {
return time1;
}

return time2;
}

static parse(timeStamp) {
const splitTimeStamp = timeStamp.split(/[ +:]/).reverse();

return new TimeStamp(
splitTimeStamp[3],
parseInt(splitTimeStamp[2]),
parseInt(splitTimeStamp[1]),
parseInt(splitTimeStamp[0])
);
}
}

class TimeInterval {
constructor(from, to) {
if (!(from instanceof TimeStamp && to instanceof TimeStamp)) {
throw new TypeError();
}
this.from = from;
this.to = to;
}

shift(timeShift) {
this.from.shift(timeShift);
this.to.shift(timeShift);

return this;
}

get duration() {
return ((this.to.weekDay.number * 24 + this.to.hours) * 60 + this.to.minutes) -
((this.from.weekDay.number * 24 + this.from.hours) * 60 + this.from.minutes);
}

static areIntersected(time1, time2) {
if (!(time1 instanceof TimeInterval && time2 instanceof TimeInterval)) {
throw new TypeError();
}

return time1.from.between(time2.from, time2.to) || time1.to.between(time2.from, time2.to) ||
time2.from.between(time1.from, time1.to) || time2.to.between(time1.from, time1.to);
}

static parse(timeInterval) {
return new TimeInterval(
TimeStamp.parse(timeInterval.from),
TimeStamp.parse(timeInterval.to)
);
}
}

class GangSchedule {
constructor(schedule) {
for (let robber in schedule) {
if (schedule.hasOwnProperty(robber)) {
this[robber] = schedule[robber];
}
}
}

forEachRobber(func, ...args) {
for (let robber in this) {
if (this.hasOwnProperty(robber)) {
this[robber] = func(this[robber], ...args);
}
}
}

static parse(schedule) {
const gangSchedule = {};
for (const robber in schedule) {
if (schedule.hasOwnProperty(robber)) {
gangSchedule[robber] = schedule[robber].map(time => TimeInterval.parse(time));
}
}

return new GangSchedule(gangSchedule);
}
}

/**
* @param {Object} schedule – Расписание Банды
Expand All @@ -15,16 +169,31 @@ const isStar = true;
* @returns {Object}
*/
function getAppropriateMoment(schedule, duration, workingHours) {
console.info(schedule, duration, workingHours);
const bankSchedule = ['ПН', 'ВТ', 'СР'].map(day => new TimeInterval(
TimeStamp.parse(`${day} ${workingHours.from}`),
TimeStamp.parse(`${day} ${workingHours.to}`)
));
const bankTimeZone = bankSchedule[0].from.timeZone;

const gangSchedule = GangSchedule.parse(schedule);
gangSchedule.forEachRobber(translateScheduleToTimeZone, bankTimeZone);
gangSchedule.forEachRobber(findFreeTimeInPeriod, new TimeInterval(
new TimeStamp('ПН', 0, 0, bankTimeZone),
new TimeStamp('СР', 23, 59, bankTimeZone)
));

const appropriateMoments = getAppropriateMoments(mergeSchedules(gangSchedule, bankSchedule))
.filter(moment => moment.duration >= duration);

return {
moments: appropriateMoments,

/**
* Найдено ли время
* @returns {Boolean}
*/
exists: function () {
return false;
return this.moments.length !== 0;
},

/**
Expand All @@ -34,7 +203,15 @@ function getAppropriateMoment(schedule, duration, workingHours) {
* @returns {String}
*/
format: function (template) {
return template;
if (!this.exists()) {
return '';
}
const start = this.moments[0].from;

return template
.replace(/%HH/, start.hours.toString().padStart(2, '0'))
.replace(/%MM/, start.minutes.toString().padStart(2, '0'))
.replace(/%DD/, start.weekDay.day);
},

/**
Expand All @@ -44,10 +221,72 @@ function getAppropriateMoment(schedule, duration, workingHours) {
*/
tryLater: function () {
return false;
// if (!this.exists()) {
// return false;
// }
}
};
}

function translateScheduleToTimeZone(schedule, targetTimeZone) {
const timeZone = schedule[0].from.timeZone;
if (timeZone !== targetTimeZone) {
const timeShift = targetTimeZone - timeZone;

return schedule.map(timeInterval => timeInterval.shift(timeShift));
}

return schedule;
}

function findFreeTimeInPeriod(schedule, timePeriod) {
schedule.sort((a, b) => TimeStamp.compare(a.from, b.from));
const freeTimes = [];
freeTimes.push({ from: timePeriod.from });
schedule.forEach(timeInterval => {
freeTimes[freeTimes.length - 1].to = timeInterval.from;
freeTimes.push({ from: timeInterval.to });
});
freeTimes[freeTimes.length - 1].to = timePeriod.to;

return freeTimes.map(interval => new TimeInterval(interval.from, interval.to));
}

function mergeSchedules(gangSchedule, bankSchedule) {
return Object.keys(gangSchedule)
.map(robber => gangSchedule[robber])
.concat([bankSchedule]);
}

function getAppropriateMoments(schedule) {
let moments = schedule[0];
for (let i = 1; i < schedule.length; i++) {
const currentMoments = [];
const currentSchedule = schedule[i];
for (let j = 0; j < currentSchedule.length; j++) {
const timeInterval = currentSchedule[j];
currentMoments.push(...trimByHours(moments, timeInterval));
}
moments = currentMoments;
}

return moments;
}

function trimByHours(schedule, hours) {
const trimmedSchedule = [];
schedule.forEach(timeInterval => {
if (TimeInterval.areIntersected(timeInterval, hours)) {
trimmedSchedule.push(new TimeInterval(
TimeStamp.max(timeInterval.from, hours.from),
TimeStamp.min(timeInterval.to, hours.to)
));
}
});

return trimmedSchedule;
}

module.exports = {
getAppropriateMoment,

Expand Down
98 changes: 98 additions & 0 deletions robbery.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,104 @@ describe('robbery.getAppropriateMoment()', () => {
);
});

it('ВТ у всех свободен', () => {
const moment = robbery.getAppropriateMoment({
Danny: [
{ from: 'ПН 10:00+5', to: 'ПН 17:00+5' }
],
Rusty: [
{ from: 'ПН 10:00+5', to: 'ПН 17:00+5' }
],
Linus: [
{ from: 'СР 10:00+5', to: 'СР 17:00+5' }
]
}, 120, { from: '12:00+5', to: '14:00+5' });

assert.ok(moment.exists());
assert.strictEqual(
moment.format('%DD %HH:%MM'),
'ВТ 12:00'
);
});

it('вычитаем время для перевода в часовой пояс банка', () => {
const moment = robbery.getAppropriateMoment({
Danny: [
{ from: 'ПН 14:00+7', to: 'ПН 19:00+7' },
{ from: 'ВТ 15:00+7', to: 'ВТ 18:00+7' }
],
Rusty: [
{ from: 'ПН 11:30+5', to: 'ПН 16:30+5' },
{ from: 'ВТ 13:00+5', to: 'ВТ 16:00+5' }
],
Linus: [
{ from: 'ПН 09:00+3', to: 'ПН 14:00+3' },
{ from: 'ПН 21:00+3', to: 'ВТ 09:30+3' },
{ from: 'СР 09:30+3', to: 'СР 15:00+3' }
]
},
90,
{ from: '10:00+5', to: '18:00+5' });

assert.ok(moment.exists());
assert.strictEqual(
moment.format('%DD %HH:%MM'),
'ВТ 11:30'
);
});

it('перевод времени изменяет день недели', () => {
const moment = robbery.getAppropriateMoment(
{
Timmy: [
{ from: 'ПН 12:00+5', to: 'ПН 17:00+5' },
{ from: 'ВТ 13:00+5', to: 'ВТ 16:00+5' }
],
Ben: [
{ from: 'ПН 11:30+5', to: 'ПН 16:30+5' },
{ from: 'ВТ 13:00+5', to: 'ВТ 16:00+5' }
],
Loki: [
{ from: 'ПН 09:00+3', to: 'ПН 14:00+3' },
{ from: 'ПН 23:00+3', to: 'ВТ 09:30+3' },
{ from: 'СР 09:30+3', to: 'СР 15:00+3' }
]
},
90,
{ from: '10:00+5', to: '18:00+5' }
);

assert.ok(moment.exists());
});

it('адекватное написание времени', () => {
const moment = robbery.getAppropriateMoment(
{
Danny: [
{ from: 'ПН 12:00+5', to: 'ПН 17:00+5' },
{ from: 'ВТ 13:00+5', to: 'ВТ 16:00+5' }
],
Rusty: [
{ from: 'ПН 11:30+5', to: 'ПН 16:30+5' },
{ from: 'ВТ 13:00+5', to: 'ВТ 16:00+5' }
],
Linus: [
{ from: 'ПН 09:00+3', to: 'ПН 14:00+3' },
{ from: 'ПН 21:00+3', to: 'ВТ 09:30+3' },
{ from: 'СР 09:30+3', to: 'СР 15:00+3' }
]
},
90,
{ from: '09:00+5', to: '18:00+5' }
);

assert.ok(moment.exists());
assert.strictEqual(
moment.format('%DD %HH:%MM'),
'ПН 09:00'
);
});

if (robbery.isStar) {
it('должен перемещаться на более поздний момент [*]', () => {
const moment = getMomentFor(90);
Expand Down