Fixes issue 20684: Time fields are passed in UTC format
authorAugusto Mauch <augusto.mauch@openbravo.com>
Tue, 12 Jun 2012 11:24:28 +0200
changeset 16806 976c8398def2
parent 16805 a6672f4c6903
child 16807 53c53ee333bb
Fixes issue 20684: Time fields are passed in UTC format

The problem was due to:
- Time fields were not being passed between client and server in UTC format
- When constructing a date based on a time (i.e. 10:00:00), the date used was 1970-01-01. This way, it was not possible to convert from UTC to local hour, because the DST offset depends
on the date.

It has been fixed by:
- Sending the time fields in UTC format. Upon receiving a time field, converting it to local time.
- Upon receiving a time field, creating a new date using todays date instead of 1970-01-01
: Enter commit message. Lines beginning with 'HG:' are removed.
modules/org.openbravo.client.application/web/org.openbravo.client.application/js/form/formitem/ob-formitem-time.js
modules/org.openbravo.client.application/web/org.openbravo.client.application/js/form/ob-view-form.js
modules/org.openbravo.client.application/web/org.openbravo.client.application/js/grid/ob-view-grid.js
modules/org.openbravo.client.application/web/org.openbravo.client.application/js/main/ob-standard-view.js
modules/org.openbravo.client.kernel/src/org/openbravo/client/kernel/reference/TimeUIDefinition.java
modules/org.openbravo.service.json/src/org/openbravo/service/json/DataToJsonConverter.java
modules/org.openbravo.service.json/src/org/openbravo/service/json/JsonToDataConverter.java
modules/org.openbravo.service.json/src/org/openbravo/service/json/JsonUtils.java
--- a/modules/org.openbravo.client.application/web/org.openbravo.client.application/js/form/formitem/ob-formitem-time.js	Tue Jun 12 10:18:59 2012 +0200
+++ b/modules/org.openbravo.client.application/web/org.openbravo.client.application/js/form/formitem/ob-formitem-time.js	Tue Jun 12 11:24:28 2012 +0200
@@ -59,6 +59,27 @@
     if (isc.isA.String(value) && (value.contains('+') || value.contains('-'))) {
       value = isc.Time.parseInput(value, null, null, true);
     }
+	if (value && isc.isA.String(value)) {
+	  value = isc.Time.parseInput(value);
+	}
+	if (value && isc.isA.Date(value)) {
+	  this.setTodaysDate(value);	
+	} 
     return this.Super('setValue', arguments);
-  }
+  },
+  
+  getValue: function () {
+	var value = this.Super('getValue', arguments);
+	if (value && isc.isA.Date(value)) {
+		  this.setTodaysDate(value);	
+		} 
+	return value;
+  },
+  
+  setTodaysDate: function(date) {
+	  var today = new Date();
+	  date.setYear(today.getFullYear());
+	  date.setMonth(today.getMonth());
+	  date.setDate(today.getDate());
+  }  
 });
\ No newline at end of file
--- a/modules/org.openbravo.client.application/web/org.openbravo.client.application/js/form/ob-view-form.js	Tue Jun 12 10:18:59 2012 +0200
+++ b/modules/org.openbravo.client.application/web/org.openbravo.client.application/js/form/ob-view-form.js	Tue Jun 12 11:24:28 2012 +0200
@@ -182,10 +182,20 @@
     }
   },
 
-  editRecord: function (record, preventFocus, hasChanges, focusFieldName) {
+  editRecord: function (record, preventFocus, hasChanges, focusFieldName, isLocalTime) {
+    var timeFields, ret;
     this.clearValues();
+    // if editRecord is called from OBStandardView.editRecord, then the time fields have already
+    //   be converted from UTC to local time
+    // if editRecord is called from fetchDataReply (ActionMethod.js) then the record comes directly  
+    //   from the datasource, so it has to be converted from UTC to local time
+    // see issue https://issues.openbravo.com/view.php?id=20684
+    if (!isLocalTime) {
+      timeFields = this.getTimeFields();
+      this.convertUTCTimeToLocalTime(record, timeFields);
+    }
 
-    var ret = this.Super('editRecord', arguments);
+    ret = this.Super('editRecord', arguments);
 
     // used when clicking on a cell in a grid
     if (!preventFocus && focusFieldName) {
@@ -248,6 +258,38 @@
     }
   },
 
+  getTimeFields: function () {
+    var i, field, timeFields = [],
+        length = this.fields.length;
+    for (i = 0; i < length; i++) {
+      field = this.fields[i];
+      if (field.type === '_id_24') {
+        timeFields.push(field.name);
+      }
+    }
+    return timeFields;
+  },
+
+  convertUTCTimeToLocalTime: function (record, timeFields) {
+    var textField, fieldToDate, i, localHour
+    timeFieldsLength = timeFields.length,
+        UTCOffset = isc.Time.getUTCHoursDisplayOffset(new Date());
+    for (i = 0; i < timeFieldsLength; i++) {
+      textField = record[timeFields[i]];
+      if (textField && textField.length > 0) {
+        fieldToDate = isc.Time.parseInput(textField);
+        localHour = fieldToDate.getHours() + UTCOffset;
+        if (localHour > 23) {
+          localHour = localHour - 24;
+        } else if (localHour < 0) {
+          localHour = localHour + 24;
+        }
+        // TODO: support time formats other than HH:mm:ss
+        record[timeFields[i]] = '' + localHour + ':' + fieldToDate.getMinutes() + ':' + fieldToDate.getSeconds();
+      }
+    }
+  },
+
   editNewRecord: function (preventFocus) {
     this.clearValues();
     var ret = this.Super('editNewRecord', arguments);
--- a/modules/org.openbravo.client.application/web/org.openbravo.client.application/js/grid/ob-view-grid.js	Tue Jun 12 10:18:59 2012 +0200
+++ b/modules/org.openbravo.client.application/web/org.openbravo.client.application/js/grid/ob-view-grid.js	Tue Jun 12 11:24:28 2012 +0200
@@ -198,10 +198,17 @@
     },
 
     transformData: function (newData, dsResponse) {
-      var i, length;
-
+      var i, length, timeFields;
+
+      // when the data is received from the datasource, time fields are formatted in UTC time. They have to be converted to local time
+      if (dsResponse && dsResponse.context && (dsResponse.context.operationType === 'fetch' || dsResponse.context.operationType === 'update' || dsResponse.context.operationType === 'add')) {
+        if (this.grid) {
+          timeFields = this.grid.getTimeFields();
+          this.grid.convertUTCTimeToLocalTime(newData, timeFields);
+        }
+      }
       // only do this stuff for fetch operations, in other cases strange things
-      // happen as update/delete operations do not return the totalRows parameter
+      // happen as update/delete operations do not return the totalRows parameter      
       if (dsResponse && dsResponse.context && dsResponse.context.operationType !== 'fetch') {
         return;
       }
@@ -228,6 +235,41 @@
     }
   },
 
+  // returns the name of all time fields (type = '_id_24')
+  getTimeFields: function () {
+    var i, field, timeFields = [],
+        length = this.completeFields.length;
+    for (i = 0; i < length; i++) {
+      field = this.completeFields[i];
+      if (field.type === '_id_24') {
+        timeFields.push(field.name);
+      }
+    }
+    return timeFields;
+  },
+
+  convertUTCTimeToLocalTime: function (newData, timeFields) {
+    var textField, fieldToDate, i, j, localHour
+    timeFieldsLength = timeFields.length,
+        newDataLength = newData.length,
+        UTCOffset = isc.Time.getUTCHoursDisplayOffset(new Date());
+    for (i = 0; i < timeFieldsLength; i++) {
+      for (j = 0; j < newDataLength; j++) {
+        textField = newData[j][timeFields[i]];
+        if (textField && textField.length > 0) {
+          fieldToDate = isc.Time.parseInput(textField);
+          localHour = fieldToDate.getHours() + UTCOffset;
+          if (localHour > 23) {
+            localHour = localHour - 24;
+          } else if (localHour < 0) {
+            localHour = localHour + 24;
+          }
+          newData[j][timeFields[i]] = '' + localHour + ':' + fieldToDate.getMinutes() + ':' + fieldToDate.getSeconds();
+        }
+      }
+    }
+  },
+
   initWidget: function () {
     var i, vwState;
 
--- a/modules/org.openbravo.client.application/web/org.openbravo.client.application/js/main/ob-standard-view.js	Tue Jun 12 10:18:59 2012 +0200
+++ b/modules/org.openbravo.client.application/web/org.openbravo.client.application/js/main/ob-standard-view.js	Tue Jun 12 11:24:28 2012 +0200
@@ -1017,7 +1017,9 @@
   // Opens the edit form and selects the record in the grid, will refresh
   // child views also
   editRecord: function (record, preventFocus, focusFieldName) {
-
+    var rowNum,
+    // at this point the time fields of the record are formatted in local time
+    localTime = true;
     this.messageBar.hide();
 
     if (!this.isShowingForm) {
@@ -1033,8 +1035,8 @@
 
       // also handle the case that there are unsaved values in the grid
       // show them in the form
-      var rowNum = this.viewGrid.getRecordIndex(record);
-      this.viewForm.editRecord(this.viewGrid.getEditedRecord(rowNum), preventFocus, this.viewGrid.recordHasChanges(rowNum), focusFieldName);
+      rowNum = this.viewGrid.getRecordIndex(record);
+      this.viewForm.editRecord(this.viewGrid.getEditedRecord(rowNum), preventFocus, this.viewGrid.recordHasChanges(rowNum), focusFieldName, localTime);
     }
   },
 
@@ -1892,13 +1894,30 @@
   },
 
   convertContextValue: function (value, type) {
-    var isTime = isc.isA.Date(value) && type && isc.SimpleType.getType(type).inheritsFrom === 'time';
+    var isTime;
+    // if a string is received, it is converted to a date so that the function
+    //   is able to return its UTC time in the HH:mm:ss format
+    if (isc.isA.String(value) && value.length > 0 && type && isc.SimpleType.getType(type).inheritsFrom === 'time') {
+      value = this.convertToDate(value);
+    }
+    isTime = isc.isA.Date(value) && type && isc.SimpleType.getType(type).inheritsFrom === 'time';
     if (isTime) {
       return value.getUTCHours() + ':' + value.getUTCMinutes() + ':' + value.getUTCSeconds();
     }
     return value;
   },
 
+  convertToDate: function (stringValue) {
+    var today = new Date(),
+        dateValue = isc.Time.parseInput(stringValue);
+    // Only the time is relevant. In order to be able to convert it from UTC to local time
+    //   properly the date value should be today's date
+    dateValue.setYear(today.getFullYear());
+    dateValue.setMonth(today.getMonth());
+    dateValue.setDate(today.getDate());
+    return dateValue;
+  },
+
   getPropertyDefinition: function (property) {
     var properties = this.propertyToColumns,
         i, length = properties.length;
--- a/modules/org.openbravo.client.kernel/src/org/openbravo/client/kernel/reference/TimeUIDefinition.java	Tue Jun 12 10:18:59 2012 +0200
+++ b/modules/org.openbravo.client.kernel/src/org/openbravo/client/kernel/reference/TimeUIDefinition.java	Tue Jun 12 11:24:28 2012 +0200
@@ -18,10 +18,19 @@
  */
 package org.openbravo.client.kernel.reference;
 
+import java.text.FieldPosition;
+import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
 
+import org.codehaus.jettison.json.JSONException;
+import org.codehaus.jettison.json.JSONObject;
 import org.openbravo.base.exception.OBException;
 import org.openbravo.base.session.OBPropertiesProvider;
+import org.openbravo.client.kernel.RequestContext;
+import org.openbravo.data.Sqlc;
+import org.openbravo.model.ad.ui.Field;
 import org.openbravo.service.json.JsonUtils;
 
 /**
@@ -66,16 +75,75 @@
     return classicFormat;
   }
 
+  // getFieldProperties has to be overridden because depending on the value of getValueFromSession,
+  // time fields have to be converted from localTime to UTC before sending the to the client
   @Override
+  public String getFieldProperties(Field field, boolean getValueFromSession) {
+    String result = super.getFieldProperties(field, getValueFromSession);
+    try {
+      JSONObject jsnobject = new JSONObject(result);
+      if (getValueFromSession) {
+        RequestContext rq = RequestContext.get();
+        String columnValue = rq.getRequestParameter("inp"
+            + Sqlc.TransformaNombreColumna(field.getColumn().getDBColumnName()));
+        if (columnValue.isEmpty()) {
+          // If the date is empty, it does not have to be converted
+          return result;
+        }
+        // createFromClassicString has been called with an UTC date, it expects a date in local
+        // time,
+        // so the time is going to be converted to local time, and going to be passed to
+        // createFromClassicString
+        Date UTCDate = getClassicFormat().parse(columnValue);
+        Calendar now = Calendar.getInstance();
+
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(UTCDate);
+        calendar.set(Calendar.DATE, now.get(Calendar.DATE));
+        calendar.set(Calendar.MONTH, now.get(Calendar.MONTH));
+        calendar.set(Calendar.YEAR, now.get(Calendar.YEAR));
+
+        int gmtHourOffset = (now.get(Calendar.ZONE_OFFSET) + now.get(Calendar.DST_OFFSET))
+            / (1000 * 60 * 60);
+        calendar.add(Calendar.HOUR, gmtHourOffset);
+        StringBuffer localTimeColumnValue = getClassicFormat().format(calendar.getTime(),
+            new StringBuffer(), new FieldPosition(0));
+        jsnobject.put("value", createFromClassicString(localTimeColumnValue.toString()));
+        jsnobject.put("classicValue", localTimeColumnValue.toString());
+        return jsnobject.toString();
+      }
+    } catch (JSONException e) {
+      throw new OBException("Exception when parsing date ", e);
+    } catch (ParseException e) {
+      throw new OBException("Exception when parsing date ", e);
+    }
+    return result;
+  }
+
+  @Override
+  // Value is a date in local time format
   public synchronized Object createFromClassicString(String value) {
     try {
       if (value == null || value.length() == 0 || value.equals("null")) {
         return null;
       }
 
-      final java.util.Date date = getClassicFormat().parse(value);
+      final Date localDate = getClassicFormat().parse(value);
+      // If a date is not specified, 01-01-1970 will be set by default
+      // Today's date should be returned
+      Calendar now = Calendar.getInstance();
+      Calendar calendar = Calendar.getInstance();
+      calendar.setTime(localDate);
+      calendar.set(Calendar.DATE, now.get(Calendar.DATE));
+      calendar.set(Calendar.MONTH, now.get(Calendar.MONTH));
+      calendar.set(Calendar.YEAR, now.get(Calendar.YEAR));
 
-      return xmlTimeFormat.format(date);
+      // Applies the zone offset and the dst offset to convert the time from local to UTC
+      int gmtHourOffset = (now.get(Calendar.ZONE_OFFSET) + now.get(Calendar.DST_OFFSET))
+          / (1000 * 60 * 60);
+      calendar.add(Calendar.HOUR, -gmtHourOffset);
+
+      return xmlTimeFormat.format(calendar.getTime());
     } catch (Exception e) {
       throw new OBException("Exception when handling value " + value, e);
     }
--- a/modules/org.openbravo.service.json/src/org/openbravo/service/json/DataToJsonConverter.java	Tue Jun 12 10:18:59 2012 +0200
+++ b/modules/org.openbravo.service.json/src/org/openbravo/service/json/DataToJsonConverter.java	Tue Jun 12 11:24:28 2012 +0200
@@ -22,6 +22,7 @@
 import java.sql.Timestamp;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Calendar;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
@@ -63,6 +64,8 @@
   private final SimpleDateFormat xmlDateFormat = JsonUtils.createDateFormat();
   private final SimpleDateFormat xmlDateTimeFormat = JsonUtils.createDateTimeFormat();
   private final static SimpleDateFormat xmlTimeFormat = JsonUtils.createTimeFormat();
+  private final static SimpleDateFormat xmlTimeFormatWithoutMTOffset = JsonUtils
+      .createTimeFormatWithoutGMTOffset();
 
   // additional properties to return as a flat list
   private List<String> additionalProperties = new ArrayList<String>();
@@ -269,8 +272,11 @@
     final Class<?> clz = property.getPrimitiveObjectType();
     if (Date.class.isAssignableFrom(clz)) {
       if (property.getDomainType() instanceof TimestampDomainType) {
-        final String formattedValue = xmlTimeFormat.format(value);
-        return JsonUtils.convertToCorrectXSDFormat(formattedValue);
+
+        Timestamp localTime = (Timestamp) value;
+        Date UTCTime = convertToUTC(localTime);
+
+        return xmlTimeFormatWithoutMTOffset.format(UTCTime.getTime());
       } else if (property.isDatetime() || Timestamp.class.isAssignableFrom(clz)) {
         final String formattedValue = xmlDateTimeFormat.format(value);
         return JsonUtils.convertToCorrectXSDFormat(formattedValue);
@@ -287,6 +293,21 @@
     return value;
   }
 
+  private static Date convertToUTC(Date localTime) {
+    Calendar now = Calendar.getInstance();
+    Calendar calendar = Calendar.getInstance();
+    calendar.setTime(localTime);
+    calendar.set(Calendar.DATE, now.get(Calendar.DATE));
+    calendar.set(Calendar.MONTH, now.get(Calendar.MONTH));
+    calendar.set(Calendar.YEAR, now.get(Calendar.YEAR));
+
+    int gmtHourOffset = (now.get(Calendar.ZONE_OFFSET) + now.get(Calendar.DST_OFFSET))
+        / (1000 * 60 * 60);
+    calendar.add(Calendar.HOUR, -gmtHourOffset);
+
+    return calendar.getTime();
+  }
+
   protected Object convertPrimitiveValue(Object value) {
     if (value == null) {
       return null;
--- a/modules/org.openbravo.service.json/src/org/openbravo/service/json/JsonToDataConverter.java	Tue Jun 12 10:18:59 2012 +0200
+++ b/modules/org.openbravo.service.json/src/org/openbravo/service/json/JsonToDataConverter.java	Tue Jun 12 11:24:28 2012 +0200
@@ -161,7 +161,11 @@
               strValue = JsonUtils.convertFromXSDToJavaFormat(strValue);
             }
 
-            return new Timestamp(xmlTimeFormat.parse(strValue).getTime());
+            Date localTime1970 = new Timestamp(xmlTimeFormat.parse(strValue).getTime());
+
+            Date localTimeCurrentDate = convertToCurrentDate(localTime1970);
+
+            return new Timestamp(localTimeCurrentDate.getTime());
           } else if (property.isDatetime() || Timestamp.class.isAssignableFrom(clz)) {
             final String repairedString = JsonUtils.convertFromXSDToJavaFormat((String) value);
             return new Timestamp(xmlDateTimeFormat.parse(repairedString).getTime());
@@ -218,6 +222,20 @@
     }
   }
 
+  private static Date convertToCurrentDate(Date localTime1970) {
+    Calendar now = Calendar.getInstance();
+    Calendar calendar = Calendar.getInstance();
+    calendar.setTime(localTime1970);
+    calendar.set(Calendar.DATE, now.get(Calendar.DATE));
+    calendar.set(Calendar.MONTH, now.get(Calendar.MONTH));
+    calendar.set(Calendar.YEAR, now.get(Calendar.YEAR));
+
+    int dstOffset = now.get(Calendar.DST_OFFSET) / (1000 * 60 * 60);
+    calendar.add(Calendar.HOUR, dstOffset);
+
+    return calendar.getTime();
+  }
+
   private static boolean isEmptyOrNull(Object value) {
     if (JSONObject.NULL.equals(value)) { // note JSONObject.NULL.equals(null) == true
       return true;
--- a/modules/org.openbravo.service.json/src/org/openbravo/service/json/JsonUtils.java	Tue Jun 12 10:18:59 2012 +0200
+++ b/modules/org.openbravo.service.json/src/org/openbravo/service/json/JsonUtils.java	Tue Jun 12 11:24:28 2012 +0200
@@ -71,6 +71,16 @@
   }
 
   /**
+   * @return a new instance of the {@link SimpleDateFormat} using a format of HH:MM:SS. The date
+   *         format has lenient set to true.
+   */
+  public static SimpleDateFormat createTimeFormatWithoutGMTOffset() {
+    final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
+    dateFormat.setLenient(true);
+    return dateFormat;
+  }
+
+  /**
    * @return a new instance of the {@link SimpleDateFormat} using a format of yyyy-MM-dd'T'HH:mm:ss
    *         (see http://www.w3.org/TR/xmlschema-2/#dateTime). The date format has lenient set to
    *         true.