I have times in my database in unix time. I am looking for an elegant way to display those times in a more human-readable format without having to write the formatting in every create/read/update view. In addition, every UTC timestamp is based on different local times and timezones that I capture in another column in the format America/Chicago. The times should always display the local time in the views.
I’m a little lost in regard to the “correct” way to achieve this. I have read about people doing this logic in the model, and others saying this sort of data conversion should be kept out of the model. afterFind() and beforeValidate() have been mentioned.
The way I have done this currently is:
//Collect date, time, and timezone from $_POST
Yii::$app->setTimezone($model->timezone);
//Convert local time to UTC based on timezone, using.
$model->start_time = gmdate('U', strtotime($model->formdate));
I did this in the controller before validation and saving, and it works. But it doesn’t feel right since I have to repeat the same for other actions in my controllers. I added
$formdate
as an attribute in my model (outside the database) since I didn’t know how else I could get data that isn’t valid until converted. That’s probably not the right way to do it.
I’m just learning Yii with very basic PHP experience so any advice would be appreciated.
Use beforeSave() & afterFind events in your model to achieve this. If you do it in every action then you should do it in a single place for DRY code. Maintainability will also benefit from this.
I haven’t used MySQL / SQL in a while now (nosql fan) but I believe you can also use TIMESTAMP data type in your database and it’s automatically the users timezone (not server), i don’t know what your doing with the data though and that is the main factor with using that data type. MySQL stores it as UTC then converts it on retrial to the users timezone. Using just this without the above might solve all of your problems too.
All formatting depends on the context and should be done only when the context is already known. Calling Yii::$app->formatter (or your own formatting helper) every time when you generate a date/time output doesn’t violate DRY because the actual implementation can be replaced with a different one at any time. Hardcoded formatting in your models’ afterFind() is what really hurts maintainability.
Thanks for all the suggestions. I have managed to make some progress. I moved the data conversion logic to the model using the beforeSave() and afterFind() events. The formatter was both helpful and caused some trouble. Using the asTimestamp method ignores Yii::$app->formatter->timeZone so I reverted to using standard PHP. I’m sure I just didn’t do it right.
This works
public function afterFind()
{
Yii::$app->formatter->timeZone = $this->timezone;
$this->class_start = Yii::$app->formatter->asDate($this->class_start, 'dd MMM yyyy HH:mm');
parent::afterFind();
return true;
And so does this, but not using the formatter method to set the timezone.
public function beforeSave($insert)
{
if (parent::beforeSave($insert)) {
// this didn't work
//Yii::$app->formatter->timeZone = $this->timezone;
// this works
date_default_timezone_set($this->timezone);
// this didn't set the correct timestamp
//$this->class_start = Yii::$app->formatter->asTimestamp($this->class_start);
// this also works
$this->class_start = gmdate('U', strtotime($this->class_start));
return true;
} else {
return false;
}
}
To give some background to the application, it’s basically an event scheduler. A user will enter a start time and timezone for the event, which will be converted to a UTC timestamp in the database. Whenever a user is concerned, the timestamp should be converted back to local time. Every entry in the DB can have a different timezone. Using the above, the data in the database is correct, and also shows correctly in local time.
There is still one issue. Since the timestamp is a BIGINT, the model expects an integer. Even after the afterFind() converts it to a human readable time, the form validation complains about it having to be an integer. The only way I got around that was by turning off client validation and removing the datatype from the model.