diff --git a/calendar/js/app.js b/calendar/js/app.js index 3c61281706..e2bb7749e6 100644 --- a/calendar/js/app.js +++ b/calendar/js/app.js @@ -119,7 +119,7 @@ app.classes.calendar = AppJS.extend( break; } }, - + /** * Observer method receives update notifications from all applications * @@ -155,7 +155,7 @@ app.classes.calendar = AppJS.extend( if (match[1]== _id) do_refresh = true; } }); - if (jQuery('div [id^="infolog'+_id+'"],div [id^="drag_infolog'+_id+'"]').length > 0) do_refresh = true; + if (jQuery('div [id^="infolog'+_id+'"],div [id^="drag_infolog'+_id+'"]').length > 0) do_refresh = true; switch (_type) { case 'add': @@ -175,9 +175,9 @@ app.classes.calendar = AppJS.extend( } } break; - } + } }, - + /** * Link hander for jDots template to just reload our iframe, instead of reloading whole admin app * @@ -602,7 +602,7 @@ app.classes.calendar = AppJS.extend( { //Get infologID if in case if it's an integrated infolog event var infolog_id = eventId.split('infolog')[1]; - + if (infolog_id) { // If it is an integrated infolog event we need to edit infolog entry @@ -720,7 +720,7 @@ app.classes.calendar = AppJS.extend( { this.egw.open_link(this.egw.link("/index.php",vars),'_blank','700x700'); }, - + /** * control delete_series popup visibility * @@ -755,7 +755,7 @@ app.classes.calendar = AppJS.extend( id: 'dialog[cancel]', image: 'cancel' } - + ]; var self = this; et2_dialog.show_dialog @@ -988,7 +988,7 @@ app.classes.calendar = AppJS.extend( { // nm action - show popup nm_open_popup(_action,_senders); - } + } return; } @@ -1244,7 +1244,6 @@ app.classes.calendar = AppJS.extend( var alarm_date = this.et2.getWidgetById('new_alarm[date]'); var alarm_options = _widget || this.et2.getWidgetById('new_alarm[options]'); var start = this.et2.getWidgetById('start'); - var date = 0; if (alarm_date && alarm_options && start) @@ -1260,7 +1259,8 @@ app.classes.calendar = AppJS.extend( var startDate = start.get_value(); if (startDate) { - date = startDate - parseInt(alarm_options.get_value()); + var date = new Date(startDate); + date.setTime(date.getTime() - 1000 * parseInt(alarm_options.get_value())); alarm_date.set_value(date); } } diff --git a/etemplate/inc/class.etemplate_widget_date.inc.php b/etemplate/inc/class.etemplate_widget_date.inc.php index 4fbfe57ef7..68ea86a57a 100644 --- a/etemplate/inc/class.etemplate_widget_date.inc.php +++ b/etemplate/inc/class.etemplate_widget_date.inc.php @@ -30,9 +30,10 @@ * * @todo validation of date-duration * - * @info beforeSendToClient is no longer neccessary, in order to handle date/time conversion, for this widget - * as we are handling both timestamp and string date/time formats on client side - * + * @info Communication between client and server is always done as a string in ISO8601/W3C + * format ("Y-m-d\TH:i:sP"). If the application specifies a different format + * for the field, the conversion is done as needed understand what the application + * sends, and to give the application what it wants when the form is submitted. */ class etemplate_widget_date extends etemplate_widget_transformer { @@ -48,9 +49,48 @@ class etemplate_widget_date extends etemplate_widget_transformer protected $legacy_options = 'dataformat,mode'; + /** + * Convert the provided date into the format needed for unambiguous communication + * with browsers (Javascript). We use W3C format to avoid timestamp issues. + */ + public function beforeSendToClient($cname) + { + if($this->type == 'date-houronly') + { + return parent::beforeSendToClient($cname); + } + + $form_name = self::form_name($cname, $this->id); + $value =& self::get_array(self::$request->content, $form_name, false, true); + + if($this->type != 'date-duration' && $value) + { + // string with formatting letters like for php's date() method + if ($this->attrs['dataformat'] && !is_numeric($value)) + { + $date = date_create_from_format($this->attrs['dataformat'], $value, new DateTimeZone('UTC')); + } + else + { + $date = new egw_time((int)$value, new DateTimeZone('UTC')); + } + if($date) + { + // Set timezone to UTC so javascript doesn't add/subtract anything + $date->setTimezone(new DateTimeZone('UTC')); + $value = $date->format(egw_time::W3C); + } + } + } + /** * Validate input * + * For dates (except duration), it is always a full timestamp in W3C format, + * which we then convert to the format the application is expecting. This can + * be either a unix timestamp, just a date, just time, or whatever is + * specified in the template. + * * @param string $cname current namespace * @param array $expand values for keys 'c', 'row', 'c_', 'row_', 'cont' * @param array $content @@ -65,7 +105,7 @@ class etemplate_widget_date extends etemplate_widget_transformer { $value = self::get_array($content, $form_name); $valid =& self::get_array($validated, $form_name, true); - + if ((string)$value === '' && $this->attrs['needed']) { self::set_validation_error($form_name,lang('Field must not be empty !!!')); @@ -78,12 +118,21 @@ class etemplate_widget_date extends etemplate_widget_transformer { $valid = (string)$value === '' ? '' : (int)$value; } + if($value) + { + $date = new egw_time($value); + } + if(!$value) + { + // Not null, blank + $value = ''; + } elseif (empty($this->attrs['dataformat'])) // integer timestamp { - $valid = (int)$value; + $valid = $date->format('ts'); } // string with formatting letters like for php's date() method - elseif (($valid = date($this->attrs['dataformat'], $value))) + elseif (($valid = $date->format($this->attrs['dataformat']))) { // Nothing to do here } @@ -92,6 +141,7 @@ class etemplate_widget_date extends etemplate_widget_transformer // this is not really a user error, but one of the clientside engine self::set_validation_error($form_name,lang("'%1' is not a valid date !!!", $value).' '.$this->dataformat); } + //error_log("$this : ($valid)" . egw_time::to($valid)); } } } diff --git a/etemplate/js/et2_widget_date.js b/etemplate/js/et2_widget_date.js index 6c83d61c42..284f4323c5 100644 --- a/etemplate/js/et2_widget_date.js +++ b/etemplate/js/et2_widget_date.js @@ -23,6 +23,9 @@ /** * Class which implements the "date" XET-Tag * + * Dates are passed to the server in ISO8601 format ("Y-m-d\TH:i:sP"), and data_format is + * handled server-side. + * * @augments et2_inputWidget */ var et2_date = et2_inputWidget.extend( @@ -35,7 +38,7 @@ var et2_date = et2_inputWidget.extend( "ignore": false }, "data_format": { - "ignore": false, + "ignore": true, "description": "Date/Time format. Can be set as an options to date widget", "default": '' } @@ -52,7 +55,7 @@ var et2_date = et2_inputWidget.extend( this._super.apply(this, arguments); this.date = new Date(); - this.date.setHours(0); + this.date.setUTCHours(0); this.date.setMinutes(0); this.date.setSeconds(0); this.input = null; @@ -121,6 +124,11 @@ var et2_date = et2_inputWidget.extend( return; } + // Check for full timestamp + if(typeof _value == 'string' && _value.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})[+-](\d{2})\:(\d{2})/)) + { + _value = new Date(_value); + } // Handle just time as a string in the form H:i if(typeof _value == 'string' && isNaN(_value)) { @@ -146,7 +154,8 @@ var et2_date = et2_inputWidget.extend( return; } this.set_validation_error(false); - this.date.setHours(parsed.hour); + // Avoid javascript timezone offset, hour is in 'user time' + this.date.setUTCHours(parsed.hour); this.date.setMinutes(parsed.minute); this.input_date.val(_value); if(old_value !== this.getValue()) @@ -157,7 +166,7 @@ var et2_date = et2_inputWidget.extend( this.change(this.input_date); } } - this._oldValue = _value; + this._oldValue = this.date.toJSON(); return; default: // Parse customfields's date with storage data_format to date object @@ -173,7 +182,6 @@ var et2_date = et2_inputWidget.extend( var DTformat = this.options.data_format.split(' '); var parsed = jQuery.datepicker.parseDateTime(this.egw().dateTimeFormat(DTformat[0]),this.egw().dateTimeFormat(DTformat[1]), _value); } - this.date = new Date(parsed); } else // Parse other date widgets date with timepicker date/time format to date onject { @@ -184,9 +192,13 @@ var et2_date = et2_inputWidget.extend( this.set_validation_error(this.egw().lang("%1' han an invalid format !!!",_value)); return; } - this.date = new Date(parsed); } - + // Update local variable, but remove the timezone offset that + // javascript adds when we parse + if(parsed) + { + this.date = new Date(parsed.valueOf() - parsed.getTimezoneOffset() * 60000); + } this.set_validation_error(false); } @@ -202,17 +214,21 @@ var et2_date = et2_inputWidget.extend( // Update input - popups do, but framework doesn't _value = ''; + // Add timezone offset back in, or formatDate will lose those hours + var formatDate = new Date(this.date.valueOf() + this.date.getTimezoneOffset() * 60 * 1000); if(this._type != 'date-timeonly') { - _value = jQuery.datepicker.formatDate(this.input_date.datepicker("option","dateFormat"),this.date); + _value = jQuery.datepicker.formatDate(this.input_date.datepicker("option","dateFormat"), + formatDate + ); } if(this._type != 'date') { if(this._type != 'date-timeonly') _value += ' '; _value += jQuery.datepicker.formatTime(this.input_date.datepicker("option","timeFormat"),{ - hour: this.date.getHours(), - minute: this.date.getMinutes(), + hour: formatDate.getHours(), + minute: formatDate.getMinutes(), seconds: 0, timezone: 0 }); @@ -240,11 +256,13 @@ var et2_date = et2_inputWidget.extend( } else if (this._type == 'date') { - this.date.setHours(12); + this.date.setUTCHours(0); + this.date.setUTCMinutes(0); } + // Convert to timestamp - no seconds this.date.setSeconds(0,0); - return Math.round(this.date.valueOf() / 1000); + return this.date.toJSON(); } }); et2_register_widget(et2_date, ["date", "date-time", "date-timeonly"]); @@ -643,6 +661,13 @@ var et2_date_ro = et2_valueWidget.extend([et2_IDetachedDOM], jQuery.datepicker.parseDateTime(this.egw().preference('dateformat'),this.egw().preference('timeformat') == '24' ? 'H:i' : 'g:i a', _value); var text = new Date(parsed); + + // Update local variable, but remove the timezone offset that javascript adds + if(parsed) + { + this.date = new Date(text.valueOf() - (text.getTimezoneOffset()*60*1000)); + } + // JS dates use milliseconds this.date.setTime(text.valueOf()); }