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",' ')));
I've implemented format function to convert input hours 9a-7p to 9:00am - 7:00pm or 9:30a-7p to 9:30am to 7:30pm.
the inputs looks like {sun: 9a-7p, mon: 8:30a - 7p}
Is there more generic way how to handle it this case, cuz this one does not cover pm - am or am - am or pm - pm
const formattedHours = (days: {[day: string]: string}) => {
return Object.keys(days).map((day) => {
const [openHours, closedHours] = days[day].replaceAll('a', '').replaceAll('p', '').split('-');
const [openHour, openMinute = '00'] = openHours.split(':');
const [closedHour, closedMinute = '00'] = closedHours.split(':');
return `${openHour}:${openMinute}am - ${closedHour}:${closedMinute}pm`;
});
};
Here's one way of going about it. By checking for the presence of 'a' or 'p' we can store the meridian and insert it after scrubbing the element of text, extra spaces and making sure it has minutes included. I used Object.entries since it allows me to iterate the object and modify the value in question in the same loop. I surround the whole thing with Object.fromEntries to convert it back into an object.
let data = {
sun: "9a-7p",
mon: "8:30a - 7p",
tues: "9pm - 11pm",
weds: "7am - 11:45 am",
thurs: "11a-12:00pm"
};
const formattedHours = days => {
return Object.fromEntries(Object.entries(days).map((day) => {
day[1] = day[1].split("-").map(t => {
let merid = t.includes('a') ? 'am' : 'pm';
t = t.replaceAll(/[a-zA-Z]/g, '').trim()
if (!t.includes(':')) t = `${t}:00`;
t += merid
return t
}).join("-");
return day
}));
};
let newdata = formattedHours(data);
console.log(newdata)
Many ways to go about it, but fundamentally it's about converting the time to a different format. The following also pads the hour with spaces to help with alignment, missing minutes are treated as "0" and single digit minutes are padded with a leading zero.
let fixTime = time => {
let range = time.split(/\s*-\s*/).map(time => {
let [h,m] = time.match(/\d+/g);
return `${h.padStart(2, ' ')}:${(m || '0').padStart(2, '0')}${/a/.test(time)? 'am' : 'pm'}`;
});
return range.join(' to ');
};
let data = {
sun: '9a-7p',
mon: '8:30a - 7p',
tue: '10:31a -1:5p',
wed: '1:31p-11:5p',
};
let result = Object.keys(data).reduce((acc, day) => {
acc[day] = fixTime(data[day]);
return acc;
}, Object.create(null));
console.log(result);
I have an array of dates:
const dates = ['date1', 'date2', 'date3', 'date4', 'date5'];
and a function that gets two values and returns true if two dates are in same week
function isSameWeek(a,b){
// some code
return true or false;
}
I want to filter the dates array in a way that none of it's values are in same week(one for each week).
For example if isSameWeek('date1', 'date2')=true , the filtered Array should be filtered=['date1', 'date3', 'date4', 'date5']
Helps are appreciated :)
If the dates are sorted, this can be accomplished with a single pass:
// monkey patching here for example sake (ignore this)
Date.prototype.getWeekNumber = function() {
var d = new Date(Date.UTC(this.getFullYear(), this.getMonth(), this.getDate()));
var dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
var yearStart = new Date(Date.UTC(d.getUTCFullYear(),0,1));
return Math.ceil((((d - yearStart) / 86400000) + 1)/7);
}
const isSameWeek = (a, b) => a.getWeekNumber() == b.getWeekNumber();
function getWeeklyDates(dates)
{
// can remove the sort() if already sorted
dates.sort((a, b) => a - b)
// filter out in a single pass
return dates.filter((date, index) => {
return index > 0 ? !isSameWeek(date, dates[index - 1]) : true; // not in the same week
});
}
const dates = [
new Date('2020-01-01'),
new Date('2020-02-01'),
new Date('2020-01-03'),
new Date('2020-03-05'),
new Date('2020-03-03')
];
console.log(getWeeklyDates(dates));
You should check out Array's reduce method:
return dates.reduce((unique, date) => {
if (unique.some(it => isSameWeek(it, date)) return unique;
return [...unique, date];
}, [])
var expenseDates = ["2018-02-06","2018-11-08","2018-11-10","2017-05-02","2017-05-02","2018-11-01"];
var sorted = expenseDates.slice()
.sort(function(a, b) {
return new Date(a) - new Date(b);
});
console.log(sorted.pop()+ '--max');
console.log(sorted.shift()+ '--min');
You don't have convert it into date object as the date are in YYYY-MM-DD format which itself is in sorted order by year => month => day. So you just have to compare the input string as localCompare. First index is minimum date while the last index is maximum date
var expenseDates = ["2018-02-06","2018-11-08","2018-11-10","2017-05-02","2017-05-02","2018-11-01"];
expenseDates = expenseDates.sort(function(a, b) {
return a.localeCompare(b);
});
console.log('--min => ',expenseDates[0]);
console.log('--max => ', expenseDates[expenseDates.length -1]);
Working jsFiddle demo - https://jsfiddle.net/rpdon5cm/1/
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