I have a mongodb collection where each document has some attributes and a utc timestamp. I need to pull out data from the collection and use the aggregation framework because I use the data from the collection to display some charts on the user interface. However, I need to do the aggregation as per the user's timezone. Assuming I know the user's timezone(passed in the request from browser or in some other manner), is there any way to use the aggregation framework to aggregate based on the [client's] timezone?
-
Can you be clearer about what you're trying to do any why you can't just convert to the client's timezone post aggregation? You could always use mapreduce instead of the aggregation framework - it will be slower but will allow any sort of ad hoc calculation you need to do – Mason Aug 17 '13 at 16:20
-
Actually, I need to generate weekly reports for the user based on his/her timezone. For the report, I need to use the aggregation framework. Problem is, I should take into account the user's timezone while aggregating. I cannot do so after aggregation as that will result in wrong results. Map reduce is an option but I need this on demand and I saw while searching StackOverflow that it shouldn't be used in place of a query. I was hoping there'd be some way. – Hrishi Aug 17 '13 at 16:58
-
I don't know about MongoDB well enough to answer, but I believe the approach would be similar to what I describe for RavenDB [here](http://ravendb.net/kb/61/working-with-date-and-time-in-ravendb#time-zone-conversions), and again in the [`Foo_ByDate_MultiZone` index here](https://github.com/mj1856/RavenDB-NodaTime/wiki/Indexing-and-Querying). If Mongo allows something similar, you would do this in the Map, just like I did in Raven. – Matt Johnson-Pint Aug 17 '13 at 18:46
-
Unfortunately with the aggregation framework you're limited to the operators that are provided (which is why it's an order of magnitude faster than mapreduce). MongoDB doesn't provide any timezone features, it expects that to be done at the application level. Can you explain what calculations you're trying to do and why it has to be converted to timezone in the aggregation instead of your application code? You might be able to store an offset representing the user's offset from UTC and use that – Mason Aug 17 '13 at 18:54
-
@Mason,@Matt Johnson thanks for trying to help out. It seems like Mongodb doesn't provide timezone features. Yes, storing the offset seems like the way ahead. I wanted the time zone conversion to happen because I wanted to show the user his/her activity graph for the week on my application. And since they may be in different timezones, the chart should be able to show them the data according to their time zone. – Hrishi Aug 17 '13 at 19:01
-
2There's no reason you need to do that in MongoDB. That's what the application layer is for. Do all your aggregation in UTC then on output adjust by their timezone. Keeping everything in UTC will ensure that things stay consistent if the user changes timezones as well. – Mason Aug 17 '13 at 19:14
-
If you're using the python driver, it has an option to do the conversion for you on the driver side; you'll have to do the conversion yourself with other drivers. http://api.mongodb.org/python/current/api/pymongo/connection.html – Dylan Tong Aug 17 '13 at 21:49
-
12@Mason - The vast majority of the time, I would agree with you that application code is the best place for this. The difference here is that it is in aggregation. To group by day, you have to know the boundaries for the day. Each time zone has a different concept of start and end of day, both in raw UTC terms, and in dealing with changes for DST. If Mongo doesn't let you do it in the index map, then another approach would be to pre-calculate multiple local times in different zones *before* doing the aggregation and save them with the document. This gets messy though. – Matt Johnson-Pint Aug 17 '13 at 23:52
3 Answers
Aside from the SERVER-6310 mentioned by Matt Johnson, one other workaround is to use the $project
operator to add or subtract from the UTC time zone to "shift the time" into the correct local zone. Turns out you can add or subtract time in milliseconds.
For example, assuming I have a Date field called orderTime
. I'd like to query for EDT. That is -4 hours from UTC. That's 4 * 60 * 60 * 1000 milliseconds.
So I would then write the following projection to get day_ordered
in local time for all my records:
db.table.aggregate(
{ $project : { orderTimeLocal : { $subtract : [ "$orderTime", 14400000] } } },
{ $project : { day_ordered : { $dayOfYear : "$orderTimeLocal" } } })

- 7,971
- 5
- 57
- 106

- 954
- 1
- 7
- 9
-
1Thank you Astral. You're answer is perfect. I really didn't want to have to use map reduce just to do a simple timezone adjustment. – cfchris Nov 21 '13 at 22:21
-
This answer makes a lot of sense. If you want to query for "Eastern Time" which incorporates both EST and EDT, how would you do that? – Joe Apr 17 '14 at 18:58
-
Isn't it `"$lastActivity"` per the explanation, versus `"$orderTime"` in the answer? – digitalextremist Jan 24 '15 at 04:17
-
12This won't work due to daylight savings. The offset is could be -4 or -5 depending on the day/month. – Ricardo Macario Apr 06 '15 at 21:50
-
-
This implementation WILL work with daylight savings time assuming you use a timezone library that can get the current UTC offset for the current day. So: `4 * 60 * 60 * 1000` turns into `utcOffset * 60 * 60 * 1000` – daino3 Jul 13 '17 at 22:06
Every approach suggested above works perfectly fine, but since there is a new version of mongodb, from 2.6 you can use $let
in the aggregation framework, this will let you create variables on the fly, thus avoiding the need to $project
before grouping. Now you could create a variable with $let
that will hold the localized time, and use it in the $group
operator.
Something like:
db.test.aggregate([
{$group: {
_id: {
$let: {
vars: {
local_time: { $subtract: ["$date", 10800000]}
},
in: {
$concat: [{$substr: [{$year: "$$local_time"}, 0, 4]},
"-",
{$substr: [{$month: "$$local_time"}, 0, 2]},
"-",
{$substr: [{$dayOfMonth: "$$local_time"}, 0, 2]}]
}
}
},
count: {$sum: 1}
}
}])
Notice that you use $let
inside definition of a block/variable, and the value of that block/variable is the returned value of the subexpression "in"
, where the above defined vars are used.
-
2This also doesn't work in cases involving Daylight Saving Time, because you assume that a fixed time can subtracted, when the correct value could be an hour different depending whether the local time is in Daylight Saving Time or not. – Mark Stosberg Apr 03 '17 at 16:29
What you're asking for is currently being discussed in MongoDB issue SERVER-6310.
I found this in a link from a discussion thread.
The problem is common for any grouping by date, including SQL databases and NoSQL databases. In fact, I recently addressed this head on in RavenDB. There is a good description of the problem and a RavenDB solution here.
The MongoDB issues discusses a workaround, which is similar to what I described in comments above. You precalculate the local times you are interested in, and group by those instead.
It will be difficult to cover every time zone in the world with either approach. You should decide on a small handful of target zones that make sense for your user base, such as the per-office approach I described in the RavenDB article.
UPDATE: This issue was solved in MongoDB in July 2017 (version 3.5.11). The solution is described in the first link above, but in short they have introduced a new object format for dates in aggregation expressions: { date: <dateExpression>, timezone: <tzExpression> }
that allows you to specify a timezone to use when aggregating. See here for another example in the Mongo docs.

- 673
- 6
- 20

- 230,703
- 74
- 448
- 575
-
-
@Matt, Is there any solution available for this in the latest mongodb 2.6.x – Rams Jul 18 '14 at 12:14
-
@rams - I haven't checked, but the issue is still marked open/unresolved. – Matt Johnson-Pint Jul 18 '14 at 15:59
-
Is there no solution for a plain old .find()? .aggregate can get super slow, probably my own fault for that but for simple .find results to be given with local time would be so gold – rom Dec 27 '22 at 21:42