Related
I have a function that I am using to try and determine if a set of date segments meets a criteria.
I am using date-fns' distanceInDays function to return an integer value of the distance between the previous segment's end date and the current segment's start date.
(current.startDate - previous.endDate)
Problem is that the reduce function is giving me type errors.
type DateSegment = {startDate: string, endDate: string} // ISO 8601 timestamps
const isValid = (current: ResidentialAddress, history: Array<ResidentialAddress>): boolean => {
if (!current || history.length === 0) return false;
const currentDate = formatInTimeZone(new Date(), 'UTC', 'yyyy-MM-dd');
// Convert current residence and residential history to segments.
const segments: DateSegment[] = [
{ startDate: current.startDate, endDate: currentDate },
...history.map(x => ({ startDate: x.startDate, endDate: x.endDate }))
];
// Sort them
const sorted = segments.sort((a, b) => isSameOrBefore(a.startDate, b.startDate));
const result = sorted.reduce((previousValue, currentValue) =>
differenceInDays(new Date(currentValue.startDate), new Date(previousValue.endDate))),
{endDate: currentDate}
);
console.log(result);
return true;
};
NB: The current residence does not have an end date, so the current date is provided to it.
This will return an array of distances between intervals.
const distances = array.map((item, index: number) => {
if (!array[index-1]) return
return item.start - array[index-1].end
}
)
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];
}, [])
20.01.2017
23.01.2017
24.01.2017
25.01.2017
26.01.2017
27.01.2017
31.01.2017
01.02.2017
Lets say i have these dates. All those dates are workdays.
Output should be
20.01.2017-27.01.2017
and
31.01.2017-01.02.2017
Since 30 January is workday and so the first period is not continiuing and new period starts.
What is the best way to approach this.
I was thinking taking first date in array and putting it into a new array. Then comparing next date in array to the previous one if it's next day (ignoring weekends). If it is not nextday then take previous date and put it to array as end date and then start a new array of next period.
Get your first date (I'm supossing you have them ordered, as in your example data they are) and store it on a variable for the start date.
Store the same value also in another variable for the end date.
Now loop through your dates checking if current looped date is the next one after your current end date. If it's, store your current looped date into the end date variable and continue to the next loop. If it isn't return current start and end dates and store your currently looped date as a new period start date, go on until loop ends and return current variables.
This would be my approach, though not the shortest or maybe best way of facing this. Just take it as an idea
var array = [
"20.01.2017",
"23.01.2017",
"24.01.2017",
"25.01.2017",
"26.01.2017",
"27.01.2017",
"31.01.2017",
"01.02.2017"
];
var isNextDay = function(day, nextDay) {
var day1 = new Date(day.slice(3, 6) + day.slice(0, 3) + day.slice(6)); //had to format the date this way to make a valid date
day1.setDate(day1.getDate() + 1); //sets the next day, nextday of 30 or 31(last day of month) is 1
var day2 = new Date(nextDay.slice(3, 6) + nextDay.slice(0, 3) + nextDay.slice(6));
if (day1.getTime() === day2.getTime()) {
return true;
} else {
return false;
}
}
var dateGroup = function(dateStrings) {
var res = [];
var aux = dateStrings[0] + "-";
for (var i = 1; i < dateStrings.length; i++) {
if (!isNextDay(dateStrings[i - 1], dateStrings[i])) {
aux += dateStrings[i - 1];
res.push(aux);
aux = dateStrings[i] + "-";
}
}
aux += dateStrings[dateStrings.length - 1];
res.push(aux); //this is because the last one never gets pushed
return res;
}
var output = dateGroup(array);
You can loop over the dates and calculate what the string for the next date should look like, then compare to see if it's the same. If not, end the previous period and start a new one.
You can use a library to parse and format the dates, but simple functions to do the job are just a couple of lines, e.g.
var dates = ['20.01.2017','23.01.2017','24.01.2017',
'25.01.2017','26.01.2017','27.01.2017',
'31.01.2017','01.02.2017'];
/* Parse date in format D/M/YYYY
** #param {string} s - date to parse lke 23.1.2017
** #returns {Date}
*/
function parseDMY(s) {
var b = s.split(/\D/);
return new Date(b[2], b[1]-1, b[0]);
}
/* Format a date in DD/MM/YYYY with supplied separator
** #param {Date} date - date to format
** #param {string} s - separator, default is /
** #returns {string} date formatted as DD/MM/YYYY with supplied separator
*/
function formatDMY(date, s) {
s = s || '/';
function z(n){return (n<10?'0':'')+n}
return [z(date.getDate()),z(date.getMonth()+1),
date.getFullYear()].join(s);
}
/* Create array of date ranges in DD.MM.YYYY-DD.MM.YYYY format
** #param {Array} data - array of date strings in DD.MM.YYYY format
** #returns {Array} array of range strings in DD.MM.YYYY-DD.MM.YYYY format
*/
function createRanges(data) {
var result = [];
var previous;
data.forEach(function(s, i) {
var previous, previousNext, current, range;
// If on first loop, create a range using first value
if (i == 0) {
result.push(s + '-' + s);
// Otherwise, get end date of last range and add one day
} else {
previous = result[result.length-1].split('-')[1];
previousNext = parseDMY(previous);
previousNext.setDate(previousNext.getDate() + 1);
previousNext = formatDMY(previousNext,'.');
// If current date is same as previousNext, update range.
// Otherwise, start a new range
if (s == previousNext) {
range = result[result.length-1];
result[result.length-1] = range.split('-')[0] + '-' + s;
} else {
result.push(s + '-' + s);
}
}
});
// Remove zero day ranges. Could do this by checking last range
// when creating a new one but seems simpler to do it here
result = result.filter(s=>!(s.split('-')[0] == s.split('-')[1]));
return result;
}
console.log(createRanges(dates));
However, a library like moment.js will help with parsing, formatting and arithmetic.
Same as made by Leandro, but made for an array with Date objects and with using moment.js
function groupDates(dates) {
const res = [];
const isNextDay = (day, nextDay) => moment(day).add(1, 'day').isSame(nextDay, 'day');
const format = "DD.MM.YYYY";
let aux = moment(dates[0]).format(format) + "-";
for (let i = 1; i < dates.length; i++) {
if (!isNextDay(dates[i - 1], dates[i])) {
aux += moment(dates[i - 1]).format(format);
res.push(aux);
aux = moment(dates[i]).format(format) + "-";
}
}
aux += moment(dates[dates.length - 1]).format(format);
res.push(aux);
return res;
}
My solution with Luxon lib
const DateTime = luxon.DateTime;
const test = [
"2022-06-23",
"2022-06-24",
"2022-06-25",
"2022-06-26",
"2022-06-27",
"2022-06-28",
"2022-06-29",
"2022-05-02",
"2022-05-03",
"2022-05-05",
"2022-05-04",
"2022-05-06",
"2022-05-07",
"2022-05-08",
];
function getRanges(datesArr) {
const periods = [];
let ix = 0;
const dates = datesArr.map((d) => DateTime.fromSQL(d));
dates.sort();
dates.forEach((date, index) => {
if (index === 0) {
periods.push([
date,
]);
} else if (date.diff(dates[index - 1], [ 'days' ]).days === 1) {
periods[ix].push(date);
} else {
ix += 1;
periods.push([ date ]);
}
})
return periods;
}
console.log(getRanges(test));
<script src="https://cdnjs.cloudflare.com/ajax/libs/luxon/2.4.0/luxon.min.js"></script>
Then you can get first and last elements from each ranges
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]);
How can I find out the min and the max date from an array of dates? Currently, I am creating an array like this:
var dates = [];
dates.push(new Date("2011/06/25"))
dates.push(new Date("2011/06/26"))
dates.push(new Date("2011/06/27"))
dates.push(new Date("2011/06/28"))
Is there a built-in function to do this or am I to write my own?
Code is tested with IE,FF,Chrome and works properly:
var dates=[];
dates.push(new Date("2011/06/25"))
dates.push(new Date("2011/06/26"))
dates.push(new Date("2011/06/27"))
dates.push(new Date("2011/06/28"))
var maxDate=new Date(Math.max.apply(null,dates));
var minDate=new Date(Math.min.apply(null,dates));
Something like:
var min = dates.reduce(function (a, b) { return a < b ? a : b; });
var max = dates.reduce(function (a, b) { return a > b ? a : b; });
Tested on Chrome 15.0.854.0 dev
Same as apply, now with spread :
const maxDate = new Date(Math.max(...dates));
(could be a comment on best answer)
_.min and _.max work on arrays of dates; use those if you're using Lodash or Underscore, and consider using Lodash (which provides many utility functions like these) if you're not already.
For example,
_.min([
new Date('2015-05-08T00:07:19Z'),
new Date('2015-04-08T00:07:19Z'),
new Date('2015-06-08T00:07:19Z')
])
will return the second date in the array (because it is the earliest).
Since dates are converted to UNIX epoch (numbers), you can use Math.max/min to find those:
var maxDate = Math.max.apply(null, dates)
// convert back to date object
maxDate = new Date(maxDate)
(tested in chrome only, but should work in most browsers)
**Use Spread Operators| ES6 **
let datesVar = [ 2017-10-26T03:37:10.876Z,
2017-10-27T03:37:10.876Z,
2017-10-23T03:37:10.876Z,
2015-10-23T03:37:10.876Z ]
Math.min(...datesVar);
That will give the minimum date from the array.
Its shorthand Math.min.apply(null, ArrayOfdates);
ONELINER:
var min= dates.sort((a,b)=>a-b)[0], max= dates.slice(-1)[0];
result in variables min and max, complexity O(nlogn), editable example here. If your array has no-date values (like null) first clean it by dates=dates.filter(d=> d instanceof Date);.
var dates = [];
dates.push(new Date("2011-06-25")); // I change "/" to "-" in "2011/06/25"
dates.push(new Date("2011-06-26")); // because conosle log write dates
dates.push(new Date("2011-06-27")); // using "-".
dates.push(new Date("2011-06-28"));
var min= dates.sort((a,b)=>a-b)[0], max= dates.slice(-1)[0];
console.log({min,max});
var max_date = dates.sort(function(d1, d2){
return d2-d1;
})[0];
if you get max or min date with string type from string date of array,you can try this:
const dates = ["2021-02-05", "2021-05-20", "2021-01-02"]
const min = dates.reduce((acc,date)=>{return acc&&new Date(acc)<new Date(date)?acc:date},'')
const max = dates.reduce((acc,date)=>{return acc&&new Date(acc)>new Date(date)?acc:date},'')
The above answers do not handle blank/undefined values to fix this I used the below code and replaced blanks with NA :
function getMax(dateArray, filler) {
filler= filler?filler:"";
if (!dateArray.length) {
return filler;
}
var max = "";
dateArray.forEach(function(date) {
if (date) {
var d = new Date(date);
if (max && d.valueOf()>max.valueOf()) {
max = d;
} else if (!max) {
max = d;
}
}
});
return max;
};
console.log(getMax([],"NA"));
console.log(getMax(datesArray,"NA"));
console.log(getMax(datesArray));
function getMin(dateArray, filler) {
filler = filler ? filler : "";
if (!dateArray.length) {
return filler;
}
var min = "";
dateArray.forEach(function(date) {
if (date) {
var d = new Date(date);
if (min && d.valueOf() < min.valueOf()) {
min = d;
} else if (!min) {
min = d;
}
}
});
return min;
}
console.log(getMin([], "NA"));
console.log(getMin(datesArray, "NA"));
console.log(getMin(datesArray));
I have added a plain javascript demo here
and used it as a filter with AngularJS in this codepen
This is a particularly great way to do this (you can get max of an array of objects using one of the object properties): Math.max.apply(Math,array.map(function(o){return o.y;}))
This is the accepted answer for this page:
Finding the max value of an attribute in an array of objects
Using only one loop in typescript you can get min/max date values at the same time:
function minMaxDates(dates: Date[]): {minDate: Date, maxDate: Date} {
let minDate = new Date(275760, 8, 13);
let maxDate = new Date(1970, 1, 1);
dates.map(date => {
minDate = minDate < date ? minDate : date;
maxDate = maxDate > date ? maxDate : date;
});
return {minDate, maxDate}
};
To call this function:
const {minDate, maxDate} = minMaxDates(dates);
I posted a demo here.
Using Moment, Underscore and jQuery, to iterate an array of dates.
Sample JSON:
"workerList": [{
"shift_start_dttm": "13/06/2017 20:21",
"shift_end_dttm": "13/06/2017 23:59"
}, {
"shift_start_dttm": "03/04/2018 00:00",
"shift_end_dttm": "03/05/2018 00:00"
}]
Javascript:
function getMinStartDttm(workerList) {
if(!_.isEmpty(workerList)) {
var startDtArr = [];
$.each(d.workerList, function(index,value) {
startDtArr.push(moment(value.shift_start_dttm.trim(), 'DD/MM/YYYY HH:mm'));
});
var startDt = _.min(startDtArr);
return start.format('DD/MM/YYYY HH:mm');
} else {
return '';
}
}
Hope it helps.