title: ExtJS: How to utilize UTC time in custom DateTimeField
body: <p>I am trying to convert UTC timestamps to UTC Dates, modify those dates, and return the UTC timestamp of the selected date/time. Since the <code>Ext.form.field.Date</code> and <code>Ext.form.field.Time</code> fields use local time internally, I have to add 5 hours to the time and make sure the date stays the same, to retrieve the timestamp, I have to reverse the process and get the ISO timestamp (without milliseconds).</p>
  
  <p>Everything seems to work OK, but when I select <code>2015-01-01T01:15:00Z</code> the date field is set to the following day (<code>01/02/2015</code>; See <em>Figure #1</em>). I am not sure where I am failing to convert the date correctly?</p>
  
  <hr>
  
  <p><img src="https://i.stack.imgur.com/jrXeJ.png" alt="Figure #1"></p>
  
  <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong><em>Figure #1:</strong> Date is set to next day.</em></p>
  
  <hr>
  
  <h2>Edit</h2>
  
  <p>After fiddling around a bit, It looks like I got the date correct, but now the time field complains (See <em>Figure #2</em>). Looks like the value that was set is removed from the time drop-down. This is extremely confusing.</p>
  
  <pre><code>setValue: function (value) {
      // ...
      if (value != null &amp;&amp; Ext.isDate(value)) {
          var timeDate = Ext.clone(value);
  
          timeDate.setFullYear(1970, 0, 1);
  
          me.lookupReference('dateField').setValue(
              Ext.Date.add(value, Ext.Date.MILLI, timeDate.getTime()));
  
          me.lookupReference('timeField').setValue(me.convertFromUTC(timeDate));
      }
      // ...
  }
  </code></pre>
  
  <hr>
  
  <p><img src="https://i.stack.imgur.com/Ixupx.png" alt="Figure #2"></p>
  
  <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<strong><em>Figure #2:</strong> Time field validation fails.</em></p>
  
  <hr>
  
  <p>You can access a demo of the following code at the following sites:</p>
  
  <ul>
  <li><a href="http://jsfiddle.net/MrPolywhirl/6y8htz42/" rel="nofollow noreferrer">JSFiddle</a></li>
  <li><a href="https://fiddle.sencha.com/#fiddle/h4f" rel="nofollow noreferrer">SenchaFiddle</a></li>
  </ul>
  
  <h1>DateTime.components.DateTimeField</h1>
  
  <pre class="lang-js prettyprint-override"><code>Ext.define('DateTime.components.DateTimeField', {
      extend: 'Ext.container.Container',
      mixins: ['Ext.form.field.Field'],
      alias: 'widget.datetimefield',
  
      config: {
          dateConfig: {},
          timeConfig: {},
          utcValue: true,
          hideDate: false
      },
  
      referenceHolder: true,
  
      layout: {
          type: 'hbox'
      },
  
      initComponent: function () {
          var me = this,
              today = new Date(),
              dateConfig = me.dateConfig,
              timeConfig = me.timeConfig;
  
          me.items = [Ext.apply({
              xtype: 'datefield',
              reference: 'dateField',
              fieldLabel: 'Date',
              value: today,
              ignoreOnSubmit: true,
              listeners: {
                  change: function (field, newValue, oldValue) {
                      me.fireEvent('dateFieldChange', field, newValue, oldValue);
                  }
              }
          }, dateConfig),
          Ext.apply({
              xtype: 'timefield',
              reference: 'timeField',
              format: 'H:i',
              value: '00:00',
              minValue: '00:00',
              maxValue: '24:00',
              increment: 15,
              padding: '0 0 0 10',
              width: 80,
              ignoreOnSubmit: true
          }, timeConfig)];
  
          me.callParent();
      },
  
      afterRender: function () {
          var me = this;
  
          if (me.hideData) {
              me.lookupReference('dateField').hide();
          }
  
          me.callParent();
      },
  
      getValue: function () {
          var me = this,
              dateValue = me.getDate(),
              timeValue = me.getTime();
  
          if (dateValue != null &amp;&amp; timeValue != null) {
              dateValue = Ext.Date.add(dateValue, Ext.Date.MILLI, timeValue);
          }
  
          return this.convertToUTC(dateValue);
      },
  
      setValue: function (value) {
          var me = this;
  
          if (value == null) {
              return;
          }
  
          if (Ext.isString(value)) {
              value = Ext.Date.parse(value, 'c');
          }
  
          // Debug
          console.log('Parsed Date: ' + value);
  
          if (value != null &amp;&amp; Ext.isDate(value)) {
              var timeDate = Ext.clone(value);
  
              timeDate.setFullYear(1970, 0, 1);
              timeDate = me.convertFromUTC(timeDate);
  
              me.lookupReference('dateField').setValue(
              Ext.Date.add(value, Ext.Date.MILLI, timeDate.getTime()));
  
              me.lookupReference('timeField').setValue(timeDate);
          }
      },
  
      getInputId: function () {
          return null;
      },
  
      getTime: function () {
          var me = this,
              timeValue = me.lookupReference('timeField').getValue();
  
          timeValue.setFullYear(1970, 0, 1);
          timeValue = this.convertToUTC(timeValue, this.getDate());
  
          return timeValue.getTime();
      },
  
      getDate: function () {
          return this.lookupReference('dateField').getValue();
      },
  
      setDate: function (value) {
          this.lookupReference('dateField').setValue(value);
      },
  
      convertToUTC: function (date, dateOffset) {
          if (dateOffset == null) {
              dateOffset = date;
          }
          if (this.utcValue) {
              return Ext.Date.subtract(date, Ext.Date.MINUTE, dateOffset.getTimezoneOffset());
          }
          return date;
      },
  
      convertFromUTC: function (date, dateOffset) {
          if (dateOffset == null) {
              dateOffset = date;
          }
          if (this.utcValue) {
              return Ext.Date.add(date, Ext.Date.MINUTE, dateOffset.getTimezoneOffset());
          }
          return date;
      }
  });
  </code></pre>
  
  <h1>Full Code</h1>
  
  <pre class="lang-js prettyprint-override"><code>//////////////////////////////////////////////////////////
  // Requires
  //////////////////////////////////////////////////////////
  Ext.require(['*']);
  
  //////////////////////////////////////////////////////////
  // Data
  //////////////////////////////////////////////////////////
  var timestampData = [
      ['2014-02-28T08:45:00Z'],
      ['2015-01-01T01:15:00Z'],
      ['2014-12-31T11:30:00Z']
  ];
  
  //////////////////////////////////////////////////////////
  // Models
  //////////////////////////////////////////////////////////
  Ext.define('DateTime.model.Timestamp', {
      extend: 'Ext.data.Model',
      fields: ['timestamp']
  });
  
  //////////////////////////////////////////////////////////
  // Stores
  //////////////////////////////////////////////////////////
  Ext.define('DateTime.store.Timestamp', {
      extend : 'Ext.data.ArrayStore',
      model: 'DateTime.model.Timestamp',
      autoLoad: true,
      autoSync: true,
      proxy: {
          type: 'memory'
      }
  });
  
  //////////////////////////////////////////////////////////
  // Mixins
  //////////////////////////////////////////////////////////
  Ext.define('DateTime.mixin.CommonUtils', {
      dateToISOString: function (date) {
          var pad = function (number) {
              return ('00' + number).slice(-2);
          };
          return date.getUTCFullYear() + '-'
              + pad(date.getUTCMonth() + 1) + '-'
              + pad(date.getUTCDate()) + 'T'
              + pad(date.getUTCHours()) + ':'
              + pad(date.getUTCMinutes()) + ':'
              + pad(date.getUTCSeconds()) + 'Z';
      }
  });
  
  //////////////////////////////////////////////////////////
  // Components
  //////////////////////////////////////////////////////////
  Ext.define('DateTime.components.DateTimeField', {
      extend: 'Ext.container.Container',
      mixins: ['Ext.form.field.Field'],
      alias: 'widget.datetimefield',
  
      config: {
          dateConfig: {},
          timeConfig: {},
          utcValue: true,
          hideDate: false
      },
  
      referenceHolder: true,
  
      layout: {
          type: 'hbox'
      },
  
      initComponent: function () {
          var me = this,
              today = new Date(),
              dateConfig = me.dateConfig,
              timeConfig = me.timeConfig;
  
          me.items = [Ext.apply({
              xtype: 'datefield',
              reference: 'dateField',
              fieldLabel: 'Date',
              value: today,
              ignoreOnSubmit: true,
              listeners: {
                  change: function (field, newValue, oldValue) {
                      me.fireEvent('dateFieldChange', field, newValue, oldValue);
                  }
              }
          }, dateConfig),
          Ext.apply({
              xtype: 'timefield',
              reference: 'timeField',
              format: 'H:i',
              value: '00:00',
              minValue: '00:00',
              maxValue: '24:00',
              increment: 15,
              padding: '0 0 0 10',
              width: 80,
              ignoreOnSubmit: true
          }, timeConfig)];
  
          me.callParent();
      },
  
      afterRender: function () {
          var me = this;
  
          if (me.hideData) {
              me.lookupReference('dateField').hide();
          }
  
          me.callParent();
      },
  
      getValue: function () {
          var me = this,
              dateValue = me.getDate(),
              timeValue = me.getTime();
  
          if (dateValue != null &amp;&amp; timeValue != null) {
              dateValue = Ext.Date.add(dateValue, Ext.Date.MILLI, timeValue);
          }
  
          return this.convertToUTC(dateValue);
      },
  
      setValue: function (value) {
          var me = this;
  
          if (value == null) {
              return;
          }
  
          if (Ext.isString(value)) {
              value = Ext.Date.parse(value, 'c');
          }
  
          // Debug
          console.log('Parsed Date: ' + value);
  
          if (value != null &amp;&amp; Ext.isDate(value)) {
              var timeDate = Ext.clone(value);
  
              timeDate.setFullYear(1970, 0, 1);
              timeDate = me.convertFromUTC(timeDate);
  
              me.lookupReference('dateField').setValue(
              Ext.Date.add(value, Ext.Date.MILLI, timeDate.getTime()));
  
              me.lookupReference('timeField').setValue(timeDate);
          }
      },
  
      getInputId: function () {
          return null;
      },
  
      getTime: function () {
          var me = this,
              timeValue = me.lookupReference('timeField').getValue();
  
          timeValue.setFullYear(1970, 0, 1);
          timeValue = this.convertToUTC(timeValue, this.getDate());
  
          return timeValue.getTime();
      },
  
      getDate: function () {
          return this.lookupReference('dateField').getValue();
      },
  
      setDate: function (value) {
          this.lookupReference('dateField').setValue(value);
      },
  
      convertToUTC: function (date, dateOffset) {
          if (dateOffset == null) {
              dateOffset = date;
          }
          if (this.utcValue) {
              return Ext.Date.subtract(date, Ext.Date.MINUTE, dateOffset.getTimezoneOffset());
          }
          return date;
      },
  
      convertFromUTC: function (date, dateOffset) {
          if (dateOffset == null) {
              dateOffset = date;
          }
          if (this.utcValue) {
              return Ext.Date.add(date, Ext.Date.MINUTE, dateOffset.getTimezoneOffset());
          }
          return date;
      }
  });
  
  //////////////////////////////////////////////////////////
  // Views
  //////////////////////////////////////////////////////////
  Ext.define('DateTime.view.MainView', {
      extend: 'Ext.panel.Panel',
      xtype: 'mainView',
      alias: 'widget.mainview',
      mixins: {
          utils: 'DateTime.mixin.CommonUtils'
      },
      title: 'Date-Time Field Example',
      referenceHolder: true,
      layout: {
          type: 'border',
          //padding: 5
      },
      initComponent: function () {
          var me = this;
  
          me.items = [{
              region: 'north',
              xtype: 'grid',
              itemId: 'timestampList',
              store: Ext.create('DateTime.store.Timestamp', {
                  data : timestampData
              }),
              cls: 'timestamp-list',
              multiSelect: false,
              hideHeaders : true,
              viewConfig: {
                  emptyText: 'No images to display'
              },
              columns: [{
                  dataIndex: 'timestamp',
                  text: 'Timestamp',
                  flex: 1
              }]
          }, {
              region: 'center',
              xtype: 'panel',
              layout: {
                  type: 'vbox'
              },
              bodyPadding: 8,
              items: [{
                  xtype: 'container',
                  layout: 'hbox',
                  margin: '8 0 0 0',
                  items: [{
                      xtype: 'textfield',
                      reference: 'txtTimestampIn',
                      fieldLabel: 'Timestamp In'
                  }, {
                      xtype: 'button',
                      itemId: 'btnSetTime',
                      text: 'Set Time',
                      margin: '0 0 0 12'
                  }]
              }, {
                  xtype: 'container',
                  layout: 'hbox',
                  margin: '8 0 0 0',
                  items: [{
                      xtype: 'datetimefield',
                      name: 'startDate',
                      itemId: 'startDate',
                      reference: 'startDate',
                      dateConfig: {
                          fieldLabel: 'Start'
                      },
                      listeners: {
                          afterrender: {
                              fn: me.dateTimeField_onComplete,
                              scope: me
                          }
                      }
                  }, {
                      xtype: 'button',
                      itemId: 'btnExportTime',
                      text: 'Get Time',
                      margin: '0 0 0 12'
                  }]
              }, {
                  xtype: 'textfield',
                  reference: 'txtTimestampOut',
                  fieldLabel: 'Timestamp Out',
                  margin: '8 0 0 0'
              }]
          }],
  
          me.callParent();
      },
  
      dateTimeField_onComplete: function (field, eOpts) {
          var me = this,
              timestamp = timestampData[0][0];
          field.setValue(timestamp);
      }
  });
  
  ////////////////////////////////////////////////////////////
  // Controllers
  ////////////////////////////////////////////////////////////
  Ext.define('DateTime.controller.MainController', {
      extend: 'Ext.app.Controller',
      views: ['DateTime.view.MainView'],
      mixins: {
          utils: 'DateTime.mixin.CommonUtils'
      },
  
      refs: [{
          ref: 'mainView',
          selector: 'mainView'
      }],
  
      init: function () {
          var me = this;
          me.control({
              '#startDate': {
                  dateFieldChange: me.handleDateChange
              },
              '#btnSetTime': {
                  click: me.handleSetTime
              },
              '#btnExportTime': {
                  click: me.handleExportTime
              },
              '#timestampList' : {
                  selectionchange: me.handleChangeTimestamp
              }
          });
      },
  
      handleDateChange: function (field, newValue, oldValue) {
          // Do nothing...
      },
  
      handleSetTime: function (button, e, eOpts) {
          var me = this,
              view = me.getMainView(),
              timestampIn = view.lookupReference('txtTimestampIn');
          view.lookupReference('startDate').setValue(timestampIn.getValue());
          me.handleExportTime();
      },
  
      handleExportTime: function (button, e, eOpts) {
          var me = this,
              toISOStr = me.mixins.utils.dateToISOString,
              view = me.getMainView(),
              timestampIn = view.lookupReference('txtTimestampOut');
          timestampIn.setValue(toISOStr(view.lookupReference('startDate').getValue()));
      },
  
      handleChangeTimestamp: function(grid, selected, eOpts) {
          var me = this,
              view = me.getMainView(),
              timestamp = selected[0].data.timestamp;
          view.lookupReference('txtTimestampIn').setValue(timestamp);        
          me.handleSetTime();
      }
  });
  
  //////////////////////////////////////////////////////////
  // Applications
  //////////////////////////////////////////////////////////
  Ext.define('DateTime.app.DateTimeApp', {
      extend: 'Ext.app.Application',
      name: 'DateTimeApp',
      controllers: ['DateTime.controller.MainController'],
      launch: function () {
          Ext.create('Ext.Viewport', {
              layout: 'fit',
              flex: 1,
              items: [{
                  xtype: 'mainview'
              }]
          });
      }
  });
  
  //////////////////////////////////////////////////////////
  // Startup
  //////////////////////////////////////////////////////////
  Ext.onReady(function () {
      Ext.application('DateTime.app.DateTimeApp');
  });
  </code></pre>