Curious Case Of Date Formats In Data Models
Every major release of AdonisJs gives me a chance to introduce breaking changes (for the good) and make sure AdonisJs addresses the majority of common use cases in development land.
This time I have been brainstorming about dateFormat
defined on Lucid models, what purpose does it serves and how flexible is it with different use-cases.
Before we really dig into the implementation details, let’s see an example of how date format works as of now and what challenges it has.
class User extends Lucid {
static get dateFormat () {
return 'YYYY-MM-DD HH:mm:ss'
}
}
Now every time you save a record to the database or fetch it from the database, the dates get formatted as per the defined format.
Challenges #
There are handful of challenges with this approach
- What if I need a different format for saving the data and different format when displaying the data?
- What if different date fields need different date formats?
My first approach was to peek into other communities and see how they do it since it seems to be a very common problem.
As you all know AdonisJs is heavily inspired by Laravel, I checked Laravel docs and couple of Github issues and found that this problem exists there too.
Weird WorkArounds #
Lots of users mentioned about creating getters
for dates which need different format when displaying it to the end user. Which seems like a valid point but if you have plenty of dates, then you will have to create getters
for each of them.
Also when trying to save a date, the format needs to be valid for the database engine as well. Let’s see an example:
class User extends Lucid {
static get dateFormat () {
return 'DD'
}
}
If I try to save a record inside MYSQL, it will throw a runtime exception. Since DD
is not a valid format for DateTime
type.
Finally, in nutshell, the dateFormat
serves nothing, since you have to create getters for display right format and cannot change it’s value to anything since it has to respect MYSQL date time format as well.
Lucid Approach #
I took the decision to remove dateFormat
completely since it sets the wrong expectations and we end up creating getters
and setters
when we need customization.
Defining Dates #
You can mark multiple fields as dates
by defining a getter on the Lucid model.
class User extends Lucid {
static get dates () {
return ['created_at', 'updated_at', 'login_at']
}
}
Automatic formatting #
Since all dates have to respect the MYSQL format, Lucid will format them in YYYY-MM-DD HH:mm:ss
format automatically before saving it to the database.
Also when dates are fetched from the database, they will be converted to moment instances and back to YYYY-MM-DD HH:mm:ss
formatted string when you call toJSON
on your model or collection of models.
So till this point, everything works the same way but the weight of date format has been removed.
Casting And Formatting Dates #
Lucid will have two new methods called castDates
and formatDates
which will be invoked for each date so that you can customize the format without writing bunch of getters
and setters
.
class User extends Lucid {
// called when saving the model
static formatDates (field, value) {
if (field === 'dob') {
return value.format('YYYY-MM-DD')
}
return super.formatDate(field, value)
}
}
The formatDates
method is called when lucid attempts to save the model to the database. The method is invoked with field name
& value
. The return value get’s saved to the database.
class User extends Lucid {
// called when toJSON is called
static castDates (field, value) {
if (field === 'dob') {
return `${value.fromNow(true)} old`
}
return super.formatDate(field, value)
}
}
The castDates
method is called when converting the model instance to JSON representation, at this point, you receive an instance of moment
and you are free to cast it the way you can want.
What’s Achieved? #
Very first we removed the weight of dateFormat
since it had so many constraints and at times confusing on how to customize it as per the needs.
Next, instead of defining multiple getters
and setters
, we rely on dedicated methods which can format and cast all the date fields.
Under the hood #
Also see how all of this works under the hood, making sure this all works automatically but still offers flexibility.
- All
dates
are formatted asYYYY-MM-DD HH:mm:ss
upon saving and fetching them from the database. This format works great with MYSQL DateTime and Timestamp data type. - You can override
castDates
andformatDates
methods to manually format a single or multiple dates. - Also, you can define
getters
andsetters
for each date and in that case, Lucid will not invokecastDates
orformatDates
for those fields.