I was trying to implement luxon in an lightweight ORM we've building, and notice it suffers the same thing I never liked about moment; I always find myself overriding toJSON method in order to properly JSON.stringify deep nested objects with moment instances, and see the formats I want, not the toISO() string
https://github.com/moment/luxon/blob/409cbacd887064a29dde068f9430e9d68945ffcb/src/datetime.js#L1520
You can see an example of what I mean in this codepen, I use dayjs on it, a package has the same issue, and I mock the instance to use a defaultFormat, and invoke this.format() so JSON.stringify(deepNestedObjectWithDates) always cast the date strings with the default format I provided to the date instance (dayjs in that case).
RFC:
const dt = DateTime.fromObject({ year: 2018, month: 4, day: 30, format: DateTime.DATETIME_MED })
vm.user = {name: 'Jon', lastSeen: dt } // <div>{{ user.lastSeen.format('...') }}</div>
console.log(JSON.stringify(user)) // '{name: "Json", lastSeen: "Apr 30, 2018, at midnight"}'
Hmm. I wonder if it would be enough to set a global setting, as opposed to a per-instance setting?
My two cents: when I see the following JSON, it raises warnings in my mind:
{name: "Json", lastSeen: "Apr 30, 2018, at midnight"}
JSON is a data exchange format. It is supposed to provide the raw-_est_ data possible, not formatted. Why do you need the JSON to contain some formatted data instead of having the consumer format the data?
Think about it: are you expecting numbers to be sent differently than using their standard string representation? Would it make sense to receive {age: "0110"} instead of {age: "6"}? If we consider that default string representation for dates is the ISO formatted string, then it makes sense for Luxon to use that format for JSON encoding. Consumers should be responsible for fancy rendering.
@icambron How would that work?
I'm concerned about Nodejs for example, and per-request localization of DateTime objects. So instead of instantiating luxon on every request with the request locale, I was thinking on creating a service module to bring the correct instance for that locale (instantiated on server bootstrap).
@ericmorand Actually, we're not specifically talking about JSON as the format, but the ToPrimitive method invoked at serialization time when an object is passed to JSON.stringify, which first looks for a toJSON method on the given object (like String(obj) invokes obj.toString, the same as Number(obj) with obj.toNumber, but there's no such thing as JSON(obj) because JSON is a format, not a primitive value).
Said method is not supposed to be accessed directly (if doing so, the user should be doing toISO() instead), or "publicly" by the user, as it has a naming convention for primitive conversions.
I think it's fair to be able to serialize JSON how you want; it isn't necessarily for a data-oriented exchange format. So I'm sympathetic to this request. However, I'm wary of making the interface for creating Luxon objects more complicated to support it.
My suggestion was this:
Settings.jsonFormat = DateTime.DATETIME_MED;
DateTime.local().toJSON() //=> formatted as datetime medium
DateTime.local().setLocale("fr").toJSON() //=> formatted as datetime medium, but in french
I'm not sold on that, though. For example, some people will want to ignore locale, which means the options would have to get more complicated. Perhaps this is a case where it's easier to hack the prototype of toJSON?
This is also a great example of where dynamic scoping would be really nice, and it's a shame JS doesn't provide it.
I played around with this and concluded it's fine to hack the prototype.
DateTime.prototype.toJSON = function() { return this.toFormat('MM/dd/yyyy'); }
DateTime.local().toJSON(); // => "06/16/2018"
I think this gives the most flexibility and as long as you use only public methods in the function body, it's no more dangerous than the global override I suggested above. And it doesn't complicate the API at all, so I'm calling this a win.
For anyone landing here: you should probably use the default unless you have a compelling reason to use another format. This is only for those exceptional cases.
Most helpful comment
I played around with this and concluded it's fine to hack the prototype.
I think this gives the most flexibility and as long as you use only public methods in the function body, it's no more dangerous than the global override I suggested above. And it doesn't complicate the API at all, so I'm calling this a win.
For anyone landing here: you should probably use the default unless you have a compelling reason to use another format. This is only for those exceptional cases.