Browse files

MINOR Initial commit

  • Loading branch information...
0 parents commit 5d3e155d8a0c0bf649b92945a0d831617c394c94 @chillu chillu committed Sep 27, 2011
Showing with 422 additions and 0 deletions.
  1. +24 −0 LICENSE
  2. +34 −0 README.md
  3. +8 −0 _config.php
  4. +183 −0 code/TZDateTimeField.php
  5. +25 −0 code/TimezoneAwareSiteConfig.php
  6. +93 −0 code/TimezoneField.php
  7. +55 −0 javascript/TZDateTimeField.js
24 LICENSE
@@ -0,0 +1,24 @@
+* Copyright (c) 2011, Silverstripe Ltd.
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are met:
+* * Redistributions of source code must retain the above copyright
+* notice, this list of conditions and the following disclaimer.
+* * Redistributions in binary form must reproduce the above copyright
+* notice, this list of conditions and the following disclaimer in the
+* documentation and/or other materials provided with the distribution.
+* * Neither the name of the <organization> nor the
+* names of its contributors may be used to endorse or promote products
+* derived from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY Silverstripe Ltd. ``AS IS'' AND ANY
+* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED. IN NO EVENT SHALL Silverstripe Ltd. BE LIABLE FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 README.md
@@ -0,0 +1,34 @@
+# Timezone Awareness Module #
+
+## Overview ##
+
+The module extends the built-in `DatetimeField` form field with `TZDateTimeField`,
+which allows selection of date and time, with a timezone dropdown.
+This enables an automatic conversion between a user selected
+timezone and the server timezone when saving the field,
+but it also allows converting the date on the fly by
+changing to a different timezone.
+Internally, the date and time are always saved
+in the server timezone, so this is a purely presentational setting.
+
+The module also extends `SiteConfig` to enable default user timezones,
+which then get picked up by `TZDateTimeField`. This is particularly
+handy when using the "[subsites](silverstripe.org/subsites-module)" module
+where each subsite has its own `SiteConfig` record. If each subsite
+stands for a geographic region with authors located all over the world,
+these authors can have individual user timezones set.
+
+The `TZDateTimeField` is automatically picked up by the
+"[cmsworkflow](http://www.silverstripe.org/cms-workflow-module/)" module
+because it uses `Object::create('DatetimeField')` instead of `new DatetimeField()`.
+In other modules and custom code, you'll have to use the field directly,
+or replace any existing fields manually via `FieldSet->replaceField()`.
+
+## Requirements ##
+
+ * SilverStripe 2.5
+
+## Maintainers ##
+
+ * Tom Rix (@rixth)
+ * Ingo Schommer (@chillu)
8 _config.php
@@ -0,0 +1,8 @@
+<?php
+
+Director::addRules(10, array(
+ 'TZDateTimeField/convert' => 'TZDateTimeField_Controller'
+));
+
+Object::add_extension('SiteConfig', 'TimezoneAwareSiteConfig');
+Object::useCustomClass('DatetimeField', 'TZDateTimeField');
183 code/TZDateTimeField.php
@@ -0,0 +1,183 @@
+<?php
+/**
+ * Extends the existing {@link DatetimeField} with a
+ * with the option to restrict available timezones.
+ */
+class TZDateTimeField extends DatetimeField {
+
+ /**
+ * @var array See {@link getTimezones()}
+ */
+ protected $timezones = null;
+
+ /**
+ * @var array See {@link getTimezones()}
+ */
+ static $default_timezones = array(
+ 'GMT+1200' => 'Dateline - International Date Line West',
+ 'Pacific/Apia' => 'Samoa - Midway Island, Samoa',
+ 'Pacific/Honolulu' => 'Hawaiian - Hawaii',
+ 'America/Anchorage' => 'Alaskan - Alaska',
+ 'America/Los_Angeles' => 'Pacific - Pacific Time (US & Canada); Tijuana',
+ 'America/Phoenix' => 'US Mountain - Arizona',
+ 'America/Denver' => 'Mountain - Mountain Time (US & Canada)',
+ 'America/Chihuahua' => 'Mexico Standard Time 2 - Chihuahua, La Paz, Mazatlan',
+ 'America/Managua' => 'Central America - Central America',
+ 'America/Regina' => 'Canada Central - Saskatchewan',
+ 'America/Mexico_City' => 'Mexico - Guadalajara, Mexico City, Monterrey',
+ 'America/Chicago' => 'Central - Central Time (US & Canada)',
+ 'America/Indianapolis' => 'US Eastern - Indiana (East)',
+ 'America/Bogota' => 'SA Pacific - Bogota, Lima, Quito',
+ 'America/New_York' => 'Eastern - Eastern Time (US & Canada)',
+ 'America/Caracas' => 'SA Western - Caracas, La Paz',
+ 'America/Santiago' => 'Pacific SA - Santiago',
+ 'America/Halifax' => 'Atlantic - Atlantic Time (Canada)',
+ 'America/St_Johns' => 'Newfoundland - Newfoundland',
+ 'America/Buenos_Aires' => 'SA Eastern - Buenos Aires, Georgetown',
+ 'America/Godthab' => 'Greenland - Greenland',
+ 'America/Sao_Paulo' => 'E. South America - Brasilia',
+ 'America/Noronha' => 'Mid-Atlantic - Mid-Atlantic',
+ 'Atlantic/Cape_Verde' => 'Cape Verde - Cape Verde Is.',
+ 'Atlantic/Azores' => 'Azores - Azores',
+ 'Africa/Casablanca' => 'Greenwich - Casablanca, Monrovia',
+ 'Europe/London' => 'GMT - Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London',
+ 'Africa/Lagos' => 'W. Central Africa - West Central Africa',
+ 'Europe/Berlin' => 'W. Europe - Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna',
+ 'Europe/Paris' => 'Romance - Brussels, Copenhagen, Madrid, Paris',
+ 'Europe/Sarajevo' => 'Central European - Sarajevo, Skopje, Warsaw, Zagreb',
+ 'Europe/Belgrade' => 'Central Europe - Belgrade, Bratislava, Budapest, Ljubljana, Prague',
+ 'Africa/Johannesburg' => 'South Africa - Harare, Pretoria',
+ 'Asia/Jerusalem' => 'Israel - Jerusalem',
+ 'Europe/Istanbul' => 'GTB - Athens, Istanbul, Minsk',
+ 'Europe/Helsinki' => 'FLE - Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius',
+ 'Africa/Cairo' => 'Egypt - Cairo',
+ 'Europe/Bucharest' => 'E. Europe - Bucharest',
+ 'Africa/Nairobi' => 'E. Africa - Nairobi',
+ 'Asia/Riyadh' => 'Arab - Kuwait, Riyadh',
+ 'Europe/Moscow' => 'Russian - Moscow, St. Petersburg, Volgograd',
+ 'Asia/Baghdad' => 'Arabic - Baghdad',
+ 'Asia/Tehran' => 'Iran - Tehran',
+ 'Asia/Muscat' => 'Arabian - Abu Dhabi, Muscat',
+ 'Asia/Tbilisi' => 'Caucasus - Baku, Tbilisi, Yerevan',
+ 'Asia/Kabul' => 'Afghanistan - Kabul',
+ 'Asia/Karachi' => 'West Asia - Islamabad, Karachi, Tashkent',
+ 'Asia/Yekaterinburg' => 'Ekaterinburg - Ekaterinburg',
+ 'Asia/Calcutta' => 'India - Chennai, Kolkata, Mumbai, New Delhi',
+ 'Asia/Katmandu' => 'Nepal - Kathmandu',
+ 'Asia/Colombo' => 'Sri Lanka - Sri Jayawardenepura',
+ 'Asia/Dhaka' => 'Central Asia - Astana, Dhaka',
+ 'Asia/Novosibirsk' => 'N. Central Asia - Almaty, Novosibirsk',
+ 'Asia/Rangoon' => 'Myanmar - Rangoon',
+ 'Asia/Bangkok' => 'SE Asia - Bangkok, Hanoi, Jakarta',
+ 'Asia/Krasnoyarsk' => 'North Asia - Krasnoyarsk',
+ 'Australia/Perth' => 'W. Australia - Perth',
+ 'Asia/Taipei' => 'Taipei - Taipei',
+ 'Asia/Singapore' => 'Singapore - Kuala Lumpur, Singapore',
+ 'Asia/Hong_Kong' => 'China - Beijing, Chongqing, Hong Kong, Urumqi',
+ 'Asia/Irkutsk' => 'North Asia East - Irkutsk, Ulaan Bataar',
+ 'Asia/Tokyo' => 'Tokyo - Osaka, Sapporo, Tokyo',
+ 'Asia/Seoul' => 'Korea - Seoul',
+ 'Asia/Yakutsk' => 'Yakutsk - Yakutsk',
+ 'Australia/Darwin' => 'AUS Central - Darwin',
+ 'Australia/Adelaide' => 'Cen. Australia - Adelaide',
+ 'Pacific/Guam' => 'West Pacific - Guam, Port Moresby',
+ 'Australia/Brisbane' => 'E. Australia - Brisbane',
+ 'Asia/Vladivostok' => 'Vladivostok - Vladivostok',
+ 'Australia/Hobart' => 'Tasmania - Hobart',
+ 'Australia/Sydney' => 'AUS Eastern - Canberra, Melbourne, Sydney',
+ 'Asia/Magadan' => 'Central Pacific - Magadan, Solomon Is., New Caledonia',
+ 'Pacific/Fiji' => 'Fiji - Fiji, Kamchatka, Marshall Is.',
+ 'Pacific/Auckland' => 'New Zealand - Auckland, Wellington',
+ 'Pacific/Tongatapu' => 'Tonga - Nuku\'alofa',
+ );
+
+ function __construct($name, $title = null, $value = "") {
+ parent::__construct($name, $title, $value);
+
+ $this->timezoneField = new DropdownField($this->Name() . '[timezone]', false, $this->getTimezones());
+ $this->timezoneField->setEmptyString(_t('TzDatetimeField.EMPTYSTRING', 'Please select a timezone'));
+
+ // Default to system timezone, which might be overwritten later
+ $this->setConfig('usertimezone', date_default_timezone_get());
+
+ // TODO Remove hardcoded date format assumptions.
+ // For now, we override field, system or user defaults for anything other than en_NZ
+ // and its date/time formats
+ $this->dateField->setConfig('dateformat', 'dd/MM/YYYY');
+ $this->dateField->setConfig('timeformat', 'hh:mma');
+ }
+
+ function Type() {
+ return parent::Type() . ' datetime';
+ }
+
+ /**
+ * Map of PHP timezone identifiers to their labels.
+ * Defaults to {@link $default_timezones} if none are set for the instance.
+ *
+ * @return array
+ * @see http://php.net/manual/en/timezones.php
+ */
+ function getTimezones() {
+ return ($this->timezones) ? $this->timezones : self::$default_timezones;
+ }
+
+ /**
+ * @var array See {@link getTimezones()}
+ */
+ function setTimezones($zones) {
+ $this->timezones = $zones;
+ $this->timezoneField->setSource($zones);
+ }
+
+ function FieldHolder() {
+ // TODO HTML5 data attributes would be better, but hard to add in the current framework
+ $this->addExtraClass(Convert::raw2att(Convert::raw2json(array(
+ // 'convertURL' => $this->Link('doConvert')
+ 'convertURL' => 'TZDateTimeField_Controller/convert'
+ ))));
+
+ return parent::FieldHolder();
+ }
+
+ function Field() {
+ $html = parent::Field();
+
+ Requirements::javascript(THIRDPARTY_DIR . '/prototype/prototype.js');
+ Requirements::javascript(THIRDPARTY_DIR . '/behaviour/behaviour.js');
+ Requirements::javascript(THIRDPARTY_DIR . '/jquery-metadata/jquery.metadata.js');
+ Requirements::javascript('timezoneawareness/javascript/TZDateTimeField.js');
+
+ return $html;
+ }
+
+ static function convert($fromTz, $toTz, $datetime, $format = 'r') {
+ $oldTz = date_default_timezone_get();
+
+ date_default_timezone_set($fromTz);
+ $time = strtotime($datetime);
+ date_default_timezone_set($toTz);
+ $return = date($format, $time);
+
+ date_default_timezone_set($oldTz);
+
+ return $return;
+ }
+}
+
+class TZDateTimeField_Controller extends Controller {
+ function convert() {
+ $fromTz = urldecode($_REQUEST['fromTz']);
+ $toTz = urldecode($_REQUEST['toTz']);
+ $datetime = $_GET['datetime'];
+
+ $return = array(
+ 'rfc' => TZDateTimeField::convert($fromTz, $toTz, $datetime, 'r'),
+ 'ymd12hr' => TZDateTimeField::convert($fromTz, $toTz, $datetime, 'Y-m-d h:i:s a'),
+ 'ymd24hr' => TZDateTimeField::convert($fromTz, $toTz, $datetime, 'Y-m-d H:i:s'),
+ 'popupdatetime' => TZDateTimeField::convert($fromTz, $toTz, $datetime, 'd/m/Y h:ia'),
+ );
+
+ echo Convert::raw2json($return);
+ }
+}
25 code/TimezoneAwareSiteConfig.php
@@ -0,0 +1,25 @@
+<?php
+
+class TimezoneAwareSiteConfig extends DataObjectDecorator {
+ public function extraStatics() {
+ return array(
+ 'db' => array(
+ "Timezone" =>"Varchar(60)",
+ ),
+ 'defaults' => array(
+ "Timezone" => "Pacific/Auckland",
+ )
+ );
+ }
+
+ function updateCMSFields(&$fields) {
+ $fields->addFieldsToTab("Root.Main", array(
+ $timezoneField = new TimezoneField("Timezone", _t('SiteConfig.TIMEZONE', "Timezone"))
+ ));
+
+ if (!Permission::check('EDIT_SITECONFIG')) {
+ $fields->makeFieldReadonly($timezoneField);
+ }
+ }
+
+}
93 code/TimezoneField.php
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * Class populated with a list of timezones. We are using the
+ * list that is similar to that of Windows, so it should over
+ * all our bases.
+ *
+ * @package default
+ * @author Tom Rix
+ */
+class TimezoneField extends DropdownField {
+ static $timezone_list = array(
+ 'GMT+1200' => 'Dateline - International Date Line West',
+ 'Pacific/Apia' => 'Samoa - Midway Island, Samoa',
+ 'Pacific/Honolulu' => 'Hawaiian - Hawaii',
+ 'America/Anchorage' => 'Alaskan - Alaska',
+ 'America/Los_Angeles' => 'Pacific - Pacific Time (US & Canada); Tijuana',
+ 'America/Phoenix' => 'US Mountain - Arizona',
+ 'America/Denver' => 'Mountain - Mountain Time (US & Canada)',
+ 'America/Chihuahua' => 'Mexico Standard Time 2 - Chihuahua, La Paz, Mazatlan',
+ 'America/Managua' => 'Central America - Central America',
+ 'America/Regina' => 'Canada Central - Saskatchewan',
+ 'America/Mexico_City' => 'Mexico - Guadalajara, Mexico City, Monterrey',
+ 'America/Chicago' => 'Central - Central Time (US & Canada)',
+ 'America/Indianapolis' => 'US Eastern - Indiana (East)',
+ 'America/Bogota' => 'SA Pacific - Bogota, Lima, Quito',
+ 'America/New_York' => 'Eastern - Eastern Time (US & Canada)',
+ 'America/Caracas' => 'SA Western - Caracas, La Paz',
+ 'America/Santiago' => 'Pacific SA - Santiago',
+ 'America/Halifax' => 'Atlantic - Atlantic Time (Canada)',
+ 'America/St_Johns' => 'Newfoundland - Newfoundland',
+ 'America/Buenos_Aires' => 'SA Eastern - Buenos Aires, Georgetown',
+ 'America/Godthab' => 'Greenland - Greenland',
+ 'America/Sao_Paulo' => 'E. South America - Brasilia',
+ 'America/Noronha' => 'Mid-Atlantic - Mid-Atlantic',
+ 'Atlantic/Cape_Verde' => 'Cape Verde - Cape Verde Is.',
+ 'Atlantic/Azores' => 'Azores - Azores',
+ 'Africa/Casablanca' => 'Greenwich - Casablanca, Monrovia',
+ 'Europe/London' => 'GMT - Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London',
+ 'Africa/Lagos' => 'W. Central Africa - West Central Africa',
+ 'Europe/Berlin' => 'W. Europe - Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna',
+ 'Europe/Paris' => 'Romance - Brussels, Copenhagen, Madrid, Paris',
+ 'Europe/Sarajevo' => 'Central European - Sarajevo, Skopje, Warsaw, Zagreb',
+ 'Europe/Belgrade' => 'Central Europe - Belgrade, Bratislava, Budapest, Ljubljana, Prague',
+ 'Africa/Johannesburg' => 'South Africa - Harare, Pretoria',
+ 'Asia/Jerusalem' => 'Israel - Jerusalem',
+ 'Europe/Istanbul' => 'GTB - Athens, Istanbul, Minsk',
+ 'Europe/Helsinki' => 'FLE - Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius',
+ 'Africa/Cairo' => 'Egypt - Cairo',
+ 'Europe/Bucharest' => 'E. Europe - Bucharest',
+ 'Africa/Nairobi' => 'E. Africa - Nairobi',
+ 'Asia/Riyadh' => 'Arab - Kuwait, Riyadh',
+ 'Europe/Moscow' => 'Russian - Moscow, St. Petersburg, Volgograd',
+ 'Asia/Baghdad' => 'Arabic - Baghdad',
+ 'Asia/Tehran' => 'Iran - Tehran',
+ 'Asia/Muscat' => 'Arabian - Abu Dhabi, Muscat',
+ 'Asia/Tbilisi' => 'Caucasus - Baku, Tbilisi, Yerevan',
+ 'Asia/Kabul' => 'Afghanistan - Kabul',
+ 'Asia/Karachi' => 'West Asia - Islamabad, Karachi, Tashkent',
+ 'Asia/Yekaterinburg' => 'Ekaterinburg - Ekaterinburg',
+ 'Asia/Calcutta' => 'India - Chennai, Kolkata, Mumbai, New Delhi',
+ 'Asia/Katmandu' => 'Nepal - Kathmandu',
+ 'Asia/Colombo' => 'Sri Lanka - Sri Jayawardenepura',
+ 'Asia/Dhaka' => 'Central Asia - Astana, Dhaka',
+ 'Asia/Novosibirsk' => 'N. Central Asia - Almaty, Novosibirsk',
+ 'Asia/Rangoon' => 'Myanmar - Rangoon',
+ 'Asia/Bangkok' => 'SE Asia - Bangkok, Hanoi, Jakarta',
+ 'Asia/Krasnoyarsk' => 'North Asia - Krasnoyarsk',
+ 'Australia/Perth' => 'W. Australia - Perth',
+ 'Asia/Taipei' => 'Taipei - Taipei',
+ 'Asia/Singapore' => 'Singapore - Kuala Lumpur, Singapore',
+ 'Asia/Hong_Kong' => 'China - Beijing, Chongqing, Hong Kong, Urumqi',
+ 'Asia/Irkutsk' => 'North Asia East - Irkutsk, Ulaan Bataar',
+ 'Asia/Tokyo' => 'Tokyo - Osaka, Sapporo, Tokyo',
+ 'Asia/Seoul' => 'Korea - Seoul',
+ 'Asia/Yakutsk' => 'Yakutsk - Yakutsk',
+ 'Australia/Darwin' => 'AUS Central - Darwin',
+ 'Australia/Adelaide' => 'Cen. Australia - Adelaide',
+ 'Pacific/Guam' => 'West Pacific - Guam, Port Moresby',
+ 'Australia/Brisbane' => 'E. Australia - Brisbane',
+ 'Asia/Vladivostok' => 'Vladivostok - Vladivostok',
+ 'Australia/Hobart' => 'Tasmania - Hobart',
+ 'Australia/Sydney' => 'AUS Eastern - Canberra, Melbourne, Sydney',
+ 'Asia/Magadan' => 'Central Pacific - Magadan, Solomon Is., New Caledonia',
+ 'Pacific/Fiji' => 'Fiji - Fiji, Kamchatka, Marshall Is.',
+ 'Pacific/Auckland' => 'New Zealand - Auckland, Wellington',
+ 'Pacific/Tongatapu' => 'Tonga - Nuku\'alofa',
+ );
+
+ function __construct($name, $title = null, $value = "", $form = null) {
+ parent::__construct($name, $title, self::$timezone_list, $value, $form, _t(__CLASS__.'.EMPTYSTRING', 'Please select a timezone'));
+ }
+}
55 javascript/TZDateTimeField.js
@@ -0,0 +1,55 @@
+TZDateTimeField = {
+ data: null,
+
+ initialise : function () {
+ this.timezoneSelector = jQuery(this).find(':input[name$="\[timezone\]"]')[0];
+ this.dateInput = jQuery(this).find(':input[name$="\[date\]"]')[0];
+ this.timeInput = jQuery(this).find(':input[name$="\[time\]"]')[0];
+ this.pastValue = this.timezoneSelector.options[this.timezoneSelector.selectedIndex].value;
+ this.timezoneSelector.onchange = this.convert.bind(this);
+ },
+
+ convert: function() {
+ if (!this.dateInput.value) return false;
+
+ var self = this;
+
+ // TODO Support more than NZ date formats
+ var dateUser = this.dateInput.value.split('/');
+ var dateISO = dateUser[2]+'-'+dateUser[1]+'-'+dateUser[0];
+
+ var metadata = jQuery(this).metadata({type: 'class'});
+ var url = metadata.convertURL;
+ url += '?' + jQuery.param({
+ fromTz: this.pastValue,
+ toTz: jQuery(this.timezoneSelector).val(),
+ datetime: dateISO + ' ' + this.timeInput.value
+ });
+
+ jQuery(this.timezoneSelector).attr('disabled', 'disabled');
+ jQuery(this.timeInput).attr('disabled', 'disabled');
+ jQuery(this.dateInput).attr('disabled', 'disabled');
+ jQuery.ajax({
+ url: url,
+ dataType: 'json',
+ success: function(data) {
+ // TODO Fixed date format assumptions
+ jQuery(self.dateInput).val(data.popupdatetime.split(' ')[0]);
+ jQuery(self.timeInput).val(data.popupdatetime.split(' ')[1]);
+ },
+ complete: function() {
+ jQuery(self.timezoneSelector).removeAttr('disabled');
+ jQuery(self.timeInput).removeAttr('disabled');
+ jQuery(self.dateInput).removeAttr('disabled');
+ }
+ });
+
+ this.pastValue = jQuery(this.timezoneSelector).val();
+
+ return false;
+ }
+};
+
+Behaviour.register({
+ 'div.tzdatetime' : TZDateTimeField
+});

0 comments on commit 5d3e155

Please sign in to comment.