How to group array of dates by month and year - javascript

Hi I'm having an array of the date object
"["2021-01-05T06:30:00.000Z","2021-01-06T06:30:00.000Z",
"2021-01-20T06:30:00.000Z","2021-02-09T06:30:00.000Z",
"2021-02-23T06:30:00.000Z","2021-02-16T06:30:00.000Z",
"2020-12-08T06:30:00.000Z","2020-12-15T06:30:00.000Z",
"2020-12-02T06:30:00.000Z","2020-12-09T06:30:00.000Z",
"2020-12-16T06:30:00.000Z"]"
I need to format into this
[
{
"month": "12",
"year": "2020",
"dates": [1,14,25]
},
{
"month": "10",
"year": "2020",
"dates": [1]
}
]
How to format like this help me. I have done like this but not completed I was stuck in adding dates. I know this is not the correct way of doing it. Please don't bother the code I have written I know it's garbage.
dateArray.reduce((initial,next)=>{
let result=[]
if(isSameYear(new Date(initial),new Date(next) &&
isSameMonth(new Date(initial),new Date(next))){
result.push({
month:new Date(nex).getMonth(),
year: new Date(next).getFullYear
})
}
})

You can group dates based on year and month in an object accumulator.
const data = ["2021-01-05T06:30:00.000Z", "2021-01-06T06:30:00.000Z", "2021-01-20T06:30:00.000Z", "2021-02-09T06:30:00.000Z", "2021-02-23T06:30:00.000Z", "2021-02-16T06:30:00.000Z", "2020-12-08T06:30:00.000Z", "2020-12-15T06:30:00.000Z", "2020-12-02T06:30:00.000Z", "2020-12-09T06:30:00.000Z", "2020-12-16T06:30:00.000Z" ],
result = Object.values(data.reduce((r, date) => {
const [year, month, day] = date.substr(0,10).split('-');
const key = `${year}_${month}`;
r[key] = r[key] || {month, year, dates: []};
r[key].dates.push(day);
return r;
},{}));
console.log(result);

When you group things in general, it's easier to group them into an object. The reason is you don't have to search an array for a matching result to append to, you only have to look up a key to concatenate to.
Here's one solution that builds an object, grouped by string keys built out of the month and year, and then maps over the values of that object to build the array you're looking for, by splitting the string keys into their significant parts.
const dates = ["2021-01-05T06:30:00.000Z","2021-01-06T06:30:00.000Z","2021-01-20T06:30:00.000Z","2021-02-09T06:30:00.000Z","2021-02-23T06:30:00.000Z","2021-02-16T06:30:00.000Z","2020-12-08T06:30:00.000Z","2020-12-15T06:30:00.000Z","2020-12-02T06:30:00.000Z","2020-12-09T06:30:00.000Z","2020-12-16T06:30:00.000Z"];
const grouped = dates.reduce((accumulator, date) => {
const parsed = new Date(date);
const year = parsed.getFullYear();
const month = parsed.getMonth();
const groupKey = `${month},${year}`;
accumulator[groupKey] = accumulator[groupKey] || {dates: []};
accumulator[groupKey].dates.push(parsed.getDay());
return accumulator;
}, {});
const result = Object.entries(grouped).map(([key, dates]) => {
const parts = key.split(',');
return {
month: parts[0],
year: parts[1],
dates: dates
};
});
console.log(result);

maybe do it in two passes
const dateArray = ["2021-01-05T06:30:00.000Z", "2021-01-06T06:30:00.000Z", "2021-01-20T06:30:00.000Z", "2021-02-09T06:30:00.000Z", "2021-02-23T06:30:00.000Z", "2021-02-16T06:30:00.000Z", "2020-12-08T06:30:00.000Z", "2020-12-15T06:30:00.000Z", "2020-12-02T06:30:00.000Z", "2020-12-09T06:30:00.000Z", "2020-12-16T06:30:00.000Z"];
const mapping = dateArray.reduce((initial, next) => {
const month = next.substring(5, 7);
const year = next.substring(0, 4);
const day = next.substring(8, 10);
initial[year] = initial[year] || {};
initial[year][month] = initial[year][month] || [];
initial[year][month].push(parseInt(day, 10));
return initial;
}, {});
const result = []
Object.keys(mapping).forEach(year => {
Object.keys(mapping[year]).forEach(month => {
result.push({
month,
year,
dates: mapping[year][month]
});
});
});
console.log(result);

One simple solution is to use an object to group by month and year like below:
const data = ["2021-01-05T06:30:00.000Z","2021-01-06T06:30:00.000Z",
"2021-01-20T06:30:00.000Z","2021-02-09T06:30:00.000Z",
"2021-02-23T06:30:00.000Z","2021-02-16T06:30:00.000Z",
"2020-12-08T06:30:00.000Z","2020-12-15T06:30:00.000Z",
"2020-12-02T06:30:00.000Z","2020-12-09T06:30:00.000Z",
"2020-12-16T06:30:00.000Z"];
function groupDates(dates) {
const groupedDates = {};
dates.forEach(d => {
const dt = new Date(d);
const date = dt.getDate();
const year = dt.getFullYear();
const month = dt.getMonth() + 1;
const key = `${year}-${month}`;
if (key in groupedDates) {
groupedDates[key].dates = [...groupedDates[key].dates, date];
} else {
groupedDates[key] = {
year,
month,
dates: [date],
};
}
});
return Object.values(groupedDates);
}
console.log(groupDates(data));

Here is a pure javascript solution without using any library. It is based on a simple O(n^2) runtime. But if you like to use some libraries for like binary search you can reduce it to O(nlogn).
The trick is to brick this task into smaller task as I did with functions getMonthYear (to convert string to object), compare and addDate:
data = ["2021-01-05T06:30:00.000Z","2021-01-06T06:30:00.000Z","2021-01-20T06:30:00.000Z","2021-02-09T06:30:00.000Z","2021-02-23T06:30:00.000Z","2021-02-16T06:30:00.000Z","2020-12-08T06:30:00.000Z","2020-12-15T06:30:00.000Z","2020-12-02T06:30:00.000Z","2020-12-09T06:30:00.000Z","2020-12-16T06:30:00.000Z"];
function categorize(data) {
// 2021-01-05T06:30:00.000Z => {month:"01", year:"2021", date:"05"}
function getMonthYear(str) {
var datePart = str.toString().trim().split("T")[0];
var datePartArr = datePart.split("-");
return {month:datePartArr[1], year:datePartArr[0], date:datePartArr[2]};
}
// testing
//var ans = getMonthYear("2021-01-06T06:30:00.000Z");
//console.log(ans);
// comparing two items to see if they have the same year and month
function compare(item1, item2) {
return (item1.month == item2.month) && (item1.year == item2.year);
}
// testing
//var ans = compare({month:"04", year:"2021"}, {month:"03", year:"2021"});
//console.log(ans);
// adding a date to the list of dates
function addDate(dateList, dateNumber) {
for(var i in dateList) {
if (dateList[i] == dateNumber) return;
}
dateList.push(dateNumber);
}
// testing
/*var ans = [2,4];
addDate(ans, 4);
console.log(ans);*/
// Now lets build the answer by looping over
// --------------------------------------------
var list = []; // the final answer list
data.forEach(function(str){
var item = getMonthYear(str);
var itemMatched = false;
// now lopping over the list to see if it has any match
for(var i in list) {
if (compare(item, list[i])) { // matched found
itemMatched = true;
addDate(list[i].date, item.date);
break;
}
}
// item had no match, add it as a new item to list
if (!itemMatched) {
list.push({
month: item.month,
year: item.year,
date: [item.date]
});
}
});
return list;
}
var ans = categorize(data);
console.log(ans);
Here is link to jsfiddle

Related

(javascript) How to sort an array of strings that has seasons and years?

The seasons need to be in this order: Spring, Summer, Fall, Winter
and each season has the year 2025 and 2026.
All the 2025's need to be with each other and all the 2026's need to be with each other (2025 and 2026 are just examples the years can be anything: 1945, 3005, 7980, etc).
for example:
const seasonArr = ['Spring2026',' Spring2025','Summer2026','Summer2025','Fall2025','Fall2026','Winter2026','Winter2025']
let sortedArr = []
const someFunction = () => {
...
}
someFunction(seasonArr) // output: sortedArr = ['Spring2025', 'Summer2025', 'Fall2025', 'Winter2025', 'Spring2026', 'Summer2026', 'Fall2026', 'Winter2026']
I know I probably have to compare the years but since they're strings I'm struggling to compare just the numbers.
this is something that I thought of:
const seasonArr = ['Spring2026',' Spring2025','Summer2026','Summer2025','Fall2025','Fall2026','Winter2026','Winter2025']
let sortedArr = []
const someFunction = (seasonArr) => {
for (const season of seasonArr) {
let year = season.split(/([0-9]+)/)
// unsure where to go from here
}
}
someFunction(seasonArr)
I split the strings into year and season, compare the year and compare the season for same years. I use an array of seasons for the order.
const seasonArr = ['Spring2026','Spring2025','Summer2026','Summer2025','Fall2025','Fall2026','Winter2026','Winter2025'];
const seasons = ['Spring', 'Summer', 'Fall', 'Winter'];
const regexp = /(.+)(\d{4})/;
const someFunction = (s) => {
return [...s].sort((lhs, rhs) => {
const [seasonL, yearL] = regexp.exec(lhs).slice(1);
const [seasonR, yearR] = regexp.exec(rhs).slice(1);
return +yearL - +yearR || seasons.indexOf(seasonL) - seasons.indexOf(seasonR);
});
}
let sortedArr = someFunction(seasonArr);
console.log(sortedArr);
I create a shallow copy with
[...s]
to keep the original array unchanged.
Same logic with better performance for large arrays
const seasonArr = ['Spring2026','Spring2025','Summer2026','Summer2025','Fall2025','Fall2026','Winter2026','Winter2025'];
const seasons = ['Spring', 'Summer', 'Fall', 'Winter'];
const regexp = /(.+)(\d{4})/;
const someFunction = (s) => {
return s
.map(el => {
const [season, year] = regexp.exec(el).slice(1);
return [season, year, seasons.indexOf(season[0])];
})
.sort((lhs, rhs) => {
return +lhs[1] - +rhs[1] || lhs[2] - rhs[2];
})
.map(el => el[0] + el[1]);
}
let sortedArr = someFunction(seasonArr);
console.log(sortedArr);
This solution builds on basically what you're suggesting.
First, split the values into an array of objects that have season and year. Then sort by year and the index of the season. Then put the values back together.
const seasonArr = ['Spring2026',' Spring2025','Summer2026','Summer2025','Fall2025','Fall2026','Winter2026','Winter2025']
const SEASONS = ["Spring", "Summer", "Fall", "Winter"]
function comparator(a, b) {
if (a.year == b.year) {
const aSeasonIndex = SEASONS.indexOf(a.season)
const bSeasonIndex = SEASONS.indexOf(b.season)
return aSeasonIndex - bSeasonIndex;
}
return a.year - b.year;
}
function seasonYearToObject(obj) {
const matches = obj.match(/([^\d]*)(\d+)/)
if (matches) {
return {season: matches[1], year: matches[2]}
}
}
function objectToSeasonYear(obj) {
return `${obj.season}${obj.year}`
}
function sortByYearAndSeason(arr) {
return arr
.map(entry => seasonYearToObject(entry))
.sort(comparator)
.map(objectToSeasonYear);
}
console.log (sortByYearAndSeason(seasonArr))
You are almost done after season.split(/([0-9]+)/) with String#split()
Just have created seasonObj with the desired sort order for all seasons and combine Array#map(), Destructuring assignment and Array#sort()
Code:
const seasonArr = ['Spring2026', 'Spring2025', 'Summer2026', 'Summer2025', 'Fall2025', 'Fall2026', 'Winter2026', 'Winter2025']
const seasonObj = { Spring: 0, Summer: 1, Fall: 2, Winter: 3 }
const result = seasonArr
.map((season) => season.split(/([0-9]+)/))
.sort(([aSeason, aYear], [bSeason, bYear]) =>
+aYear - bYear || seasonObj[aSeason] - seasonObj[bSeason])
.map(([season, year]) => `${season}${year}`)
console.log(result)

How to return specific year from Date array ? react

I've got a dates array and I wanted to create a function that returns all the dates from 2014 or 2015 depending on what the user clicks
i tried using substring(), although that gives me an array of the 2014 dates, it also returns 2 undefined's which I dont want. How would I return an array of only 2014 dates?
const datefunc = () => {
const date = ['2014-4-4', '2014-5-4', '2015-4-4', '2015-3-4']
const dates = date.map((d) => {
if (d.substring(0, 4) === '2014') {
return d;
}
})
console.log(dates)
}
datefunc()
Array.map modifies each element in an array => when you run date.map and only return a value if the date starts with '2014', all the other values in the array will become undefined.
Array.filter filters the elements in an array based on a predicate => when running date.filter, all the elements for which the predicate is false will be excluded in the returned array.
Changing your code to this should work:
const datefunc = () => {
const date = ['2014-4-4', '2014-5-4', '2015-4-4', '2015-3-4'];
const dates = date.filter((d) => d.substring(0, 4) === '2014');
// You could also use this
// const dates = date.filter((d) => d.startsWith('2014'));
console.log(dates);
}
datefunc();
If your dates aren't all of the same form, but all valid date strings you could also tranfsorm them into Date values and then filter them like this:
const datefunc = () => {
const date = ['2014-4-4', '2014-5-4', '2015-4-4', '2015-3-4'];
const dates = date
.map((d) => new Date(d))
.filter((d) => d.getFullYear() === 2014);
console.log(dates);
}
datefunc();
const datefunc = () => {
const date = ['2014-4-4', '2014-5-4', '2015-4-4', '2015-3-4']
const dates = date.filter((d) => d.substring(0, 4) === '2014')
console.log(dates)
}
datefunc()
A more robust way to do this would be to convert string to date. Also, since you need to filter out dates, map wouldn't really work here. Even for false conditions, even though you are not returning anything, you end up returning undefined anyways. That's why those two undefined values
const datefunc = () => {
const date = ['2014-4-4', '2014-5-4', '2015-4-4', '2015-3-4']
const targetYears = [2014, 2015]; // Add the years here which you want to filter on
const dates = date.filter((d) => {
const curYear = new Date(d).getFullYear();
return targetYears.includes(curYear);
})
console.log(dates)
}
datefunc()
You can add the years you want in the filteredYears array
const datefunc = () => {
const dates = ['2014-4-4', '2014-5-4', '2015-4-4', '2015-3-4']
const filteredYears = [2014];
const filteredDates = dates.filter(date => filteredYears.includes(new Date(date).getFullYear()));
console.log(filteredDates)
}
datefunc()
You may convert datestring to date type first, then make use "filter()" to get particular year date. Hopefully this helps you!
date = ["2014-4-4", "2014-5-4", "2015-4-4", "2015-3-4"];
convertedDate: Date[] = [];
getYearList() {
this.date.forEach(x => {
this.convertedDate.push(new Date(x));
});
this.convertedDate = this.convertedDate.filter(
x => x.getFullYear() == 2014
);
console.log(this.convertedDate);
}
const datesInYear = (dates, year) => {
return dates.filter((x) => x.includes(year));
};
const dates = ["2014-4-4", "2014-5-4", "2015-4-4", "2015-3-4"];
console.log(datesInYear(dates, "2014"));

Getting loop when checking Array Object for match and pushing object when none found

Here is my code:
function createMatchList(data) {
var matchArray = [];
for (var i = 0; i < initialData.length; i++) {
var listData = cleanData(initialData[i][data]);
if (matchArray.length) {
for (var a = 0; a < matchArray.length; a++) {
if (matchArray[a][data] == listData) {
matchArray[a].Count = matchArray[a].Count + 1;
} else {
// THIS CAUSES LOOP
matchArray.push({ [data]: listData, "Count": 1 });
}
}
} else {
matchArray.push({ [data]: listData, "Count": 1 });
}
}
}
Essentially, this appears to work outside of when a matching object isn't found, I have an else to push that object to the array and it causes a loop.
I'm not sure why this would happen and I'm totally lost..
I have an initial if/else to see if the array is empty, if it is empty, push the initial object into the array, past this point if the array isn't empty, I check the array using a for loop and the if statement works, but my else statement causes the loop. If I were to take that matchArray.push({ [data]: listData, "Count": 1 }); out of the else statement and just console.log something, it would successfully log for each additional iteration in the first for loop for the initialData.
And just to give an idea of what the array looks like with an object in it:
[
{
Date: "27 June 1911",
Count: 1
}
]
My goal is to search the Array for matching Dates and if there is a match, don't push to the Array but instead update the Count. The reason for this is I have a huge raw JSON file with duplicates and I'm trying to create a new data source removing duplicates but counting the occurrences.
Thanks
Use Set and Array.filter():
[...new Set(dates)].map(date => ({
date,
count: dates.filter(currDate => date === currDate).length
}));
Note: By convention object keys should be lowercased, date and not Date.
Or if you feel brave:
const counts = {};
dates.forEach(date => (counts[date] = (counts[date] || 0) + 1));
const dates = [
'27 June 1911',
'27 June 1952',
'27 March 1911',
'27 June 1952',
'24 June 1911',
'27 June 1952'
];
const datesCount = [...new Set(dates)].map(date => ({
date,
count: dates.filter(currDate => date === currDate).length
}));
console.log('datesCount', datesCount);
const counts = {};
dates.forEach(date => (counts[date] = (counts[date] || 0) + 1));
const dateCount2 = Object.entries(counts).map(([date, count]) => ({
date,
count
}));
console.log('dateCount2', dateCount2);
// Make unique array of dates.
const datesSet = [...new Set(dates)];
// Count by filtering the array and getting it's length.
const datesCount = datesSet.map(date => ({
date,
count: dates.filter(currDate => date === currDate).length
}));
How important is performance and are you using >= 2015? If so, you could just map instead and reduce some code complexity like this answer:
const counts = new Map([...new Set(a)].map(
x => [x, a.filter(y => y === x).length]
));
Then to get the count you would just call:
counts.get(x)
In the case where you need to store it in an object that looks exactly as you've outlined above, I would do something like:
let unique = Set(dates);
var matchArray = [];
unique.forEach(function (date) {
matchArray.push({
"Date": date,
"Count": dates.filter(x => x === date).length
});
});

How to combine consecutive dates in javascript?

I have an array of dates such as :
test = [ '2018-07-18', '2018-07-19', '2018-07-21', '2018-07-23', '2018-07-24', '2018-07-26'];
And I want to return an array of sub arrays of consecutive dates like this:
result = [['2018-07-18', '2018-07-19'], ['2018-07-21'], ['2018-07-23', '2018-07-24'], ['2018-07-26']]
I'm trying to write a snippet code:
const moment = require('moment');
let visited = [];
const alpha = test.reduce((accumlator, current_date, current_index, array) => {
let start_date = current_date;
let successive_date = array[current_index + 1];
visited.push(start_date);
if(successive_date && moment(successive_date).diff(moment(start_date), 'days') === 1
&& visited.includes(successive_date) === false) {
accumlator.concat(start_date);
accumlator.concat(successive_date);
}
if(successive_date && moment(successive_date).diff(moment(start_date), 'days') !== 1
&& visited.includes(successive_date) === false) {
accumlator.concat(successive_date);
}
return accumlator;
}, []);
console.log('alpha: ', alpha);
The result when using concat was:
alpha: []
I used push() and it returns an array such test:
alpha: [ '2018-07-18','2018-07-19','2018-07-21','2018-07-23','2018-07-23','2018-07-24''2018-07-26' ]
How can I fix this in order to get the result such as mentioned above?
You can try with:
test.reduce((acc, date) => {
const group = acc[acc.length - 1];
if (moment(date).diff(moment(group[group.length - 1] || date), 'days') > 1) {
acc.push([date])
} else {
group.push(date);
}
return acc;
}, [[]])
Output:
[
[
"2018-07-18",
"2018-07-19"
],
[
"2018-07-21"
],
[
"2018-07-23",
"2018-07-24"
],
[
"2018-07-26"
]
]
Th following helps, if the order of the dates in the array is not maintained. For example, '2018-07-18', '2018-07-19', '2018-07-17' are consecutive but scattered at the start and end of the array.
var test = [ '2018-07-18', '2018-07-19', '2018-07-21', '2018-07-23', '2018-07-24', '2018-07-26', '2018-07-17'], dateformat = "YYYY-MM-DD";
var result = test.reduce(function(acc,val){
var present, date = moment(val,dateformat);
acc.forEach(function(arr,index){
if(present) return;
if(arr.indexOf(date.clone().subtract(1,'day').format(dateformat))>-1 || arr.indexOf(date.clone().add(1,'day').format(dateformat))>-1)
{
present = true;
arr.push(val);
}
});
if(!present) acc.push([val]);
return acc;
},[]);
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
You can use this function but dates should be in sorted order.
function get_relative_dates(dates){
var format = 'YYYY-MM-DD';
var newDate = [];
dates.forEach(function(date){
var lastArr, lastDate;
if(newDate.length){
lastArr = newDate[newDate.length -1];
if(!lastArr.length)
lastArr.push(date);
else{
var lastDate = lastArr[lastArr.length -1];
if(moment(lastDate, format).add(1,'d').format(format) == date)
lastArr.push(date);
else
newDate.push([date]);
}
}
else
newDate.push([date]);
});
return newDate;
}

Javascript: Filter Array with duplicate Dates

I´m using Angular2 and I have an array with Date-Objects (~1000).
Most of the Date-Objects have got the exactly same dates (for example 2016_11_02; Dates have no hours & minutes).
Normally there should be about ~10-20 different Dates in the Array.
Now i want to filter this array and delete the duplicate Dates.
So in the end there should be about ~10-20 Date-Objects in the array.
Here´s the code i tried:
let uniqueArray = duplicatesArray.filter(function(elem, pos) {
return channelEPGDates.indexOf(elem) == pos;
});
console.log('unique: ' + uniqueArray.length);
I know this is not correct, cause the unique-Array has the same length as the old array. But how can i compare the Dates itself in the filter-function?
Thanks so much!
I would map the dates to epoch time using the getTime method, and then map them back to Date objects.
let uniqueArray = duplicatesArray
.map(function (date) { return date.getTime() })
.filter(function (date, i, array) {
return array.indexOf(date) === i;
})
.map(function (time) { return new Date(time); });
You could use Set and the spread syntax ... for it.
unique = src => [...new Set(src)];
var unique = src => [...new Set(src)];
array = ["Mike", "Matt", "Nancy", "Adam", "Jenny", "Nancy", "Carl"];
console.log(unique(array));
To get objects with unique dates, you could filter the data by using a closure over a Set and a callback for getting the part which has to be unique.
var unique = (src, fn) => src.filter((s => o => !s.has(fn(o)) && s.add(fn(o)))(new Set));
array = [{ date: new Date('2019-11-01')}, { date: new Date('2019-11-01')}, { date: new Date('2019-11-01')}, { date: new Date('2019-11-02')}, { date: new Date('2019-11-01')}, { date: new Date('2019-11-05')}, { date: new Date('2019-11-05')}, { date: new Date('2019-11-04')}, { date: new Date('2019-11-07')}];
console.log(unique(array, ({ date }) => date.toISOString().slice(0, 10)));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can do with jQuery like this :
var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
var uniqueNames = [];
$.each(names, function(i, el){
if($.inArray(el, uniqueNames) === -1) uniqueNames.push(el);
});
Here is a method using the built-in methods on Array.
var arr = ["John", "Jimmy", "John"];
var dArr = []; //Date array
while (dArr.length < 10000) { //Load a lot of pseudo dates
dArr.push(
new Date().getTime() + Math.round(Math.random() * 10)
);
}
function arrayUnique(arr) {
return arr.reduce(function(previousValue, currentValue) {
if (previousValue.some(function(value, index) {
return value === currentValue;
}) === false) {
previousValue.push(currentValue);
}
return previousValue;
}, []);
}
console.log(arrayUnique(arr));
console.log(arrayUnique(dArr).sort());

Categories

Resources