Skip to content

Instantly share code, notes, and snippets.

@eckenroed
Created May 3, 2015 16:15
Show Gist options
  • Save eckenroed/e44081f60a79a28614f4 to your computer and use it in GitHub Desktop.
Save eckenroed/e44081f60a79a28614f4 to your computer and use it in GitHub Desktop.
A simple trait that can be included on any Models in Laravel 5 that will user either the logged in users preferred timezone. or the timezone of the App for displaying all dates, while storing them in UTC in the database. Assumes a "timezone" field in your users table.
<?php
use Carbon\Carbon;
trait Timezone {
protected $defaultTimezone = null;
public function getDefaultTimezone()
{
if( is_null($this->defaultTimezone) ) {
$this->defaultTimezone = \Auth::check() ? \Auth::user()->timezone : \Config::get('app.timezone');
}
return $this->defaultTimezone;
}
public function setCreatedAtAttribute($value)
{
$dt = Carbon::createFromFormat('Y-m-d H:i:s',$value, $this->getDefaultTimezone());
$dt->setTimezone('UTC');
$this->attributes['created_at'] = $dt->format('Y-m-d H:i:s');
}
public function setTimezoneForDates()
{
if(empty($this->dates)) {
return;
}
foreach( $this->dates as $column ) {
$dt = Carbon::createFromFormat('Y-m-d H:i:s',$this->attributes($column), $this->getDefaultTimezone());
$dt->setTimezone('UTC');
$this->attributes[$column] = $dt->format('Y-m-d H:i:s');
}
}
/**
* Convert a DateTime to a storable string.
*
* STOLEN from Illuminate\Database\Eloquent\Model to override.
* @param \DateTime|int $value
* @return string
*/
public function fromDateTime($value)
{
$format = $this->getDateFormat();
// If the value is already a DateTime instance, we will just skip the rest of
// these checks since they will be a waste of time, and hinder performance
// when checking the field. We will just return the DateTime right away.
if ($value instanceof DateTime)
{
//
}
// If the value is totally numeric, we will assume it is a UNIX timestamp and
// format the date as such. Once we have the date in DateTime form we will
// format it according to the proper format for the database connection.
elseif (is_numeric($value))
{
$value = Carbon::createFromTimestamp($value, $this->getDefaultTimezone());
}
// If the value is in simple year, month, day format, we will format it using
// that setup. This is for simple "date" fields which do not have hours on
// the field. This conveniently picks up those dates and format correct.
elseif (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $value))
{
$value = Carbon::createFromFormat('Y-m-d', $value, $this->getDefaultTimezone())->startOfDay();
}
// If this value is some other type of string, we'll create the DateTime with
// the format used by the database connection. Once we get the instance we
// can return back the finally formatted DateTime instances to the devs.
else
{
$value = Carbon::createFromFormat($format, $value, $this->getDefaultTimezone());
}
$value->setTimezone('UTC');
return $value->format($format);
}
/**
* Return a timestamp as DateTime object.
*
* STOLEN from Illuminate\Database\Eloquent\Model to override.
*
* @param mixed $value
* @return \Carbon\Carbon
*/
protected function asDateTime($value)
{
// If this value is an integer, we will assume it is a UNIX timestamp's value
// and format a Carbon object from this timestamp. This allows flexibility
// when defining your date fields as they might be UNIX timestamps here.
if (is_numeric($value))
{
$dt = Carbon::createFromTimestamp($value, "UTC");
$dt->setTimezone($this->getDefaultTimezone());
return $dt;
}
// If the value is in simply year, month, day format, we will instantiate the
// Carbon instances from that format. Again, this provides for simple date
// fields on the database, while still supporting Carbonized conversion.
elseif (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $value))
{
$dt = Carbon::createFromFormat('Y-m-d', $value, "UTC")->startOfDay();
$dt->setTimezone($this->getDefaultTimezone());
return $dt;
}
// Finally, we will just assume this date is in the format used by default on
// the database connection and use that format to create the Carbon object
// that is returned back out to the developers after we convert it here.
elseif ( ! $value instanceof DateTime)
{
$format = $this->getDateFormat();
$dt = Carbon::createFromFormat($format, $value, 'UTC');
$dt->setTimezone($this->getDefaultTimezone());
return $dt;
}
return Carbon::instance($value);
}
/**
* Convert the model's attributes to an array.
*
* @return array
*/
public function attributesToArray()
{
$attributes = $this->getArrayableAttributes();
// If an attribute is a date, we will cast it to a string after converting it
// to a DateTime / Carbon instance. This is so we will get some consistent
// formatting while accessing attributes vs. arraying / JSONing a model.
foreach ($this->getDates() as $key)
{
if ( ! isset($attributes[$key])) continue;
$attributes[$key] = (string) $this->asDateTime($attributes[$key]);
}
$mutatedAttributes = $this->getMutatedAttributes();
// We want to spin through all the mutated attributes for this model and call
// the mutator for the attribute. We cache off every mutated attributes so
// we don't have to constantly check on attributes that actually change.
foreach ($mutatedAttributes as $key)
{
if ( ! array_key_exists($key, $attributes)) continue;
$attributes[$key] = $this->mutateAttributeForArray(
$key, $attributes[$key]
);
}
// Next we will handle any casts that have been setup for this model and cast
// the values to their appropriate type. If the attribute has a mutator we
// will not perform the cast on those attributes to avoid any confusion.
foreach ($this->casts as $key => $value)
{
if ( ! array_key_exists($key, $attributes) ||
in_array($key, $mutatedAttributes)) continue;
$attributes[$key] = $this->castAttribute(
$key, $attributes[$key]
);
}
// Here we will grab all of the appended, calculated attributes to this model
// as these attributes are not really in the attributes array, but are run
// when we need to array or JSON the model for convenience to the coder.
foreach ($this->getArrayableAppends() as $key)
{
$attributes[$key] = $this->mutateAttributeForArray($key, null);
}
return $attributes;
}
}
@eckenroed
Copy link
Author

Notes

I will put together a full package of how I accomplish this ,but by using Moment.js, it's timezone companion, along with a Timezone Javascript package (that you would condense into one file with Elixir), you can establish the visitors current timezone.

This allows you to drop in a piece of middleware that can access a cookie set by the javascript for the current timezone and set it in your app (or if you have a timezone field in your users table that is not null, it will use that users preferred timezone).

This makes it easy to insure all your dates in the database are in a uniform timezone (UTC), while allowing all the display and Carbon's diffForHumans functions to be in the users current (or preferred) timezone.

It will apply this timezone to:

  • The default date fields in the table (created_at, updated_at, deleted_at)
  • Any dates listed in the $dates array

It will display the dates in the current/preferred timezone:

  • when the date is called directly such as User::created_at
  • when the model is cast to an array with the toArray() function

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment