How to sort a collection of reservations - javascript

Model looks like so:
{
time: String,
date: Date,
user_id: String
},
return of end point get user reservations are like so
"reservations": [
{
"_id": "63e3bda0a2b3be512b44ba51",
"time": "10:40",
"date": "2023-02-06T00:00:00.000Z",
"user_id": "63d4657708205fda2de700f1"
},
{
"_id": "63e3bda2a2b3be512b44ba56",
"time": "11:00",
"date": "2023-02-06T00:00:00.000Z",
"user_id": "63d4657708205fda2de700f1"
},
{
"_id": "63e3c22bf83f5954d210eb56",
"time": "13:00",
"date": "2023-02-06T00:00:00.000Z",
"user_id": "63d4657708205fda2de700f1"
},
{
"_id": "63e3c224f83f5954d210eb48",
"time": "10:00",
"date": "2023-02-10T00:00:00.000Z",
"user_id": "63d4657708205fda2de700f1"
},
{
"_id": "63e3c228f83f5954d210eb4f",
"time": "10:00",
"date": "2023-02-16T00:00:00.000Z",
"user_id": "63d4657708205fda2de700f1"
}
]
How do i sort these reservations based on date and then time. So the earliest date with the earliest time slot, and this done inn all dates.
Can this be achieved with mongoose?
What i have tried:
const dates = reservations.map((res) => res.date.toISOString());
function onlyUnique(value: any, index: any, self: string | any[]) {
return self.indexOf(value) === index;
}
var distinctDates = dates.filter(onlyUnique);
First get all unique dates
for (let i = 0; i < distinctDates.length; i++) {
const date = new Date(distinctDates[i]);
let distinctReservations: { _id: any; time: number; date: Date; user_id: string }[] = [];
for (let j = 0; j < reservations.length; j++) {
const reservation = reservations[j];
if (reservation.date.toISOString() === date.toISOString()) {
distinctReservations.push({
_id: reservation.id,
time: hhMMToMinutes(reservation.time),
date: reservation.date,
user_id: reservation.user_id
});
}
}
const sortedByMinutes = distinctReservations.sort((a, b) => a.time - b.time);
sortedReservationsSlotToMinutes.push(sortedByMinutes);
}
const flated = sortedReservationsSlotToMinutes.flat(2);
const sortedReservations = flated.map((res) => {
let timeSlot = {
hour: (res.time / 60).toFixed(),
minute: ''
};
const minuteModule = res.time % 60;
if (minuteModule) {
timeSlot.minute = minuteModule.toString();
} else {
timeSlot.minute = '00';
}
return {
_id: res._id,
time: timeSlot.hour + ':' + timeSlot.minute,
date: res.date,
user_id: res.user_id
};
});
return sortedReservations;
Then loop through all unique dates and find matching of reservations. Reservations is a param inn this function. Then convert their time to minutes, sort based on the smalest value, then push those reservations into an array. After that i then push all reservations sorted by minutes to an outer array then i flaten that array and return it.
Then i convert back the minutes to string.

You can construct a datetime using $dateFromParts and use the datetime for sorting.
db.reservations.aggregate([
{
"$addFields": {
"dateTime": {
"$dateFromParts": {
"year": {
$year: "$date"
},
"month": {
$month: "$date"
},
"day": {
$year: "$date"
},
"hour": {
$toInt: {
"$substrCP": [
"$time",
0,
2
]
}
},
"minute": {
$toInt: {
"$substrCP": [
"$time",
3,
2
]
}
}
}
}
}
},
{
$sort: {
dateTime: 1
}
}
])
Mongo Playground

Related

Is there a way to count distinct values in mongoose with specific cut off date

For example, I have the following two docs
[
{
"_id": "5fc534505144dd0030c44f8e",
"createdAt": "2020-12-14T15:11:21.327Z"
"user_id": "2",
},
{
"_id": "5fc534505144dd0030c44f8e",
"createdAt": "2020-12-14T14:10:40.427Z",
"user_id": "1"
},
{
"_id": "5fc534595144dd0030c44f95",
"createdAt": "2020-12-13T14:10:58.027Z",
"user_id": "1"
}
]
the results should be
[
{
"date": "2020-12-13",
"count":1
},
{
"date": "2020-12-14",
"count":2
}
]
where the count is the number of distinct docs via user_ids till the date that specific cut off date
given data
data=[
{
"createdAt": "2020-12-14T15:11:21.327Z",
"user_id": "2",
},
{
"createdAt": "2020-12-14T14:10:40.427Z",
"user_id": "1"
},
{
"createdAt": "2020-12-13T14:10:58.027Z",
"user_id": "1"
},{
"createdAt": new Date("2020-12-14T14:10:58.027Z"),
}
]
> db.dummy.insert(data)
You may use aggregate: use $group with _id being the date's day in conjunction with $sum)
> db.dummy.aggregate({$group:{_id:{$substr:['$createdAt', 0, 10]}, count:{$sum:1}}})
{ "_id" : "2020-12-14", "count" : 3 }
{ "_id" : "2020-12-13", "count" : 1 }
edit: mongoose wise same may hold
const mongoose = require('mongoose')
mongoose.connect('mongodb://localhost:27017/dummy')
const UDate = mongoose.model('X', { createdAt:String, user_id: String }, 'dummy')
;(async()=>{
mongoose.set('debug', true)
const group = {$group:{_id:{$substr:['$createdAt', 0, 10]}, count:{$sum:1}}}
const v = await UDate.aggregate([group])
console.log('s : ', JSON.stringify(v))
mongoose.disconnect()
})()
edit2: to handle unicity of userIds so there are not counted twice per date, you may use $addToSet instead of sum followed by a projection using $size
const group = {$group:{_id:{$substr:['$createdAt', 0, 10]}, userIds:{$addToSet:'$user_id'}}}
const count = {$project:{date:'$_id', count: {$size: '$userIds'}} }
const v = await Stock.aggregate([group, count])
Lastly, if you feel always more, you can "rename" the _id field as date during the projection
{$project:{date:'$_id', _id:0, count: {$size: '$userIds'}} }
$gorup by createdAt date after getting substring using $substr and make array of unique user ids on the base of $addToset
get total element in count array using $size
db.collection.aggregate([
{
$group: {
_id: { $substr: ["$createdAt", 0, 10] },
count: { $addToSet: "$user_id" }
}
},
{ $addFields: { count: { $size: "$count" } } }
])
Playground

Sort array by one key that changes its form

I have the following problem. I have an array of activities that I have to sort by date. The problem is that the "date" key is not always the same. If it's a one day activity it looks like:
date: "2019-10-25T00:00:00.000Z"
But if it's two days or longer, it looks like:
date:{dateFrom: "2017-05-13T00:00:00.000Z", dateTo: "2017-05-14T00:00:00.000Z"}
I've tried a normal sorting or the type of function that sort two keys that are never null.
So, how could I sort this array by date?
activities = [{
"date": {dateTo:"2019-05-20T00:00:00.000Z", dateFrom: "not important"},
activity: 5
},{
"date": {dateTo:"2019-05-05T00:00:00.000Z", dateFrom: "not important"},
activity: 2
},{
"date": "2019-05-10T00:00:00.000Z",
activity: 3
},{
"date": "2019-05-25T00:00:00.000Z",
activity: 6
},{
"date": "2019-05-01T00:00:00.000Z",
activity: 1
},{
"date": "2019-05-15T00:00:00.000Z",
activity: 4
}]
One solution would be to define a helper function like getItemDate(), combined with the regular Array#sort() method to achieve what you require:
const activities = [{
"date": {dateTo:"2019-05-20T00:00:00.000Z", dateFrom: "not important"},
activity: 5
},{
"date": {dateTo:"2019-05-05T00:00:00.000Z", dateFrom: "not important"},
activity: 2
},{
"date": "2019-05-10T00:00:00.000Z",
activity: 3
},{
"date": "2019-05-25T00:00:00.000Z",
activity: 6
},{
"date": "2019-05-01T00:00:00.000Z",
activity: 1
},{
"date": "2019-05-15T00:00:00.000Z",
activity: 4
}];
/* Define helper function that obtains a date timestamp from activities list item. If
date key is an object, item.date.dateTo is used, otherwise item.date is used */
function getItemDate(item) {
let date = (typeof item.date === 'object') ? item.date.dateTo : item.date;
return Date.parse(date);
}
/* Use helper function to sort items in activities list */
activities.sort((a,b) => getItemDate(a) - getItemDate(b))
console.log(activities)
You can use the sort-method of arrays and do a custom comparison. Inside the compare-function you can get the date-string from the two objects and convert it to a timestamp with the help of Date.parse. After that you have to compare these timestamps and return the sorting-rule (-1, 1 or 0).
let activities = [{
"date": {dateTo:"2019-05-20T00:00:00.000Z", dateFrom: "not important"},
activity: 5
},{
"date": {dateTo:"2019-05-05T00:00:00.000Z", dateFrom: "not important"},
activity: 2
},{
"date": "2019-05-10T00:00:00.000Z",
activity: 3
},{
"date": "2019-05-25T00:00:00.000Z",
activity: 6
},{
"date": "2019-05-01T00:00:00.000Z",
activity: 1
},{
"date": "2019-05-15T00:00:00.000Z",
activity: 4
}];
function compare( a, b ) {
let aDateString = a.date.dateTo ? a.date.dateTo : a.date;
let bDateString = b.date.dateTo ? b.date.dateTo : b.date;
let aDate = Date.parse(aDateString);
let bDate = Date.parse(bDateString);
if ( aDate < bDate ){
return -1;
}
if ( aDate > bDate ){
return 1;
}
return 0;
}
activities.sort( compare );
console.log(activities);
Check date property whether it has dateTo property or not and sort it with its value.
const activities = [{
"date": {
dateTo: "2019-05-20T00:00:00.000Z",
dateFrom: "not important"
},
activity: 5
}, {
"date": {
dateTo: "2019-05-05T00:00:00.000Z",
dateFrom: "not important"
},
activity: 2
}, {
"date": "2019-05-10T00:00:00.000Z",
activity: 3
}, {
"date": "2019-05-25T00:00:00.000Z",
activity: 6
}, {
"date": "2019-05-01T00:00:00.000Z",
activity: 1
}, {
"date": "2019-05-15T00:00:00.000Z",
activity: 4
}]
activities.sort((a, b) => {
const x = a.date.dateTo || a.date
const y = b.date.dateTo || b.date
if (x > y) return 1
if (x < y) return -1
return 0
})
console.log(activities)
const newActivities = activities.map((act) => {
const { activity, date } = act;
if(typeof date === 'string'){
return { date: Date.parse(date), activity }
}
return { date: Date.parse(date.dateTo), activity}
})
newActivties.sort((a,b) => a - b)

Find the least and max date in array using yyyy-mm-dd format

How can I get the least and max date in an array using YYYY-mm-dd format? Here is a sample of my date values.
const data = [{
"date": "2012-10-21",
"value": 60
}, {
"date": "2012-10-22",
"value": 61
}, {
"date": "2012-10-23",
"value": 69
}, {
"date": "2012-10-24",
"value": 67
}]
console.log(data);
You can use reduce and Date.parse
const data = [{"date": "2012-10-21","value": 60}, { "date": "2012-10-22","value": 61}, {"date": "2012-10-23","value": 69}, {"date": "2012-10-24","value": 67}]
let maxDate = data.reduce((op,inp)=>{
if(Date.parse(inp.date) > Date.parse(op.max)){
op.max = inp.date
}
if(Date.parse(inp.date) < Date.parse(op.max)){
op.least = inp.date
}
return op
},{least:data[0].date,max:data[0].date})
console.log(maxDate)
If you just want the dates you can use Math.min and Math.max if you map your array to dates using .map:
const data = [{
"date": "2012-10-21",
"value": 60
}, {
"date": "2012-10-22",
"value": 61
}, {
"date": "2012-10-23",
"value": 69
}, {
"date": "2012-10-24",
"value": 67
}];
const dates = data.map(({date}) => new Date(date));
const minDate = new Date(Math.min(...dates));
const maxDate = new Date(Math.max(...dates));
console.log("min", minDate.toISOString().slice(0,10));
console.log("max", maxDate.toISOString().slice(0,10));
Alternatively, you could sort the array and use the first and last elements:
const data = [{
"date": "2012-10-21",
"value": 60
}, {
"date": "2012-10-22",
"value": 61
}, {
"date": "2012-10-23",
"value": 69
}, {
"date": "2012-10-24",
"value": 67
}];
const dates = data.map(({date}) => new Date(date));
const sortedDates = dates.sort((a, b) => a - b);
const minDate = sortedDates[0];
const maxDate = sortedDates[sortedDates.length-1];
console.log("min", minDate.toISOString().slice(0,10));
console.log("max", maxDate.toISOString().slice(0,10));
You could reduce the data and take just a string comparison.
const
data = [{ date: "2012-10-21", value: 60 }, { date: "2012-10-22", value: 61 }, { date: "2012-10-23", value: 69 }, { date: "2012-10-24", value: 67 }],
result = data.reduce((r, { date }) => {
if (!r) return { min: date, max: date };
if (r.min > date) r.min = date;
if (r.max < date) r.max = date;
return r;
}, undefined);
console.log(result);
If this is the date format used, a simple .sort() on the date property will work. The earliest date is the first element the array and the last date the last element.
This is the big advantage of using a date format ( like the ISO standard ) where the lexical storting by string value is the same as the logical sorting by date.
const data = [{
"date": "2012-10-21",
"value": 60
}, {
"date": "2012-10-22",
"value": 61
}, {
"date": "2012-10-23",
"value": 69
}, {
"date": "2012-10-24",
"value": 67
}, {
"date": "2012-10-22",
"value": 102
}];
const sorted_by_date = data.sort(( a, b ) => a.date.localeCompare( b.date ));
const earliest_date = sorted_by_date[ 0 ];
const latest_date = sorted_by_date[ sorted_by_date.length - 1 ];
console.log( earliest_date );
console.log( latest_date );
function checkDate(data,condition){
if(condition == 'max'){
var max = 0
data.map((item)=>{
if(new Date(item.date)>max){
max = new Date(item.date)
}
})
return max;
}else if(condition == 'least'){
var least = new Date()
data.map((item)=>{
if(new Date(item.date)<least){
least = new Date(item.date)
}
})
return least;
}
}

re-organize json data with vanilla javascript

I am consuming data from a web service that sends JSON data with the following structure. You can see my at https://es6console.com/jkadxuk1/.
Problems: my current attempt is very slow. And it works only if all dates have the same number of reporting times. For e.g. in the records below, there are two times files for 2018-08-01 and one for 2018-07-31 but the 31st still shows two records when re-organized. Hope I am making sense.
function dbDateTimeToDateTme(value){
/*
value isforamtted as 2018-08-04T22:00:00Z
Here, we split them
#output: an array of date and time. Note we don't validate
*/
reply=[];
if(value){
date=value.substring(0,10)
time=value.substring(11,19)
reply.push(date,time);
}
return reply;
}
function viewFullTablar(acquired_data){
total=acquired_data.length;
if(total==0){
return false;
}
data=[]; //reorganized data
location_name="";
location_code=0;
datetime=[];
for(var i=0;i<total;i++){
date_index=-1;
place_index=-1; //
location_name=acquired_data[i]['store']
location_code=acquired_data[i]['location_id']
datetime=dbDateTimeToDateTme(acquired_data[i]['for_date']); //0=date,1=time
//now check if we have already added the location by its location_code
for(var counter=0;counter<data.length;counter++){
if (data[counter]['location_id']==location_code){
place_index=counter;
break;
}
}
//do we add the place?
if(place_index==-1){
//yes add it
data.push(
{
'index':i,
'store':location_name,
'location_id':location_code,
'dates':[]
}
);
place_index=0; //first element now
}
//does the date exist yet or not?
date_blocks=data[place_index]['dates'];
for(counter=0;counter<date_blocks.length;counter++){
if (date_blocks[counter]['date']==datetime[0]){
date_index=counter;
break;
}
}
//add the date to the place or not?
if(date_index==-1){
data[place_index]['dates'].push(
{
'date':datetime[0],
'main':[]
}
);
date_index=0;
}
//now add time and weather details
data[place_index]['dates'][date_index]['main'].push(
{
'time':datetime[1],
'income':acquired_data[i]['income']
}
);
}
return data;
}
var data={
"data": [
{
"expense": "1026.2100",
"income": "869.4500",
"location_id": 1,
"for_date": "2018-07-31T04:00:00Z",
"store": "Eastern Province"
},
{
"expense": "1026.3300",
"income": "869.0300",
"location_id": 1,
"for_date": "2018-08-01T00:00:00Z",
"store": "Eastern Province"
},
{
"expense": "1026.7600",
"income": "870.2000",
"location_id": 1,
"for_date": "2018-08-01T04:00:00Z",
"store": "Eastern Province",
},
]
}
console.log(viewFullTablar(data['data']));
Each day can have a maximum 8 different hours of reporting but the minimum can be as low as 1 or 0 even (if nothing has been filed yet).
For display purposes, I want to get the following array out of if:
[{
store:"Eastern Province",
location_id:1,
dates:[
{
'date':'2018-07-31',
main:[
{'time':04:00:00,
'income':1026
}];
}];
}];
or:
East Province
2018-07-31
04:00:00 value value
09:00:00 value value
2018-08-01
09:00:00 value value
10:00:10 value value
I have added https://es6console.com/jkadxuk1/ here.
You can use reduce to greatly improve readability. Use .find() to look back through the array to find objects that already exist, and update them accordingly:
const data = {
"data": [
{
"expense": "1026.2100",
"income": "869.4500",
"location_id": 1,
"for_date": "2018-07-31T04:00:00Z",
"store": "Eastern Province"
},
{
"expense": "1026.3300",
"income": "869.0300",
"location_id": 1,
"for_date": "2018-08-01T00:00:00Z",
"store": "Eastern Province"
},
{
"expense": "1026.7600",
"income": "870.2000",
"location_id": 1,
"for_date": "2018-08-01T04:00:00Z",
"store": "Eastern Province",
},
]
};
const res = data.data.reduce((obj, item) => {
// Do we already have this location in the final array?
const foundItem = obj.find((i) => i.location_id == item.location_id);
const date = item.for_date.substr(0,10);
const time = item.for_date.substr(11,8);
// Let's just append the date
if (foundItem) {
// Search for the current date
const foundDate = foundItem.dates.find((i) => i.date == date);
// Is there already a date in the array?
if (foundDate) {
// Push the time into the array
foundDate.main.push({
time,
expense: item.expense,
income: item.income
});
} else {
// Push a new date object, with this time
foundItem.dates.push({
date,
main: [{
time,
expense: item.expense,
income: item.income
}]
});
}
} else {
// Push a whole new location
obj.push({
store: item.store,
location_id: item.location_id,
dates: [
{
date,
main: [{
time,
expense: item.expense,
income: item.income
}]
}
]
});
}
return obj;
}, []);
console.log(res);
You can use reduce using id and date as key value if you don't want to do a lot of iterations, after that you can use map or other function to unwind result
const data = [
{
"expense": "1026.2100",
"income": "869.4500",
"location_id": 1,
"for_date": "2018-07-31T04:00:00Z",
"store": "Eastern Province"
},
{
"expense": "1026.3300",
"income": "869.0300",
"location_id": 1,
"for_date": "2018-08-01T00:00:00Z",
"store": "Eastern Province"
},
{
"expense": "1026.7600",
"income": "870.2000",
"location_id": 1,
"for_date": "2018-08-01T04:00:00Z",
"store": "Eastern Province",
},
];
const result = data.reduce((acum = {}, current) => {
const year = current.for_date.slice(0, 10);
const hour = current.for_date.slice(11, 19);
if (!acum[current.location_id]) {
acum[current.location_id] = {
store: current.store,
location_id: current.location_id,
dates: {
[current.for_date]: {
date: current.for_date,
main: [
{
time: '',
income: current.income,
expense: current.expense,
},
],
}
}
}
} else if (!acum[current.location_id].dates[year]) {
acum[current.location_id].dates[year] = {
date: year,
main: [
{
time: '',
income: current.income,
expense: current.expense,
},
],
}
} else {
acum[current.location_id].dates[year].main.push({
time: '',
income: current.income,
expense: current.expense,
});
}
return acum;
}, {});
console.log(result);
console.log('---------')
let arr = Object.keys(result).map(key => {
let res = result[key]
res.dates = Object.keys(result[key].dates).map(date => result[key].dates[date])
return res;
});
console.log(arr)

Sort through an array, and push duplicates into a new array

I have an array of objects with dates. What I want to do is select all objects with the same date value and push those into a new array.
here's my code.
var posts = [
{
"userid": 1,
"rating": 4,
"mood": "happy",
"date": "2017-05-24T04:00:00.000Z"
},
{
"userid": 1,
"rating": 3,
"mood": "happy",
"date": "2017-05-24T04:00:00.000Z"
},
{
"userid": 1,
"rating": 3,
"mood": "angry",
"date": "2017-05-25T04:00:00.000Z"
},
{
"userid": 1,
"rating": 5,
"mood": "hungry",
"date": "2017-05-25T04:00:00.000Z"
}]
var may25=[];
for(i=0;i < posts.length;i++){
if(posts[i].date === posts[i].date){
may25.push(posts[i].date)
}
}
You could slice the date from the string and take it as key for an object for grouping items by date. The result is an object with all grouped objects.
var posts = [{ userid: 1, rating: 4, mood: "happy", date: "2017-05-24T04:00:00.000Z" }, { userid: 1, rating: 3, mood: "happy", date: "2017-05-24T04:00:00.000Z" }, { userid: 1, rating: 3, mood: "angry", date: "2017-05-25T04:00:00.000Z" }, { userid: 1, rating: 5, mood: "hungry", date: "2017-05-25T04:00:00.000Z" }],
groups = Object.create(null);
posts.forEach(function (o) {
var date = o.date.slice(0, 10);
groups[date] = groups[date] || [];
groups[date].push(o)
});
console.log(groups);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If you are just looking for may 25 dates (as your question suggests):
var may25=[];
for (i=0;i < posts.length;i++) {
if (posts[i].date === "2017-05-25T04:00:00.000Z") {
may25.push(posts[i].date)
}
}
You may want to create an object that contains date => event arrays :
var result=posts.reduce((obj,event)=>((obj[event.date]=obj[event.date] || []).push(event),obj),{});
Now you can do:
result["2017-05-25T04:00:00.000Z"].forEach(console.log);
How it works:
posts.reduce((obj,event)=>...,{}) //iterate over the posts and pass each as event to the function and also pass an object to be filled
(obj[event.date]=obj[event.date] || [])//return the date array, or create a new one if it doesnt exist
.push(event)//append our event to it

Categories

Resources