Skip to content

Instantly share code, notes, and snippets.

@lorenzo
Created September 1, 2015 11:38
Show Gist options
  • Save lorenzo/08e24f49c62e0a819868 to your computer and use it in GitHub Desktop.
Save lorenzo/08e24f49c62e0a819868 to your computer and use it in GitHub Desktop.
<?php
namespace App\Database\Type;
use Cake\Database\Type\DateTimeType;
/**
* Datetime type converter.
*
* Use to convert datetime instances to strings & back.
*/
class TimezoneAwareDateTimeType extends DateTimeType
{
/**
* Returns the base type name that this class is inheriting.
* This is useful when extending base type for adding extra functionality
* but still want the rest of the framework to use the same assumptions it would
* do about the base type it inherits from.
*
* @return string
*/
public function getBaseType()
{
return 'datetime';
}
/**
* Converts a string into a DateTime object after parseing it using the locale
* aware parser with the specified format.
*
* @param string $value The value to parse and convert to an object.
* @return \Cake\I18n\Time|null
*/
protected function _parseValue($value)
{
date_default_timezone_set('Europe/Copenhagen');
$value = parent::_parseValue($value);
date_default_timezone_set('UTC');
$value->timezone = 'UTC';
return $value;
}
}
@davidyell
Copy link

This is where I ended up.

<?php
namespace App\Database\Type;

use Cake\Database\Type\DateTimeType;

/**
 * Datetime type converter.
 *
 * Use to convert datetime instances to strings & back.
 */
class TimezoneAwareDateTimeType extends DateTimeType
{
    const SOURCE = 'Europe/London';
    const TARGET = 'UTC';

    /**
     * Returns the base type name that this class is inheriting.
     * This is useful when extending base type for adding extra functionality
     * but still want the rest of the framework to use the same assumptions it would
     * do about the base type it inherits from.
     *
     * @return string
     */
    public function getBaseType()
    {
        return 'datetime';
    }

    /**
     * Convert request data into a datetime object.
     *
     * @param mixed $value Request data
     * @return \Cake\I18n\Time
     */
    public function marshal($value)
    {
        date_default_timezone_set(self::SOURCE);
        if ($value instanceof \DateTime) {
            $value->setTimezone(new \DateTimeZone(self::TARGET));
            return $value;
        }

        $class = static::$dateTimeClass;

        try {
            $compare = $date = false;
            if ($value === '' || $value === null || $value === false || $value === true) {
                return null;
            } elseif (is_numeric($value)) {
                $date = new $class('@' . $value);
                $date->timezone(new \DateTimeZone(self::TARGET));
            } elseif (is_string($value) && $this->_useLocaleParser) {
                return $this->_parseValue($value);
            } elseif (is_string($value)) {
                $date = new $class($value, new \DateTimeZone(self::TARGET));
                $compare = true;
            }
            if ($compare && $date && $date->format($this->_format) !== $value) {
                return $value;
            }
            if ($date) {
                return $date;
            }
        } catch (\Exception $e) {
            return $value;
        }

        if (is_array($value) && implode('', $value) === '') {
            return null;
        }
        $value += ['hour' => 0, 'minute' => 0, 'second' => 0];

        $format = '';
        if (isset($value['year'], $value['month'], $value['day']) &&
            (is_numeric($value['year']) && is_numeric($value['month']) && is_numeric($value['day']))
        ) {
            $format .= sprintf('%d-%02d-%02d', $value['year'], $value['month'], $value['day']);
        }

        if (isset($value['meridian'])) {
            $value['hour'] = strtolower($value['meridian']) === 'am' ? $value['hour'] : $value['hour'] + 12;
        }
        $format .= sprintf(
            '%s%02d:%02d:%02d',
            empty($format) ? '' : ' ',
            $value['hour'],
            $value['minute'],
            $value['second']
        );

        $date = new $class($format, new \DateTimeZone(self::SOURCE));
        $date->timezone(self::TARGET);
        return $date;

    }

    /**
     * Converts a string into a DateTime object after parseing it using the locale
     * aware parser with the specified format.
     *
     * @param string $value The value to parse and convert to an object.
     * @return \Cake\I18n\Time|null
     */
    protected function _parseValue($value)
    {
        date_default_timezone_set(self::SOURCE);
        $value = parent::_parseValue($value);
        date_default_timezone_set(self::TARGET);
        $value->timezone(self::TARGET);
        return $value;
    }
}

@GuidoHendriks
Copy link

Both examples work to get it from a known source to a known target. In this case I won't know what timezone the user will have set on his computer.

@burzum
Copy link

burzum commented Dec 19, 2015

@GuidoHendriks you can read it from the browser and push it via AJAX into the users auth storage (most of the time session) and if it is present there write it via Configure to App.defaultTimezone. In your type class you can then check if it is set and use it instead self::SOURCE.

Another way would be to add a 2nd arg array $config = [] to the base classes Type constructor. This would allow us to configure the types. But that's a core change.

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