How do I get the current school hour and time remaining? - javascript

I have this function:
function getInfoSchoolTime() {
var date = new Date();
var schoolBellTime = ["8:10","9:02","9:54","9:59","10:51","11:43","11:58","12:48","13:35","13:40","14:10","15:02","15:54"];
var remainingTime, currentHour;
for (var i = 0; i < schoolBellTime.length-1; i++) {
var startTime = schoolBellTime[i].split(":");
var endTime = schoolBellTime[i+1].split(":");
if (parseInt(startTime[0]) >= date.getHours() && parseInt(startTime[1]) >= date.getMinutes())
if (parseInt(endTime[0]) <= date.getHours() && parseInt(endTime[1]) <= date.getMinutes()) {
currentHour = i;
remainingTime=(parseInt(endTime[1])-date.getMinutes()+60)%60;
break;
}
}
if (currentHour == undefined)
return {current: -1, remaining: "not available"};
return {current: currentHour, remaining: remainingTime};
}
var info = getInfoSchoolTime();
console.log(info.current, info.remaining);
I have the schoolBellTime array that contains the timestamps of my school bell (I know, my school has strange bell times, these timestamps includes playtimes and lunchtime), this function is meant to return the 1st hour/2nd hour/3rd hour ... and the minutes that remains to the next hour/breaktime.
I checked all the code and can't find the error, it keeps returning {current: -1, remaining: "not available"}

The function at the top: setDateTime() takes a date and a time, and constructs a date object for that time.
Then I updated your function, I convert start and end to times on the current day, and then check if date.getTime() occurs between them. Then I simply subtract date.getTime() from end, and convert the result to minutes from milliseconds.
var setDateTime = function(date, str) {
var sp = str.split(':');
date.setHours(parseInt(sp[0], 10));
date.setMinutes(parseInt(sp[1], 10));
return date;
}
function getInfoSchoolTime() {
var date = new Date();
var schoolBellTime = ["8:10", "9:02", "9:54", "9:59", "10:51", "11:43", "11:58", "12:48", "13:35", "13:40", "14:10", "14:10", "15:02", "15:54"];
var remainingTime, currentHour, currentPeriod;
for (var i = 0; i < schoolBellTime.length - 1; i++) {
start = setDateTime(new Date(), schoolBellTime[i])
end = setDateTime(new Date(), schoolBellTime[i + 1])
if (date.getTime() > start.getTime() && date.getTime() < end.getTime()) {
currentHour = i
remainingTime = end.getTime() - date.getTime()
currentPeriod = ([schoolBellTime[i], schoolBellTime[i+1]]).join('-')
}
}
return {current: currentHour, currentPeriod: currentPeriod, remaining: Math.round(remainingTime * 0.0000166667)}
}
console.log(getInfoSchoolTime())

Here's a somewhat different approach, both to the code and the API. It uses two helper functions. Each should be obvious with a single example: pad(7) //=> "07" and pairs(['foo', 'bar', 'baz', 'qux']) //=> [['foo', 'bar'], ['bar', 'baz'], ['baz', 'qux']].
The main function takes a list of bell times and returns a function which itself accepts a date object and returns the sort of output you're looking for (period, remaining time in period.) This API makes it much easier to test.
const pad = nbr => ('00' + nbr).slice(-2)
const pairs = vals => vals.reduce((res, val, idx) => idx < 1 ? res : res.concat([[vals[idx - 1], val]]), [])
const schoolPeriods = (schoolBellTime) => {
const subtractTimes = (t1, t2) => 60 * t1.hour + t1.minute - (60 * t2.hour + t2.minute)
const periods = pairs(schoolBellTime.map(time => ({hour: time.split(':')[0], minute: +time.split(':')[1]})))
return date => {
const current = {hour: date.getHours(), minute: date.getMinutes()}
if (subtractTimes(current, periods[0][0]) < 0) {
return {message: 'before school day'}
}
if (subtractTimes(current, periods[periods.length - 1][1]) > 0) {
return {message: 'after school day'}
}
const idx = periods.findIndex(period => subtractTimes(current, period[0]) >= 0 && subtractTimes(period[1], current) > 0)
const period = periods[idx]
return {
current: idx + 1,
currentPeriod: `${period[0].hour}:${pad(period[0].minute)} - ${period[1].hour}:${pad(period[1].minute)}`,
remaining: subtractTimes(period[1], current)
}
}
}
const getPeriod = schoolPeriods(["8:10","9:02","9:54","9:59","10:51","11:43","11:58","12:48","13:35","13:40","14:10","14:10","15:02","15:54"])
console.log("Using current time")
console.log(getPeriod(new Date()))
console.log("Using a fixed time")
console.log(getPeriod(new Date(2017, 11, 22, 14, 27))) // will Christmas break ever come?!
I made a random guess at the behavior you would want if the date is outside the period range.
Internally, it creates a list of period objects that look like
[{hour:9, minute: 59}, {hour: 10, minute: 51}]
Perhaps it would be cleaner if instead of a two-element array it was an object with start and end properties. That would be an easy change.
Do note that for this to make sense, the bells need to be listed in order. We could fix this with a sort call, but I don't see a good reason to do so.

Here is an ES6 example using deconstruct (const [a,b]=[1,2]), array map, array reduce, partial application (closure) and fat arrow function syntax.
This may not work in older browsers.
//pass date and bellTimes to function so you can test it more easily
// you can partially apply bellTimes
const getInfoSchoolTime = bellTimes => {
//convert hour and minute to a number
const convertedBellTimes = bellTimes
.map(bellTime=>bellTime.split(":"))//split hour and minute
.map(([hour,minute])=>[new Number(hour),new Number(minute)])//convert to number
.map(([hour,minute])=>(hour*60)+minute)//create single number (hour*60)+minutes
.reduce(//zip with next
(ret,item,index,all)=>
(index!==all.length-1)//do not do last one, create [1,2][2,3][3,4]...
? ret.concat([[item,all[index+1]]])
: ret,
[]
);
return date =>{
//convert passed in date to a number (hour*60)+minutes
const passedInTime = (date.getHours()*60)+date.getMinutes();
return convertedBellTimes.reduce(
([ret,goOn],[low,high],index,all)=>
//if goOn is true and passedInTime between current and next bell item
(goOn && passedInTime<high && passedInTime>=low)
? [//found the item, return object and set goOn to false
{
current: index+1,
currentPeriod: bellTimes[index]+"-"+bellTimes[index+1],
remaining: high-passedInTime
},
false//set goOn to false, do not continue checking
]
: [ret,goOn],//continue looking or continue skipping (if goOn is false)
[
{current: 0, currentPeriod: "School is out", remaining: 0},//default value
true//initial value for goOn
]
)[0];//reduced to multiple values (value, go on) only need value
}
};
//some tests
const date = new Date();
//partially apply with some bell times
const schoolTime = getInfoSchoolTime(
[
"8:10", "9:02", "9:54", "9:59", "10:51",
"11:43", "11:58", "12:48", "13:35", "13:40",
"14:10", "14:10", "15:02", "15:54"
]
);
//helper to log time from a date
const formatTime = date =>
("0"+date.getHours()).slice(-2)+":"+("0"+date.getMinutes()).slice(-2);
date.setHours(11);
date.setMinutes(1);
console.log(formatTime(date),schoolTime(date));//11:01
date.setHours(15);
date.setMinutes(53);
console.log(formatTime(date),schoolTime(date));//15:53
date.setHours(23);
date.setMinutes(1);
console.log(formatTime(date),schoolTime(date));//23:01

Related

Capture every Friday, Sunday and Wednesday between two dates

Goodnight. I'm trying to capture every Friday, Sunday and Wednesday between two dates using Moment.js. I couldn't understand why it doesn't capture the days:
2021-12-08
2021-12-10
I managed to get this far:
const allDays = [0, 3, 5];
function formatToPush(dt_inicio, dt_final, dia, horas) {
let start = moment(dt_inicio);
let end = moment(dt_final);
let result = [];
let datas = [];
let current = start.clone();
if ((current.day(dia).isSameOrAfter(start)) || (current.day(dia).isSameOrAfter(end)) || (current.day(7 + dia).isSameOrBefore(end))) {
result.push(current.clone());
}
result.map(m => {
horas.map(h => {
m.set({ hour: h.split(':')[0], minute: h.split(':')[1], second: 0, millisecond: 0 });
datas.push(m.format('YYYY-MM-DD HH:mm:ss'))
})
});
return datas;
}
let final = [];
for (let i in allDays) {
final.push(...formatToPush('2021-12-01', '2021-12-10', allDays[i], ["10:00", "16:00", "22:30"]))
}
console.log(final)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
Can anyone help me find the error?
Thanks!
The condition for whether the day is within the bounds is always true for either or both of the first two clauses. This means the whole expression is true without evaluating the third clause which is the one that could set the date to one of the later dates you are missing. Since you aren't running that if-statement in a loop, it will only ever push one date to the result array.
A more generalized algorithm would use a loop.
let current = start.clone();
if (current.day(dia).isSameOrAfter(start) && current.isSameOrBefore(end)) {
result.push(current.clone());
}
while (current.day(7 + dia).isSameOrAfter(start) && current.isSameOrBefore(end)) {
result.push(current.clone());
}
Note: I also changed the conjunction to && because with the loop, isAfter would always be true. Also I omitted current.day(dia) in the second clause since the first one is already setting the day of the week for current.
You push a day (result.push(current.clone())) exactly once, so you can't expect to have more than one date per day. You can make a second if statement with this condition: (current.day(7 + dia).isSameOrBefore(end)) and push it second time.
Also, in js, when first condition in if is met, the other are not resolved.
const allDays = [0, 3, 5];
function formatToPush(dt_inicio, dt_final, dia, horas) {
let start = moment(dt_inicio);
let end = moment(dt_final);
let result = [];
let datas = [];
let current = start.clone();
if ((current.day(dia).isSameOrAfter(start)) || (current.day(dia).isSameOrAfter(end)) || (current.day(7 + dia).isSameOrBefore(end))) {
result.push(current.clone());
}
if (current.day(7 + dia).isSameOrBefore(end)) {
result.push(current.clone());
}
result.map(m => {
horas.map(h => {
m.set({ hour: h.split(':')[0], minute: h.split(':')[1], second: 0, millisecond: 0 });
datas.push(m.format('YYYY-MM-DD HH:mm:ss'))
})
});
return datas;
}
let final = [];
for (let i in allDays) {
final.push(...formatToPush('2021-12-01', '2021-12-10', allDays[i], ["10:00", "16:00", "22:30"]))
}
console.log(final)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
Here is an alternative if want to do this in three easy lines using the Date API.
I offer this alternative considering momentjs is 17,000+ lines of code in 42k.
For your consideration.
While processing the range from start to finish (inclusive)
convert the date to a dateString and look for Fri or Sun or Wed
if found -> Add the date to the result array
check the next date
const startDate = new Date('2021', '11', '1');
const endDate = new Date('2021', '11', '31');
var tempDate = new Date('2021', '11', '1');
var result = [];
while (tempDate.valueOf() !== endDate.valueOf()) {
if (/Fri|Sun|Wed/.test(tempDate.toDateString())) result.push(new Date(tempDate));
tempDate.setDate(tempDate.getDate() + 1);
}
result.forEach(day => console.log(day.toDateString(), day.toISOString().replace("T",' ')));

To sort array based on startdtate and present job or no

I am making a dynamic portfolio for myself using VueJS.
I created a way to update experiences and order it based on currently ongoing jobs showing first sorted in ascending order meaning a job with start date May 2021 will show first and then March 2021 (both being present).
Next, if I set an end date for the job, it should update and place the current jobs in the front which isn't happening.
Algorithm:
newExp() {
this.editableExperience.sort((a,b) => {
a = a.period.split(' - ');
b = b.period.split(' - ');
let aStartDate = a[0];
let aEndDate = a[1];
let bStartDate = b[0];
let bEndDate = b[1];
if (aEndDate == 'Present' && bEndDate == 'Present') {
return new Date(bStartDate) - new Date(aStartDate);
} else if (aEndDate == 'Present') {
return a;
} else if (bEndDate == 'Present') {
return b;
} else {
return new Date(bStartDate) - new Date(aStartDate);
}
})
this.experience = this.editableExperience;
}
editableExperience is an array of experiences: (I have added only required information)
editableExperience = [{period: 'May 2021 - Present'}, {period: 'November 2020 - Present'}, {period: 'January 2021 - March 2021'}, {period: 'March 2018 - July 2020'}]
Exact issue situation:
Setting the third element to present job brings it to position 2 but giving it an end date again does not send it to position 3 again.
Setting the last element to present does not bring it in front of the non-present jobs.
Your compare function is returning a string or a number while the compare function should return either 1, 0 or -1 as per the MDN docs.
I have made changes to your code below:
newExp() {
this.editableExperience.sort((a,b) => {
a = a.period.split(' - ');
b = b.period.split(' - ');
let aStartDate = a[0];
let aEndDate = a[1];
let bStartDate = b[0];
let bEndDate = b[1];
if (aEndDate == 'Present' && bEndDate == 'Present') {
return (new Date(bStartDate) - new Date(aStartDate)) > 1 ? 1 : -1;
} else if (aEndDate == 'Present') {
return -1;
} else if (bEndDate == 'Present') {
return 1;
} else {
return (new Date(bStartDate) - new Date(aStartDate)) > 1 ? 1 : -1;
}
});
this.experience = this.editableExperience;
}
The view model is a little bit mixed with data model, I would suggest to keep a clean data model which hold the original values, it is good for processing like sort. then a a computed property as view model which is depend on the data model.
data: () => ({
editableExperience: [
{start: 202105, end: 999999},
{start: 202011, end: 999999},
{start: 202101, end: 202103},
{start: 201803, end: 202107},
],
}),
then the sorting will looks like:
this.editableExperience.sort((a,b) => {
return b['end'] === a['end']? b['start'] - a['start'] : b['end'] - a['end']
})
for your view(display)
computed: {
viewExperiences() {
const ve = []
for(const e of this.editableExperience) {
ve.push(this.getExperienceDisplay(e))
}
return ve
}
},
methods: {
formatExperienceDate(dateInt) {
if(dateInt === 999999) return 'Present'
const dateStr = dateInt.toString()
const date = new Date(dateStr.substring(0, 4) + '-' + dateStr.substring(4, 6))
return date.toLocaleDateString("en-US", {year: 'numeric', month: 'long'})
},
getExperienceDisplay(exp) {
const startDate = this.formatExperienceDate(exp['start'])
const endDate = this.formatExperienceDate(exp['end'])
return `${startDate} - ${endDate}`
},
}

Check if dates consecutives in array with starting date today

I am trying to check if in this following array, the dates are consecutives with starting date today (07/01/2020).
var arrayDate = [
'06/30/2020', '06/29/2020', '06/28/2020', '06/26/2020'
]
It should return
var nbDatesConsecutives = 3
On the other hand, this following example should return 0 :
var arrayDate = [
'06/29/2020', '06/28/2020', '06/26/2020', '06/25/2020'
]
I have tried many times to resolve it but I still blocked. Here is one of my attempts :
let arrayDiff = []
arrayDate.map((element, i) => {
arrayDiff.push(today.diff(moment(element), 'days'));
});
let previousValue = null;
arrayDiff.map((element, i) => {
let currentValue = arrayDiff[i];
if (i > 0) {
if (currentValue > previousValue) {
strike++;
}
}
previousValue = currentValue;
})
Thanks !
Your idea of mapping to the day diff is good. Let me build on that:
You could...
Get "today" as the start of the current day
Map the dates to their difference to today, in days
Find the first array index where this difference is no longer equal to the index plus one (since you expect an array like [1, 2, 3, 4] in the perfect case, so e.g. array[2]=2 + 1=3)
This first mismatching index is already your result, except in the case where the whole array has the expected dates, so no index will mismatch - in that case you return the length of the array
Here you can see it working:
function getConsecutive (dates) {
// Note: I hardcoded the date so that the snippet always works.
// For real use, you need to remove the hardcoded date.
// const today = moment().startOf('day')
const today = moment('2020-07-01').startOf('day')
const diffs = dates.map(date => today.diff(moment(date, 'MM/DD/YYYY'), 'days'))
const firstIncorrectIndex = diffs.findIndex((diff, i) => diff !== i + 1)
return firstIncorrectIndex === -1 ? dates.length : firstIncorrectIndex
}
// Outputs 4:
console.log(getConsecutive(['06/30/2020', '06/29/2020', '06/28/2020', '06/27/2020']))
// Outputs 3:
console.log(getConsecutive(['06/30/2020', '06/29/2020', '06/28/2020', '06/26/2020']))
// Outputs 0:
console.log(getConsecutive(['06/29/2020', '06/28/2020', '06/26/2020', '06/25/2020']))
<script src="https://momentjs.com/downloads/moment.js"></script>
The mistake you are doing is 1) currentValue > previousValue instead you should have checked the difference, which must be 1 and when not 1 break the loop. So, here comes the mistake 2) you are using map function rather use simple for loop so that you can break.
`
function getConsecutiveDateCount(arrayDate) {
let arrayDiff = [];
let today = moment();
arrayDate.map((element, i) => {
arrayDiff.push(today.diff(moment(element), 'days'));
});
let strike = 0;
arrayDiff.unshift(0); /// insert 0 for today
let previousValue = arrayDiff[0];
for (let i = 1; i < arrayDiff.length; i++) {
currentValue = arrayDiff[i];
if (currentValue - previousValue === 1) {
strike++;
} else {
break;
}
previousValue = currentValue;
}
return strike;
}
`

Find the next closest date in MM/DD/YYYY format JavaScript

I have an array of dates formatted as MM/DD/YYYY. I need to find the next closest date in the future starting from today. Say today was 1/22/2016 then 2/19/2016 would return.
2/3/2015
7/5/2015
1/21/2016
2/19/2016
7/1/2016
I've tried doing substrings to get the month, day, year separate and attempting a sort based off those values but surely there has to be a better way.
There is no need for a sorting algorithm. You only need to iterate once and find the closest date that is greater or equals today.
Pseudocode
closest <- infinity
foreach date in dates:
if (date >= now and date < closest) then
closest <- d
return closest
JavaScript
const dates = [
'2/3/2035',
'7/5/2035',
'1/21/2036',
'2/19/2036',
'7/1/2036',
'10/22/2039',
'08/12/2039',
];
const now = new Date();
let closest = Infinity;
dates.forEach(function(d) {
const date = new Date(d);
if (date >= now && (date < new Date(closest) || date < closest)) {
closest = d;
}
});
console.log(closest);
Personally I would use a library such as the very good Moment.JS library, to handle all the horrible complexity of dates.
It has a difference method:
http://momentjs.com/docs/#/displaying/difference/
e.g.
var a = moment([2007, 0, 29]);
var b = moment([2007, 0, 28]);
a.diff(b) // 86400000
It would then be trivial to Math.min() the differences of each date in your list.
There's also a moment.min, which might shortcut this entirely, if all your dates are in the future already:
http://momentjs.com/docs/#/get-set/min/
A naïve implementation would be to parse each date as a string and sort them in ascending order. Then, remove any dates that are in the past, and get the first child of the array of remaining dates. See this jsbin example:
var dates = [
'2/3/2015',
'7/5/2015',
'1/21/2016',
'2/19/2016',
'7/1/2016'
];
// parse each string as a Date object and sort them in ascending order
function sortDates(dates) {
return dates.map(function(date) {
return new Date(date).getTime();
}).sort(function(a, b) {
return a - b;
});
}
var orderedDates = sortDates(dates);
// remove any dates in the past, and get the first child of the array of remaining dates
var nextDate = orderedDates.filter(function(date) {
return (Date.now() - date) > 0;
})[0];
Keep in mind that this depends on the format of the date string that you pass to the Date object (in other words, is 1/12/2015 January 12th, or December 1st? JavaScript will parse it as January 12th.
You can use while loop, new Date()
var dates = ["2/3/2015","7/5/2015","1/21/2016","2/19/2016","7/1/2016"]
, d = "1/22/2016", n = -1, res = null;
while (++n < dates.length && new Date(dates[n]) < new Date(d));
res = dates[n] || d;
console.log(res)
Lots of answers, one more can't hurt.
Date strings should always be manually parsed. A library can help, but if you only have a single format, a simple function is all that's required.
The following uses reduce to loop over the array of dates and finds the closest future date. If no date is in the future, it returns null.
The returned value is the string from the array, not a Date.
function parseMDY(s) {
var b = (s || '').split(/\D/);
return new Date(b[2], b[0]-1, b[1])
}
function getClosestDateToToday(arr) {
var now = new Date();
now.setHours(23,59,59);
return arr.reduce(function (acc, s) {
var d = parseMDY(s);
return d < now? acc : (acc && d > parseMDY(acc)? acc : s);
}, null);
}
var dates = ['2/3/2015', '7/5/2015','1/21/2016',
'2/19/2016','7/1/2016'];
document.write(getClosestDateToToday(dates));
This really depends upon your dates and data structures (the ones shown in original example are not so great for me).
From the other answers...
To take the example from Josh, you could also keep a pointer to which date you are using, or simply shift off of a sorted queue of dates to make it work, but it's really adding noise to your code, disrupting the purpose.
Frederik.L answer is really beautiful code, but it would still have to be executed multiple times, so I cannot recommend it.
Feedback warning
I've been given feedback in comments that Date.parse can behave inconsistently. I'll move to passing a date parsing callback function, and demonstrate Date.UTC usage in the callback for OP-specific date format. Please be careful when defining your own callbacks, and please do not copy-paste.
Suggestion
I'd suggest utilizing Date functions i.e. Date.parse; but also try where possible to get data sources sorted without needing application-level sorting. Then you can store-once and step through the array using array.shift() or similar;
Ideally also YYYY-MM-DD
Four-Digit Year
Two-Digit Month
Two-Digit Day
... (continue from least occurring to most occurring)
sample code
var dates = [
'2/3/2015',
'7/5/2015',
'7/1/2016',
'1/21/2016',
'2/19/2016'
]; // unsorted garbage dates
var DateList = function( dateList, getDate ) {
var sortedDates = dateList.sort( function(a, b) {
return getDate(a) - getDate(b);
});
this.next = function() {
var dt = sortedDates.shift();
sortedDates.push(dt); // comment to remove cyclical nature
return dt;
}
};
// specific implementation parser for this format
var getDisgustingDateFormat = function(dStr) {
var dParts = dStr.split('/');
return new Date(Date.UTC(dParts[2],dParts[0],dParts[1]));
};
var dl = new DateList( dates, getDisgustingDateFormat );
Usage
dl.next(); // "2/3/2015"
dl.next(); // "7/5/2015"
dl.next(); // "1/21/2016"
dl.next(); // "2/19/2016"
dl.next(); // "7/1/2016"
dl.next(); // "2/3/2015"
Hope this helps (Updated for clarity)
What about this version using for of and momentjs:
const getClosestFutureDate = (dates) => {
if (dates.length === 0) {
return null;
}
let minDiff = 0;
for (const date of dates) {
minDiff += minDiff + 30;
var currentDate = moment(date);
if (currentDate.isAfter(moment()) && currentDate.diff(moment(), "days") <= minDiff) {
break;
}
}
return currentDate;
};
Assuming now = 2019-08-21
console.log(getClosestFutureDate(["2019-05-07", "2019-06-01", "2019-07-13", "2019-11-09", "2019-11-10", "2019-11-11"]));
// 2019-11-09
I am fan of momentjs, but this can be easily refactored to use only vanilla Date.
const FindDate = (date, allDate) => {
// moment().diff only works on moment(). Make sure both date and elements in allDate list is in moment
let nearestDate = -1;
allDate.some(d => {
const currentDate = moment(d)
const difference = currentDate.diff(date); // Or date.diff(currentDate) depending on what you're trying to find
if(difference >= 0){
nearestDate = d
}
});
console.log(nearestDate)
}
In Livescript:
x =
* "2/3/2015"
* "7/5/2015"
* "1/21/2016"
* "2/19/2016"
* "7/1/2016"
sim-unix-ts = (date-str) ->
# Simulate unix timestamp like concatenating
# convert "MM/DD/YYYY" to YYYYMMDD (integer)
# so we can simply compare these integers
[MM, DD, YYYY] = date-str.split "/"
MM = "0#{MM}".slice -2 # apply zero padding
DD = "0#{DD}".slice -2 # apply zero padding
parse-int "#{YYYY}#{MM}#{DD}"
today = sim-unix-ts "2/18/2016"
date-list = [sim-unix-ts(..) for x]
# find next date
next-dates = [.. for date-list when .. > today]
next-date = next-dates.0
next-date-orig = x[date-list.index-of next-date]
alert [next-date, next-date-orig]
..in Javascript:
var x, simUnixTs, today, dateList, res$, i$, x$, len$, nextDates, y$, nextDate, nextDateOrig;
x = ["2/3/2015", "7/5/2015", "1/21/2016", "2/19/2016", "7/1/2016"];
simUnixTs = function(dateStr){
var ref$, MM, DD, YYYY;
ref$ = dateStr.toString().split("/"), MM = ref$[0], DD = ref$[1], YYYY = ref$[2];
MM = ("0" + MM).slice(-2);
DD = ("0" + DD).slice(-2);
return parseInt(YYYY + "" + MM + DD);
};
today = simUnixTs("2/18/2016");
res$ = [];
for (i$ = 0, len$ = x.length; i$ < len$; ++i$) {
x$ = x[i$];
res$.push(simUnixTs(x$));
}
dateList = res$;
res$ = [];
for (i$ = 0, len$ = dateList.length; i$ < len$; ++i$) {
y$ = dateList[i$];
if (y$ > today) {
res$.push(y$);
}
}
nextDates = res$;
nextDate = nextDates[0];
nextDateOrig = x[dateList.indexOf(nextDate)];
alert([nextDate, nextDateOrig]);

JavaScript check if time ranges overlap

I have e.g. an array with 2 objects (myObject1 and myObject2 like ).
Now when I add an third object I will check if time range overlaps.
Actually I don't know how I can do this in a performant way.
var myObjectArray = [];
var myObject1 = {};
myObject1.startTime = '08:00';
myObject1.endTime = '12:30';
...
var myObject2 = {};
myObject2.startTime = '11:20';
myObject2.endTime = '18:30';
...
myObjectArray.push(myObject1);
myObjectArray.push(myObject2);
Let assume we have some intervals
const INTERVALS = [
['14:00', '15:00'],
['08:00', '12:30'],
['12:35', '12:36'],
['13:35', '13:50'],
];
If we want to add new interval to this list we should check if new interval is not overlapping with some of them.
You can loop trough intervals and check if the new one is overlapping with others. Note that when comparing intervals you do not need Date object if you are sure it is the same day as you can convert time to number:
function convertTimeToNumber(time) {
const hours = Number(time.split(':')[0]);
const minutes = Number(time.split(':')[1]) / 60;
return hours + minutes;
}
There are two cases where intervals are NOT overlapping:
Before (a < c && a < d) && (b < c && b <d):
a b
|----------|
c d
|----------|
After where (a > c && a > d) && (b > c && b > d):
a b
|----------|
c d
|----------|
Because always c < d, it is enough to say that condition for NOT overlapping intervals is (a < c && b < c) || (a > d && b > d) and because always a < b, it is enough to say that this condition is equivalent to:
b < c || a > d
Negation of this condition should give us a condition for overlapping intervals. Base on De Morgan's laws it is:
b >= c && a <= d
Note that in both cases, intervals can not "touch" each other which means 5:00-8:00 and 8:00-9:00 will overlap. If you want to allow it the condition should be:
b > c && a < d
There are at least 5 situation of overlapping intervals to consider:
a b
|----------|
c d
|----------|
a b
|----------|
c d
|----------|
a b
|----------|
c d
|--------------------|
a b
|--------------------|
c d
|----------|
a b
|----------|
c d
|----------|
Full code with extra add and sort intervals functions is below:
const INTERVALS = [
['14:00', '15:00'],
['08:00', '12:30'],
['12:35', '12:36'],
['13:35', '13:50'],
];
function convertTimeToNumber(time) {
const hours = Number(time.split(':')[0]);
const minutes = Number(time.split(':')[1]) / 60;
return hours + minutes;
}
// assuming current intervals do not overlap
function sortIntervals(intervals) {
return intervals.sort((intA, intB) => {
const startA = convertTimeToNumber(intA[0]);
const endA = convertTimeToNumber(intA[1]);
const startB = convertTimeToNumber(intB[0]);
const endB = convertTimeToNumber(intB[1]);
if (startA > endB) {
return 1
}
if (startB > endA) {
return -1
}
return 0;
})
}
function isOverlapping(intervals, newInterval) {
const a = convertTimeToNumber(newInterval[0]);
const b = convertTimeToNumber(newInterval[1]);
for (const interval of intervals) {
const c = convertTimeToNumber(interval[0]);
const d = convertTimeToNumber(interval[1]);
if (a < d && b > c) {
console.log('This one overlap: ', newInterval);
console.log('with interval: ', interval);
console.log('----');
return true;
}
}
return false;
}
function isGoodInterval(interval) {
let good = false;
if (interval.length === 2) {
// If you want you can also do extra check if this is the same day
const start = convertTimeToNumber(interval[0]);
const end = convertTimeToNumber(interval[1]);
if (start < end) {
good = true;
}
}
return good;
}
function addInterval(interval) {
if (!isGoodInterval(interval)) {
console.log('This is not an interval');
return;
}
if (!isOverlapping(INTERVALS, interval)) {
INTERVALS.push(interval);
// you may also want to keep those intervals sorted
const sortedIntervals = sortIntervals(INTERVALS);
console.log('Sorted intervals', sortedIntervals);
}
}
// --------------------------------------
const goodIntervals = [
['05:31', '06:32'],
['16:00', '17:00'],
['12:31', '12:34']
];
let goodCount = 0;
for (const goodInterval of goodIntervals) {
if (!isOverlapping(INTERVALS, goodInterval)) {
goodCount += 1
}
}
console.log('Check good intervals: ', goodCount === goodIntervals.length);
// --------------------------------------
const ovelappingIntervals = [
['09:30', '12:40'],
['05:36', '08:50'],
['13:36', '13:37'],
['06:00', '20:00'],
['14:00', '15:00']
]
let badCount = 0;
for (const badInterval of ovelappingIntervals) {
if (isOverlapping(INTERVALS, badInterval)) {
badCount += 1
}
}
console.log('Check bad intervals: ', badCount === ovelappingIntervals.length);
// --------------------------------------
addInterval(goodIntervals[0])
You can try something like this:
var timeList = [];
function addTime() {
var startTime = document.getElementById("startTime").value;
var endTime = document.getElementById("endTime").value;
if (validate(startTime, endTime)){
timeList.push({
startTime: startTime,
endTime: endTime
});
print(timeList);
document.getElementById("error").innerHTML = "";
}
else
document.getElementById("error").innerHTML = "Please select valid time";
}
function validate(sTime, eTime) {
if (+getDate(sTime) < +getDate(eTime)) {
var len = timeList.length;
return len>0?(+getDate(timeList[len - 1].endTime) < +getDate(sTime) ):true;
} else {
return false;
}
}
function getDate(time) {
var today = new Date();
var _t = time.split(":");
today.setHours(_t[0], _t[1], 0, 0);
return today;
}
function print(data){
document.getElementById("content").innerHTML = "<pre>" + JSON.stringify(data, 0, 4) + "</pre>";
}
<input type="text" id="startTime" />
<input type="text" id="endTime" />
<button onclick="addTime()">Add Time</button>
<p id="error"></p>
<div id="content"></div>
Use moment-js with moment-range (broken reference)
Tested example:
const range1 = moment.range(a, c);
const range2 = moment.range(b, d);
range1.overlaps(range2); // true
See more examples in https://github.com/rotaready/moment-range#overlaps
Note, for the above code to work maybe you first do:
<script src="moment.js"></script>
<script src="moment-range.js"></script>
window['moment-range'].extendMoment(moment);
HTML code
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.12.0/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-range/2.2.0/moment-range.min.js"></script>
JavaScript code
var range = moment.range(new Date(year, month, day, hours, minutes), new Date(year, month, day, hours, minutes));
var range2 = moment.range(new Date(year, month, day, hours, minutes), new Date(year, month, day, hours, minutes));
range.overlaps(range2); // true or flase
Pretty neat solution and momentjs comes with tons of date and time utilities.
Use JavaScript Date() object to store time and then compare them if ending time of object1 is greater than starting time of object2 then they are overlapping.
You can compare them using > operator.
date1.getTime() > date2.getTime()
Demonstration given here
Usage of Date object
To determine whether the time range overlaps other time ranges you can utilize both moment.js and moment-range libraries.
First install moment-js and moment-range
Given you have an INTERVALS array that contains example objects:
const INTERVALS = [
{ START: 0, END: 10 },
{ START: 12, END: 30 },
...
]
You can use a function below:
const validateIntervalOverlaps = () => {
if (INTERVAL_START && INTERVAL__END) {
const timeInterval = moment.range(moment(INTERVAL_START), moment(INTERVAL_ENDS))
const overlappingInterval = INTERVALS.find(intervalItem => {
const interval = moment.range(moment(intervalItem.START), moment(intervalItem.END))
return timeInterval.overlaps(interval)
})
return overlappingInterval
}
}
Next, you can do what you need to do with overlappingInterval :) F.e. determine if it exists or use it in any other way. Good luck!
Here's something that might work.
// check if time overlaps with existing times
for (var j = 0; j < times.length; j++) {
let existing_start_time = moment(this.parseDateTime(this.times[j].start_time)).format();
let existing_end_time = moment(this.parseDateTime(this.times[j].end_time)).format();
// check if start time is between start and end time of other times
if (moment(start_time).isBetween(existing_start_time, existing_end_time)) {
times[i].error = 'Time overlaps with another time';
return false;
}
// check if end time is between start and end time of other times
if (moment(end_time).isBetween(existing_start_time, existing_end_time)) {
times[i].error = 'Time overlaps with another time';
return false;
}
}
https://momentjs.com/
You can check if there is an overlap by trying to merge a time range to the existing time ranges, if the total count of time ranges decrease after merge, then there is an overlap.
I found following articles which might help on handle merging ranges
Merge arrays with overlapping values
merge-ranges

Categories

Resources