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];
}, [])
Related
I have an empty array:
const timeArray = []
const a = new Date(startTime_minTime)
const b = new Date(startTime_maxTime)
const disabledValueStart = new Date(maxStart)
// where startTime_minTime and startTime_maxTime are only two dates (date and time) and maxStart is the last value
Date.prototype.addHours = function (h) {
this.setTime(this.getTime() + h * 60 * 30 * 1000)
return this
}
// Now I'm trying to do a while to populate the array
while (a <= b) {
timeArray.push(a)
if (a !== disabledValueStart) {
a.addHours(1)
}
}
The problem is that my array has only the last value
repeated for the number of elements that should populate it, how do I add one element at a time, so that I have them all at the end and not just the same repeated value?
You are pushing the same date (a) to the array, over and over. Clone the date and push the clone. That way each item in the array is a different date.
Here is a working snippet. The key is timeArray.push(new Date(a)).
const timeArray = []
const a = new Date()
const b = new Date(a.getTime()+60*60*24*1000)
const disabledValueStart = new Date(a.getTime()-60*60*24*1000)
Date.prototype.addHours = function (h) {
this.setTime(this.getTime() + h * 60 * 30 * 1000)
return this
}
while (a <= b) {
timeArray.push(new Date(a)) // here is the important change
if (a !== disabledValueStart) {
a.addHours(1)
}
}
console.log(timeArray)
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}`
},
}
I have an array of stringified dates like,
arr = ["9-7-2020", "11-7-2020", "12-7-2020", "10-7-2020", "16-7-2020", "15-7-2020", "19-7-2020"]
now I want to check if this array consist of atleast 3 consecutive dates, so what should be the best possible way to do it in node?
One approach could be this, where we get and sort the timestamps of each date, and then we can check for consecutive dates by subtracting a day's milliseconds multiplied by its index and checking if the same timestamp occurs more than or equal to N times.
const arr = ["9-7-2020", "11-7-2020", "12-7-2020", "10-7-2020", "16-7-2020", "15-7-2020", "19-7-2020"];
const ONE_DAYS_MILLIS = 1000 * 60 * 60 * 24;
const hasNConsecutive = (dates, N) => {
// Remove duplicate dates if any
const uniqueDays = [... new Set(dates)];
const dateOccurrences = uniqueDays
.map((date) => date.split('-'))
// Get a timestamp for each date
.map(([day, month, year]) => new Date(year, month, day).getTime())
.sort()
// Since we have sorted the timestamps we can now check for
// consecutive dates by subtracting a day multiplied
// by the index and checking if the same timestamp
// occurs more than or equal to N times
.map((ts, index) => ts - index * ONE_DAYS_MILLIS)
.reduce((count, ts) => {
count[ts] = (count[ts] || 0) + 1;
return count;
},
{});
return Object.values(dateOccurrences).some((times) => times >= N);
};
const result = hasNConsecutive(arr, 3);
console.log(result);
Or if you are using lodash you can do the same thing a little bit easier.
const arr = ["9-7-2020", "11-7-2020", "12-7-2020", "10-7-2020", "16-7-2020", "15-7-2020", "19-7-2020"];
const ONE_DAYS_MILLIS = 1000 * 60 * 60 * 24;
const result = _
.chain(arr)
.uniq()
.map((date) => date.split('-'))
.map(([day, month, year]) => new Date(year, month, day).getTime())
.sort()
.map((ts, index) => ts - index * ONE_DAYS_MILLIS)
.countBy(_.identity)
.values()
.some((count) => count >= 3)
.value();
console.log(result);
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.19/lodash.min.js"></script>
You could crate Date objects from those date string, sort them and then go through the array and check if the previous date plus one day is equal to the current date and keep a count of how many times that condition is true in a row.
Here is an example:
const arr = ["9-7-2020", "11-7-2020", "12-7-2020", "10-7-2020", "16-7-2020", "15-7-2020", "19-7-2020"]
const requiredConsecutiveCount = 3;
const getDate = (dateStr) => new Date(dateStr.split("-").reverse().map(v => parseInt(v)).map((v, i) => i === 1 ? (v + 1) : v));
const hasConsecutive = arr.map(getDate)
.sort((a, b) => a.getTime() - b.getTime())
.some(function(v, i, arr) {
if (i > 0) {
const tmp = new Date(arr[i - 1]);
tmp.setDate(tmp.getDate() + 1);
if (tmp.getTime() === v.getTime()) {
this.consecutiveCount++;
} else {
this.consecutiveCount = 0;
}
}
return this.consecutiveCount === requiredConsecutiveCount;
}, {
consecutiveCount: 0
});
console.log(hasConsecutive);
Probably my way of converting from a string date to a Date object is more complex than it has to be.
Two things needed here:
Sort the array by date, probably need to convert dates along the way
Then check if date diff to next element is 1 day and date diff to the element after that is 2 days
var arr = ["9-7-2020", "11-7-2020", "12-7-2020", "10-7-2020", "16-7-2020", "15-7-2020", "19-7-2020"]
var ONE_DAY = 24 * 60 * 60 * 1000;
function hasNConsecutive(arr, numConsecutive){
// new Date() is locale dependent. You might need to modify this heavily if you need to be locale-agnostic
var dateArray = arr.map(function(d){return new Date(d).getTime()}).sort();
var consecutiveCount = 1;
for(var i=1; i<dateArray.length; i++) {
if(dateArray[i] - dateArray[i-1] != ONE_DAY) {
consecutiveCount = 1;
continue;
}
consecutiveCount++;
if(consecutiveCount == numConsecutive) return true;
}
return false;
}
console.log(hasNConsecutive(arr, 3));
what's happening here is, we are making the month or the day a two digit if it isn't one, then we are constructing integer which has value like this "YYYYMMDD" then we are subtracting from three times of the first day with sum of three consecutive days, answer will be three if they are consecutive days
(caution: no duplicate dates must exist)
var arr = ["9-7-2020", "11-7-2020", "12-7-2020", "10-7-2020", "16-7-2020", "15-7-2020", "19-7-2020"]
arr.map((v) => (v.split("-").map((val) => (val.length == 1 ? "0" + val : val)).reverse().join(""))).map((value) => (parseInt(value))).sort((a, b) => (a > b ? 1 : a < b ? -1 : 0)).forEach((v, i, arr2) => {
if (i + 2 < arr2.length) {
var three = (v + arr2[i + 1] + arr2[i + 2]) - (v * 3)
if (three == 3) console.log('three consecutive days')
}
})
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