Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongodb dealing with opening hours and how to find our if a place is opened or not

Tags:

mongodb

I'm trying to find the best way to deal with restaurant opening hours. The complexity is due to the fact that:

  • places may be opened multiple times during the same day (e.g. 7:00-14:30, 18:30-22:00)
  • sometimes the closing time goes past the midnight (e.g. 19:00-2:00)
  • not everyday shares the same timing

A good approach seemed thus to register arrays of opened/closed times per each day of the week, in the format: seconds since the beginning of the week. Here's an example

{
    "address": "street 1, city, postcode",
    "hours" : {
        "mon" : [
            {
                "opened" : 25200, // 07:00
                "closed" : 52200 // 14:30
            }
        ],
        "tue" : [
            {
                "opened" : 111600, // 07:00
                "closed" : 138600 // 14:30
            },
            {
                "opened" : 153000, // 18:30
                "closed" : 180000 // 02:00 (of the day after)
            }
        ],
        ...
    }
}

(In the model I have a static variable rendered using a small piece of code I wrote to do the autoform dropdowns per each field)

In my view the opening hours for the whole week are rendered like so:

Monday: {{#each hours.mon}}
    {{#if @index ">" 0}}<br />{{/if}} // this just breaks the second array item in a new line
    {{getOpeningTime opened}} - {{getOpeningTime closed}}
{{/each}}
<br/>
Tuesday: ...

Where getOpeningTime is a template helper that returns the time in a HH:mm format using moment.js:

getOpeningTime: function(seconds) {
    return moment().startOf('day').seconds(seconds).format('HH:mm');
}

So far all good - although I'm still not sure whether that's the right approach

Now, I'm trying to notice the user whether the restaurant is opened or not at the moment he/she's looking at the page. Here's what I wrote so far:

openedOrClosed: function() {
    now = moment();
    ddd = now.format('ddd').toLowerCase(); // I now I will look into the field hours.day - the first three letters all lowercase

    day = now.day();
    day = (day === 0 ? 7 : day); // This is just a conversion I do because I want to have Sunday at the end of the week

    seconds = now.clone().diff(now.clone().startOf('day'), 'seconds') + (86400 * day) - 86400; // Minutes since midnight in Momentjs (http://stackoverflow.com/a/25390707/1435476) plus the sum of all the other days of the week to work out the global seconds since the beginning of the week

    query = {};

    query._id = this._id;

    // here I have available all I need. but I'm not sure how to build the query

    if (Restaurants.find(query).count() > 0) {
        return "Opened";
    } else {
        return "Closed";
    }
}
like image 506
cellulosa Avatar asked Oct 19 '22 01:10

cellulosa


2 Answers

First of all: working with dates and time is a challenge :).

I think your 'seconds since the beginning of the week' format is not necessary. Momentjs can parse 'now' to day and hour (and more).

You should also account for exceptions like christmas etc. Besides that, a restaurant can be open multiple times a day (nine to two, and five to eleven for instance).

I would go for something like this:

restaurantname:{
     //special days array of objects
      specialdays: 
           [{date: date, 
             openinghours: 
            //again array of objects
                  [{start: sometime, end: sometime},
                   {start: somtime, end: sometime},
                   //etc
                   ]
             }]
            },

        normaldays: {mon: //openinghours like above, again array of objects with start and end, tue, wed etc}

After that you can check first: is it a special day? if yes => check if current time is after start and before end (loop over array of objects). Else if not a special day check the specific 'normal day' and do the same.

I think a switch would be ideal. You could start with specific to general, to make your condition fall through the specific cases first.

This is just an outline and I know it is challenging :).

Hope this helps...

like image 198
R_Ice Avatar answered Nov 02 '22 14:11

R_Ice


Here you go,

var days = ['MakingIndex1ForMonday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ];
var query = {};
query._id  = this._id;
query.dayName = days[now.day()];

if (Restaurants.find({
_id : query._id,
'hours.' + query.dayName.closed : {$lte : seconds},
'hours.' + query.dayName.opened : {$gte : seconds}
}).count() > 0) {
    return "Opened";
} else {
    return "Closed";
}

Queries i tried in mongoshell with your provided data,
so is collection name here, 160000 & 190000 is the testing value in seconds here

this returns 1 row, as desired

db.so.find({'hours.tue.opened' : {$lte : 160000}, 'hours.tue.closed' : {$gte : 160000}})

this returns empty set aka 0 row, as desired

db.so.find({'hours.tue.opened' : {$lte : 190000}, 'hours.tue.closed' : {$gte : 190000}})

BTW i liked your implementation with seconds, it makes is very easier to manage. If you stuck again somewhere i will be here only

like image 38
Gaurav Gandhi Avatar answered Nov 02 '22 14:11

Gaurav Gandhi