I am using Mongoose and Luxon to display a date, selected by the user from a form. But the date is console.logging one date, but displaying on the page as the day before.
This is my model:
const mongoose = require("mongoose");
const { DateTime, Settings } = require("luxon");
// Configure time zone
console.log(Settings);
const Schema = mongoose.Schema;
let AccomplishmentSchema = new Schema({
dateInput: {
type: Date,
required: true,
},
textInput: {
type: String,
required: true,
},
});
AccomplishmentSchema.virtual("dateInput_formatted").get(function () {
return DateTime.fromJSDate(this.dateInput).toLocaleString(DateTime.DATE_FULL); // format 'YYYY-MM-DD
});
module.exports = mongoose.model("Accomplishment", AccomplishmentSchema);
And this is my console log vs what is showing up on the page
dateInput: 2023-01-01T00:00:00.000Z,
textInput: 'etst',
December 31, 2022 etst
I can only assume this has to do with some kind of time conversion, but I've tried changing the time zone, and the settings, although I could've read the docs wrong. I cannot find a way to fix this issue.
CodePudding user response:
When working with JS Date
, the most important thing to understand is that Date
doesn't store or know about a time zone. It simply stores the number of milliseconds since midnight on Jan 1, 1970 (aka UNIX epoch) in the UTC time zone. Then, when you call Date
methods, the results are calculated for the current user's time zone (for methods like getMinutes()
) or UTC (for methods like getUTCMinutes()
). But the underlying data inside the Date
is just a timezone-less number of milliseconds.
With that in mind, let's look at a simple example: new Date(0)
, which is equivalent to new Date('1970-01-01T00:00Z')
.
If your computer's time zone set to the Europe/Paris
time zone, then when you call new Date(0).toLocaleDateString('en-US')
then you'll get a result of '1/1/1970'
. But if you change your computer's time zone to America/Los_Angeles
and run the same code, you'll get a result of '12/31/1969'
.
This happens because the Date
's stored value corresponds to midnight on Jan 1, 1970 in the UTC time zone. In Paris that instant was 1:00AM on Jan 1, 1970, while in California it was 4:00PM on December 31, 1969. The same exact Date
will print a different date depending on the time zone that's active at the time that date is printed.
Where this becomes particularly problematic is in cases where you're trying to express a date without a time. For example, assume you store a Date
value of 2023-01-01T00:00:00.000Z
in MongoDB.
When you display that Date
in a date picker in a browser in California, the date shown will be Dec 31, 2022, because California is 8 hours ahead of UTC.
One way to solve this problem is to tell Date
that the date you're using is a UTC date. Like this:
utcDateStoredInMongoDB = new Date ('2023-01-01T00:00:00.000Z');
// Get the year, month, and day in that Date, from the perspective
// of the UTC time zone.
year = utcDateStoredInMongoDB.getUTCFullYear();
month = utcDateStoredInMongoDB.getUTCMonth();
day = utcDateStoredInMongoDB.getUTCDate();
// Use that year, month, day to initialize a new Date in
// the user's time zone.
dateForDatePicker = new Date(year, month, day);
// Use that for your date picker.
dateForDatePicker.toLocaleDateString();
// => '1/1/2023'
// To go in the other direction, pull out the year/month/day
// in the current time zone, and turn that into a UTC date.
dateReturnedByDatePicker = new Date(2023, 02, 15);
year = dateReturnedByDatePicker.getFullYear();
month = dateReturnedByDatePicker.getMonth();
day = dateReturnedByDatePicker.getDate();
dateToStoreInMongoDB = new Date(Date.UTC(year, month, day));
dateToStoreInMongoDB.toISOString();
// => '2023-03-15T00:00:00.000Z'
Note that this all will get a lot easier in a year or so when the new JavaScript built-in date/time API, called Temporal starts shipping in browsers and Node.js. With Temporal, these kinds of problems are much easier because there's a dedicated API, Temporal.PlainDate
for dealing with date-only values.
CodePudding user response:
Luxon is working as intended and the display is correct. What is wrong is the data in your MongoDB.
Most libraries consider current time zone when you parse a date. I live in Switzerland.
const moment = require("moment");
console.log( moment('2023-01-01').toDate() )
> 2022-12-31T23:00:00.000Z
const { DateTime } = require("luxon");
console.log( DateTime.fromISO('2023-01-01').toJSDate() )
> 2022-12-31T23:00:00.000Z
const dayjs = require('dayjs')
console.log( dayjs('2023-01-01').toDate() )
> 2022-12-31T23:00:00.000Z
However, the native JavaScript new Date()
constructor does not!
console.log( new Date('2023-01-01') )
> 2023-01-01T00:00:00.000Z
Maybe this behavior depends on your environment and scripting engine, I don't know.
You should also use Luxon when you parse the input data - don't forget to use .toJSDate()
to convert the DateTime object back to Javascript native Date
object.
Luxon uses the system time zone by default. You can change it:
const { DateTime, Settings } = require("luxon");
Settings.defaultZone = 'America/New_York';
If you don't care about the time, you can also use startOf('day')
Settings.defaultZone = 'America/New_York';
DateTime.now().startOf('day').toJSDate()
> 2023-01-13T05:00:00.000Z
DateTime.now().startOf('day').toISO()
> '2023-01-13T00:00:00.000-05:00'