Re: Timestamp Summary - Mailing list pgsql-jdbc

From Mark Lewis
Subject Re: Timestamp Summary
Date
Msg-id 1122314736.11938.64.camel@archimedes
Whole thread Raw
In response to Re: Timestamp Summary  (Mark Lewis <mark.lewis@mir3.com>)
List pgsql-jdbc
Sorry, spoke before I finished thinking.  Ignore this as line noise.
-- Mark

On Mon, 2005-07-25 at 10:16 -0700, Mark Lewis wrote:
> If this code only executes when the connection is established, then it
> will break for any long-lived connection that lives across a change in
> DST.
>
> This could be fixed by moving this code from connection init to just
> before we send any queries to the back end, sort of like:
>
> if(defaultTz.useDaylightTime() != oldUseDaylightTime) {
>   runDSTCorrectionCode();
> }
>
> I don't know how cheap the useDaylightTime() call is, so I'm not sure
> what the performance impact of doing this on every query would be like.
> If it's bad, then maybe we'd need to program in some other way to know
> when DST turns on/off.
>
> On Mon, 2005-07-25 at 10:45 -0600, Christian Cryder wrote:
> > Hey folks,
> >
> > I just wanted to take a few moments and summarize where we are in our
> > Timestamp woes issue. I'm also going to post a piece of code that
> > illustrates some of the problems we've experienced, and then try and
> > explain the conclusions we've arrived at. Hopefully this well help
> > others in the future.
> >
> > First of all, the code snippet to easily duplicate the problem (see
> > the bottom of this email). What this code does is create a table Foo,
> > and then it inserts 4 rows using a SQL Statement. It then turns around
> > and reads those values via JDBC, and then simply re-inserts the values
> > back into the DB. As the example illustrates, the data written is NOT
> > the same as the data read (you can see this by querying the table
> > through the sql console after you've run the example).
> >
> > The key point to note here is that the 4 dates being inserted
> > originally fall immediately around and in the daylight savings cutover
> > (2-3 AM, 1st Sunday of April). So t1 is before daylight savings, t2 is
> > during the rollover (and thus technically not a valid daylight savings
> > time), t3 and t4 are after the rollover.
> >
> > With DST turned on on the client, here's the results in the table...
> > ----------------------------------------------------
> > 101;"2005-04-03 00:06:02"
> > 102;"2005-04-03 02:29:43"
> > 103;"2005-04-03 03:29:43"
> > 104;"2005-04-03 04:35:17"
> >
> > 105;"2005-04-03 00:06:02"
> > 106;"2005-04-03 03:29:43"
> > 107;"2005-04-03 03:29:43"
> > 108;"2005-04-03 04:35:17"
> >
> > This looks pretty good except that the original t2 (02:29) got munged
> > to 03:29 in the DB..
> >
> > With DST programatically turned off on the client, the dates display
> > properly in the client output (no munging), but they still get munged
> > on the server side of things (because there, DST is still turned on) -
> > and there, everything above the cutoff gets adjusted.
> > -----------------------------------------------------
> > 125;"2005-04-03 00:06:02"
> > 126;"2005-04-03 02:29:43"
> > 127;"2005-04-03 03:29:43"
> > 128;"2005-04-03 04:35:17"
> >
> > 129;"2005-04-03 00:06:02"
> > 130;"2005-04-03 03:29:43"
> > 131;"2005-04-03 04:29:43"
> > 132;"2005-04-03 05:35:17"
> >
> > So if we turn off DST on both the client -AND- the server (for me, the
> > server part happens by issuing a "set timezone='etc/gmt+7'" before I
> > use the connection), we finally get what we're looking for - write,
> > read, write, w/ proper values on both display and in the db when we're
> > all done.
> > -----------------------------------------------------
> > 133;"2005-04-03 00:06:02"
> > 134;"2005-04-03 02:29:43"
> > 135;"2005-04-03 03:29:43"
> > 136;"2005-04-03 04:35:17"
> >
> > 137;"2005-04-03 00:06:02"
> > 138;"2005-04-03 02:29:43"
> > 139;"2005-04-03 03:29:43"
> > 140;"2005-04-03 04:35:17"
> >
> > In other words, nothing got munged.
> >
> > At this point, I'd like to submit a piece of code that will ensure
> > that the server is operating in the same time zone as the client
> > connection, taking DST settings into consideration as well (Dave
> > should understand where this would go as he was playing around with
> > this type of thing when helping me debug last week):
> >
> >         //in order to avoid munging dates on insert, we need to set
> > the server to the same timezone
> >         //as the client for the duration of this connection. If
> > daylight savings is turned on, this is
> >         //simple: just send the current timezone; if dst is turned
> > off, however, then we need to send the GMT
> >         //equivalent (w/ no DST offset), which takes the form of
> > etc/gmt+X (where X is the number of hours)
> >         TimeZone tz = TimeZone.getDefault();
> >         int roff = tz.getRawOffset();
> >         if (tz.useDaylightTime()) {
> >             stmt.execute("set timezone='"+tz.getID().toLowerCase()+"'");
> >         } else {
> >             stmt.execute("set timezone='etc/gmt"+(roff<=0 ? "+" :
> > "")+(roff/-3600000)+"'");
> >         }
> >
> > This piece of code only needs to get executed when the connection is
> > first opened to the DB, and from that point on it will ensure that the
> > db is in functionally the same timezone as the server (taking DST
> > on/off into account as well). In other words, if my client is running
> > in MST w/ DST turned off, the server will be configured to etc/gmt+7,
> > and any dates I insert will be left untouched.
> >
> > That make sense?
> >
> > If you'd like to see an example of the strings generated for each
> > timezone, here's a snippet...
> >
> > String zones[] = TimeZone.getAvailableIDs();
> > for (int i=0; i<zones.length; i++) {
> >     TimeZone tz = TimeZone.getTimeZone(zones[i]);
> >     int roff = tz.getRawOffset();
> >     if (!tz.useDaylightTime()) {
> >         System.out.println("server: 'etc/gmt"+(roff<=0 ? "+" :
> > "")+(roff/-3600000)+"' ... "+i+": "+tz);
> >     } else {
> >         System.out.println("server: '"+tz.getID().toLowerCase()+"' ...
> > "+i+": "+tz);
> >     }
> > }
> >
> > Ok, so here's the summary:
> >
> > a) w/ DST turned on on the client, the client munges t2 forward to a
> > valid time and inserts into the db (so we read 02:29 and write 03:29)
> >
> > b) w/ DST turned off on the client, the client does not munge t2, and
> > neither does the db because its been tweaked to match (so we read
> > 02:29 and write 02:29)
> >
> > So if I want to insert dates without munging, all I have to do is turn
> > DST off in the client (and the example below illustrates how to do
> > this for the current timezone).
> >
> > I'd like to request that we apply this patch to the JDBC drivers
> > (again, Dave will know where to put it). This should not break any
> > existing code, and it will make it possible to ensure that client code
> > can insert dates without munging, and without forcing DST to be turned
> > off systemwide on the server - it basically allows us to drive the
> > setting on a per connection basis.
> >
> > Any feedback?
> >
> > Thanks,
> > Christian
> >
> > (and thanks especially to Dave for helping me debug all this stuff
> > over the past 10 days)
> >
> > -------------------------------------------------
> > Code snippet to duplicate problem
> > -------------------------------------------------
> >     //see what time zone we are running in (and optionally turn off DST)
> >     TimeZone curTz = TimeZone.getDefault();
> >     TimeZone curTzNoDST = new SimpleTimeZone(curTz.getRawOffset(),
> > curTz.getID());
> > //uncomment the line below to see what happens w/ DST turned off
> > //    TimeZone.setDefault(curTzNoDST);   //this will turn off DST in
> > the local JVM
> >     System.out.println("current tz:"+TimeZone.getDefault());
> >
> >     //here we go...
> >     Connection conn = null;
> >     Statement stmt = null;
> >     PreparedStatement pstmt = null;
> >     Timestamp t = null;
> >     Calendar cal = Calendar.getInstance();
> >     boolean usepstmt = true;
> >     try {
> >         conn = ds.getConnection();
> >         stmt = conn.createStatement();
> >         pstmt = conn.prepareStatement("INSERT INTO Foo (TrxTime) VALUES (?)");
> >
> >         //drop, create the table
> >         try {stmt.execute("TRUNCATE TABLE Foo");}
> >         catch (SQLException e) {stmt.execute("CREATE TABLE Foo (UID
> > SERIAL, TrxTime timestamp without time zone NOT NULL);");}
> >
> >         //in order to avoid munging dates on insert, we need to set
> > the server to the same timezone
> >         //as the client for the duration of this connection. If
> > daylight savings is turned on, this is
> >         //simple: just send the current timezone; if dst is turned
> > off, however, then we need to send the GMT
> >         //equivalent (w/ no DST offset), which takes the form of
> > etc/gmt+X (where X is the number of hours)
> >         TimeZone tz = TimeZone.getDefault();
> >         int roff = tz.getRawOffset();
> >         if (tz.useDaylightTime()) {
> >             stmt.execute("set timezone='"+tz.getID().toLowerCase()+"'");
> >         } else {
> >             stmt.execute("set timezone='etc/gmt"+(roff<=0 ? "+" :
> > "")+(roff/-3600000)+"'");
> >         }
> >
> >         //now start with raw inserts (via statement) - these will
> > correspond to the 1st 3 rows of data in the db
> >         System.out.println();
> >         stmt.execute("INSERT INTO Foo (TrxTime) VALUES ('2005-04-03
> > 00:06:02.0')");
> >         stmt.execute("INSERT INTO Foo (TrxTime) VALUES ('2005-04-03
> > 02:29:43.0')");
> >         stmt.execute("INSERT INTO Foo (TrxTime) VALUES ('2005-04-03
> > 03:29:43.0')");
> >         stmt.execute("INSERT INTO Foo (TrxTime) VALUES ('2005-04-03
> > 04:35:17.0')");
> >         System.out.println("insert-1 t1: 2005-04-03 00:06:02.0
> > (millis: "+Timestamp.valueOf("2005-04-03 00:06:02.0").getTime()+")");
> >         System.out.println("insert-1 t2: 2005-04-03 02:29:43.0
> > (millis: "+Timestamp.valueOf("2005-04-03 02:29:43.0").getTime()+")");
> >         System.out.println("insert-1 t3: 2005-04-03 03:29:43.0
> > (millis: "+Timestamp.valueOf("2005-04-03 03:29:43.0").getTime()+")");
> >         System.out.println("insert-1 t4: 2005-04-03 04:35:17.0
> > (millis: "+Timestamp.valueOf("2005-04-03 04:35:17.0").getTime()+")");
> >
> >         //now read the table, getting the data we just inserted (in
> > your output, you will notice the timestamps differ)
> >         System.out.println();
> >         ResultSet rs = stmt.executeQuery("SELECT * FROM Foo");
> >         List rows = new ArrayList();
> >         int cntr = 0;
> >         while (rs.next()) {
> >             Object uid = rs.getObject(1);
> >             t = rs.getTimestamp(2);
> >             System.out.println("result-1 t"+(++cntr)+": "+t+" (millis:
> > "+t.getTime()+")");
> >             rows.add(new Object[] {uid, t});
> >         }
> >         rs.close();
> >
> >         //now iterate through the sample data and re-insert
> >         System.out.println();
> >         Iterator it = rows.iterator();
> >         cntr = 0;
> >         while (it.hasNext()) {
> >             Object[] cols = (Object[]) it.next();
> >             t = (Timestamp) cols[1];
> >             pstmt.setTimestamp(1, t);
> >             System.out.println("insert-2 t"+(++cntr)+": "+t+" (millis:
> > "+t.getTime()+")");
> >             pstmt.executeUpdate();
> >         }
> >
> >         //now read the values back out (here we'll get all 6 values
> > out; the last 3 correspond to 2nd set of inserts)
> >         System.out.println();
> >         rs = stmt.executeQuery("SELECT * FROM Foo");
> >         cntr = 0;
> >         while (rs.next()) {
> >             t = rs.getTimestamp(2);
> >             System.out.println("result-2 t"+(++cntr)+": "+t+" (millis:
> > "+t.getTime()+")");
> >         }
> >         rs.close();
> >
> >     } catch (SQLException e) {
> >         System.out.println("Unexpected SQLException: "+e);
> >         e.printStackTrace();
> >
> >     } finally {
> >         if (stmt!=null) try {stmt.close();} catch (SQLException e) {}
> >         if (pstmt!=null) try {pstmt.close();} catch (SQLException e) {}
> >         if (conn!=null) try {conn.close();} catch (SQLException e) {}
> >     }
> >
> > ---------------------------(end of broadcast)---------------------------
> > TIP 2: Don't 'kill -9' the postmaster
>
>
> ---------------------------(end of broadcast)---------------------------
> TIP 6: explain analyze is your friend


pgsql-jdbc by date:

Previous
From: Dave Cramer
Date:
Subject: Re: Timestamp Summary
Next
From: "Kevin Grittner"
Date:
Subject: Re: Timestamp Summary