Get all the day sums between two days inside their month object - javascript

There will be 2 inputs for dates and the output should look like this.
Assume that 2 inputs are 2020-01-01 and 2020-02-23
{
"2021-01" : 31,
"2021-02" : 23,
}
And the second thing is, if a month includes all its days, it should be represented as:
{
"2021-01" : {
days: 31,
isAll : true
},
"2021-02" : {
days: 23,
isAll : false
},
}
How could I do that?

You can use .reduce to iterate over the object entries, and for each key, save an object of the number of days and whether it is the last day:
const _getLastDay = date => {
const [year, month] = date.split('-');
return new Date(year, month, 0).getDate();
}
const getDates = (date1, date2) => {
const data = {
[`${date1.getFullYear()}-${('0'+(date1.getMonth()+1)).slice(-2)}`]: date1.getDate(),
[`${date2.getFullYear()}-${('0'+(date2.getMonth()+1)).slice(-2)}`]: date2.getDate()
};
return Object.entries(data).reduce((acc, [date,days]) => {
acc[date] = { days, isAll: _getLastDay(date)===days };
return acc;
}, {});
}
console.log( getDates(new Date("2021-01-31"),new Date("2021-02-23")) );

Related

Getting the occurences of a date in a specific week

I would like to get the occurences of a day in a specific week (the week that includes today). I have an array of visits with a specified location and date that I want to transform.
const today = new Date(Date.parse("2021-05-13")); // set today
const numDay = today.getDate();
const twoDaysAgo = new Date();
twoDaysAgo.setDate(numDay - 2); // set 2 days ago from today
const twelveDaysAgo = new Date();
twelveDaysAgo.setDate(numDay - 12); // set 12 days ago from today
const visits = [{
location: "Paris",
date: today
},
{
location: "Berlin",
date: twoDaysAgo
},
{
location: "Brussels",
date: twoDaysAgo
},
{
location: "Rome",
date: twelveDaysAgo
}
];
I would like to process the data so that the number of times a day occurs in the specific week can be tracked as a number and the locations are stored in an array. If there are no visits for a certain day, they do not need to be stored and it should just say numVisits: 0.
This is the outcome I would like to achieve.
const thisWeekResult = [{
monday: {
numVisits: 0
},
tuesday: {
numVisits: 2,
locations: ["Berlin", "Brussels"]
},
wednesday: {
numVisits: 0,
},
thursday: {
numVisits: 1,
locations: ["Paris"]
},
friday: {
numVisits: 0,
},
saturday: {
numVisits: 0,
},
sunday: {
numVisits: 0,
}
}];
You could start with the beginning of the week (start in the code below being sunday just gone).
Then you reduce the 7 days beyond that date, and look for records on that dat from the original set using filter. Finally you just build up the object required for each day
const today = new Date(Date.parse("2021-05-13")); // set today
const numDay = today.getDate();
const twoDaysAgo = new Date();
twoDaysAgo.setDate(numDay - 2); // set 2 days ago from today
const twelveDaysAgo = new Date();
twelveDaysAgo.setDate(numDay - 12); // set 12 days ago from today
const visits = [{
location: "Paris",
date: today
},
{
location: "Berlin",
date: twoDaysAgo
},
{
location: "Brussels",
date: twoDaysAgo
},
{
location: "Rome",
date: twelveDaysAgo
}
];
const start = new Date()
start.setDate(today.getDate() - today.getDay())
const result = [1,2,3,4,5,6,7].reduce( (acc,d) => {
const date = new Date()
date.setDate(start.getDate() + d);
const day = new Intl.DateTimeFormat('en', {weekday:'long'}).format(date).toLowerCase();
const records = visits.filter(v => v.date.getYear() == date.getYear() && v.date.getMonth() == date.getMonth() && v.date.getDate() == date.getDate());
acc[day] = records.length
? {
numVisits: records.length,
locations: records.map(r => r.location)
}
: {numVisits: 0 };
return acc;
},{});
console.log(result);

Convert Rows to Columns using RxJS / TypeScript / JavaScript

I have the following table data:
date
value
01/01/2000
1
01/02/2000
2
01/01/2001
2
01/01/2002
1.5
01/02/2002
1.6
[{date: "01/01/2000", value: "1"},{date: "01/02/2000", value: "2"},{date: "01/01/2001", value: "2"},{date: "01/01/2002", value: "1.5"},{date: "01/02/2002", value: "1.6"}]
I would like to convert it to columns:
Year
Jan
Feb
2000
1
2
2001
2
2002
1.5
1.6
[{Year: "2000", Jan: "1", Feb: "2"},
{Year: "2001", Jan: "", Feb: "2"},
{Year: "2002", Jan: "1.5", Feb: "1.6"}]
How can I change it using RxJS / TypeScript / JavaScipt in Angular?
Thanks.
See below approach using reduce
const initial = [{date: "01/01/2000", value: "1"},{date: "01/02/2000", value: "2"},{date: "01/01/2001", value: "2"},{date: "01/01/2002", value: "1.5"},{date: "01/02/2002", value: "1.6"}]
const allMonths = initial.reduce((prev, next) => {
const date = next.date.substr(6,4) + '/' + next.date.substr(3,2)
const month = new Date(date).toLocaleString('default', { month: 'short' });
return {...prev, [month]: ''}
}, {})
const temp = initial.reduce(
(prev, next) => {
const date = next.date.substr(6,4) + '/' + next.date.substr(3,2)
const month = new Date(date).toLocaleString('default', { month: 'short' });
const Year = new Date(date).getFullYear()
let prevYearVal = prev[Year]
if(!prevYearVal) { prevYearVal = {Year,...allMonths} ;}
return {...prev, [Year]: {...prevYearVal,Year, [month]: next.value}}
return prev
},
{}
)
const final = Object.values(temp)
console.log(final)
This requires a number of steps to complete the transformation.
Convert string date into usable JavaScript date object
because the date is mm/dd/YYYY, rather then dd/mm/YYYY it must be transposed
then a new Date is constructed using the decomposed string
Then it is just a matter of constructing the new object
The output from that is each date with corresponding values that must be combined
The second iterator (.reduce()) combines the objects if the Year properly match
const input = [{date: "01/01/2000", value: "1"},{date: "01/02/2000", value: "2"},{date: "01/01/2001", value: "2"},{date: "01/01/2002", value: "1.5"},{date: "01/02/2002", value: "1.6"}];
const result = input.map(i => {
const [day, month, year] = i.date.split('/');
const [_, monthName] = new Date(year, month - 1, day).toDateString().split(' ');
return {
Year: year,
[monthName]: i.value
}
}).reduce((acc, d, idx) => {
if (idx == 0) {
acc.push(d);
} else if (acc[acc.length - 1].Year == d.Year) {
acc[acc.length - 1] = Object.assign(acc[acc.length-1], d);
} else {
acc.push(d);
}
return acc;
}, []);
console.log(result);
Here is a version that works similarly to answers from Owen and Randy, but it separates the month-name processing into its own helper function. Written independently, the local variable names are all different, but they do the same work. It is also structured as a single function call rather than a set of steps:
const monthName = ((months) => (m) => months [m - 1])(
'01|02|03|04|05|06|07|08|09|10|11|12' .split ('|') .map (
m => new Date (`2021/${m}`).toLocaleString('default', {month: 'short'})
)
)
const transform = (xs) => {
const base = Object .fromEntries (
[...new Set(input .map (
({date}) => date .slice (3, 5)
))]
.map (month => [monthName (month), ""])
)
return Object .values (xs .reduce ((years, {date, value}) => {
const Year = date .slice (6, 10),
Month = date.slice (3, 5)
years [Year] = years [Year] || {Year, ...base}
years [Year] [monthName(Month)] = value
return years
}, {}))
}
const input = [{date: "01/01/2000", value: "1"}, {date: "01/02/2000", value: "2"}, {date: "01/01/2001", value: "2"}, {date: "01/01/2002", value: "1.5"}, {date: "01/02/2002", value: "1.6"}]
console .log (transform (input))
.as-console-wrapper {max-height: 100% !important; top: 0}
One advantage is that it doesn't call the Date constructor for every object, simply calling it once for every calendar month. And if you don't want the locale string version but a fixed set of month names the helper can be even simpler:
const monthName = ((months) => (m) => months [m - 1]) (
'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec' .split ('|')
)
I tend to prefer functions written purely as expressions and not statement, so an alternate version might look like this:
const transform = (
xs,
base = Object .fromEntries (
[...new Set (xs .map (
({date}) => date .slice (3, 5)
))]
.map (month => [monthName (month), ""])
)
) => Object .values (
xs .reduce ((a, {date, value}, _, __,
Year = date .slice (6, 10), Month = date.slice (3, 5)
) => ({
...a,
[Year]: {Year, ...(a [Year] || base), [monthName (Month)]: value}
}), {})
)
It works the same way, and is somewhat less efficient, but I find it cleaner.

Group array by two different keys and sum values

I'm working with an array on React and i'm trying to filter it by month and year, i managed to do it by month but for some reason i can't add the year key, this is what i have so far:
This is the array that i have originally:
paid = [
{amount:155, month:11, year:2020, date:11-11-2020}
{amount:120, month:11, year:2021, date:05-11-2021}
{amount:135, month:12, year:2020, date:11-12-2020}
...
]
const group = groupBy(d, (invoices) => invoices.month); //d is the array called "paid"
This is the groupBy function:
function groupBy(list, keyGetter) {
const map = new Map();
list.forEach((item) => {
const key = keyGetter(item);
const collection = map.get(key);
if (!collection) {
map.set(key, [parseInt(item.amount)]);
} else {
collection.push(parseInt(item.amount));
}
});
return map;
}
And this is the result i have:
grouped = [
{name:11, values: [155,120...]},
{name:12, values: [135...]
];
And what i want to to is to also have it grouped by the year, so for example, the month 11, shouldn't have two values, because on the original array i have on month that's 11 from 2020 and one from 2021, so what i want to have as a result is this:
grouped = [
{name:11/2020, values: [155...]},
{name:11/2021, values: [120...]},
{name:12/2020, values: [135...]
];
Can anyone help me with this?
If I understand correctly you want to sum all values per month per year. I guess this will work.
// Generate random data.
const genData = () => {
const d = [];
for(let i =0; i < 1000; i++) {
d.push({
year: Math.round(Math.random() * 10) + 2001,
month: Math.round(Math.random() * 12),
amount: Math.round(Math.random() * 100) + 100
})
}
return d;
};
// Sum all data per month per year
const sumData = (d) => {
const summed = {};
for(const {year,month,amount} of d) {
// By using a unique key for each year/month combi you can easily access the
// intermedeiate result and add another value.
const key = year + '/' + month;
// If it does not yet exist, create an empty record for this year/month
if(!(key in summed)) {
summed[key] = {year,month, sum:0, values:[]};
}
// Grab the intermediate values and add amount to sum and to values
summed[key].values.push(amount)
summed[key].sum += amount;
}
return Object.values(summed);
};
// Run all
const d = genData();
console.log(d);
console.log(sumData(d));
Working with Array.reduce method could be more simple, for the example I added some values:
const paid = [
{amount:155, month:11, year:2020, date:'11-11-2020'},
{amount:160, month:11, year:2020, date:'11-11-2020'},
{amount:120, month:11, year:2021, date:'05-11-2021'},
{amount:130, month:11, year:2021, date:'05-11-2021'},
{amount:135, month:12, year:2020, date:'11-12-2020'},
{amount:145, month:12, year:2020, date:'11-12-2020'}
]
const grouped = paid.reduce((acc,val)=>{
if(acc[val.month+'/'+val.year]){
acc[val.month+'/'+val.year].push(val.amount)
} else {
acc[val.month+'/'+val.year] = [val.amount]
}
return acc
}, {})
console.log(JSON.stringify(grouped,null,2))
EDIT -----------------
I edited the code to use the group by function and to produce an array containing the values and the sum. you can group passing an array containing first level key (like ['month'] or ['month', 'year']):
const paid = [
{ amount: 155, month: 11, year: 2020, date: "11-11-2020" },
{ amount: 160, month: 11, year: 2020, date: "11-11-2020" },
{ amount: 120, month: 11, year: 2021, date: "05-11-2021" },
{ amount: 130, month: 11, year: 2021, date: "05-11-2021" },
{ amount: 135, month: 12, year: 2020, date: "11-12-2020" },
{ amount: 145, month: 12, year: 2020, date: "11-12-2020" }
];
const groupBy = (data, keys) => {
return Object.values(
data.reduce((acc, val) => {
const name = keys.reduce((finalName,key)=> finalName + val[key]+'/','').slice(0, -1)
if (acc[name]) {
acc[name].values.push(val.amount);
acc[name].sum += val.amount;
} else {
acc[name] = {
name,
sum:val.amount,
values:[val.amount]
};;
}
return acc;
}, {})
);
};
console.log(JSON.stringify(groupBy(paid, ['month','year']), null, 2));

Sort array with timestamps and merge similar dates

I am trying to take this array :
[
{
"date": a timestamp: June 20, 2020 at 7:32:42 PM UTC
"value": 3
..
}
..
]
and accomplish 3 things effeciently
Convert the timestamp to normal date and replace with the timestamp
Merge dates of the same day. ( so i add their values and set under a single day.
Sort the array when recent are first.
I have started with something like this :
array.sort(function(a,b){ return new Date(b.date) - new Date(a.date);
var salesDates = sales.map(function(element){element.date = new Date(element.date); return element }); });
Which will only sort, but i need to replace timestamp/date, sort and merge same dates elegantly and effeciently
Is it possible with only sort function ?
Here. First i group it with .reduce(). Then i sort it .sort(). After that i change the timestamp to a date format.
let arr = [
{
"date": 1594023899426,
"value": 3
},
{
"date": 1592423499234,
"value": 2
},
{
"date": 1594023899426,
"value": 1
}
];
let result = arr
.reduce((a, v) => {
let index = a.findIndex(el => el.date === v.date);
if (index !== -1) {
a[index].value += v.value;
return a;
}
a.push({
date: v.date,
value: v.value
});
return a;
}, [])
.sort((a, b) => b.date - a.date)
.map(el => {
el.date = new Date(el.date);
return el;
});
console.log(result);
Here's another approach to it:
let sortingReducer = (accumulator, obj) => {
// This is the merging logic
let existingObj = accumulator.find(
(compareObj) => {
return obj.date?.getDate() === compareObj.date?.getDate()
}
);
if (existingObj) {
existingObj.value += obj.value;
return accumulator;
}
// This is the sorting logic
const nextIndex = accumulator.findIndex(
(compareObj) => obj.date?.getTime() < compareObj.date?.getTime()
);
const index = nextIndex > -1 ? nextIndex : accumulator.length;
accumulator.splice(index, 0, obj);
return accumulator;
};
const input = [
{
date: new Date(),
value: 2,
},
{
date: new Date(new Date().setDate(1)),
value: 4,
},
{
date: new Date(new Date().setDate(1)),
value: 1,
},
{
date: new Date(new Date().setDate(2)),
value: 7,
},
];
const output = input.reduce(sortingReducer, []);
I've take some help from this answer: https://stackoverflow.com/a/50246275/1912288
I like the approach in the above answer, this is just a different approach to it.

How to get unique date values from object array

I need to get all unique days of multiple date values in the format DD.MM.. In this example data, there are two values for the 24th of december:
const data = [
{ date: ISODate("2019-12-24T03:24:00Z") },
{ date: ISODate("2019-12-24T04:56:00Z") },
{ date: ISODate("2019-12-25T02:34:00Z") },
{ date: ISODate("2019-12-26T01:23:00Z") }
]
So the result should be
const result = [
'24.12.',
'25.12.',
'26.12.'
]
So first of all I'll map my data and split the values only for the dates:
const dates = data.map(d => d.date.toString().split('T')[0])
But how do I get the unique values and change the output format?
Update
I came up with this, but it looks very complicated...
data.map(d => {
const dateSplit = d.date.toString().split('T')[0].split('-')
return dateSplit[2] + '.' + dateSplit[1] + '.'
})
.filter((value, index, self) {
return self.indexOf(value) === index
})
It seems that ISODate returns a standard JS Date object. You can use Date.getDate() to get the day, and Date.getMonth() to get the month (0 based, so we need to add 1):
const data = [
{ date: new Date('2019-12-24T03:24:00Z') },
{ date: new Date('2019-12-24T04:56:00Z') },
{ date: new Date('2019-12-25T02:34:00Z') },
{ date: new Date('2019-12-26T01:23:00Z') }
]
const result = [...new Set(data.map(({ date: d }) =>
`${d.getDate()}.${d.getMonth() + 1}.`
))]
console.log(result)
Previous answer:
Use a regular expression to match the month and the day, and assign them to consts using destructuring. Assemble the string using template literal. Remove duplicates by assigning the values to a Set, and then spreading back to an array.
Note: Since I don't have access to the ISODate, I've removed it. I left .toString() although it's not needed in this example, but will be needed when used with ISODate.
const data = [
{ date: '2019-12-24T03:24:00Z' },
{ date: '2019-12-24T04:56:00Z' },
{ date: '2019-12-25T02:34:00Z' },
{ date: '2019-12-26T01:23:00Z' }
]
const pattern = /-([0-9]{2})-([0-9]{2})T/
const result = [...new Set(data.map(d => {
const [, mon, day] = d.date.toString().match(pattern)
return `${day}.${mon}.`;
}))]
console.log(result)
Use .filter() to filter through only values that are the first of their value.
//temporary function
const ISODate = (d) => d;
const data = [{
date: ISODate("2019-12-24T03:24:00Z")
},
{
date: ISODate("2019-12-24T04:56:00Z")
},
{
date: ISODate("2019-12-25T02:34:00Z")
},
{
date: ISODate("2019-12-26T01:23:00Z")
}
]
const dates = data.map(d => d.date.toString().split('T')[0].split("-").slice(1, 3).reverse().join(".") + ".")
console.log(dates.filter((v, i, a) => a.indexOf(v) === i));
You can do this pretty easily by using Array.reduce. Note that I converted ISODate to be Date since I don't have that class, but it should be the same concept.
const data = [
{ date: new Date("2019-12-24T03:24:00Z") },
{ date: new Date("2019-12-24T04:56:00Z") },
{ date: new Date("2019-12-25T02:34:00Z") },
{ date: new Date("2019-12-26T01:23:00Z") }
];
const result = data.reduce( (acc, curr) => {
if (acc.length > 0) {
const hasDate = acc.find(d => d.date.getMonth() === curr.date.getMonth() && d.date.getDate() === curr.date.getDate());
if (!hasDate) { acc.push(curr); }
} else {
acc.push(curr);
}
return acc;
}, []);
console.log(result);
I would use the uniq function in the Underscore.js library:
const data = [
{ date: ISODate("2019-12-24T03:24:00Z") },
{ date: ISODate("2019-12-24T04:56:00Z") },
{ date: ISODate("2019-12-25T02:34:00Z") },
{ date: ISODate("2019-12-26T01:23:00Z") }
];
let dates = _.uniq(data.map(d => d.date.toString().split('T')[0]));
A nice considerable way is:
const array = [1, 2, 6, 5,5, 5, 3, 7, 8];
const uniqueKeys = array.reduce((hashMap, value) => {
if (!hashMap[value]) {
hashMap[value] = true;
}
return hashMap;
}, {});
const uniqueValues = Object.keys(uniqueKeys);
console.log(uniqueValues);
It is nice because it iterates the array once, instead of x * x (a.k.a log(n) instead of log(n^2) as with .filter() example
const array = [1, 2, 6, 5,5, 5, 3, 7, 8];
const uniqueKeys = array.reduce((hashMap, value) => {
if (!hashMap[value]) {
hashMap[value] = true;
}
return hashMap;
}, {});
const uniqueValues = Object.keys(uniqueKeys);
console.log(uniqueValues);

Categories