How can i get the nearest next time in array using moment? - javascript

I want to find the nearest next time to a given time, I got this code from stack overflow but I am unable to get the desired result
// Current time in millis
const now = +moment('10:07', 'HH:mm').format('x');
// List of times
const times = ["10:00", "10:18", "23:30", "12:00"];
// Times in milliseconds
const timesInMillis = times.map(t => +moment(t, "HH:mm").format("x"));
function closestTime(arr, time) {
return arr.reduce(function(prev, curr) {
return Math.abs(curr - time) < Math.abs(prev - time) ? curr : prev;
});
}
const closest = moment(closestTime(timesInMillis, now)).format('HH:mm');
// closest is 10:00 but i want the next time 10:18
console.log(closest);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>

No need for moment at all
24-hour time is sortable and directly comparable
const times = ["10:00", "10:18", "23:30", "12:00"].sort();
const getClosest = targetTime => times.find(time => time >= targetTime) || "N/A";
console.log(getClosest("10:07"));
console.log(getClosest("11:30"));
console.log(getClosest("13:30"));
console.log(getClosest("23:40")); // not available in array

Related

split time data in Javascript vanilla

I'm trying to create a dynamic calendar.
I have opening and closing hours.
I would like to make a loop to get all the half hour slots between these two times.
The goal is to create a div that contains the list of these schedules.
do you have a method, an idea please to help me achieve this?
My data, inside a json document, looks like this:
"minMax": {
"min": "07:00",
"max": "18:00"
}
(I have already retrieved the day) my goal is to display the schedules.
const minHour = week[weekNumber].minMax.min // => 07:00
const maxHour = week[weekNumber].minMax.max // => 18:00
The problem is that I don't know how to split the schedules by half hour between the min hour and the max hour.
Based on the information you gave I came up with this solution to generate half hour slots. If there is any corrections needed to be made or edge cases to handle, please let me know.
function halfHourSlots(min, max) {
let slots = [];
let [minHour] = min.split(':');
let [maxHour] = max.split(':');
minHour = Number(minHour);
maxHour = Number(maxHour);
while (minHour != maxHour) {
let halfHourString = `${minHour}:30`.padStart(
2,
'0'
);
minHour += 1;
let hourString = `${
minHour == 24 ? '00' : minHour
}:00`.padStart(2, '0');
slots.push(halfHourString);
slots.push(hourString);
}
return slots;
}
console.log(halfHourSlots('07:00', '18:00'));
You can try this,
var convertValue = time => ((hour,min) => hour*2 + min/30)(...time.split(':').map(parseFloat)),
toTime = int => [Math.floor(int/2), int%2 ? '30' : '00'].join(':'),
range = (from, to) => Array(to-from+1).fill().map((_,i) => from + i),
halfHourInterval = (t1, t2) => range(...[t1, t2].map(convertValue)).map(toTime);
console.log(halfHourInterval('07:00', '15:30'));

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",' ')));

Calculate number of data received in past 30 seconds

I have a websocket where i receive price of stock randomly like 300ms or sometimes 1 second.I want to calculate how many price I received in past 30 seconds only.
var arr = [];
function onReceive(price) {
var timestamp = Number(new Date());
arr[timestamp] = [];
arr[timestamp].push(price);
if (arrarr[timestamp].length > 1000) {
arr.shift();
}
}
Now I just want to count how many price is received in last 30 seconds , I cannot come up with any logic.
I tried something like slicing last 30 items in array and calculating difference between last time stamp and -30 timestamp , which tells me how much time it took to receive 30 price ticks ,but i dont know how to calculate how to find how many ticks received in past 30 seconds , any ideas please.thank you.
arr[timestamp][arr[timestamp].length-1].key-arr[timestamp][0].key;
Personally I would create some sort of named instance for a log item, holding the UNIX timestamp and the price.
To retrieve anything in the last X seconds, you'd get the current UNIX timestamp, subtract X * 1000 from it, and use .filter() do a reverse iteration to retrieve all items where the timestamp is greater than that.
EDIT: As Robby pointed out, there's no need to search through the entire array as the timestamps are guaranteed to be in increasing order. By iterating in reverse, we can exit the loop when we find the first result outside of the desired window.
var priceLog = [];
function PriceLogItem(price) {
this.price = price;
this.timestamp = new Date().getTime();
}
function onReceive(price) {
priceLog.push(new PriceLogItem(price));
if (priceLog.length > 1000) log.shift();
}
function getPriceLogsSince(secondsAgo) {
let millisecondsAgo = secondsAgo * 1000;
let time = new Date().getTime() - millisecondsAgo;
let result = [];
for (let i = priceLog.length - 1; i >= 0; i--) {
if (priceLog[i].timestamp >= time) result.push(priceLog[i]);
else break;
}
return result;
}
//Usage
let priceLogs = getPriceLogsSince(30); //Get all logs within past 30 seconds
console.log(priceLogs);

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

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

Checking if date matches recurring date

I have a date variable containing a date, and an interval variable containing an array of numbers. Each number on the interval array represents a date, which is acquired by adding said number to the day count of the previous date. For example:
If date is equal to 2016-09-01 and interval is equal to [15, 15, 20], the resulting dates would be 2016-09-16, 2016-10-01, 2016-10-21, 2016-11-06 and so on.
I want to check if a given date matches that pattern. To do this, I tried using moment-recur, which does exactly what I want with the .every() function, but intervals with repeating numbers don't seem to work ([15, 15, 20] would be parsed as [15, 20] for example). How can I accomplish this, either with moment-recur or a different library?
Here's the desired output using moment-recur:
const now = moment();
const date = moment("2016-09-10", "YYYY-MM-DD");
console.log(date.recur().every([18, 18, 57]).days().matches(now));
The accumulative nature of what you are trying to do is a little tricky.
There may be a nicer way, but I think this works pretty well so long as your interval list isn't too big.
The main insight is that if you are looking for an accumulative interval of say [2, 10, 11] then you will be looking for every 2, 12, 23, 25, 35, 46, etc. This amount to looking for three different dates at regular intervals of the sum of your accumulator -- in this case 23. So you could just use moment's every() with each of the three cases and a single interval.
For example if you have:
const now = moment();
const date = moment("2016-10-22", "YYYY-MM-DD");
const interval = [18, 18, 57]
// sum of the intervals -- i.e. the main interval (93)
const main_int = interval.reduce( (prev, curr) => prev + curr );
// an array of days for each small interval
const dates = interval.map(i => moment(date.add(i ,'days')))
// moment mutates the original so this results in
// [ moment("2016-11-09T00:00:00.000"),
// moment("2016-11-27T00:00:00.000"),
// moment("2017-01-23T00:00:00.000") ]
// Now see if any of these days matches the main interval
const match = dates.some(d => d.recur().every(main_int).days().matches(now))
In a very kludgy way, what you're trying to do is generate new dates that accumulate according to your sequence, then see if at some point it matches a test date.
The following uses moment.js, but really doesn't need it. The moment functionality could be replaced with about 10 lines of code in a couple of separate functions.
/* #param {Date} sequenceStart - start of sequence
** #param {Array} sequence - sequence of intervals
** #param {Date} date - date for comparison
*/
function inSequence(sequenceStart, sequence, date) {
// Copy start date so don't affect original
var s = moment(sequenceStart);
// Get test date in suitable format
var d = moment(date).format('YYYYMMDD');
var i = 0;
do {
// debug
console.log(s.format('YYYYMMDD') + ' ' + d)
// If dates match, return true
if (s.format('YYYYMMDD') == d) {
return true;
}
// If didn't match, add the next value in the sequence
s.add(sequence[i++ % sequence.length], 'day');
// Stop if go past test date
} while (s.format('YYYYMMDD') <= d)
// If didn't return true, return false
return false;
}
var sequence = [15,15,20];
var start = new Date(2017,8,1); // 1 Sep 2017
var test = new Date(2017,10,5) // 5 Nov 2017
console.log(inSequence(start, sequence, test));
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.19.1/moment.min.js"></script>
You can do this without having to use moment-recur if you want to, using a similar approach to what #Mark_M described
The first step is to determine the number of days between the given date and the start date: moment(endDate).diff(startDate, 'days');.
Then, create an array of cumulative totals for the days since start date for each of the entries in the interval array:
function sumSoFar(nums) {
return nums.reduce((sums, num) => {
const prevSum = last(sums) || 0;
return sums.concat(num + prevSum);
}, []);
}
// ...
const sums = sumSoFar(interval);
Finally, the sum of the whole interval array is just the last entry in that list, and so we can find out which entry in the interval list it matches by taking the days difference modulo interval sum. If that is 0, or an entry in the sums array, then the date matches the interval. If not, then it doesn't.
Here is the complete code I came up with:
const startDate = moment('2016-09-01');
const interval = [15, 15, 20];
const last = (arr) => arr[arr.length - 1];
const sum = (nums) => nums.reduce((acc, num) => acc + num, 0);
function sumSoFar(nums) {
return nums.reduce((sums, num) => {
const prevSum = last(sums) || 0;
return sums.concat(num + prevSum);
}, []);
}
const validDates = [moment('2016-09-16'), moment('2016-10-01'), moment('2016-10-21'), moment('2016-11-05')];
function isValid(startDate, interval, date) {
const days = moment(date).diff(startDate, 'days');
const sums = sumSoFar(interval);
const remainingDays = days % last(sums);
return remainingDays === 0 || sums.indexOf(remainingDays) >= 0;
}
validDates.forEach(d => console.log(isValid(startDate, interval, d)));
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.19.1/moment.min.js"></script>

Categories

Resources