JavaScript detect if giving region and date is a holiday - javascript

const regions = [
{
id: 1,
name: "Baden-Württemberg",
holidays: HOLIDAYS_BW,
},
{
id: 2,
name: "Bayern",
holidays: HOLIDAYS_BY,
},
]
const HOLIDAYS_BW = [
{ date: "01.01.2022", day: "Samstag" },
{ date: "06.01.2022", day: "Donnerstag" },
];
const HOLIDAYS_BY = [
{ date: "01.01.2022", day: "Samstag" },
{ date: "06.01.2022", day: "Donnerstag" },
];
How can I check wether the region and date the user picks is a holiday.
For instance I would like to check wether the region Bayern and the date 05.05.2022 is a holiday, the array shows all holidays
I tried this so far, but it does not seems to work
regions.find((region) => region.holidays.includes(date));
The date which I gave to the "query" is in this format
Tue Mar 22 2022 01:00:00 GMT+0100
That is an issue I think

You can try like so, finding if the region exists first (returns undefined if not), and then if the date is in the list.
return regions.find(r => r.name == region)?.holidays.some(h => h.date == date)
If you don't like the undefined and always want false, just add ?? false at the end.
return regions.find(r => r.name == region)?.holidays.some(h => h.date == date) ?? false
const HOLIDAYS_BW = [{
date: "01.01.2022",
day: "Samstag"
},
{
date: "06.01.2022",
day: "Donnerstag"
},
];
const HOLIDAYS_BY = [{
date: "01.01.2022",
day: "Samstag"
},
{
date: "06.01.2022",
day: "Donnerstag"
},
];
const regions = [{
id: 1,
name: "Baden-Württemberg",
holidays: HOLIDAYS_BW,
},
{
id: 2,
name: "Bayern",
holidays: HOLIDAYS_BY,
},
]
function formatDate(date) {
return `${('0' + (date.getDate())).slice(-2)}.${('0' + (date.getMonth())).slice(-2)}.${date.getFullYear()}`
}
function isHoliday(region, date) {
var formattedDate = formatDate(date);
return regions.find(r => r.name == region)?.holidays.some(h => h.date == formattedDate);
}
console.log(isHoliday('Bayern', new Date(2022, 01, 06))); // true
console.log(isHoliday('Baden-Württemberg', new Date(2022, 01, 01))); // true
console.log(isHoliday('Bayern', new Date())); // false
console.log(isHoliday('Hamburg', new Date(2022, 01, 06))); // undefined as Hamburg is not on the list
UPDATE
Turns out the input is a date, you can format it first with something like:
var formattedDate = `${('0' + (date.getDate())).slice(-2)}.${('0' + (date.getMonth())).slice(-2)}.${date.getFullYear()}`
and then use the string for the comparison.

Related

How to get average order data for days of week between two dates in mongodb aggregate?

I'm trying to get all orders between two dates, group them by day of week, then average them. Currently the code looks like this:
export const getOrderValuesBetweenTwoDates = async (
from: number,
to: number,
) => {
// from, and to are guaranteed to be Mondays, 00:00
const orders = await OrderModel.find({
createdAt: { $lt: to, $gte: from },
}).exec();
const totalOfDaysOfWeek = [0, 0, 0, 0, 0, 0, 0];
orders.forEach((order) => {
const daysSinceFrom = (order.createdAt - from) / dayInMilliseconds;
const dayOfWeek = Math.floor(daysSinceFrom) % 7;
totalOfDaysOfWeek[dayOfWeek] =
(totalOfDaysOfWeek[dayOfWeek] || 0) + order.value;
});
const numberOfWeeks = Math.floor((to - from) / dayInMilliseconds / 7);
const averageOfDaysOfWeek = totalOfDaysOfWeek.map((v) =>
Number((v / numberOfWeeks).toFixed(2)),
);
return averageOfDaysOfWeek;
};
However, this is not really performant, and I guess if it could be written in aggregation, it would be. Is that possible to convert the above into aggregation?
Sample input (2 weeks):
[
// 1st mon (total 5)
{ createdAt: 345600000, value: 2 },
{ createdAt: 345600000, value: 3 },
// 1st tue
{ createdAt: 432000000, value: 1 },
// 1st wed
{ createdAt: 518400000, value: 1 },
// 1st thu
{ createdAt: 604800000, value: 1 },
// 1st fri
{ createdAt: 691200000, value: 1 },
// 1st sat
{ createdAt: 777600000, value: 1 },
// 1st sun (2 total)
{ createdAt: 864000000, value: 2 },
// 2nd mon (1 total)
{ createdAt: 950400000, value: 1 },
// 2nd tue
{ createdAt: 1036800000, value: 1 },
// 2nd wed
{ createdAt: 1123200000, value: 1 },
// 2nd thu
{ createdAt: 1209600000, value: 1 },
// 2nd fri
{ createdAt: 1296000000, value: 1 },
// 2nd sat
{ createdAt: 1382400000, value: 1 },
// 2nd sun (4 total)
{ createdAt: 1468800000, value: 1 },
{ createdAt: 1468800000, value: 1 },
{ createdAt: 1468800000, value: 2 },
]
In the above example I've made 2 special cases, for Monday, and Sunday. There are multiple orders for those days.
For the first Monday there is an order with value 2, and 3, to 5 in total. For the second Monday there is only one order with value 1. The average should be 3.
For Sunday, the first one, there's an order with value 2, and for the second Sunday, there are 3 orders with total value of 4. I'm expecting the average to be 3.
I'm expecting the result to be [3,1,1,1,1,1,3]
format the date using $dateToString
use $sum to get sum of same day of week
get day of week by $dayOfWeek
group by days of week and get average by $avg
project to get data as desired format
weekDay in output will be number between 1 (Sunday) and 7 (Saturday).
test it at mongoPlayground
db.collection.aggregate([
{
"$addFields": {
createdAt: {
"$dateToString": {
"date": {
"$toDate": "$createdAt"
},
"format": "%Y-%m-%d"
}
}
}
},
{
"$group": {
"_id": "$createdAt",
"value": {
"$sum": "$value"
}
}
},
{
"$addFields": {
"createdAt": {
$dayOfWeek: {
"$toDate": "$_id"
}
}
}
},
{
"$group": {
"_id": "$createdAt",
"average": {
"$avg": "$value"
}
}
},
{
"$project": {
_id: 0,
weekDay: "$_id",
average: 1
}
}
])

How to generate empty value from aggregate results with Mongodb

First of all the function :
static async getInitRegistrationMetric(account) {
// we want only full day so we exclude current date
const exclude = DateTime.now()
.setZone('utc')
.startOf('day')
.toISODate();
// this count the number of client created by day.
const groupByDate = {
$group: {
_id: {
$dateToString: { format: '%Y-%m-%d', date: '$createdAt' },
},
count: { $sum: 1 },
},
};
// this is a way to rename (_id, count) to (date, value)
const renameData = {
$project: {
_id: 0,
date: '$_id',
value: '$count',
},
};
// this is done to filter data, I want to clean the null date and the today result
const excludeTodayAndNull = {
$match: {
$and: [
{
date: {
$ne: exclude,
},
},
{
date: {
$ne: null,
},
},
],
},
};
// account is the mongoose model.
return account.aggregate([groupByDate, renameData, excludeTodayAndNull]);
}
this code will produce data like this:
const data = [
{ date: '2000-10-01', value: 50 },
{ date: '2000-10-03', value: 12 },
{ date: '2000-10-07', value: 112 },
];
the problem is I don't have value for the 2nd, 4th, 5th and 6th of the month. My idea was to force mongo to "create" void valid for the other days, like this:
const data = [
{ date: '2000-10-01', value: 50 },
{ date: '2000-10-02', value: 0 },
{ date: '2000-10-03', value: 12 },
{ date: '2000-10-04', value: 0 },
{ date: '2000-10-05', value: 0 },
{ date: '2000-10-06', value: 0 },
{ date: '2000-10-07', value: 112 },
];
How can I ask "aggregate" to fill the gap between significant dates with data with 0 as a value ?
Thanks
PS: I already did it by code in js but it looks heavy and ugly. I try to do it cleaner.
You could use the $fill or $densify operators to fill with zeros if you're running a recent enough version of MongoDB:
https://www.mongodb.com/docs/manual/reference/operator/aggregation/densify/
https://www.mongodb.com/docs/manual/reference/operator/aggregation/fill/
e.g
$fill:
{
sortBy: { date: 1 },
output:
{
"value": 0
}
}

Filter date time in json based on date

I'm writing a code that has date time in a json, And I want to filter the json and show the data where date from datetime is greater than today's date.
Here is my code.
var data = [{
datetime: "2021-08-09T06:00:00.000Z",
id: "1"
}, {
datetime: "2021-06-07T02:00:00.000Z",
id: "2"
}, {
datetime: "2021-08-04T11:00:00.000Z",
id: "3"
}, {
datetime: "2021-08-04T10:00:00.000Z",
id: "4"
}, {
datetime: "2021-08-05T12:55:00.000Z",
id: "5"
}, {
datetime: "2020-08-10T13:30:00.000Z",
id: "6"
}]
data = data.filter(item=> { return new Date(item.datetime).getDate() > new Date().getDate()});
console.log(data);
it should print every thing apart from id 3, 4 and 6. Also when I do a getDate(), I think it only considers DD from MMDDYYYY, that's why 6 is getting printed. what would be the best way to achieve this?
There seems to be a typo:
data = data.filter(item=> { return new Date(item.datatime).getDate() > new Date().getDate()});
// ^
// datetime
data = data.filter(item => {
const date = new Date(item.datetime);
const today = new Date();
return date.getUTCFullYear() * 10000 + date.getUTCMonth() * 100 + date.getUTCDate() > today.getUTCFullYear() * 10000 + today.getUTCMonth() * 100 + today.getUTCDate();
});

Dynamically split an array into subarrays

const giftcards = [
{
fromuserid: 1,
amount: 10,
date: new Date(),
touserid: 11,
},
{
fromuserid: 1,
amount: 20,
date: new Date(),
touserid: 11,
},
{
fromuserid: 2,
amount: 10,
date: new Date(),
touserid: 11,
},
{
fromuserid: 2,
amount: 10,
date: new Date(),
touserid: 11,
},
{
fromuserid: 3,
amount: 10,
date: new Date(),
touserid: 11,
}
]
I achieved this, which is shown in the useEffect hook:
const giftcards = [
{
fromuserid: 1,
amounts: [{
amount: 10,
date: new Date(),
touserid: 11,
},
{
amount: 20,
date: new Date(),
touserid: 11,
}
]
},
{
fromuserid: 2,
amounts: [{
amount: 10,
date: new Date(),
touserid: 11,
},
{
amount: 10,
date: new Date(),
touserid: 11,
}
]
},
{
fromuserid: 3,
amounts: [{
amount: 10,
date: new Date(),
touserid: 11,
}]
}
]
The solution that was given works except that i would like to make it dynamic.
Meaning, in my app, I allow the user to arrange how the array will be sorted.
For example,
const [sort, setSort] = useState('fromuserid')
const [results, setResults] = useState([])
<div>
<select value={sort} onChange={(e)=> setSort(e.target.value)}>
<option value='fromuserid'>Sort by Buyer</option>
<option value='touserid'>Sort by Receiver</option>
<option value='date'>Sort by Date</option>
</select>
</div>
then in the:
useEffect(() => {
allgiftcards.forEach(({
fromuserid,
date,
from,
giftcardid,
message,
template,
touserid,
amount,
paid
}) => {
map.has(fromuserid) || map.set(fromuserid, {
fromuserid,
cards: []
})
map.get(fromuserid).cards.push({
date,
from,
giftcardid,
message,
template,
touserid,
amount,
paid
})
})
setResults([...map.values()])
}, [sort])
Here is what i mean by dynamic. If the user selected date, I would like for it to look something like:
useEffect(() => {
allgiftcards.forEach(({
fromuserid,
date,
from,
giftcardid,
message,
template,
touserid,
amount,
paid
}) => {
map.has(date) || map.set(date, {
date,
cards: []
})
map.get(date).cards.push({
date,
from,
giftcardid,
message,
template,
touserid,
amount,
paid
})
})
setResults([...map.values()])
}, [sort])
But It seems to me that having a bunch of if and else statements would be bad coding and would create a lot of extra code, so looking for a nice and clean solution
This really isn't a question of React (or where-ever useEffect comes from). This is really a general Javascript question, and it's a problem that suits itself well for solving with functional programming, or at least, with one of the staples of functional programming: reduce
In this case, you could supply the 2nd argument which is the initial value of the accumulator -- in this example an empty object works well. You can choose any key from the data to bucket the results:
// Choose a key that each object in the set has, e.g. 'fromuserid' or 'touserid'
const group_by = 'fromuserid';
let bucketed = giftcards.reduce(function (acc, x) {
let pivot = x[group_by]
let current_vals = (acc.hasOwnProperty(pivot) ? acc[pivot] : []);
current_vals.push(x);
acc[pivot] = current_vals;
return acc
}, {});
console.log(bucketed);
If you really needed to land on the second data structure you shared, you could jostle your initialValue and the exact placement of values into the accumulator, but hopefully this demonstrate the concept of how to dynamically choose how to group the data.
This codepen shows how to dynamically bucket an array of objects based on a specific property. Below is a sample of the callback function you can pass to the reducer -- included in the example.
function groupBy(accum, current, groupKey){
const val = current[groupKey]
const {[groupKey]:_, ...content} = current
if (val in accum){
accum[val].push(content)
}
else accum[val] = [content]
return accum
}

Angularjs 1: sort date by week

i have list of objs:
[{
name: one,
date: 2017-09-18
}, {
name: two,
date: 2017-09-11
}, {
name: three,
date: 2017-09-13
}]
And i want to sort it by week.
Maybe like:
{
1week(or , maybe better key like start of week): [{
name: two,
date: 2017-09-11
}, {
name: three,
date: 2017-09-13
],
2week: [{
name: one,
date: 2017-09-18
}]
}
how can I determine by what week the dates belong?
how can I do better?
I played around with this, and I think this is similar to what you need:
https://jsfiddle.net/pegla/ytmayemr/
code:
let arrayOfDates = [{
name: 'one',
date: '2017-09-18'
}, {
name: 'two',
date: '2017-09-11'
}, {
name: 'three',
date: '2017-09-13'
}];
function getWeekNumber(d) {
// Copy date so don't modify original
d = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
// Set to nearest Thursday: current date + 4 - current day number
// Make Sunday's day number 7
d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay()||7));
// Get first day of year
var yearStart = new Date(Date.UTC(d.getUTCFullYear(),0,1));
// Calculate full weeks to nearest Thursday
var weekNo = Math.ceil(( ( (d - yearStart) / 86400000) + 1)/7);
// Return array of year and week number
return [d.getUTCFullYear(), weekNo];
}
let newArrayOfDatesByWeek = arrayOfDates.reduce((prevVal, currVal, index)=>{
let week = `week ${getWeekNumber(new Date(currVal.date))[1]} of ${getWeekNumber(new Date(currVal.date))[0]}`;
if(!(week in prevVal)) {
prevVal[week] = [];
}
prevVal[week].push(currVal);
return prevVal;
}, []);
console.log(newArrayOfDatesByWeek);
getWeekNumber function is taken from this answer by RobG, so thanks for that:
Get week of year in JavaScript like in PHP

Categories

Resources