Automattic / mongoose

MongoDB object modeling designed to work in an asynchronous environment.
https://mongoosejs.com
MIT License
26.88k stars 3.83k forks source link

Mongoose sets time in database to 08:00 when no time provided #7505

Open jpgklassen opened 5 years ago

jpgklassen commented 5 years ago

Do you want to request a feature or report a bug? Report a bug.

What is the current behavior? The following code sets the 'created' date/time in the database to 2017-03-06T08:00:00.000+00:00, even though no time is provided:

var url = 'http://example.com'
var title = 'This is the Title'
var created = 'March 7, 2017'
var description = 'A paragraph'

Campaign.findOneAndUpdate(
  { url: url },
  {
    url: url,
    title: title,
    created: created,
    description: description
  },
  { upsert: true }
)

If the current behavior is a bug, please provide the steps to reproduce. See above.

What is the expected behavior? I would expect the 'created' date/time in the database to be 2017-03-06T00:00:00.000+00:00 (ie. time set to midnight instead of 8am)

Please mention your node.js, mongoose and MongoDB version. Node v8.11.1 Mongoose v5.4.10 MongoDB v4.0.1

Ncifra commented 5 years ago

This should be related to the server's timezone rather than mongoose. You need to take into account how Date() objects are initialized.

vkarpov15 commented 5 years ago

In MongoDB, dates are stored fundamentally as 64 bit ints: http://bsonspec.org/spec.html . In other words, the database doesn't have a notion of a date's timezone. You're getting 8:00 because of your machine's timezone similar to how JavaScript does it natively: https://www.w3schools.com/js/js_date_formats.asp . We'll consider allowing overwriting this in Mongoose, but till then make sure you add hours yourself, or call date.setHours(0)

Ncifra commented 5 years ago

We've had our own problems with date inconsistencies and couldn't reply more thoroughly yesterday. First you can check out the Date() syntax documentation over here and note the bolded text:

Note: parsing of date strings with the Date constructor (and Date.parse, they are equivalent) is strongly discouraged due to browser differences and inconsistencies. Support for RFC 2822 format strings is by convention only. Support for ISO 8601 formats differs in that date-only strings (e.g. "1970-01-01") are treated as UTC, not local.

This means that if you don't create a Date object with the ISO 8601 string format as above, the object will be initiliazed with a time depending on the time zone of the instance where the object is being created (be that client Javascript in the browser, NodeJS server or MongoDB server). If you can't / don't want to change the format, we have implemented a "transformation" that gets the current date, and sets the Date() object to UTC/GMT at midnight:

getDateToGMTMidnight: function (date) {
            //Either we have been provided with the date object, or if not, create it
            let dateObject = date ? new Date(date) : new Date();
            //Set the time  to midnight of the current date
            dateObject.setHours(0, 0, 0, 0);
            //Convert the current datetime to ISODate format, without changing the time or date
            dateObject.setTime(dateObject.getTime() - dateObject.getTimezoneOffset() * 60000);
            return dateObject;
        }

Depending on your usage, you can apply the above function either on client sending the request side (assuming it is one in Javascript be that web or Node server), or you can do the filtering on the receiving side, but that would mean filtering manually the request properties that may pollute the code.

jpgklassen commented 5 years ago

@Ncifra Thank you! That makes sense considering 8 hours was only being added to this database field, whereas the other fields were fine and they were entered with the format yyyy-mm-dd. I will use the function you provided.

Ncifra commented 5 years ago

@jpgklassen One thing to notice with the above function is that if you use it on the client sending the request, you will have a Date() object returned, and you will need to use the toISOString() function to actually get the ISO 8601 format (with hours) before sending the request. If you send it directly from the Date object its toString() method/function will be called implicitly which will give you another string format of the date similar to your current case.

If you use it on the server on the other hand you can insert it directly.