[libgda] Correctly handle timezone in time and timestamp values



commit 587152acfdaa20db8abbb9c36448fe1c0f0691ca
Author: Vivien Malerba <malerba gnome-db org>
Date:   Wed Aug 28 22:04:00 2013 +0200

    Correctly handle timezone in time and timestamp values
    
    most of the databases store time and timestamp in the GMT timezone,
    so the time and timestamp in other timezones need to be converted
    to GMT before being used

 doc/C/libgda-sections.txt                          |    2 +
 doc/C/limitations.xml                              |   34 +++
 libgda/gda-statement.c                             |   37 +++
 libgda/gda-statement.h                             |    4 +-
 libgda/gda-value.c                                 |  150 +++++++++-
 libgda/gda-value.h                                 |   39 ++--
 libgda/handlers/gda-handler-time.c                 |   40 ++-
 libgda/libgda.symbols                              |    2 +
 libgda/sqlite/gda-sqlite-provider.c                |   77 ++++--
 libgda/sqlite/gda-sqlite-recordset.c               |   10 +-
 providers/mysql/gda-mysql-provider.c               |   32 ++-
 providers/mysql/gda-mysql-recordset.c              |    4 +-
 providers/postgres/gda-postgres-provider.c         |   61 ++++-
 providers/postgres/gda-postgres-recordset.c        |   10 +-
 .../skel-implementation/capi/gda-capi-provider.c   |    5 +
 tests/.gitignore                                   |    1 +
 tests/Makefile.am                                  |   13 +-
 tests/data-models/check_pmodel.c                   |    8 +-
 tests/data-models/check_vcnc.c                     |    4 +-
 tests/providers/check_mysql.c                      |    3 +-
 tests/providers/check_postgres.c                   |    3 +-
 tests/providers/check_sqlite.c                     |    3 +-
 tests/providers/prov-test-common.c                 |  131 +++++++++-
 tests/providers/prov-test-common.h                 |   15 +-
 tests/providers/prov_dbstruct.xml                  |    5 +
 tests/test-sql-renderer.c                          |  291 ++++++++++++++++++++
 26 files changed, 876 insertions(+), 108 deletions(-)
---
diff --git a/doc/C/libgda-sections.txt b/doc/C/libgda-sections.txt
index 1d6acbe..f08cfc4 100644
--- a/doc/C/libgda-sections.txt
+++ b/doc/C/libgda-sections.txt
@@ -936,6 +936,7 @@ GdaTime
 gda_time_copy
 gda_time_free
 gda_time_valid
+gda_time_change_timezone
 gda_value_get_time
 gda_value_set_time
 <SUBSECTION>
@@ -943,6 +944,7 @@ GdaTimestamp
 gda_timestamp_copy
 gda_timestamp_free
 gda_timestamp_valid
+gda_timestamp_change_timezone
 gda_value_get_timestamp
 gda_value_set_timestamp
 gda_value_new_timestamp_from_timet
diff --git a/doc/C/limitations.xml b/doc/C/limitations.xml
index e755576..76270f0 100644
--- a/doc/C/limitations.xml
+++ b/doc/C/limitations.xml
@@ -54,6 +54,17 @@
       </para>
     </sect2>
 
+    <sect2>
+      <title>Timezone information</title>
+      <para>
+       Timezone information associated with time and timestamp data types is not stored by MySQL, so when 
executing
+       statements all the variables containing a time or timestamp information are converted to GMT 
(timezone 0) before
+       the execution happens. The consequence is that for example if a variable holds the "11:23:55+2" time 
(11 hours,
+       23 minutes and 55 seconds, at GMT + 2), then the actual time stored in the database will be 
"09:23:55", the same
+       time but GMT.
+      </para>
+    </sect2>
+
   </sect1>
 
   <sect1 id="limitations_oracle"><title>For Oracle</title>
@@ -113,6 +124,18 @@
        </itemizedlist>
       </para>
     </sect2>
+
+    <sect2>
+      <title>Timezone information</title>
+      <para>
+       Timezone information associated with time and timestamp data types is not stored by PostgreSQL, so 
when executing
+       statements all the variables containing a time or timestamp information are converted to GMT 
(timezone 0) before
+       the execution happens. The consequence is that for example if a variable holds the "11:23:55+2" time 
(11 hours,
+       23 minutes and 55 seconds, at GMT + 2), then the actual time stored in the database will be 
"09:23:55", the same
+       time but GMT.
+      </para>
+    </sect2>
+
   </sect1>
 
   <sect1 id="limitations_sqlite"><title>For SQLite</title>
@@ -133,6 +156,17 @@
       </para>
     </sect2>
 
+    <sect2>
+      <title>Timezone information</title>
+      <para>
+       Timezone information associated with time and timestamp data types is not stored by PostgreSQL, so 
when executing
+       statements all the variables containing a time or timestamp information are converted to GMT 
(timezone 0) before
+       the execution happens. The consequence is that for example if a variable holds the "11:23:55+2" time 
(11 hours,
+       23 minutes and 55 seconds, at GMT + 2), then the actual time stored in the database will be 
"09:23:55", the same
+       time but GMT.
+      </para>
+    </sect2>
+
     <sect2><title>Multi threaded environment</title>
       <para>
        No limitation if sqlite has been compiled with the SQLITE_THREADSAFE=1 flag (which is the case for the
diff --git a/libgda/gda-statement.c b/libgda/gda-statement.c
index c80fe08..08bea9b 100644
--- a/libgda/gda-statement.c
+++ b/libgda/gda-statement.c
@@ -796,6 +796,43 @@ default_render_value (const GValue *value, GdaSqlRenderingContext *context, GErr
                                return NULL;
                        }
                }
+               if (context->flags & GDA_STATEMENT_SQL_TIMEZONE_TO_GMT) {
+                       if (G_VALUE_TYPE (value) == GDA_TYPE_TIME) {
+                               GdaTime *nts;
+                               nts = (GdaTime*) gda_value_get_time (value);
+                               if (nts && (nts->timezone != GDA_TIMEZONE_INVALID)) {
+                                       nts = gda_time_copy (nts);
+                                       gda_time_change_timezone (nts, 0);
+                                       nts->timezone = GDA_TIMEZONE_INVALID;
+                                       GValue v = {0};
+                                       g_value_init (&v, GDA_TYPE_TIME);
+                                       gda_value_set_time (&v, nts);
+                                       gda_time_free (nts);
+                                       gchar *tmp;
+                                       tmp = gda_data_handler_get_sql_from_value (dh, &v);
+                                       g_value_reset (&v);
+                                       return tmp;
+                               }
+                       }
+                       else if (G_VALUE_TYPE (value) == GDA_TYPE_TIMESTAMP) {
+                               GdaTimestamp *nts;
+                               nts = (GdaTimestamp*) gda_value_get_timestamp (value);
+                               if (nts && (nts->timezone != GDA_TIMEZONE_INVALID)) {
+                                       nts = gda_timestamp_copy (nts);
+                                       gda_timestamp_change_timezone (nts, 0);
+                                       nts->timezone = GDA_TIMEZONE_INVALID;
+                                       GValue v = {0};
+                                       g_value_init (&v, GDA_TYPE_TIMESTAMP);
+                                       gda_value_set_timestamp (&v, nts);
+                                       gda_timestamp_free (nts);
+                                       gchar *tmp;
+                                       tmp = gda_data_handler_get_sql_from_value (dh, &v);
+                                       g_value_reset (&v);
+                                       return tmp;
+                               }
+                       }
+               }
+
                return gda_data_handler_get_sql_from_value (dh, value);
        }
        else
diff --git a/libgda/gda-statement.h b/libgda/gda-statement.h
index 6979592..9521177 100644
--- a/libgda/gda-statement.h
+++ b/libgda/gda-statement.h
@@ -86,6 +86,7 @@ typedef enum {
  * @GDA_STATEMENT_SQL_PARAMS_AS_DOLLAR: parameters will be rendered using the "$&lt;param_number&gt;" syntax 
where parameters are numbered starting from 1
  * @GDA_STATEMENT_SQL_PARAMS_AS_QMARK: parameters will be rendered using the "?&lt;param_number&gt;" syntax 
where parameters are numbered starting from 1
  * @GDA_STATEMENT_SQL_PARAMS_AS_UQMARK: parameters will be rendered using the "?" syntax
+ * @GDA_STATEMENT_SQL_TIMEZONE_TO_GMT: time and timestamp with a timezone information are converted to GMT 
before rendering, and rendering does not show the timezone information
  *
  * Specifies rendering options
  */
@@ -97,7 +98,8 @@ typedef enum {
         GDA_STATEMENT_SQL_PARAMS_AS_COLON    = 1 << 3,
         GDA_STATEMENT_SQL_PARAMS_AS_DOLLAR   = 1 << 4,
         GDA_STATEMENT_SQL_PARAMS_AS_QMARK    = 1 << 5,
-        GDA_STATEMENT_SQL_PARAMS_AS_UQMARK   = 1 << 6
+        GDA_STATEMENT_SQL_PARAMS_AS_UQMARK   = 1 << 6,
+        GDA_STATEMENT_SQL_TIMEZONE_TO_GMT    = 1 << 7
 } GdaStatementSqlFlag;
 
 /* struct for the object's data */
diff --git a/libgda/gda-value.c b/libgda/gda-value.c
index 658c9fc..93d47e2 100644
--- a/libgda/gda-value.c
+++ b/libgda/gda-value.c
@@ -1240,14 +1240,65 @@ gda_time_valid (const GdaTime *time)
 {
        g_return_val_if_fail (time, FALSE);
 
-       if (time->hour > 24 ||
-           time->minute > 59 ||
-           time->second > 59)
+       if ((time->hour > 23) ||
+           (time->minute > 59) ||
+           (time->second > 59))
                return FALSE;
-       else
-               return TRUE;
+       if ((time->fraction >= 1000000) ||
+           (time->timezone <= -12 * 3600) ||
+           (time->timezone >= 12 * 3600))
+               return FALSE;
+       return TRUE;
 }
 
+/**
+ * gda_time_change_timezone:
+ * @time: a valid #GdaTime
+ * @ntz: a new timezone to use, in seconds added to GMT
+ *
+ * Changes @time's timezone (for example to convert from GMT to another time zone).
+ * If @time's current timezone is unset (i.e. equal to %GDA_TIMEZONE_INVALID), then this function simply sets
+ * @time's timezone attribute; Otherwise, it adds or removes hours, minutes or seconds to reflect the time 
in the new timezone.
+ *
+ * Note: the resulting will always be a valid time.
+ *
+ * Since: 5.2
+ */
+void
+gda_time_change_timezone (GdaTime *time, glong ntz)
+{
+       g_return_if_fail (time);
+       g_return_if_fail (gda_time_valid (time));
+       g_return_if_fail ((ntz > - 12 * 3600) && (ntz < 12 * 3600));
+
+       if (time->timezone == ntz)
+               return;
+
+       if (time->timezone != GDA_TIMEZONE_INVALID) {
+               glong nsec;
+               nsec = time->hour * 3600 + time->minute * 60 + time->second - time->timezone + ntz;
+               if (nsec < 0)
+                       nsec += 86400;
+               else if (nsec >= 86400)
+                       nsec -= 86400;
+
+               /* hours */
+               gint n;
+               n = nsec / 3600;
+               time->hour = (gushort) n;
+
+               /* minutes */
+               nsec -= n * 3600;
+               n = nsec / 60;
+               time->minute = (gushort) n;
+
+               /* seconds */
+               nsec -= n * 60;
+               time->second = (gushort) nsec;
+       }
+
+       time->timezone = ntz;
+}
 
 /*
  * Register the GdaTimestamp type in the GType system
@@ -1366,12 +1417,79 @@ gda_timestamp_valid (const GdaTimestamp *timestamp)
                return FALSE;
 
        /* check the time part */
-       if (timestamp->hour > 24 ||
-           timestamp->minute > 59 ||
-           timestamp->second > 59)
+       if ((timestamp->hour > 23) ||
+           (timestamp->minute > 59) ||
+           (timestamp->second > 59))
                return FALSE;
-       else
-               return TRUE;
+       if ((timestamp->fraction >= 1000000) ||
+           (timestamp->timezone <= -12 * 3600) ||
+           (timestamp->timezone >= 12 * 3600))
+               return FALSE;
+
+       return TRUE;
+}
+
+/**
+ * gda_timestamp_change_timezone:
+ * @time: a valid #GdaTimestamp
+ * @ntz: a new timezone to use, in seconds added to GMT
+ *
+ * This function is similar to gda_time_change_timezone() but operates on time stamps.
+ *
+ * Note: the resulting will always be a valid time.
+ *
+ * Since: 5.2
+ */
+void
+gda_timestamp_change_timezone (GdaTimestamp *ts, glong ntz)
+{
+       g_return_if_fail (ts);
+       g_return_if_fail (gda_timestamp_valid (ts));
+       g_return_if_fail ((ntz > - 12 * 3600) && (ntz < 12 * 3600));
+
+       if (ts->timezone == ntz)
+               return;
+
+       if (ts->timezone != GDA_TIMEZONE_INVALID) {
+               glong nsec;
+               nsec = ts->hour * 3600 + ts->minute * 60 + ts->second - ts->timezone + ntz;
+               if (nsec < 0) {
+                       GDate *date;
+                       date = g_date_new_dmy ((GDateDay) ts->day, (GDateMonth) ts->month, (GDateYear) 
ts->year);
+                       g_date_subtract_days (date, 1);
+                       ts->year = g_date_get_year (date);
+                       ts->month = g_date_get_month (date);
+                       ts->day = g_date_get_day (date);
+                       g_date_free (date);
+                       nsec += 86400;
+               }
+               else if (nsec >= 86400) {
+                       GDate *date;
+                       date = g_date_new_dmy ((GDateDay) ts->day, (GDateMonth) ts->month, (GDateYear) 
ts->year);
+                       g_date_add_days (date, 1);
+                       ts->year = g_date_get_year (date);
+                       ts->month = g_date_get_month (date);
+                       ts->day = g_date_get_day (date);
+                       g_date_free (date);
+                       nsec -= 86400;
+               }
+
+               /* hours */
+               gint n;
+               n = nsec / 3600;
+               ts->hour = (gushort) n;
+
+               /* minutes */
+               nsec -= n * 3600;
+               n = nsec / 60;
+               ts->minute = (gushort) n;
+
+               /* seconds */
+               nsec -= n * 60;
+               ts->second = (gushort) nsec;
+       }
+
+       ts->timezone = ntz;
 }
 
 /**
@@ -1521,7 +1639,14 @@ gda_value_new_blob_from_file (const gchar *filename)
  * @val: value to set for the new #GValue.
  *
  * Makes a new #GValue of type #GDA_TYPE_TIMESTAMP with value @val
- * (of type time_t).
+ * (of type time_t). The returned timestamp has a timezone initialized with the
+ * current timezone, taking into account the daylight savings.
+ *
+ * For example, to get a time stamp representing the current date and time, use:
+ *
+ * <code>
+ * ts = gda_value_new_timestamp_from_timet (time (NULL));
+ * </code>
  *
  * Returns: (transfer full): the newly created #GValue.
  *
@@ -1537,6 +1662,7 @@ gda_value_new_timestamp_from_timet (time_t val)
         value = g_new0 (GValue, 1);
 #ifdef HAVE_LOCALTIME_R
        struct tm tmpstm;
+       tzset ();
        ltm = localtime_r ((const time_t *) &val, &tmpstm);
 #elif HAVE_LOCALTIME_S
        struct tm tmpstm;
@@ -1557,7 +1683,7 @@ gda_value_new_timestamp_from_timet (time_t val)
                 tstamp.minute = ltm->tm_min;
                 tstamp.second = ltm->tm_sec;
                 tstamp.fraction = 0;
-                tstamp.timezone = GDA_TIMEZONE_INVALID;
+                tstamp.timezone = - timezone + daylight * 3600;
                 gda_value_set_timestamp (value, (const GdaTimestamp *) &tstamp);
         }
 
diff --git a/libgda/gda-value.h b/libgda/gda-value.h
index 2d280fa..d84ed82 100644
--- a/libgda/gda-value.h
+++ b/libgda/gda-value.h
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2001 - 2003 Rodrigo Moya <rodrigo gnome-db org>
  * Copyright (C) 2002 - 2003 Gonzalo Paniagua Javier <gonzalo gnome-db org>
- * Copyright (C) 2002 - 2011 Vivien Malerba <malerba gnome-db org>
+ * Copyright (C) 2002 - 2013 Vivien Malerba <malerba gnome-db org>
  * Copyright (C) 2003 Akira TAGOH <tagoh gnome-db org>
  * Copyright (C) 2003 Danilo Schoeneberg <dschoene src gnome org>
  * Copyright (C) 2003 Laurent Sansonetti <laurent datarescue be>
@@ -91,30 +91,34 @@ struct _GdaNumeric {
 
 /**
  * GdaTime:
- * @hour: 
- * @minute: 
- * @second: 
- * @fraction: 
- * @timezone: 
+ * @hour: hour representation of the time, as a number between 0 and 23
+ * @minute: minute representation of the time, as a number between 0 and 59
+ * @second: second representation of the time, as a number between 0 and 59
+ * @fraction: fractionnal part of the seconds, in millionth' of second
+ * @timezone: number of seconds added to the GMT timezone
+ *
+ * Represents a time information.
  */
 typedef struct {
        gushort hour;
        gushort minute;
        gushort second;
        gulong  fraction;
-       glong   timezone;       /* # of seconds to the east UTC */
+       glong   timezone;
 } GdaTime;
 
 /**
  * GdaTimestamp:
- * @year: representation of the date
- * @month: month representation of the date, as a number between 1 and 12
- * @day: day representation of the date, as a number between 1 and 31
- * @hour: 
- * @minute: 
- * @second: 
- * @fraction: 
- * @timezone:
+ * @year: year representation of the time stamp
+ * @month: month representation of the time stamp, as a number between 1 and 12
+ * @day: day representation of the time stamp, as a number between 1 and 31
+ * @hour: hour representation of the time stamp, as a number between 0 and 23
+ * @minute: minute representation of the time stamp, as a number between 0 and 59
+ * @second: second representation of the time stamp, as a number between 0 and 59
+ * @fraction: fractionnal part of the seconds, in millionth' of second
+ * @timezone: number of seconds added to the GMT timezone
+ *
+ * Represents an instant (a time stamp)
  */
 typedef struct {
        gshort  year;
@@ -124,7 +128,7 @@ typedef struct {
        gushort minute;
        gushort second;
        gulong  fraction;
-       glong   timezone;       /* # of seconds to the east UTC */
+       glong   timezone;
 } GdaTimestamp;
 
 /**
@@ -271,12 +275,13 @@ GType                             gda_time_get_type (void) G_GNUC_CONST;
 gpointer                          gda_time_copy (gpointer boxed);
 void                              gda_time_free (gpointer boxed);
 gboolean                          gda_time_valid (const GdaTime *time);
-
+void                              gda_time_change_timezone (GdaTime *time, glong ntz);
 
 GType                             gda_timestamp_get_type (void) G_GNUC_CONST;
 gpointer                          gda_timestamp_copy (gpointer boxed);
 void                              gda_timestamp_free (gpointer boxed);
 gboolean                          gda_timestamp_valid (const GdaTimestamp *timestamp);
+void                              gda_timestamp_change_timezone (GdaTimestamp *ts, glong ntz);
 
 
 GType                             gda_geometricpoint_get_type (void) G_GNUC_CONST;
diff --git a/libgda/handlers/gda-handler-time.c b/libgda/handlers/gda-handler-time.c
index 085f397..ef4235e 100644
--- a/libgda/handlers/gda-handler-time.c
+++ b/libgda/handlers/gda-handler-time.c
@@ -453,12 +453,19 @@ gda_handler_time_get_no_locale_str_from_value (GdaHandlerTime *dh, const GValue
        }
        else if (type == GDA_TYPE_TIME) {
                const GdaTime *tim;
-               
+               GString *string;
+               string = g_string_new ("");
+               g_string_append_c (string, '\'');
                tim = gda_value_get_time ((GValue *) value);
-               retval = g_strdup_printf ("'%02d:%02d:%02d'",
-                                         tim->hour,
-                                         tim->minute,
-                                         tim->second);
+               g_string_append_printf (string, "%02d:%02d:%02d",
+                                       tim->hour,
+                                       tim->minute,
+                                       tim->second);
+               if (tim->timezone != GDA_TIMEZONE_INVALID)
+                       g_string_append_printf (string, "%+02d",
+                                               (int) tim->timezone / 3600);
+               g_string_append_c (string, '\'');
+               retval = g_string_free (string, FALSE);
        }
        else if (type == GDA_TYPE_TIMESTAMP) {
                const GdaTimestamp *gdats;
@@ -480,7 +487,7 @@ gda_handler_time_get_no_locale_str_from_value (GdaHandlerTime *dh, const GValue
                                g_string_append_printf (string, ".%lu", gdats->fraction);
                        
                        if (gdats->timezone != GDA_TIMEZONE_INVALID)
-                               g_string_append_printf (string, "%+02d", 
+                               g_string_append_printf (string, "%+02d",
                                                        (int) gdats->timezone / 3600);
                        
                        retval = g_strdup_printf ("%s %s", str, string->str);
@@ -621,12 +628,19 @@ gda_handler_time_get_sql_from_value (GdaDataHandler *iface, const GValue *value)
        }
        else if (type == GDA_TYPE_TIME) {
                const GdaTime *tim;
-
+               GString *string;
+               string = g_string_new ("");
                tim = gda_value_get_time ((GValue *) value);
-               retval = g_strdup_printf ("'%02d:%02d:%02d'",
-                                         tim->hour,
-                                         tim->minute,
-                                         tim->second);
+               g_string_append_c (string, '\'');
+               g_string_append_printf (string, "%02d:%02d:%02d",
+                                       tim->hour,
+                                       tim->minute,
+                                       tim->second);
+               if (tim->timezone != GDA_TIMEZONE_INVALID)
+                       g_string_append_printf (string, "%+02d",
+                                               (int) tim->timezone / 3600);
+               g_string_append_c (string, '\'');
+               retval = g_string_free (string, FALSE);
        }
        else if (type == GDA_TYPE_TIMESTAMP) {
                const GdaTimestamp *gdats;
@@ -648,7 +662,7 @@ gda_handler_time_get_sql_from_value (GdaDataHandler *iface, const GValue *value)
                                g_string_append_printf (string, ".%lu", gdats->fraction);
                        
                        if (gdats->timezone != GDA_TIMEZONE_INVALID)
-                               g_string_append_printf (string, "%+02d", 
+                               g_string_append_printf (string, "%+02d",
                                                        (int) gdats->timezone / 3600);
                        
                        retval = g_strdup_printf ("'%s %s'", str, string->str);
@@ -752,7 +766,7 @@ gda_handler_time_get_str_from_value (GdaDataHandler *iface, const GValue *value)
                                g_string_append_printf (string, ".%lu", gdats->fraction);
                        
                        if (gdats->timezone != GDA_TIMEZONE_INVALID)
-                               g_string_append_printf (string, "%+02d", 
+                               g_string_append_printf (string, "%+02d",
                                                        (int) gdats->timezone / 3600);
                        
                        retval = g_strdup_printf ("%s %s", str, string->str);
diff --git a/libgda/libgda.symbols b/libgda/libgda.symbols
index 1e476e9..e6bd926 100644
--- a/libgda/libgda.symbols
+++ b/libgda/libgda.symbols
@@ -886,10 +886,12 @@
        gda_thread_wrapper_steal_signal
        gda_thread_wrapper_unset_io_channel
        gda_time_copy
+       gda_time_change_timezone
        gda_time_free
        gda_time_get_type
        gda_time_valid
        gda_timestamp_copy
+       gda_timestamp_change_timezone
        gda_timestamp_free
        gda_timestamp_get_type
        gda_timestamp_valid
diff --git a/libgda/sqlite/gda-sqlite-provider.c b/libgda/sqlite/gda-sqlite-provider.c
index 3ea3d8f..01a8627 100644
--- a/libgda/sqlite/gda-sqlite-provider.c
+++ b/libgda/sqlite/gda-sqlite-provider.c
@@ -2972,13 +2972,14 @@ gda_sqlite_provider_statement_execute (GdaServerProvider *provider, GdaConnectio
        ps = (GdaSqlitePStmt *) gda_connection_get_prepared_statement (cnc, stmt);
        if (!ps) {
                if (!gda_sqlite_provider_statement_prepare (provider, cnc, stmt, NULL)) {
-                       /* try to use the SQL when parameters are rendered with their values */
+                       /* try to use the SQL when parameters are rendered with their values, using GMT for 
timezones  */
                        gchar *sql;
                        int status;
                        sqlite3_stmt *sqlite_stmt;
                        char *left;
 
-                       sql = gda_sqlite_provider_statement_to_sql (provider, cnc, stmt, params, 0, NULL, 
error);
+                       sql = gda_sqlite_provider_statement_to_sql (provider, cnc, stmt, params,
+                                                                   GDA_STATEMENT_SQL_TIMEZONE_TO_GMT, NULL, 
error);
                        if (!sql)
                                return NULL;
 
@@ -3288,17 +3289,31 @@ gda_sqlite_provider_statement_execute (GdaServerProvider *provider, GdaConnectio
                                                          bin->data, bin->binary_length, SQLITE_TRANSIENT);
                }
                else if (G_VALUE_TYPE (value) == GDA_TYPE_TIME) {
-                       gchar *str;
-                       const GdaTime *ts;
+                       GString *string;
+                       GdaTime *gtime;
+                       gboolean tofree = FALSE;
 
-                       ts = gda_value_get_time (value);
-                       if (ts->fraction != 0)
-                               str = g_strdup_printf ("%02d:%02d:%02d.%ld",
-                                                      ts->hour, ts->minute, ts->second, ts->fraction);
-                       else
-                               str = g_strdup_printf ("%02d:%02d:%02d",
-                                                      ts->hour, ts->minute, ts->second);
-                       SQLITE3_CALL (sqlite3_bind_text) (ps->sqlite_stmt, i, str, -1, g_free);
+                       gtime = (GdaTime *) gda_value_get_time (value);
+
+                       string = g_string_new ("");
+                       if (gtime->timezone != GDA_TIMEZONE_INVALID) {
+                               /* SQLite cant' store timezone information, so if timezone information is
+                                * provided, we do our best and convert it to GMT */
+                               gtime = gda_time_copy (gtime);
+                               tofree = TRUE;
+                               gda_time_change_timezone (gtime, 0);
+                       }
+
+                       g_string_append_printf (string, "%02u:%02u:%02u",
+                                               gtime->hour,
+                                               gtime->minute,
+                                               gtime->second);
+                       if (gtime->fraction > 0)
+                               g_string_append_printf (string, ".%lu", gtime->fraction);
+
+                       if (tofree)
+                               gda_time_free (gtime);
+                       SQLITE3_CALL (sqlite3_bind_text) (ps->sqlite_stmt, i, g_string_free (string, FALSE), 
-1, g_free);
                }
                else if (G_VALUE_TYPE (value) == G_TYPE_DATE) {
                        gchar *str;
@@ -3310,17 +3325,35 @@ gda_sqlite_provider_statement_execute (GdaServerProvider *provider, GdaConnectio
                        SQLITE3_CALL (sqlite3_bind_text) (ps->sqlite_stmt, i, str, -1, g_free);
                }
                else if (G_VALUE_TYPE (value) == GDA_TYPE_TIMESTAMP) {
-                       gchar *str;
-                       const GdaTimestamp *ts;
+                       GString *string;
+                       GdaTimestamp *timestamp;
+                       gboolean tofree = FALSE;
 
-                       ts = gda_value_get_timestamp (value);
-                       if (ts->fraction != 0)
-                               str = g_strdup_printf ("%4d-%02d-%02d %02d:%02d:%02d.%ld", ts->year, 
ts->month, ts->day,
-                                                      ts->hour, ts->minute, ts->second, ts->fraction);
-                       else
-                               str = g_strdup_printf ("%4d-%02d-%02d %02d:%02d:%02d", ts->year, ts->month, 
ts->day,
-                                                      ts->hour, ts->minute, ts->second);
-                       SQLITE3_CALL (sqlite3_bind_text) (ps->sqlite_stmt, i, str, -1, g_free);
+                       timestamp = (GdaTimestamp *) gda_value_get_timestamp (value);
+
+                       string = g_string_new ("");
+                       if (timestamp->timezone != GDA_TIMEZONE_INVALID) {
+                               /* SQLite cant' store timezone information, so if timezone information is
+                                * provided, we do our best and convert it to GMT */
+                               timestamp = gda_timestamp_copy (timestamp);
+                               tofree = TRUE;
+                               gda_timestamp_change_timezone (timestamp, 0);
+                       }
+
+                       g_string_append_printf (string, "%04u-%02u-%02u %02u:%02u:%02u",
+                                               timestamp->year,
+                                               timestamp->month,
+                                               timestamp->day,
+                                               timestamp->hour,
+                                               timestamp->minute,
+                                               timestamp->second);
+                       if (timestamp->fraction > 0)
+                               g_string_append_printf (string, ".%lu", timestamp->fraction);
+
+                       if (tofree)
+                               gda_timestamp_free (timestamp);
+
+                       SQLITE3_CALL (sqlite3_bind_text) (ps->sqlite_stmt, i, g_string_free (string, FALSE), 
-1, g_free);
                }
                else if (G_VALUE_TYPE (value) == GDA_TYPE_NUMERIC) {
                        const GdaNumeric *gdan;
diff --git a/libgda/sqlite/gda-sqlite-recordset.c b/libgda/sqlite/gda-sqlite-recordset.c
index 6ef4914..e1c0376 100644
--- a/libgda/sqlite/gda-sqlite-recordset.c
+++ b/libgda/sqlite/gda-sqlite-recordset.c
@@ -550,8 +550,11 @@ fetch_next_sqlite_row (GdaSqliteRecordset *model, gboolean do_store, GError **er
                                                             (gchar *) SQLITE3_CALL (sqlite3_column_text) 
(ps->sqlite_stmt, real_col));
                                                gda_row_invalidate_value_e (prow, value, lerror);
                                        }
-                                       else
+                                       else {
+                                               if (timegda.timezone == GDA_TIMEZONE_INVALID)
+                                                       timegda.timezone = 0; /* set to GMT */
                                                gda_value_set_time (value, &timegda);
+                                       }
                                }
                                else if (type == GDA_TYPE_TIMESTAMP) {
                                        GdaTimestamp timestamp;
@@ -565,8 +568,11 @@ fetch_next_sqlite_row (GdaSqliteRecordset *model, gboolean do_store, GError **er
                                                             (gchar *) SQLITE3_CALL (sqlite3_column_text) 
(ps->sqlite_stmt, real_col));
                                                gda_row_invalidate_value_e (prow, value, lerror);
                                        }
-                                       else
+                                       else {
+                                               if (timestamp.timezone == GDA_TIMEZONE_INVALID)
+                                                       timestamp.timezone = 0; /* set to GMT */
                                                gda_value_set_timestamp (value, &timestamp);
+                                       }
                                }
                                else if (type == G_TYPE_CHAR) {
                                        gint64 i;
diff --git a/providers/mysql/gda-mysql-provider.c b/providers/mysql/gda-mysql-provider.c
index 2aa3e74..4f8b856 100644
--- a/providers/mysql/gda-mysql-provider.c
+++ b/providers/mysql/gda-mysql-provider.c
@@ -2210,7 +2210,7 @@ gda_mysql_provider_statement_execute (GdaServerProvider               *provider,
                         * and use that SQL instead of @stmt to create another GdaMysqlPStmt object.
                         */
                        gchar *sql = gda_mysql_provider_statement_to_sql (provider, cnc, stmt, 
-                                                                         params, 0, NULL, error);
+                                                                         params, 
GDA_STATEMENT_SQL_TIMEZONE_TO_GMT, NULL, error);
                        gboolean proto_error;
                        if (!sql)
                                return NULL;
@@ -2454,14 +2454,23 @@ gda_mysql_provider_statement_execute (GdaServerProvider               *provider,
                        }
                }
                else if (G_VALUE_TYPE (value) == GDA_TYPE_TIMESTAMP) {
-                       const GdaTimestamp *ts;
+                       GdaTimestamp *ts;
 
-                       ts = gda_value_get_timestamp (value);
+                       ts = (GdaTimestamp*) gda_value_get_timestamp (value);
                        if (!ts) {
                                mysql_bind_param[i].buffer_type = MYSQL_TYPE_NULL;
                                mysql_bind_param[i].is_null = (my_bool*)1;
                        }
                        else {
+                               gboolean tofree = FALSE;
+                               if (ts->timezone != GDA_TIMEZONE_INVALID) {
+                                       /* MySQL does not store timezone information, so if timezone 
information is
+                                        * provided, we do our best and convert it to GMT */
+                                       ts = gda_timestamp_copy (ts);
+                                       tofree = TRUE;
+                                       gda_timestamp_change_timezone (ts, 0);
+                               }
+
                                MYSQL_TIME *mtime;
                                mtime = g_new0 (MYSQL_TIME, 1);
                                mem_to_free = g_slist_prepend (mem_to_free, mtime);
@@ -2472,6 +2481,8 @@ gda_mysql_provider_statement_execute (GdaServerProvider               *provider,
                                mtime->minute = ts->minute;
                                mtime->second = ts->second;
                                mtime->second_part = ts->fraction;
+                               if (tofree)
+                                       gda_timestamp_free (ts);
 
                                mysql_bind_param[i].buffer_type= MYSQL_TYPE_TIMESTAMP;
                                mysql_bind_param[i].buffer= (char *)mtime;
@@ -2479,14 +2490,23 @@ gda_mysql_provider_statement_execute (GdaServerProvider               *provider,
                        }
                }
                else if (G_VALUE_TYPE (value) == GDA_TYPE_TIME) {
-                       const GdaTime *ts;
+                       GdaTime *ts;
 
-                       ts = gda_value_get_time (value);
+                       ts = (GdaTime*) gda_value_get_time (value);
                        if (!ts) {
                                mysql_bind_param[i].buffer_type = MYSQL_TYPE_NULL;
                                mysql_bind_param[i].is_null = (my_bool*)1;
                        }
                        else {
+                               gboolean tofree = FALSE;
+                               if (ts->timezone != GDA_TIMEZONE_INVALID) {
+                                       /* MySQL does not store timezone information, so if timezone 
information is
+                                        * provided, we do our best and convert it to GMT */
+                                       ts = gda_time_copy (ts);
+                                       tofree = TRUE;
+                                       gda_time_change_timezone (ts, 0);
+                               }
+
                                MYSQL_TIME *mtime;
                                mtime = g_new0 (MYSQL_TIME, 1);
                                mem_to_free = g_slist_prepend (mem_to_free, mtime);
@@ -2494,6 +2514,8 @@ gda_mysql_provider_statement_execute (GdaServerProvider               *provider,
                                mtime->minute = ts->minute;
                                mtime->second = ts->second;
                                mtime->second_part = ts->fraction;
+                               if (tofree)
+                                       gda_time_free (ts);
 
                                mysql_bind_param[i].buffer_type= MYSQL_TYPE_TIME;
                                mysql_bind_param[i].buffer= (char *)mtime;
diff --git a/providers/mysql/gda-mysql-recordset.c b/providers/mysql/gda-mysql-recordset.c
index d2d7f3a..e534cad 100644
--- a/providers/mysql/gda-mysql-recordset.c
+++ b/providers/mysql/gda-mysql-recordset.c
@@ -7,7 +7,7 @@
  * Copyright (C) 2003 Chris Silles <csilles src gnome org>
  * Copyright (C) 2003 Laurent Sansonetti <lrz gnome org>
  * Copyright (C) 2003 Paisa Seeluangsawat <paisa users sf net>
- * Copyright (C) 2004 - 2012 Vivien Malerba <malerba gnome-db org>
+ * Copyright (C) 2004 - 2013 Vivien Malerba <malerba gnome-db org>
  * Copyright (C) 2005 Alan Knowles <alan akbkhome com>
  * Copyright (C) 2005 - 2009 Bas Driessen <bas driessen xobas com>
  * Copyright (C) 2005 Mike Fisk <mfisk woozle org>
@@ -864,6 +864,7 @@ new_row_from_mysql_stmt (GdaMysqlRecordset *imodel, G_GNUC_UNUSED gint rownum, G
                                        .minute = bvalue.minute,
                                        .second = bvalue.second,
                                        .fraction = bvalue.second_part,
+                                       .timezone = 0 /* GMT */
                                };
                                gda_value_set_time (value, &time);
                        }
@@ -883,6 +884,7 @@ new_row_from_mysql_stmt (GdaMysqlRecordset *imodel, G_GNUC_UNUSED gint rownum, G
                                        .minute = bvalue.minute,
                                        .second = bvalue.second,
                                        .fraction = bvalue.second_part,
+                                       .timezone = 0 /* GMT */
                                };
                                gda_value_set_timestamp (value, &timestamp);
                        }
diff --git a/providers/postgres/gda-postgres-provider.c b/providers/postgres/gda-postgres-provider.c
index e2ce2a7..d9bccb4 100644
--- a/providers/postgres/gda-postgres-provider.c
+++ b/providers/postgres/gda-postgres-provider.c
@@ -1844,8 +1844,9 @@ gda_postgres_provider_statement_execute (GdaServerProvider *provider, GdaConnect
                GdaDataModel *recset;
                GdaConnectionEvent *event;
 
-               /* convert to SQL to remove any reference to a variable */
-               sql = gda_postgres_provider_statement_to_sql (provider, cnc, stmt, params, 0, NULL, error);
+               /* convert to SQL to remove any reference to a variable, using GMT for timezones */
+               sql = gda_postgres_provider_statement_to_sql (provider, cnc, stmt, params,
+                                                             GDA_STATEMENT_SQL_TIMEZONE_TO_GMT, NULL, error);
                if (!sql)
                        return NULL;
 
@@ -1891,7 +1892,8 @@ gda_postgres_provider_statement_execute (GdaServerProvider *provider, GdaConnect
                         */
                        gchar *sql;
 
-                       sql = gda_postgres_provider_statement_to_sql (provider, cnc, stmt, params, 0, NULL, 
error);
+                       sql = gda_postgres_provider_statement_to_sql (provider, cnc, stmt, params,
+                                                                     GDA_STATEMENT_SQL_TIMEZONE_TO_GMT, 
NULL, error);
                        if (!sql)
                                return NULL;
                        ps = prepare_stmt_simple (cdata, sql, error); // FIXME: this @ps is leaked!
@@ -2108,16 +2110,59 @@ gda_postgres_provider_statement_execute (GdaServerProvider *provider, GdaConnect
                        param_formats [i] = 1; /* binary format */
                        param_mem [i] = TRUE; /* don't free later */
                }
-               else if ((G_VALUE_TYPE (value) == G_TYPE_DATE) ||
-                        (G_VALUE_TYPE (value) == GDA_TYPE_TIMESTAMP) ||
-                        (G_VALUE_TYPE (value) == GDA_TYPE_TIME)) {
+               else if (G_VALUE_TYPE (value) == G_TYPE_DATE) {
                        GdaHandlerTime *timdh;
-
                        timdh = GDA_HANDLER_TIME (gda_server_provider_get_data_handler_g_type (provider, cnc,
-                                                                                             G_VALUE_TYPE 
(value)));
+                                                                                              G_VALUE_TYPE 
(value)));
                        g_assert (timdh);
                        param_values [i] = gda_handler_time_get_no_locale_str_from_value (timdh, value);
                }
+               else if (G_VALUE_TYPE (value) == GDA_TYPE_TIMESTAMP) {
+                       GdaHandlerTime *timdh;
+                       timdh = GDA_HANDLER_TIME (gda_server_provider_get_data_handler_g_type (provider, cnc,
+                                                                                              G_VALUE_TYPE 
(value)));
+                       g_assert (timdh);
+
+                       GdaTimestamp *timestamp;
+                       timestamp = (GdaTimestamp *) gda_value_get_timestamp (value);
+                       if (timestamp->timezone != GDA_TIMEZONE_INVALID) {
+                               /* PostgreSQL does not store timezone information, so if timezone information 
is
+                                * provided, we do our best and convert it to GMT */
+                               timestamp = gda_timestamp_copy (timestamp);
+                               gda_timestamp_change_timezone (timestamp, 0);
+                               GValue *rv;
+                               rv = gda_value_new (GDA_TYPE_TIMESTAMP);
+                               gda_value_set_timestamp (rv, timestamp);
+                               gda_timestamp_free (timestamp);
+                               param_values [i] = gda_handler_time_get_no_locale_str_from_value (timdh, rv);
+                               gda_value_free (rv);
+                       }
+                       else
+                               param_values [i] = gda_handler_time_get_no_locale_str_from_value (timdh, 
value);
+               }
+               else if (G_VALUE_TYPE (value) == GDA_TYPE_TIME) {
+                       GdaHandlerTime *timdh;
+                       timdh = GDA_HANDLER_TIME (gda_server_provider_get_data_handler_g_type (provider, cnc,
+                                                                                              G_VALUE_TYPE 
(value)));
+                       g_assert (timdh);
+
+                       GdaTime *gdatime;
+                       gdatime = (GdaTime*) gda_value_get_time (value);
+                       if (gdatime->timezone != GDA_TIMEZONE_INVALID) {
+                               /* PostgreSQL does not store timezone information, so if timezone information 
is
+                                * provided, we do our best and convert it to GMT */
+                               gdatime = gda_time_copy (gdatime);
+                               gda_time_change_timezone (gdatime, 0);
+                               GValue *rv;
+                               rv = gda_value_new (GDA_TYPE_TIME);
+                               gda_value_set_time (rv, gdatime);
+                               gda_time_free (gdatime);
+                               param_values [i] = gda_handler_time_get_no_locale_str_from_value (timdh, rv);
+                               gda_value_free (rv);
+                       }
+                       else
+                               param_values [i] = gda_handler_time_get_no_locale_str_from_value (timdh, 
value);
+               }
                else {
                        GdaDataHandler *dh;
 
diff --git a/providers/postgres/gda-postgres-recordset.c b/providers/postgres/gda-postgres-recordset.c
index 8096f4a..60b1170 100644
--- a/providers/postgres/gda-postgres-recordset.c
+++ b/providers/postgres/gda-postgres-recordset.c
@@ -628,8 +628,11 @@ set_value (GdaConnection *cnc, GdaRow *row, GValue *value, GType type, const gch
                                     GDA_SERVER_PROVIDER_DATA_ERROR,
                                     _("Invalid time '%s' (time format should be HH:MM:SS[.ms])"), thevalue);
                }
-               else
+               else {
+                       if (timegda.timezone == GDA_TIMEZONE_INVALID)
+                               timegda.timezone = 0; /* set to GMT */
                        gda_value_set_time (value, &timegda);
+               }
        }
        else if (type == G_TYPE_INT64)
                g_value_set_int64 (value, atoll (thevalue));
@@ -671,8 +674,11 @@ set_value (GdaConnection *cnc, GdaRow *row, GValue *value, GType type, const gch
                                     _("Invalid timestamp '%s' (format should be YYYY-MM-DD HH:MM:SS[.ms])"), 
                                     thevalue);
                }
-               else
+               else {
+                       if (timestamp.timezone == GDA_TIMEZONE_INVALID)
+                               timestamp.timezone = 0; /* set to GMT */
                        gda_value_set_timestamp (value, &timestamp);
+               }
        }
        else if (type == GDA_TYPE_BINARY) {
                /*
diff --git a/providers/skel-implementation/capi/gda-capi-provider.c 
b/providers/skel-implementation/capi/gda-capi-provider.c
index 5d59710..2742b64 100644
--- a/providers/skel-implementation/capi/gda-capi-provider.c
+++ b/providers/skel-implementation/capi/gda-capi-provider.c
@@ -1239,6 +1239,11 @@ gda_capi_provider_statement_execute (GdaServerProvider *provider, GdaConnection
                        }
                }
                else {
+                       /* usually the way to bind parameters is different depending on the type of @value.
+                        * Also, if the database engine does not support storing timezone information for 
time and
+                        * timestamp values, then before binding, the value must be converted to GMT using
+                        * gda_time_change_timezone (xxx, 0) or gda_timestamp_change_timezone (xxx, 0)
+                        */
                        TO_IMPLEMENT;
                }
        }
diff --git a/tests/.gitignore b/tests/.gitignore
index 0429d8a..9bc2f53 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -6,5 +6,6 @@ test-sql-builder
 test-connection-string-split
 test-input-parsers
 test-quark-list
+test-sql-renderer
 *.log
 *.trs
\ No newline at end of file
diff --git a/tests/Makefile.am b/tests/Makefile.am
index f23467f..ac3b8ce 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,8 +1,8 @@
 noinst_LTLIBRARIES = libgda-test-5.0.la
 
 TESTS_ENVIRONMENT = GDA_TOP_SRC_DIR="$(abs_top_srcdir)" GDA_TOP_BUILD_DIR="$(abs_top_builddir)"
-TESTS = test-ddl-creator test-bin-converter test-sql-identifier test-identifiers-quotes test-sql-builder 
test-connection-string-split test-input-parsers test-quark-list
-check_PROGRAMS = test-ddl-creator test-bin-converter test-sql-identifier test-identifiers-quotes 
test-sql-builder test-connection-string-split test-input-parsers test-quark-list
+TESTS = test-ddl-creator test-bin-converter test-sql-identifier test-identifiers-quotes test-sql-builder 
test-connection-string-split test-input-parsers test-quark-list test-sql-renderer
+check_PROGRAMS = test-ddl-creator test-bin-converter test-sql-identifier test-identifiers-quotes 
test-sql-builder test-connection-string-split test-input-parsers test-quark-list test-sql-renderer
 
 if ENABLE_VALA_EXTENSIONS
     VALA_EXTENSIONS= vala
@@ -98,4 +98,13 @@ test_quark_list_LDADD = \
         $(top_builddir)/libgda/libgda-5.0.la \
         $(COREDEPS_LIBS)
 
+test_sql_renderer_SOURCES = \
+        test-sql-renderer.c
+
+test_sql_renderer_LDADD = \
+        $(top_builddir)/libgda/libgda-5.0.la \
+       libgda-test-5.0.la \
+        $(COREDEPS_LIBS)
+
+
 EXTRA_DIST = dbstruct.xml
diff --git a/tests/data-models/check_pmodel.c b/tests/data-models/check_pmodel.c
index 326cdb4..a78224e 100644
--- a/tests/data-models/check_pmodel.c
+++ b/tests/data-models/check_pmodel.c
@@ -451,7 +451,7 @@ test3 (GdaConnection *cnc)
 
        /****/
        gda_value_set_from_string ((value = gda_value_new (GDA_TYPE_TIMESTAMP)), 
-                                  "2009-11-30 11:22:33", GDA_TYPE_TIMESTAMP);
+                                  "2009-11-30 11:22:33+0", GDA_TYPE_TIMESTAMP);
        if (! check_set_value_at (model, 2, 1, value, cnc, stmt, NULL)) {
                nfailed ++;
                goto out;
@@ -879,7 +879,7 @@ test7 (GdaConnection *cnc)
        }
 
        gda_value_set_from_string ((value = gda_value_new (GDA_TYPE_TIMESTAMP)), 
-                                  "2004-03-22 11:22:44", GDA_TYPE_TIMESTAMP);
+                                  "2004-03-22 11:22:44+0", GDA_TYPE_TIMESTAMP);
        if (! check_set_value_at (model, 2, 1, value, cnc, stmt, params)) {
                nfailed ++;
                goto out;
@@ -1453,7 +1453,7 @@ test13 (GdaConnection *cnc)
 
        /****/
        gda_value_set_from_string ((value = gda_value_new (GDA_TYPE_TIMESTAMP)), 
-                                  "2009-11-30 11:22:33", GDA_TYPE_TIMESTAMP);
+                                  "2009-11-30 11:22:33+0", GDA_TYPE_TIMESTAMP);
        if (! check_set_value_at (model, 1, 1, value, cnc, stmt, NULL)) {
                nfailed ++;
                goto out;
@@ -1548,7 +1548,7 @@ test14 (GdaConnection *cnc)
 
        /****/
        gda_value_set_from_string ((value = gda_value_new (GDA_TYPE_TIMESTAMP)), 
-                                  "2019-11-30 11:22:33", GDA_TYPE_TIMESTAMP);
+                                  "2019-11-30 11:22:33+0", GDA_TYPE_TIMESTAMP);
        if (! check_set_value_at (model, 1, 1, value, cnc, stmt, NULL)) {
                nfailed ++;
                goto out;
diff --git a/tests/data-models/check_vcnc.c b/tests/data-models/check_vcnc.c
index 617088e..9b8908b 100644
--- a/tests/data-models/check_vcnc.c
+++ b/tests/data-models/check_vcnc.c
@@ -568,8 +568,8 @@ check_date (GdaConnection *virtual)
 {
        g_print ("*** insert dates into 'misc' table...\n");
        GdaSet *set;
-       GdaTimestamp ts = {2011, 01, 31, 12, 34, 56, 0, GDA_TIMEZONE_INVALID};
-       GdaTime atime = {13, 45, 59, 0, GDA_TIMEZONE_INVALID};
+       GdaTimestamp ts = {2011, 01, 31, 12, 34, 56, 0, 0};
+       GdaTime atime = {13, 45, 59, 0, 0};
        GDate *adate;
        GdaDataModel *model;
        GError *error = NULL;
diff --git a/tests/providers/check_mysql.c b/tests/providers/check_mysql.c
index 3247eb2..d14890d 100644
--- a/tests/providers/check_mysql.c
+++ b/tests/providers/check_mysql.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 - 2011 Vivien Malerba <malerba gnome-db org>
+ * Copyright (C) 2007 - 2013 Vivien Malerba <malerba gnome-db org>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -42,6 +42,7 @@ main (int argc, char **argv)
        number_failed = prov_test_common_setup ();
 
        if (cnc) {
+               number_failed += prov_test_common_check_timestamp ();
                number_failed += prov_test_common_check_meta ();
                number_failed += prov_test_common_clean ();
        }
diff --git a/tests/providers/check_postgres.c b/tests/providers/check_postgres.c
index 9dfef83..e244cab 100644
--- a/tests/providers/check_postgres.c
+++ b/tests/providers/check_postgres.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 - 2011 Vivien Malerba <malerba gnome-db org>
+ * Copyright (C) 2007 - 2013 Vivien Malerba <malerba gnome-db org>
  * Copyright (C) 2012 Daniel Espinosa <despinosa src gnome org>
  *
  * This program is free software; you can redistribute it and/or
@@ -45,6 +45,7 @@ main (int argc, char **argv)
        number_failed = prov_test_common_setup ();
 
        if (cnc) {
+               number_failed += prov_test_common_check_timestamp ();
                number_failed += prov_test_common_check_meta ();
                number_failed += prov_test_common_check_meta_identifiers (TRUE, TRUE);
                number_failed += prov_test_common_check_meta_identifiers (TRUE, FALSE);
diff --git a/tests/providers/check_sqlite.c b/tests/providers/check_sqlite.c
index 11fac25..8db20e3 100644
--- a/tests/providers/check_sqlite.c
+++ b/tests/providers/check_sqlite.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 - 2011 Vivien Malerba <malerba gnome-db org>
+ * Copyright (C) 2007 - 2013 Vivien Malerba <malerba gnome-db org>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -44,6 +44,7 @@ main (int argc, char **argv)
        number_failed = prov_test_common_setup ();
 
        if (cnc) {
+               number_failed += prov_test_common_check_timestamp ();
                number_failed += prov_test_common_check_meta ();
                number_failed += prov_test_common_check_meta_identifiers (TRUE, TRUE);
                number_failed += prov_test_common_check_meta_identifiers (TRUE, FALSE);
diff --git a/tests/providers/prov-test-common.c b/tests/providers/prov-test-common.c
index b8a135e..ce91185 100644
--- a/tests/providers/prov-test-common.c
+++ b/tests/providers/prov-test-common.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 - 2011 Vivien Malerba <malerba gnome-db org>
+ * Copyright (C) 2007 - 2013 Vivien Malerba <malerba gnome-db org>
  * Copyright (C) 2011 Murray Cumming <murrayc murrayc com>
  * Copyright (C) 2012 Daniel Espinosa <despinosa src gnome org>
  *
@@ -42,7 +42,7 @@ gboolean         fork_tests = TRUE;
  */
 
 int
-prov_test_common_setup ()
+prov_test_common_setup (void)
 {
        int number_failed = 0;
        GError *error = NULL;
@@ -90,7 +90,7 @@ prov_test_common_setup ()
  *
  */
 int
-prov_test_common_clean ()
+prov_test_common_clean (void)
 {
        int number_failed = 0;
 
@@ -111,7 +111,7 @@ prov_test_common_clean ()
  */
 
 int
-prov_test_common_check_meta ()
+prov_test_common_check_meta (void)
 {
        int number_failed = 0;
        GSList *tables = NULL, *list;
@@ -529,7 +529,7 @@ prov_test_common_check_meta_identifiers (gboolean case_sensitive, gboolean updat
  *
  */
 int
-prov_test_common_load_data ()
+prov_test_common_load_data (void)
 {
        int number_failed = 0;
 
@@ -555,7 +555,7 @@ prov_test_common_load_data ()
  *
  */
 int
-prov_test_common_check_cursor_models ()
+prov_test_common_check_cursor_models (void)
 {
        int number_failed = 0;
 
@@ -584,7 +584,7 @@ prov_test_common_check_cursor_models ()
  *
  */
 int 
-prov_test_common_check_data_select ()
+prov_test_common_check_data_select (void)
 {
        GdaSqlParser *parser = NULL;
        GdaStatement *stmt = NULL;
@@ -696,3 +696,120 @@ prov_test_common_check_data_select ()
 
        return number_failed;
 }
+
+
+/*
+ * Check that timezones are handled correctly when storing and retreiving timestamps
+ */
+static gboolean
+gda_timestamp_equal (const GValue *cv1, const GValue *cv2)
+{
+       g_assert (G_VALUE_TYPE (cv1) == GDA_TYPE_TIMESTAMP);
+       g_assert (G_VALUE_TYPE (cv2) == GDA_TYPE_TIMESTAMP);
+       const GdaTimestamp *ts1, *ts2;
+       ts1 = gda_value_get_timestamp (cv1);
+       ts2 = gda_value_get_timestamp (cv2);
+       if (ts1->timezone == ts2->timezone)
+               return gda_value_differ (cv1, cv2) ? FALSE : TRUE;
+
+       GdaTimestamp *ts;
+       ts = gda_timestamp_copy ((GdaTimestamp*) ts1);
+       gda_timestamp_change_timezone (ts, ts2->timezone);
+       gboolean res;
+       res = memcmp (ts2, ts, sizeof (GdaTimestamp)) ? FALSE : TRUE;
+       gda_timestamp_free (ts);
+       return res;
+}
+
+int
+prov_test_common_check_timestamp (void)
+{
+       GdaSqlParser *parser = NULL;
+       GdaStatement *stmt = NULL;
+       GdaSet *params = NULL;
+       GError *error = NULL;
+       int number_failed = 0;
+
+#ifdef CHECK_EXTRA_INFO
+       g_print ("\n============= %s() =============\n", __FUNCTION__);
+#endif
+
+       parser = gda_connection_create_parser (cnc);
+       if (!parser)
+               parser = gda_sql_parser_new ();
+
+       GValue *tso;
+       tso = gda_value_new_timestamp_from_timet (time (NULL));
+
+       /* insert timestamp */
+       stmt = gda_sql_parser_parse_string (parser, "INSERT INTO tstest (ts) VALUES (##ts::timestamp)", 
+                                           NULL, &error);
+       if (!stmt ||
+           ! gda_statement_get_parameters (stmt, &params, &error) ||
+           ! gda_set_set_holder_value (params, &error, "ts", gda_value_get_timestamp (tso)) ||
+           (gda_connection_statement_execute_non_select (cnc, stmt, params, NULL, &error) == -1)) {
+               number_failed ++;
+               goto out;
+       }
+
+       g_print ("Inserted TS %s\n", gda_value_stringify (tso));
+
+       /* retreive timestamp */
+       stmt = gda_sql_parser_parse_string (parser, "SELECT ts FROM tstest", 
+                                           NULL, &error);
+       if (!stmt) {
+               number_failed ++;
+               goto out;
+       }
+
+       GdaDataModel *model;
+       model = gda_connection_statement_execute_select (cnc, stmt, NULL, &error);
+       if (!model) {
+               number_failed ++;
+               goto out;
+       }
+       gda_data_model_dump (model, NULL);
+       if (gda_data_model_get_n_rows (model) != 1) {
+               g_set_error (&error, TEST_ERROR, TEST_ERROR_GENERIC,
+                            "Data model should have exactly 1 row");
+               number_failed ++;
+               goto out;
+       }
+
+       const GValue *cvalue;
+       cvalue = gda_data_model_get_typed_value_at (model, 0, 0, GDA_TYPE_TIMESTAMP, FALSE, &error);
+       if (!cvalue) {
+               number_failed ++;
+               goto out;
+       }
+       if (! gda_timestamp_equal (tso, cvalue)) {
+               gchar *tmpg, *tmpe;
+               tmpg = gda_value_stringify (cvalue);
+               tmpe = gda_value_stringify (tso);
+               g_set_error (&error, TEST_ERROR, TEST_ERROR_GENERIC,
+                            "Retreived time stamp differs from expected: got '%s' and expected '%s'", tmpg, 
tmpe);
+               g_free (tmpg);
+               g_free (tmpe);
+               number_failed ++;
+               goto out;
+       }
+
+out:
+       if (stmt)
+               g_object_unref (stmt);
+       if (params)
+               g_object_unref (params);
+       if (model)
+               g_object_unref (model);
+       g_object_unref (parser);
+
+#ifdef CHECK_EXTRA_INFO
+       g_print ("Timestamp test resulted in %d error(s)\n", number_failed);
+       if (number_failed != 0) 
+               g_print ("error: %s\n", error && error->message ? error->message : "No detail");
+       if (error)
+               g_error_free (error);
+#endif
+
+       return number_failed;
+}
diff --git a/tests/providers/prov-test-common.h b/tests/providers/prov-test-common.h
index 521eda5..0479e29 100644
--- a/tests/providers/prov-test-common.h
+++ b/tests/providers/prov-test-common.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 - 2011 Vivien Malerba <malerba gnome-db org>
+ * Copyright (C) 2007 - 2013 Vivien Malerba <malerba gnome-db org>
  * Copyright (C) 2011 Murray Cumming <murrayc murrayc com>
  *
  * This program is free software; you can redistribute it and/or
@@ -24,12 +24,13 @@
 #include <libgda/libgda.h>
 #include "prov-test-util.h"
 
-int prov_test_common_setup ();
-int prov_test_common_load_data ();
-int prov_test_common_check_meta ();
+int prov_test_common_setup (void);
+int prov_test_common_load_data (void);
+int prov_test_common_check_meta (void);
 int prov_test_common_check_meta_identifiers (gboolean case_sensitive, gboolean update_all);
-int prov_test_common_check_cursor_models ();
-int prov_test_common_check_data_select ();
-int prov_test_common_clean ();
+int prov_test_common_check_cursor_models (void);
+int prov_test_common_check_data_select (void);
+int prov_test_common_check_timestamp (void);
+int prov_test_common_clean (void);
 
 #endif
diff --git a/tests/providers/prov_dbstruct.xml b/tests/providers/prov_dbstruct.xml
index 46e2fa6..6ee5ace 100644
--- a/tests/providers/prov_dbstruct.xml
+++ b/tests/providers/prov_dbstruct.xml
@@ -117,4 +117,9 @@
     <definition>SELECT * FROM films ORDER BY last_update</definition>
   </view>
 
+  <!-- testing timestamps -->
+  <table name="tstest">
+    <column name="ts" type="timestamp"/>
+  </table>
+
 </schema>
diff --git a/tests/test-sql-renderer.c b/tests/test-sql-renderer.c
new file mode 100644
index 0000000..11b6b45
--- /dev/null
+++ b/tests/test-sql-renderer.c
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2013 Vivien Malerba <malerba gnome-db org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include <libgda/libgda.h>
+#include <sql-parser/gda-sql-parser.h>
+#include <string.h>
+
+#define PROV_CLASS(provider) (GDA_SERVER_PROVIDER_CLASS (G_OBJECT_GET_CLASS (provider)))
+
+static gboolean
+string_equal_to_template (const gchar *str, const gchar *tmpl)
+{
+       const gchar *ptrs, *ptrt;
+       for (ptrs = str; *ptrs != '('; ptrs++);
+
+       for (ptrt = tmpl;
+            *ptrs && *ptrt;
+            ptrs++, ptrt++) {
+               if (*ptrt == '@')
+                       continue;
+               if (*ptrs != *ptrt)
+                       break;
+       }
+       if (*ptrs || *ptrt)
+               return FALSE;
+       else
+               return TRUE;
+}
+
+GdaTimestamp ts = {
+       .year = 2013,
+       .month = 8,
+       .day = 28,
+       .hour = 17,
+       .minute = 10,
+       .second = 23,
+       .timezone = 3600 * 2
+};
+GdaTime gt = {
+       .hour = 16,
+       .minute = 9,
+       .second = 22,
+       .timezone = - 3600 * 3
+};
+
+
+static guint
+do_a_test (GdaServerProvider *prov, GdaSqlParser *parser)
+{
+       guint nfailed = 0;
+       GdaStatement *stmt;
+       GdaSet *params;
+       gchar *sql;
+       GError *error = NULL;
+
+       /* SQL parsed as an INSERT statement */
+       sql = "INSERT INTO tstest VALUES (##ts::timestamp, ##time::time)";
+       stmt = gda_sql_parser_parse_string (parser, sql, NULL, &error);
+       if (!stmt) {
+               g_print ("Failed to parse [%s]: %s\n", sql, error && error->message ? error->message : "No 
detail");
+               g_clear_error (&error);
+               nfailed ++;
+               goto endtest;
+       }
+
+       g_assert (gda_statement_get_statement_type (stmt) == GDA_SQL_STATEMENT_INSERT);
+       if (! gda_statement_get_parameters (stmt, &params, &error)) {
+               g_print ("Failed to obtain parameters: %s\n", error && error->message ? error->message : "No 
detail");
+               g_clear_error (&error);
+               g_object_unref (stmt);
+               nfailed ++;
+               goto endtest;
+       }
+
+       if (! gda_set_set_holder_value (params, &error, "ts", &ts)) {
+               g_print ("Failed to bind 'ts' parameter: %s\n", error && error->message ? error->message : 
"No detail");
+               g_clear_error (&error);
+               g_object_unref (stmt);
+               g_object_unref (params);
+               nfailed ++;
+               goto endtest;
+       }
+
+       if (! gda_set_set_holder_value (params, &error, "time", &gt)) {
+               g_print ("Failed to bind 'time' parameter: %s\n", error && error->message ? error->message : 
"No detail");
+               g_clear_error (&error);
+               g_object_unref (stmt);
+               g_object_unref (params);
+               nfailed ++;
+               goto endtest;
+       }
+
+       gchar *expected;
+       expected = "('@@@@@@@@@@ 17:10:23+2', '16:09:22-3')";
+       if (prov && PROV_CLASS (prov)->statement_to_sql)
+               sql = PROV_CLASS (prov)->statement_to_sql (prov, NULL, stmt, params, 0, NULL, &error);
+       else
+               sql = gda_statement_to_sql_extended (stmt, NULL, params, 0, NULL, &error);
+       if (!sql) {
+               g_print ("Failed to render as SQL: %s\n", error && error->message ? error->message : "No 
detail");
+               g_clear_error (&error);
+               g_object_unref (stmt);
+               g_object_unref (params);
+               nfailed ++;
+               goto endtest;
+       }
+       if (!string_equal_to_template (sql, expected)) {
+               g_print ("Wrong rendered SQL: [%s] instead of [%s]\n", sql, expected);
+               g_object_unref (stmt);
+               g_object_unref (params);
+               g_free (sql);
+               nfailed ++;
+               goto endtest;
+       }
+       g_free (sql);
+
+       expected = "('@@@@@@@@@@ 15:10:23', '19:09:22')";
+       if (prov && PROV_CLASS (prov)->statement_to_sql)
+               sql = PROV_CLASS (prov)->statement_to_sql (prov, NULL, stmt, params, 
GDA_STATEMENT_SQL_TIMEZONE_TO_GMT, NULL, &error);
+       else
+               sql = gda_statement_to_sql_extended (stmt, NULL, params, GDA_STATEMENT_SQL_TIMEZONE_TO_GMT, 
NULL, &error);
+       if (!sql) {
+               g_print ("Failed to render as SQL: %s\n", error && error->message ? error->message : "No 
detail");
+               g_clear_error (&error);
+               g_object_unref (stmt);
+               g_object_unref (params);
+               nfailed ++;
+               goto endtest;
+       }
+       if (!string_equal_to_template (sql, expected)) {
+               g_print ("Wrong rendered SQL for GMT timezone: [%s] instead of [%s]\n", sql, expected);
+               g_object_unref (stmt);
+               g_object_unref (params);
+               g_free (sql);
+               nfailed ++;
+               goto endtest;
+       }
+       g_free (sql);
+
+       /* SQL not parsed as a valid statement */
+       sql = "AAAA (##ts::timestamp, ##time::time)";
+       stmt = gda_sql_parser_parse_string (parser, sql, NULL, &error);
+       if (!stmt) {
+               g_print ("Failed to parse [%s]: %s\n", sql, error && error->message ? error->message : "No 
detail");
+               g_clear_error (&error);
+               nfailed ++;
+               goto endtest;
+       }
+
+       g_assert (gda_statement_get_statement_type (stmt) == GDA_SQL_STATEMENT_UNKNOWN);
+       if (! gda_statement_get_parameters (stmt, &params, &error)) {
+               g_print ("Failed to obtain parameters: %s\n", error && error->message ? error->message : "No 
detail");
+               g_clear_error (&error);
+               g_object_unref (stmt);
+               nfailed ++;
+               goto endtest;
+       }
+
+       if (! gda_set_set_holder_value (params, &error, "ts", &ts)) {
+               g_print ("Failed to bind 'ts' parameter: %s\n", error && error->message ? error->message : 
"No detail");
+               g_clear_error (&error);
+               g_object_unref (stmt);
+               g_object_unref (params);
+               nfailed ++;
+               goto endtest;
+       }
+
+       if (! gda_set_set_holder_value (params, &error, "time", &gt)) {
+               g_print ("Failed to bind 'time' parameter: %s\n", error && error->message ? error->message : 
"No detail");
+               g_clear_error (&error);
+               g_object_unref (stmt);
+               g_object_unref (params);
+               nfailed ++;
+               goto endtest;
+       }
+
+       expected = "('@@@@@@@@@@ 17:10:23+2', '16:09:22-3')";
+       if (prov && PROV_CLASS (prov)->statement_to_sql)
+               sql = PROV_CLASS (prov)->statement_to_sql (prov, NULL, stmt, params, 0, NULL, &error);
+       else
+               sql = gda_statement_to_sql_extended (stmt, NULL, params, 0, NULL, &error);
+       if (!sql) {
+               g_print ("Failed to render as SQL: %s\n", error && error->message ? error->message : "No 
detail");
+               g_clear_error (&error);
+               g_object_unref (stmt);
+               g_object_unref (params);
+               nfailed ++;
+               goto endtest;
+       }
+       if (!string_equal_to_template (sql, expected)) {
+               g_print ("Wrong rendered SQL: [%s] instead of [%s]\n", sql, expected);
+               g_object_unref (stmt);
+               g_object_unref (params);
+               g_free (sql);
+               nfailed ++;
+               goto endtest;
+       }
+       g_free (sql);
+
+       expected = "('@@@@@@@@@@ 15:10:23', '19:09:22')";
+       if (prov && PROV_CLASS (prov)->statement_to_sql)
+               sql = PROV_CLASS (prov)->statement_to_sql (prov, NULL, stmt, params, 
GDA_STATEMENT_SQL_TIMEZONE_TO_GMT, NULL, &error);
+       else
+               sql = gda_statement_to_sql_extended (stmt, NULL, params, GDA_STATEMENT_SQL_TIMEZONE_TO_GMT, 
NULL, &error);
+       if (!sql) {
+               g_print ("Failed to render as SQL: %s\n", error && error->message ? error->message : "No 
detail");
+               g_clear_error (&error);
+               g_object_unref (stmt);
+               g_object_unref (params);
+               nfailed ++;
+               goto endtest;
+       }
+       if (!string_equal_to_template (sql, expected)) {
+               g_print ("Wrong rendered SQL for GMT timezone: [%s] instead of [%s]\n", sql, expected);
+               g_object_unref (stmt);
+               g_object_unref (params);
+               g_free (sql);
+               nfailed ++;
+               goto endtest;
+       }
+       g_free (sql);
+
+ endtest:
+       return nfailed;
+}
+
+int
+main (int argc, char** argv)
+{
+       gchar *file;
+       GError *error = NULL;
+       GdaDataModel *model;
+       guint i, nrows;
+       guint nfailed = 0;
+       gda_init ();
+
+       /* generic parser */
+       GdaSqlParser *parser;
+       parser = gda_sql_parser_new ();
+       nfailed += do_a_test (NULL, parser);
+       g_object_unref (parser);
+
+       /* test other parsers only if generic one is Ok */
+       if (nfailed == 0) {
+               model = gda_config_list_providers ();
+               nrows = gda_data_model_get_n_rows (model);
+               for (i = 0; i < nrows; i++) {
+                       const GValue *cvalue;
+                       cvalue = gda_data_model_get_value_at (model, 0, i, NULL);
+                       g_assert (cvalue && (G_VALUE_TYPE (cvalue) == G_TYPE_STRING));
+                       g_print ("Testing database provider '%s'\n", g_value_get_string (cvalue));
+
+                       GdaServerProvider *prov;
+                       prov = gda_config_get_provider (g_value_get_string (cvalue), NULL);
+                       g_assert (prov);
+
+                       GdaSqlParser *parser;
+                       parser = gda_server_provider_create_parser (prov, NULL);
+                       if (!parser)
+                               parser = gda_sql_parser_new ();
+
+                       nfailed += do_a_test (prov, parser);
+                       g_object_unref (parser);
+               }
+               g_object_unref (model);
+       }
+
+       if (nfailed == 0) {
+               g_print ("Ok\n");
+               return EXIT_SUCCESS;
+       }
+       else {
+               g_print ("%u failed\n", nfailed);
+               return EXIT_FAILURE;
+       }
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]