Mongoose examples frequently use JS Date
objects for time. JavaScript Date
s are in local timezone by default. Using specific timezones for server-side data is generally considered bad practice. See Daylight saving time and time zone best practices or The 5 laws of API dates and times
Mongoose docs should encourage best practice for storing dates, specifically by replacing:
time: { type: Date, default: Date.now },
with
time: { type: Number, default: function(){return new Date().getTime()} }
I respectfully disagree. I'm working on a project right now that is date-heavy in the models. I've used timestamps before and saving real dates has only made my life easier. Saving as a number requires you to always cast it back into a date to be usable and makes it impossible to read raw data from the Mongo shell. Plus, Mongo/Mongoose already converts dates into ISODate format (doesn't have a timezone offset). The Mongoose example would result in the following format:
ISODate("2014-08-18T18:35:47.376Z")
Further, dates are converted to ISOStrings when being sent through JSON, which removes the offset from the browser, and your servers should be in UTC time as another "best practice." Basically, what I have learned is that so long as you have the right date, whatever timezone, it will always look the same in the database.
Saving as a number requires you to always cast it back into a date to be usable
No it doesn't: many arithmetic operations are even actually more easily performed on numbers.
impossible to read raw data from the Mongo shell
new Date(value)
is fairly simple.
Mongo/Mongoose already converts dates into ISODate format (doesn't have a timezone offset).
Do you have a cite for this?
your servers should be in UTC time as another "best practice."
That's correct and agreed. However your development environment (eg, typical Mac desktop) probably won't be: having inconsistent behaviour between your development environment and server environment is one of the dangers on having using TZ-dependent Dates
as you have the right date, whatever timezone, it will always look the same in the database.
See the cited docs: storing data on the server in a TZ specific format then converting to another TZ specific format to display is much more work than storing it in a non-TZ-specific format and then converting it later.
I must disagree as well. MongoDB stores dates as ints internally (and thus TZ-independent), but provides you with extra features on top of the date type that you wouldn't get with a plain old int. Plus, having to convert every path that you want to actually be a date but is just a number before you use it for any real date-related work is precisely the type of gnarly boilerplate that mongoose is designed to avoid.
@vkarpov15 Thanks for replying - I understand what you're saying, but where on that link does it say MongoDB stored Numbers are always against UTC? I could infer it (Numbers don't have a TZ component), but I don't want to make a decision based on an inference (eg, the mongo engine could still use the local TZ rather than UTC).
I suspect you're right but there's something missing in the docs here, unless I'm missing it.
You can type "new Date(number)" to see the date in mongo shell, but this is an annoying extra step especially if you're QA'ing between dates or across documents. Typing in queries in a CLI is painful enough! We can go on about cases where number vs Date makes more sense, but as a 'standard' IMHO ISODate is a good timezone-agnostic standard type that provides more features than Number.
I don't have any citation about conversion, only experience. I have Date type fields in Mongoose and they all get stored as ISODate, so it doesn't matter if I created it on my computer or a server in UTC, or sent from a browser to the server... the timezone offset gets dropped and the date is always converted to UTC.
@mikemaccana internally dates are milliseconds since unix epoch, which implies UTC. The BSON type Date currently only accepts 64-bit signed integers, so there's no way to send a date with timezone information to MongoDB. Internally, mongoose's BSON conversion does use getTime()
to get millis since the epoch, it just does it in a way that MongoDB knows that its a date so you can access functionality like $week
Is there any way we can save date object in timestamp in mongoDB?
Regards
Deepak Sisodiya
AFAIK that's not possible with mongoose atm.
This actually clears a lot of doubts regarding dates around mongoose. 馃憤
@ktkaushik yeah dates in JS in general are confusing. Nowadays I just end up using https://www.npmjs.com/package/mongodb-moment and just using moment for everything, makes things a lot more sane.
I'm not sure if this is related but if I save a date from the application (using Mongoose) the date gets "truncated" e.g. Tue, 15 Nov 2016 01:48:24 GMT
is a date that's saved to the database from the application with new Date(Date.now() + 60 * 60 * 1000)
but if I used a default date in the (Mongoose) model e.g. appointment: {type: Date, default: new Date(Date.now() + 60 * 60 * 1000)}
it generates a date with the following format: Tue Nov 15 2016 01:36:26 GMT+0000 (UTC)
, as you can see the default date gets created with the offset and without the comma after the day. When I log new Date()
from the application it also has the offset (and it's without a comma as well) so I would have to assume that Mongoose is modifying the date and this causes { $gte: new Date() }
or { $gt: new Date() }
to not find any matches when there are actually documents that match but the date formats are slightly different. Mongoose version 4.3.3
, is this suppose to have this behaviour?
@nateislate are the dates saved as actual dates in the database or as strings? If they're saved as dates, the string format and offset doesn't matter because dates in mongodb are just 64-bit ints.
@vkarpov15 Which is what I thought. The attribute on the model looks like this: appointment: {type: Date, default: new Date(Date.now() + 60 * 60 * 1000)}
this is with findOneAndUpdate
& {new: true, upsert: true}
but a {$gte: .....}
query doesn't seem to work. I'll have to do some more digging. It works as expected when I save the date from the application (from the route) but not if I use default: new Date(Date.now() + 60 * 60 * 1000)
in the model.
So defaults don't get applied on findOneAndUpdate
unless you set the setDefaultsOnInsert
option to true.
@nateislate
default: new Date(Date.now() + 60 * 60 * 1000)
will be calculated only once and Date.now()
there will be the number of milliseconds passed since 1970 till the moment you require your schema module and 60 * 60 * 1000
will add and hour to it.
In other words it doesn't make a lot of sense for me...
Iam found solition in mongoose-timezone - mongoose plugin to normalize stored dates timezone
see here https://www.npmjs.com/package/mongoose-timezone
This timestamp 2020-05-03T11:00:00.000Z
in new Date() outputs the following Sun May 03 2020 12:00:00 GMT+0100 (British Summer Time)
, would it output a different time in any other country? I'm hoping it does, this would mean I can have these set appointment times easily in my model. Do these mongoose timestamps save with local servers timezones and then convert them to the user time zone?
@azmolmiah yes it does. JavaScript formats dates according to the local timezone in Node.js or the browser.
Mongoose doesn't store timezones with dates, in MongoDB dates are stored as 64 bit ints.
Most helpful comment
You can type "new Date(number)" to see the date in mongo shell, but this is an annoying extra step especially if you're QA'ing between dates or across documents. Typing in queries in a CLI is painful enough! We can go on about cases where number vs Date makes more sense, but as a 'standard' IMHO ISODate is a good timezone-agnostic standard type that provides more features than Number.
I don't have any citation about conversion, only experience. I have Date type fields in Mongoose and they all get stored as ISODate, so it doesn't matter if I created it on my computer or a server in UTC, or sent from a browser to the server... the timezone offset gets dropped and the date is always converted to UTC.