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

  1. What if I need a different format for saving the data and different format when displaying the data?
  2. 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.

  1. All dates are formatted as YYYY-MM-DD HH:mm:ss upon saving and fetching them from the database. This format works great with MYSQL DateTime and Timestamp data type.
  2. You can override castDates and formatDates methods to manually format a single or multiple dates.
  3. Also, you can define getters and setters for each date and in that case, Lucid will not invoke castDates or formatDates for those fields.
 
103
Kudos
 
103
Kudos

Now read this

# Petit mais grand (AdonisJs 3.2.1)

I am about to step the paddle for the major release of Adonisjs version 4.0 (called dawn). Dawn will be released with lots of exciting features including first class support for async/await, testing, tooling for deployment and bunch of ?... Continue →