Consecutive business days in Javascript (no weekends) - javascript

I need to do a 3 consecutive business day check against an array of dates in javascript. I have it working with any 3 days but I can't figure out for the life of me how I can make this work with the weekend breaking up the consecutive days.
NOTE - This requires moment() library (https://momentjs.com)
My code that works for 3 actual consecutive days:
var allDates = ["2017-03-07", "2017-03-09", "2017-03-10", "2017-03-13", "2017-03-15"];
var diff = 86400000;
var consecutive = 0;
allDates.sort(function(a,b){
return new Date(a) - new Date(b);
});
for (i = 2; i < allDates.length; i++) {
var d = moment(allDates[i], "YYYY-MM-DD").format('x');
var d1 = moment(allDates[i-1], "YYYY-MM-DD").format('x');
var d2 = moment(allDates[i-2], "YYYY-MM-DD").format('x');
if (d1 - d2 == diff && d - d2 == diff * 2) {
consecutive = 1;
break;
}
}
As you can see by the calendar above, I would like March 9th, 10th and 13th to act as 3 consecutive business days. Anyone have any feedback that can guide me onto a right path?
Thanks in advance

You can use the javascript Date object to figure out which days are weekends.
Here is the documentation
And a quick example:
var d1 = new Date("2017-03-07");
var d2 = new Date("2017-03-08");
d.getDay() // this will return 1, which corresponds with Monday. 2 is Tuesday, 3 is Wednesday, etc.
So a small function to tell if 2 dates are consecutive, even if separated by a weekend, could be:
var millisecondsInDay = 86400000;
function datesAreConsecutiveBusinessDays(date1, date2){
// If date1 is a Friday and date2 is a Monday
if (date1.getDay() === 5 && date2.getDay() === 1){
// And the dates are 2 days apart
date2.valeOf() - date1.valueOf() === millisecondsInDay * 2
return true;
} else if (date2.valeOf() - date1.valueOf() === millisecondsInDay){
return true;
} else {
return false;
}
}
So with this method, you go through your list of dates and call it on every two that are next to eachother.
var consecutive = 0;
var allDates = ["2017-03-07", "2017-03-09", "2017-03-10", "2017-03-13", "2017-03-15"];
for (var i = 0; i < allDates.length - 1; i++){
if (datesAreConsecutiveBusinessDays(new Date(allDates[i]), new Date(allDates[i+1])){
consecutive++;
}
// If you want to start over if you hit a nonconsecutive date, add this else:
else {
consecutive = 0; // this will start the counter over
}
}
You might need to tweak the solution a bit depending on how consecutive is calculated, but this should be enough to get you started.

This can be solved by writing a function which determines if any 2 momentjs instances are either 1 day different, or if one is friday and the other is monday (but must only be 3 days apart). Simply, this does it:
function isConsecutive(a,b){
return b.diff(a,"days") == 1
|| (a.weekday() == 5 && b.weekday() == 1 && b.diff(a,"days") == 3);
}
You can then take your array of dates, turn them all to momentJS instances, order them and iterate through calling the above method for each pair. Keep a running total of the count:
function countConsecutiveDays(arr){
var momentDates = arr.map(function(d){
return moment(d, "YYYY-MM-DD") ;
}).sort(function(a,b){return a.diff(b);})
var count = 0;
for(var i=1;i<momentDates.length;i++){
if(isConsecutive(momentDates[i-1],momentDates[i]))
count++;
}
return count+1;
}
A working example is below
function countConsecutiveDays(arr){
var momentDates = arr.map(function(d){
return moment(d, "YYYY-MM-DD") ;
}).sort(function(a,b){return a.diff(b);})
var count = 0;
for(var i=1;i<momentDates.length;i++){
if(isConsecutive(momentDates[i-1],momentDates[i]))
count++;
}
return count+1;
}
function isConsecutive(a,b){
return b.diff(a,"days") == 1
|| (a.weekday() == 5 && b.weekday() == 1 && b.diff(a,"days") == 3);
}
console.log(countConsecutiveDays( ["2017-03-07", "2017-03-09", "2017-03-10", "2017-03-13", "2017-03-15"]));
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment.js"></script>

This seems to be working from all my tests so far...unless someone can point out an issue or bug.
NOTE - This requires moment() library (https://momentjs.com)
var allDates = ["2017-03-07", "2017-03-09", "2017-03-10", "2017-03-13", "2017-03-15"];
var consecutive = 0;
allDates.sort(function(a,b){
return new Date(a) - new Date(b);
});
for (i = 2; i < allDates.length; i++) {
var d = moment(allDates[i], "YYYY-MM-DD");
var d1 = moment(allDates[i-1], "YYYY-MM-DD");
var d2 = moment(allDates[i-2], "YYYY-MM-DD");
if (d1.diff(d2,"days") == 1 && d.diff(d2,"days") == 2 ||
d2.weekday() == 5 && d1.weekday() == 1 && d1.diff(d2,"days") == 3 && d.diff(d1,"days") == 1 ||
d1.weekday() == 5 && d.weekday() == 1 && d.diff(d1,"days") == 3 && d1.diff(d2,"days") == 1) {
consecutive = 1;
break;
}
}
console.log('dates are consecutive: ' + consecutive)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment.js"></script>

Related

MultipleDatePicker - How to show specific range of years in dropdown?

I have started learning Multipledatepicker and I am making it customize. I have a scenario in which I am taking the calendar to the last date selected for any year/month. Now it works good but now I need to show years in the dropdown from 1960-2028.
But the official documentation says 'When you select a year it'll re-render the select to always have the same range of the selected year'. I don't want this to pick years around the selected year.
<multiple-date-picker change-year-past="5" change-year-future="10"/>
How can I do this?
Js
getYearsForSelect = function () {
var now = moment(),
changeYearPast = Math.max(0, parseInt(scope.changeYearPast, 10) || 0),
changeYearFuture = Math.max(0, parseInt(scope.changeYearFuture, 10) || 0),
min = moment(scope.month).subtract(changeYearPast, 'year'),
max = moment(scope.month).add(changeYearFuture, 'year'),
result = [];
max.add(1, 'year');
for (var m = moment(min); max.isAfter(m, 'YEAR'); m.add(1, 'year')) {
if ((!scope.disallowBackPastMonths || (m.isAfter(now, 'year') || m.isSame(now, 'year'))) && (!scope.disallowGoFuturMonths || (m.isBefore(now, 'year') || m.isSame(now, 'year')))) {
result.push(m.format('YYYY'));
}
}
return result;
};
I came up with this :)
getYearsForSelect = function () {
// var now = moment(),
// changeYearPast = Math.max(0, parseInt(scope.changeYearPast, 10) || 0),
// changeYearFuture = Math.max(0, parseInt(scope.changeYearFuture, 10) || 0),
// min = moment(scope.month).subtract(changeYearPast, 'year'),
// max = moment(scope.month).add(changeYearFuture, 'year'),
var result = [];
// max.add(1, 'year');
for (var m = 1960; m<=2028 ; m++) {
// if ((!scope.disallowBackPastMonths || (m.isAfter(now, 'year') || m.isSame(now, 'year'))) && (!scope.disallowGoFuturMonths || (m.isBefore(now, 'year') || m.isSame(now, 'year')))) {
result.push(m);
// }
}
return result;
}

Comparison with parsed numbers fails

I'm trying to compare an array of times with the current local time. But comparing the numbers fails for some values. As far as I could detect, it is not always failing the same time string. Im really out of ideas what the reason could be.
function lastPassedTimeIndex() {
const d = new Date();
const nowHr = d.getHours();
const nowMin = d.getMinutes();
let lastPassedTimeIndex = 0;
const currentTimes = ['05:20', '06:57', '12:46', '15:48', '18:30', '20:02'];
for (const time of currentTimes) {
const t = time.split(':');
const printHr = parseInt(t[0], 10);
const printMin = parseInt(t[1], 10);
if ((printHr < nowHr) && (printMin < nowMin)) {
console.log(time, 'previous from now');
lastPassedTimeIndex++;
}
else if ((printHr === nowHr) && (printMin < nowMin)) {
console.log(time, 'previous from now, but hour is correct');
lastPassedTimeIndex++;
}
else if ((printHr === nowHr) && (printMin >= nowMin)) {
console.log(time, 'is upcoming or now and hour is correct');
lastPassedTimeIndex++;
}
else if ((printHr > nowHr)) {
console.log(time, 'is upcoming, hour is larger');
}
else {
console.log('I have no idea!');
}
}
console.log('LastPassedTimeIndex ', lastPassedTimeIndex);
return lastPassedTimeIndex;
}
This is my console output:
> I have no idea!
> I have no idea!
> 12:46 is upcoming, hour is larger
> 15:48 is upcoming, hour is larger
> 18:30 is upcoming, hour is larger
> 20:02 is upcoming, hour is larger
> LastPassedTimeIndex 0
I only see one error, fixed this way:
const printHr = parseInt(t[0], 10);
const printMin = parseInt(t[1], 10);
if ((printHr < nowHr) /* Take out what was here */) {
console.log(time, 'previous from now');
lastPassedTimeIndex++;
}
The minutes have to be compared only when the hours are the same, or just compare as strings:
var time = new Date().toTimeString().slice(0, 5)
var times = ['05:20', '06:57', '12:46', '15:48', '18:30', '20:02']
console.log( time, times.findIndex(t => t >= time) - 1 )

How to load a site depending on hours and minutes

I have four screens that I want to display in the following order.
Screen1(2 seconds) -> Screen2 (2 seconds) -> Screen3 (2 Seconds)
I also have a fourth screen which should only show when the time is between 05:55-06:05 and 17:55-18:05
In order to accomplish this my code looks like this till now:
function timecondition() {
var hours = new Date();
var minutes = new Date();
var h = hours.getHours();
var m = minutes.getMinutes();
var timecondition;
if((h == 5 && m >= 55) || (h == 6 && m <= 5) || (h == 17 && m >= 55) || (h == 18 && m <= 5)) {
timecondition = true;
}
else {
timecondition = false;
}
return timecondition;
}
$(document).ready(
function() {
setInterval(function() {
setTimeout(function() {
if(timecondition()) {
$('#show').load("http://localhost:8084/test/screen4");
}
else {
$('#show').load("http://localhost:8084/test/screen1");
}
}, 2000);
setTimeout(function() {
if(timecondition()) {
$('#show').load("http://localhost:8084/test/screen4");
}
else {
$('#show').load("http://localhost:8084/test/screen2");
}
}, 4000);
setTimeout(function() {
if(timecondition()) {
$('#show').load("http://localhost:8084/test/screen4");
}
else {
$('#show').load("http://localhost:8084/test/screen3");
}
}, 6000);
}, 6000);
}
);
Unfortunately it doesnt work like I want it to be.
When I start the webapplication at 05:54 the sequence(screen1->screen2->screen3)
But once the clock hits 05:55 it won't display the fourth screen, like it was in my intention.
When I start the application within the timecondition eg. at 05:56 it shows the fourth screen, but won't leave screen4 when the timecondition is not true anymore a few minutes later.
Is it because I need dynamic functions?
Couple of potential bugs there.
1. You are taking the time twice
Every time you call new Date() you are taking a snapshot of an instant. In this case they are only milliseconds away from each other, but it's a bug nonetheless.
var hours = new Date();
var minutes = new Date();
One object should be enough:
var now = new Date();
var h = now.getHours();
var m = now.getMinutes()
2. Your time condition is wrong
var timecondition;
if((h == 5 || h == 6 || h == 17 || h == 18) && (m >= 55 || m <= 05)) {
timecondition = true;
}
else {
timecondition = false;
}
return timecondition;
There is two problems with this:
a) It is hiding the function name from within. This is just a minor bug and doesn't affect the functionality here yet.
b) You are checking for hours and minutes independently. This IS a serious bug because it doesn't comply with your business logic.
That whole code above can be smarter rewritten as:
h = h % 12
return (h == 5 && m >= 55) || (h == 6 && m <= 5)
3. You reload the page every few seconds
The second argument to setInterval and setTimeout respectively is in milliseconds. So you are issuing a load every 2 seconds.
4. You are nesting timeouts within intervals
This basically means that every six seconds you are setting a timer for the next 2, 4 and 6 seconds. This is not really a bug, but unnecessarily complex. Why not set one interval for running every two seconds?
Here's some refactored and hopefully fixed code. Didn't try it out yet, though.
function slideshow() {
var screens = [
"http://localhost:8084/test/screen1",
"http://localhost:8084/test/screen2",
"http://localhost:8084/test/screen3"
];
var specialScreen = "http://localhost:8084/test/screen4";
// Contains the index of currently shown screen or -1
// when the special screen is shown
var currentScreen = 0;
// Cache the element here so we don't need to search for it every two seconds
var show = $('#show');
function timecondition() {
var now = new Date();
var h = now.getHours();
var m = now.getMinutes();
h = h % 12;
return (h == 5 && m >= 55) || (h == 6 && m <= 5);
}
function update() {
if (timecondition()) {
if (currentScreen != -1) {
show.load(specialScreen);
currentScreen = -1;
}
return;
}
currentScreen = (currentScreen + 1) % screens.length;
show.load(screens[currentScreen]);
}
setInterval(update, 2000);
}
$(document).ready(slideshow);
If you wanted different durations for the screens, you could do it roughly like this:
function slideshow() {
var screens = [
{url: "http://localhost:8084/test/screen1", t: 2000},
{url: "http://localhost:8084/test/screen2", t: 3000},
{url: "http://localhost:8084/test/screen3", t: 10000}
];
var specialScreen = "http://localhost:8084/test/screen4";
// Contains the index of currently shown screen or -1
// when the special screen is shown
var currentScreen = 0;
// Cache the element here so we don't need to search for it every two seconds
var show = $('#show');
function timecondition() {
var now = new Date();
var h = now.getHours();
var m = now.getMinutes();
h = h % 12;
return (h == 5 && m >= 55) || (h == 6 && m <= 5);
}
var step = 1000;
var screenTimer = 0;
function update() {
if (timecondition()) {
if (currentScreen != -1) {
show.load(specialScreen);
currentScreen = -1;
}
return;
}
if ((screenTimer += step) >= screeens[currentScreen].t) {
currentScreen = (currentScreen + 1) % screens.length;
show.load(screens[currentScreen].url);
screenTimer = 0;
}
}
setInterval(update, step);
}
$(document).ready(slideshow);

Inserting an event in an array of events with specific rules

I have a list of events on a given day. (events time is a moment object)
[
{ id: 1, start: '2017-05-01 05:00'},
{ id: 2, start: '2017-05-01 08:00'},
{ id: 3, start: '2017-05-01 14:00'},
{ id: 4, start: '2017-05-01 17:00'}
]
I need to add one event in this array so that the new event time is not less than 5am, is at least 3 hours after the previous event (if there is one) and 3h before the next event (if there is one). The new event should be planned on the same day.
In this case, it would insert the new event just after id = 2 and at 11am.
I started something but it ended up with a dozen of ifs and I got lost after a few hours thinking:
// for each event, check if there's time before or after (not less than 5am, not more than 11:59pm)
for(var i = 0; i < events.length; i++) {
var eventTime = events[i].start.clone();
var before = eventTime.clone();
var after = eventTime.clone();
before.subtract('hours', 3);
after.add('hours', 3);
if(i == 0 && !before.hour() < 5 && !before.isBefore(date, 'day')) {
// first event of the day, new time cannot be before 5am and cannot be on the previous day
hour = before.hour();
} else if(i == 0 && !after.isAfter(date, 'day')) {
// same as above but we test time + 3h
} else if(i == events.length - 1 && !after.isAfter(date, 'day')) {
// last event of the day, new time cannot be the next day (after 11:59pm)
hour = after.hour();
} else if (i > 0 && i < events.length - 1) {
// middle events
// new time should be at least 3H after previous event and 3H before next event
// ex: 5h, 8h, 12h ok but 5h, 8h, 10h not ok or 5h, 6h, 9h not ok
var previousEventTime = events[i-1].start.clone();
var nextEventTime = events[i+1].start.clone();
if(before.hour() >= previousEventTime.hour() && after.hour() <= nextEventTime.hour()) {
}
//
}
}
Here is a snippet that can be used:
var events = [
{ id: 1, start: 'Mon, 01 May 2017 05:00:00 +0200'},
{ id: 2, start: 'Mon, 01 May 2017 08:00:00 +0200'},
{ id: 3, start: 'Mon, 01 May 2017 14:00:00 +0200'},
{ id: 4, start: 'Mon, 01 May 2017 17:00:00 +0200'}
];
for(var i = 0; i < events.length; i++) {
var eventTime = moment(events[i].start).clone();
var before = moment(eventTime).clone();
var after = moment(eventTime).clone();
before.subtract(3, 'hours');
after.add(3, 'hours');
console.log(eventTime, before, after);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
After spending a day on this I came up with a "mathematical" solution.
In my array I want to insert an event y between an event x and an event z. (x being the previous event and z the next one)
Since I need 3 hours minimum between each event I figured:
x+3 <= y-3 || y+3 <= z-3
The one I implemented is a little bit different. I figured I needed at least 6 hours between 2 events to be able to insert a new event in the middle.
y-x > =6 || z-y >= 6
Since my brain is not far from burning out the code looks like this...
for(var i = 0; i < events.length; i++) {
var x = null;
var y = events[i].start.clone().hour();
var z = null;
if(i > 0) {
x = events[i-1].start.clone().hour();
}
if(i < events.length -1) {
z = events[i+1].start.clone().hour();
}
// first event or only event
if(i == 0) {
// check if removing 3h00 doesn't cross the 5am limit
if(y - 3 >= 5) {
hour = y - 3;
} else if(z-y >= 6) {
// check if there's enough time before next event
hour = y + 3;
}
}
// middle event
if(x && z) {
if (y-x >= 6) {
hour = y - 3;
} else if (z-y >= 6) {
hour = y + 3;
}
}
// last event (can also be first if only 1 element)
// with !hour we make sure that a "middle" event hour is not overriden if found above
if(!hour && i == events.length - 1 && i != 0) {
// check if adding 3h00 doesn't change day (23h00)
if(y + 3 <= 23) {
hour = y + 3;
} else if(y-x >= 6) {
// check if there's enough time after previous event
hour = y - 3;
}
}
}
return hour;
I'd be glad to accept an answer that improves on this algorithm.

Sort an array of objects by hour minutes in ascending order from earliest to latest

I have this code to sort an array of objects. The data in the objects is channel and time (hour, minutes). I want the sort to be by channels based on time from earliest to latest.
The channel data is accessed in this way:
channel_array[icount].data[0].hour
channel_array[icount].data[0].minutes
That data object array is like this and is already sorted:
[{hour:1, minutes:10},{hour:4, minutes:01}...]
Now all I need is to sort the channels from earliest to latest on the first element of the data array {hour:1, minutes: 10}. I do this with three nested loops. But this is not ideal. Is there a better way to do the sorting?
var current_time = new Date();
var current_hour = current_time.getHours();
var comp_hour = current_hour - 1;
for (var ih = 0; ih < 24; ih++) {
comp_hour += 1;
if (comp_hour == 24) { comp_hour = 0; }
for (var minutes = 0; minutes < 60; minutes++) {
for (var icount = 0; icount < channel_array.length; icount++) {
if (channel_array[icount].data.length > 0) {
var channel_hour = channel_array[icount].data[0].hour;
var channel_minutes = channel_array[icount].data[0].minutes;
var channel_phase = channel_array[icount].data[0].phase;
var next_day = channel_array[icount].data[0].next_day;
if (channel_phase.toLowerCase() == "pm" && channel_hour != 12) { channel_hour += 12; }
if ( parseInt(channel_hour) == parseInt(comp_hour) && parseInt(channel_minutes) == parseInt(minutes) && next_day != 1 ) {
channel_array_sort.push(channel_array[icount]);
}
}
}
}
}
Good lord, this is overcomplicated! How about just passing a custom comparator to Array.sort?
I'm honestly having a hard time figuring out exactly which array you are trying to sort, but in general, it would look something like this:
var input = [{hour:1, minutes:10},{hour:4, minutes: 1}, ...];
input.sort(function (a, b)
{
// compare hours first
if (a.hour < b.hour) return -1;
if (a.hour > b.hour) return 1;
// else a.hour === b.hour, so compare minutes to break the tie
if (a.minute < b.minute) return -1;
if (a.minute > b.minute) return 1;
// couldn't break the tie
return 0;
});
N.B. this performs an in-place sort, which means that the original array is modified. If that's not acceptable, just make a copy of the array before sorting it.
var input = /* same as before */;
var output = input.concat();
output.sort(function ()
{
// same as before
});
Starting point for the solution, from the OP:
channel_array_sort = channel_array.concat();
channel_array_sort.sort(function (a, b)
{
if (a.data == undefined || b.data == undefined) return 0;
if (a.data.length <= 0 || b.data.length <= 0) return 0;
// compare hours first
var a_hour = a.data[0].hour;
if (a.data[0].phase == "pm") a_hour += 12;
var b_hour = b.data[0].hour;
if (b.data[0].phase == "pm") b_hour += 12;
if (a_hour < b_hour) return -1;
if (a_hour > b_hour) return 1;
// else a.hour === b.hour, so compare minutes to break the tie
if (a.data[0].minutes < b.data[0].minutes) return -1;
if (a.data[0].minutes > b.data[0].minutes) return 1;
// couldn't break the tie
return 0;
});
var print_sort = JSON.stringify(channel_array_sort);
alert('print_sort b '+print_sort);

Categories

Resources