Thread: ResultSet.getTimestamp(Calendar) off by one-hour

ResultSet.getTimestamp(Calendar) off by one-hour

From
Roland Roberts
Date:
I'm using Hiberate and JBoss with PostgreSQL as my backend.  I have a
column which is set by a database trigger

CREATE FUNCTION security_biur_trg() RETURNS trigger AS $$
    BEGIN
        -- Check that empname and salary are given
        -- Remember who changed the payroll when

        NEW.active_on := CURRENT_TIMESTAMP at time zone 'UTC';
        RETURN NEW;
    END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER security_bur BEFORE INSERT OR UPDATE
    ON drpg.security FOR EACH ROW
    EXECUTE PROCEDURE security_biur_trg();

I'm running this in New York, USA where the current offset is -4 hours
(daylight savings in effect).  After the insert of a row, I can query
and confirm that the timestamp is +4 hours from my local time.  But,
when I query to retrieve a row, I end up with a timestamp which is -1
from local.  The query looks like this:

        public Object nullSafeGet(ResultSet rs, String[] names, Object
owner) throws SQLException {
            Calendar utcCalendar = (Calendar) UTC_CALENDAR.clone();
            return rs.getTimestamp(names[0], utcCalendar);
        }

where UTC_CALENDAR is intialized like so

    private static final TimeZone TZ_UTC;
    private static final Calendar UTC_CALENDAR;
    static {
        TZ_UTC = TimeZone.getTimeZone("UTC");
        UTC_CALENDAR = Calendar.getInstance(TZ_UTC);
    }

If I drop the Calendar from the getTimestamp, I get exactly what is in
the database, the actual insert time +4 hours, as expected.  But with
the Calendar, I'm getting what I would expect during standard time, not
DST.  Since the inserted date is today's date and today is DST, this is
simply wrong.

I'm trying to figure out if I'm missing something here or if this is a
real bug.  Can someone comment?  Is there something else I need to look at?

roland

--
               PGP Key ID: 66 BC 3B CD
Roland B. Roberts, PhD                             RL Enterprises
roland@rlenter.com                            6818 Madeline Court
roland@astrofoto.org                           Brooklyn, NY 11220


Re: ResultSet.getTimestamp(Calendar) off by one-hour

From
Roland Roberts
Date:
Okay, postgresql appears to be using the standard TZ offset regardless
of whether or not DST is in effect on the day in question.

I'm using these versions:

    297 roland> rpm -q postgresql-server postgresql-jdbc
    postgresql-server-8.3.6-1.fc10.i386
    postgresql-jdbc-8.3.603-1.1.fc10.i386

with Sun's java 1.6.12 on Fedora Core 10.

The server and client are on the same host.  I've eliminated all the
intermediate layers  of JBoss and Hibernate and written a simple, small
program that inserts a row then pulls it back from the database.  I
explicitly set the Timstamp value in the code.  Here's the code:

    import java.sql.*;
    import java.util.Calendar;
    import java.util.TimeZone;

    public class PgTest {
        private static final TimeZone TZ_UTC = TimeZone.getTimeZone("UTC");
        private static final Calendar UTC_CALENDAR =
Calendar.getInstance(TZ_UTC);

        public PgTest() {}

        public static void main(String [] args)
            throws SQLException, ClassNotFoundException,
                   IllegalAccessException, InstantiationException {

            String database = args[0];
            String username = (args.length > 1) ? args[1] : null;
            String password = (args.length > 2) ? args[2] : null;

            Class.forName("org.postgresql.Driver").newInstance();
            String url = "jdbc:postgresql:" + database;
            Connection conn = DriverManager.getConnection(url, username,
password);
            doInsert(conn);
            conn.close();

            conn = DriverManager.getConnection(url, username, password);
            doQuery(conn);
            conn.close();
        }

        public static void doInsert(Connection conn)
            throws SQLException{
            Calendar now = Calendar.getInstance();
            now.set(Calendar.YEAR, 2009);
            now.set(Calendar.MONTH, Calendar.MARCH);
            now.set(Calendar.DATE, 13);
            now.set(Calendar.HOUR_OF_DAY, 10);
            now.set(Calendar.MINUTE, 0);
            now.set(Calendar.SECOND, 0);
            now.set(Calendar.MILLISECOND, 0);

            Timestamp ts = new Timestamp(now.getTimeInMillis());
            System.out.println("timestamp is " + ts);

            PreparedStatement insert
                = conn.prepareStatement("INSERT INTO mytable (mytime)
VALUES ( ? )");
            Calendar cal = (Calendar) UTC_CALENDAR.clone();

            insert.setTimestamp(1, ts, cal);

            insert.execute();
            conn.commit();
        }

        public static void doQuery(Connection conn)
            throws SQLException {

            PreparedStatement query
                = conn.prepareStatement("SELECT mytime FROM mytable "
                                        + " WHERE id = (SELECT MAX(id)
FROM mytable)");
            ResultSet rs = query.executeQuery();
            rs.next();
            Calendar cal = (Calendar) UTC_CALENDAR.clone();
            Timestamp ts = rs.getTimestamp(1, cal);
            System.out.println("timestamp is " + ts);
            conn.commit();

        }
    }

And here is the table definition:

    create table mytable (id serial, mytime timestamp);

And here's what I get when I run it:

    305 roland> javac PgTest.java306 roland> java -cp
.:/usr/share/java/postgresql-jdbc.jar PgTest roland roland
    timestamp is 2009-03-13 10:00:00.0
    timestamp is 2009-03-13 10:00:00.0

And here is what is in the database:

    roland=# select * from mytable;
     id |       mytime
    ----+---------------------
     12 | 2009-03-13 15:00:00
    (1 row)

My system clock on this host is set to UTC, but the location is
correctly set as America/New_York, so the date shows correctly for all
application, including CURRENT_TIMESTAMP for postgresql.  But as you can
see, it added 5 to the local time to get UTC instead of 4 as it should have.

I assume this is a bug unless someone can tell me where I've gone wrong....

roland

--
               PGP Key ID: 66 BC 3B CD
Roland B. Roberts, PhD                             RL Enterprises
roland@rlenter.com                            6818 Madeline Court
roland@astrofoto.org                           Brooklyn, NY 11220


Re: ResultSet.getTimestamp(Calendar) off by one-hour

From
Roland Roberts
Date:
Roland Roberts wrote:
> Okay, postgresql appears to be using the standard TZ offset regardless
> of whether or not DST is in effect on the day in question.
Hmmm, I *can* get the correct behavior IF I assign the environment
variable TZ=America/New_York before I run the client program.  But I
don't need to do that when I'm talking to Oracle.  For the Oracle case,
the database is on another host, both still in US/Eastern time zone.

Are there other ways to get PostgreSQL JDBC to understand the client
timezone w/o explicitly setting TZ?  I'm trying to find an idiot-proof
(well, I'll settle for an idiot-resistant) method.

roland

--
               PGP Key ID: 66 BC 3B CD
Roland B. Roberts, PhD                             RL Enterprises
roland@rlenter.com                            6818 Madeline Court
roland@astrofoto.org                           Brooklyn, NY 11220


Re: ResultSet.getTimestamp(Calendar) off by one-hour

From
Tom Lane
Date:
Roland Roberts <roland@astrofoto.org> writes:
> And here is what is in the database:

>     roland=# select * from mytable;
>      id |       mytime
>     ----+---------------------
>      12 | 2009-03-13 15:00:00
>     (1 row)

Apparently you're using a timestamp WITHOUT time zone column to store
the data.  Not recommended if you are worried about timezone effects,
since by definition the apparent value depends on caller's timezone
context.  Use timestamp WITH time zone and see if it gets better.

            regards, tom lane

Re: ResultSet.getTimestamp(Calendar) off by one-hour

From
Roland Roberts
Date:
Tom Lane wrote:
> Apparently you're using a timestamp WITHOUT time zone column to store
> the data.  Not recommended if you are worried about timezone effects,
> since by definition the apparent value depends on caller's timezone
> context.  Use timestamp WITH time zone and see if it gets better.
>
Ultimately, I need something that will be (mostly) compatible across
different databases.  The application will be using Hibernate and
switching datasources will be routine, especially as part of the testing
process.  To that end, I'm using a user defined type with Hibernate to
intercept the database access and store and retrieve times in UTC
regardless of the local JVM time.

I can certainly try timestamp with time zone, but the plan is to deploy
with a column that does not include time zone because all times are
supposed to be stored in UTC.  And clients WILL be connecting from other
time zones.

Based on the tests I just ran, it looks like the JVM doesn't understand
the local time zone.  Although my host sure seems to know that the zone
is America/New_York, the JVM is telling me it is GMT-05:00.  I'm not
sure why, but it doesn't really look like a postgresql problem at this
point.

roland

--
               PGP Key ID: 66 BC 3B CD
Roland B. Roberts, PhD                             RL Enterprises
roland@rlenter.com                            6818 Madeline Court
roland@astrofoto.org                           Brooklyn, NY 11220


Re: ResultSet.getTimestamp(Calendar) off by one-hour

From
Roland Roberts
Date:
Roland Roberts wrote:
> Roland Roberts wrote:
>> Okay, postgresql appears to be using the standard TZ offset
>> regardless of whether or not DST is in effect on the day in question.
> Hmmm, I *can* get the correct behavior IF I assign the environment
> variable TZ=America/New_York before I run the client program.  But I
> don't need to do that when I'm talking to Oracle.  For the Oracle
> case, the database is on another host, both still in US/Eastern time
> zone.
>
> Are there other ways to get PostgreSQL JDBC to understand the client
> timezone w/o explicitly setting TZ?  I'm trying to find an idiot-proof
> (well, I'll settle for an idiot-resistant) method.
Blah.  It looks like this is actually a known Java bug,
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6456628

Although the bug was posted in August 2006(!) against Java 1.5, the most
recent comment is from Aug 2008 still complaining that it is not fixed.
I don't know how Java comes up with the GMT-05:00, but changing
/etc/sysconfig/clock from ZONE="America/New York" to
ZONE="America/New_York" (Java naming convention with the underscore) is
sufficient to get everything to work although when I now include

    System.out.println("default TZ=" +
TimeZone.getDefault().getDisplayName());

in my code, it prints

    default TZ=Eastern Standard Time

which is a lie, its Eastern Daylight Time.  In spite of that, the
correct value is inserted into PostgreSQL.

roland

--
               PGP Key ID: 66 BC 3B CD
Roland B. Roberts, PhD                             RL Enterprises
roland@rlenter.com                            6818 Madeline Court
roland@astrofoto.org                           Brooklyn, NY 11220


Re: ResultSet.getTimestamp(Calendar) off by one-hour

From
Tom Lane
Date:
Roland Roberts <roland@astrofoto.org> writes:
> I can certainly try timestamp with time zone, but the plan is to deploy
> with a column that does not include time zone because all times are
> supposed to be stored in UTC.

Hm, do you have the server's TimeZone setting set to UTC?  Maybe the
JDBC driver is overriding that?

            regards, tom lane