Subject | AW: AW: AW: AW: [Firebird-Java] Timezones |
---|---|
Author | Steffen Heil |
Post date | 2009-11-05T14:53:34Z |
Hi
Java(TM) SE Runtime Environment
1.6.0_16-b01
Date Sun Oct 25 02:30:00 CET 2009 : 1256434200641 == 1256434200641
Date Sun Oct 25 02:30:00 CET 2009 : 1256434200641 == 1256434200641
Date Sun Oct 25 02:30:00 CET 2009 : 1256434200641 == 1256437800641
Propably you fixed some things after copy'n'paste?
This seems incorrect:
java.util.Date timeDstLocal = localCalendar.getTime();
This fixed the second line:
java.util.Date timeDstLocal = localDstCalendar.getTime();
Resulting in:
Date Sun Oct 25 02:30:00 CET 2009 : 1256434200741 == 1256434200741
Date Sun Oct 25 03:30:00 CET 2009 : 1256437800773 == 1256437800773
Date Sun Oct 25 02:30:00 CET 2009 : 1256434200741 == 1256437800741
Also for future testing, I changed the first line to:
Calendar localCalendar = new GregorianCalendar(
TimeZone.getTimeZone( "CET" ) );
(Which makes no difference "right now" and "right here".)
So the first line shows there in "usual" cases, jaybird works.
The second line shows my problem: There are cases, where the encoding is not
bijective.
I am unsure about the third line. I never used those "translation"
functions. Are those required any more? (As the datetime class now has a
Calenar in it's own...)
Additionally I tried to understand the function of "isInvertTimeZone", which
was used earlier in some places, but is not used any more except in
FBStringField. Maybe this needs changes, too? But I don't even really
understand it's usage.
Firebird would be two days a year for 21 hour incorrect and very likely will
cause an error for the last hour - the number of seconds since day start
would tell that we have a 25th hour, but that does not fit the domain
definition of the TS datatype.
You are right on this point.
However this pushes me even more into forcing the interpretation of
timestamps as UTC (or if really required any other timezone WITHOUT DST).
If I think about it, with different timezones (or even in one timezone
with/without DST) days don't start at the same point in time.
But that's what I've timestamps always seen as - a notion of a fixed point
in time. No matter what notation is used for printing...
I guess a lot of other users do the same.
not want to have such code in the driver - it is hard to maintain.
As a failsafe solution, I am planning on placing the following to the
startup class of my web application:
System.setProperty( "user.timezone", "UTC" );
if ( !TimeZone.getDefault().getID().equals( "UTC" ) )
System.exit( 1 );
A rather hard solution, but it ensures the correctness of database access.
(Yes, I do NOT like this way either.)
Reading more in XSQLVAR I would like to suggest, not to call the following
"every time":
Calendar.getInstance().getTimeZone().getRawOffset()
Instead have a static member calling this once using a static initializer.
Calender.getInstance() is a rather expensive function.
I thought about implementing my own getTimestamp function but I cannot get a
timestamp using ResultSet.getBytes().
For my former idea to have a switch to always use UTC for persistence I have
prepared some very simple converting functions [see attachment], which are
by the way much faster than working with calendars. Internally Timestamp is
a thin wrapper around Date and Date is a thin wrapper around long...
I just have no idea where to place such a switch and where to place the
condition selection between the default implementations and the UTC
implementation.
Best regards,
Steffen
BTW: I have just seen the jaybird wiki for the first time. However it seems
broken:
Writing
/var/domains/jaybird.rokytskyy.de/web/htdocs/jaybird/data/cache/a/ad75f108c7
1f990574b15250201efe6b.xhtml failed
----------
### Eclipse Workspace Patch 1.0
#P client-java
Index: src/main/org/firebirdsql/gds/XSQLVAR.java
===================================================================
RCS file: /cvsroot/firebird/client-java/src/main/org/firebirdsql/gds/XSQLVAR.java,v
retrieving revision 1.28
diff -u -r1.28 XSQLVAR.java
--- src/main/org/firebirdsql/gds/XSQLVAR.java 5 Nov 2009 09:12:39 -0000 1.28
+++ src/main/org/firebirdsql/gds/XSQLVAR.java 5 Nov 2009 14:55:30 -0000
@@ -72,6 +72,17 @@
public XSQLVAR() {
}
+
+ static int days( long time )
+ {
+ return (int) ( time < 0 ? ( time + 1 ) / 86400000l - 1 : time / 86400000l );
+ }
+
+ static int millis( long time )
+ {
+ return (int) ( ( time < 0 ? time + 5937362789990400000l : time ) % 86400000l );
+ }
+
/**
* Get a deep copy of this object.
*
@@ -162,6 +173,36 @@
}
/**
+ * Decode a part of a <code>byte</code> array into an <code>int</code> value.
+ *
+ * @param byte_int The <code>byte</code> array to be decoded
+ * @param offset The position in the byte array to start decoding at
+ * @return The <code>int</code> value of the decoded
+ * <code>byte</code> array
+ */
+ public int decodeInt(byte[] byte_int, int offset){
+ int b1 = byte_int[offset+0]&0xFF;
+ int b2 = byte_int[offset+1]&0xFF;
+ int b3 = byte_int[offset+2]&0xFF;
+ int b4 = byte_int[offset+3]&0xFF;
+ return ((b1 << 24) + (b2 << 16) + (b3 << 8) + (b4 << 0));
+ }
+
+ /**
+ * Encode an <code>int</code> value into a <code>byte</code> array.
+ *
+ * @param value The value to be encoded
+ * @param byte_int The byte array to encode the value into
+ * @param offset The position in the byte array to start encoding at
+ */
+ public void encodeInt(int value, byte[] byte_int, int offset){
+ byte_int[offset+0] = (byte) ((value >>> 24) & 0xff);
+ byte_int[offset+1] = (byte) ((value >>> 16) & 0xff);
+ byte_int[offset+2] = (byte) ((value >>> 8) & 0xff);
+ byte_int[offset+3] = (byte) ((value >>> 0) & 0xff);
+ }
+
+ /**
* Decode a <code>byte</code> array into an <code>int</code> value.
*
* @param byte_int The <code>byte</code> array to be decoded
@@ -391,6 +432,14 @@
return encodeTimestampCalendar(value, new GregorianCalendar());
}
+ public byte[] encodeTimestampUTC(Timestamp value){
+ byte[] result = new byte[8];
+ long time = value.getTime();
+ encodeInt(days(time),result,0);
+ encodeInt(millis(time)*10,result,4);
+ return result;
+ }
+
public byte[] encodeTimestampCalendar(Timestamp value, Calendar c){
// note, we cannot simply pass millis to the database, because
@@ -461,7 +510,12 @@
public Timestamp decodeTimestamp(byte[] byte_int){
return decodeTimestampCalendar(byte_int, new GregorianCalendar());
}
- public Timestamp decodeTimestampCalendar(byte[] byte_int, Calendar c){
+
+ public Timestamp decodeTimestampUTC(byte[] byte_int){
+ return new Timestamp(decodeInt(byte_int,0)*86400000l+decodeInt(byte_int,4)/10);
+ }
+
+ public Timestamp decodeTimestampCalendar(byte[] byte_int, Calendar c){
if (byte_int.length != 8)
@@ -514,11 +568,15 @@
return encodeTimeCalendar(d, new GregorianCalendar());
}
- public byte[] encodeTimeCalendar(Time d, Calendar c) {
-
- datetime dt = new datetime(d, c);
- return dt.toTimeBytes();
- }
+ public byte[] encodeTimeUTC(Time d) {
+ return encodeInt(millis(d.getTime())*10);
+ }
+
+ public byte[] encodeTimeCalendar(Time d, Calendar c) {
+
+ datetime dt = new datetime(d, c);
+ return dt.toTimeBytes();
+ }
/**
@@ -552,9 +610,14 @@
public Time decodeTime(byte[] int_byte) {
return decodeTimeCalendar(int_byte, new GregorianCalendar());
}
- public Time decodeTimeCalendar(byte[] int_byte, Calendar c) {
- datetime dt = new datetime(null,int_byte);
- return dt.toTime(c);
+
+ public Time decodeTimeUTC(byte[] int_byte) {
+ return new Time(decodeInt(int_byte)/10);
+ }
+
+ public Time decodeTimeCalendar(byte[] int_byte, Calendar c) {
+ datetime dt = new datetime(null,int_byte);
+ return dt.toTime(c);
}
@@ -587,7 +650,12 @@
public byte[] encodeDate(Date d) {
return encodeDateCalendar(d, new GregorianCalendar());
}
- public byte[] encodeDateCalendar(Date d, Calendar c) {
+
+ public byte[] encodeDateUTC(Date d) {
+ return encodeInt(days(d.getTime()));
+ }
+
+ public byte[] encodeDateCalendar(Date d, Calendar c) {
datetime dt = new datetime(d, c);
return dt.toDateBytes();
}
@@ -621,7 +689,12 @@
public Date decodeDate(byte[] byte_int) {
return decodeDateCalendar(byte_int, new GregorianCalendar());
}
- public Date decodeDateCalendar(byte[] byte_int, Calendar c) {
+
+ public Date decodeDateUTC(byte[] byte_int) {
+ return new Date(decodeInt(byte_int)*86400000l);
+ }
+
+ public Date decodeDateCalendar(byte[] byte_int, Calendar c) {
datetime dt = new datetime(byte_int, null);
return dt.toDate(c);
}
[Non-text portions of this message have been removed]
> ... Output is:Strange. Mine differs:
> Date Sun Oct 25 02:30:00 CET 2009 : 1256434200562 == 1256434200562
> Date Sun Oct 25 03:30:00 CET 2009 : 1256437800572 == 1256437800572
> Date Sun Oct 25 02:30:00 CET 2009 : 1256434200562 == 1256437800562
Java(TM) SE Runtime Environment
1.6.0_16-b01
Date Sun Oct 25 02:30:00 CET 2009 : 1256434200641 == 1256434200641
Date Sun Oct 25 02:30:00 CET 2009 : 1256434200641 == 1256434200641
Date Sun Oct 25 02:30:00 CET 2009 : 1256434200641 == 1256437800641
Propably you fixed some things after copy'n'paste?
This seems incorrect:
java.util.Date timeDstLocal = localCalendar.getTime();
This fixed the second line:
java.util.Date timeDstLocal = localDstCalendar.getTime();
Resulting in:
Date Sun Oct 25 02:30:00 CET 2009 : 1256434200741 == 1256434200741
Date Sun Oct 25 03:30:00 CET 2009 : 1256437800773 == 1256437800773
Date Sun Oct 25 02:30:00 CET 2009 : 1256434200741 == 1256437800741
Also for future testing, I changed the first line to:
Calendar localCalendar = new GregorianCalendar(
TimeZone.getTimeZone( "CET" ) );
(Which makes no difference "right now" and "right here".)
So the first line shows there in "usual" cases, jaybird works.
The second line shows my problem: There are cases, where the encoding is not
bijective.
I am unsure about the third line. I never used those "translation"
functions. Are those required any more? (As the datetime class now has a
Calenar in it's own...)
Additionally I tried to understand the function of "isInvertTimeZone", which
was used earlier in some places, but is not used any more except in
FBStringField. Maybe this needs changes, too? But I don't even really
understand it's usage.
> Yes, but we get problem with DST. Firebird knows nothing about DST, so ifI would count number of seconds since day start, timestamps stored in
Firebird would be two days a year for 21 hour incorrect and very likely will
cause an error for the last hour - the number of seconds since day start
would tell that we have a 25th hour, but that does not fit the domain
definition of the TS datatype.
You are right on this point.
However this pushes me even more into forcing the interpretation of
timestamps as UTC (or if really required any other timezone WITHOUT DST).
If I think about it, with different timezones (or even in one timezone
with/without DST) days don't start at the same point in time.
But that's what I've timestamps always seen as - a notion of a fixed point
in time. No matter what notation is used for printing...
I guess a lot of other users do the same.
> > Maybe there could be a global parameter to tell jaybird to use a utc inany place?
> Do not know yet. Issue is that it seems that in this case one would needto use completely different routines to encode/decode timestamps and I do
not want to have such code in the driver - it is hard to maintain.
As a failsafe solution, I am planning on placing the following to the
startup class of my web application:
System.setProperty( "user.timezone", "UTC" );
if ( !TimeZone.getDefault().getID().equals( "UTC" ) )
System.exit( 1 );
A rather hard solution, but it ensures the correctness of database access.
(Yes, I do NOT like this way either.)
Reading more in XSQLVAR I would like to suggest, not to call the following
"every time":
Calendar.getInstance().getTimeZone().getRawOffset()
Instead have a static member calling this once using a static initializer.
Calender.getInstance() is a rather expensive function.
I thought about implementing my own getTimestamp function but I cannot get a
timestamp using ResultSet.getBytes().
For my former idea to have a switch to always use UTC for persistence I have
prepared some very simple converting functions [see attachment], which are
by the way much faster than working with calendars. Internally Timestamp is
a thin wrapper around Date and Date is a thin wrapper around long...
I just have no idea where to place such a switch and where to place the
condition selection between the default implementations and the UTC
implementation.
Best regards,
Steffen
BTW: I have just seen the jaybird wiki for the first time. However it seems
broken:
Writing
/var/domains/jaybird.rokytskyy.de/web/htdocs/jaybird/data/cache/a/ad75f108c7
1f990574b15250201efe6b.xhtml failed
----------
### Eclipse Workspace Patch 1.0
#P client-java
Index: src/main/org/firebirdsql/gds/XSQLVAR.java
===================================================================
RCS file: /cvsroot/firebird/client-java/src/main/org/firebirdsql/gds/XSQLVAR.java,v
retrieving revision 1.28
diff -u -r1.28 XSQLVAR.java
--- src/main/org/firebirdsql/gds/XSQLVAR.java 5 Nov 2009 09:12:39 -0000 1.28
+++ src/main/org/firebirdsql/gds/XSQLVAR.java 5 Nov 2009 14:55:30 -0000
@@ -72,6 +72,17 @@
public XSQLVAR() {
}
+
+ static int days( long time )
+ {
+ return (int) ( time < 0 ? ( time + 1 ) / 86400000l - 1 : time / 86400000l );
+ }
+
+ static int millis( long time )
+ {
+ return (int) ( ( time < 0 ? time + 5937362789990400000l : time ) % 86400000l );
+ }
+
/**
* Get a deep copy of this object.
*
@@ -162,6 +173,36 @@
}
/**
+ * Decode a part of a <code>byte</code> array into an <code>int</code> value.
+ *
+ * @param byte_int The <code>byte</code> array to be decoded
+ * @param offset The position in the byte array to start decoding at
+ * @return The <code>int</code> value of the decoded
+ * <code>byte</code> array
+ */
+ public int decodeInt(byte[] byte_int, int offset){
+ int b1 = byte_int[offset+0]&0xFF;
+ int b2 = byte_int[offset+1]&0xFF;
+ int b3 = byte_int[offset+2]&0xFF;
+ int b4 = byte_int[offset+3]&0xFF;
+ return ((b1 << 24) + (b2 << 16) + (b3 << 8) + (b4 << 0));
+ }
+
+ /**
+ * Encode an <code>int</code> value into a <code>byte</code> array.
+ *
+ * @param value The value to be encoded
+ * @param byte_int The byte array to encode the value into
+ * @param offset The position in the byte array to start encoding at
+ */
+ public void encodeInt(int value, byte[] byte_int, int offset){
+ byte_int[offset+0] = (byte) ((value >>> 24) & 0xff);
+ byte_int[offset+1] = (byte) ((value >>> 16) & 0xff);
+ byte_int[offset+2] = (byte) ((value >>> 8) & 0xff);
+ byte_int[offset+3] = (byte) ((value >>> 0) & 0xff);
+ }
+
+ /**
* Decode a <code>byte</code> array into an <code>int</code> value.
*
* @param byte_int The <code>byte</code> array to be decoded
@@ -391,6 +432,14 @@
return encodeTimestampCalendar(value, new GregorianCalendar());
}
+ public byte[] encodeTimestampUTC(Timestamp value){
+ byte[] result = new byte[8];
+ long time = value.getTime();
+ encodeInt(days(time),result,0);
+ encodeInt(millis(time)*10,result,4);
+ return result;
+ }
+
public byte[] encodeTimestampCalendar(Timestamp value, Calendar c){
// note, we cannot simply pass millis to the database, because
@@ -461,7 +510,12 @@
public Timestamp decodeTimestamp(byte[] byte_int){
return decodeTimestampCalendar(byte_int, new GregorianCalendar());
}
- public Timestamp decodeTimestampCalendar(byte[] byte_int, Calendar c){
+
+ public Timestamp decodeTimestampUTC(byte[] byte_int){
+ return new Timestamp(decodeInt(byte_int,0)*86400000l+decodeInt(byte_int,4)/10);
+ }
+
+ public Timestamp decodeTimestampCalendar(byte[] byte_int, Calendar c){
if (byte_int.length != 8)
@@ -514,11 +568,15 @@
return encodeTimeCalendar(d, new GregorianCalendar());
}
- public byte[] encodeTimeCalendar(Time d, Calendar c) {
-
- datetime dt = new datetime(d, c);
- return dt.toTimeBytes();
- }
+ public byte[] encodeTimeUTC(Time d) {
+ return encodeInt(millis(d.getTime())*10);
+ }
+
+ public byte[] encodeTimeCalendar(Time d, Calendar c) {
+
+ datetime dt = new datetime(d, c);
+ return dt.toTimeBytes();
+ }
/**
@@ -552,9 +610,14 @@
public Time decodeTime(byte[] int_byte) {
return decodeTimeCalendar(int_byte, new GregorianCalendar());
}
- public Time decodeTimeCalendar(byte[] int_byte, Calendar c) {
- datetime dt = new datetime(null,int_byte);
- return dt.toTime(c);
+
+ public Time decodeTimeUTC(byte[] int_byte) {
+ return new Time(decodeInt(int_byte)/10);
+ }
+
+ public Time decodeTimeCalendar(byte[] int_byte, Calendar c) {
+ datetime dt = new datetime(null,int_byte);
+ return dt.toTime(c);
}
@@ -587,7 +650,12 @@
public byte[] encodeDate(Date d) {
return encodeDateCalendar(d, new GregorianCalendar());
}
- public byte[] encodeDateCalendar(Date d, Calendar c) {
+
+ public byte[] encodeDateUTC(Date d) {
+ return encodeInt(days(d.getTime()));
+ }
+
+ public byte[] encodeDateCalendar(Date d, Calendar c) {
datetime dt = new datetime(d, c);
return dt.toDateBytes();
}
@@ -621,7 +689,12 @@
public Date decodeDate(byte[] byte_int) {
return decodeDateCalendar(byte_int, new GregorianCalendar());
}
- public Date decodeDateCalendar(byte[] byte_int, Calendar c) {
+
+ public Date decodeDateUTC(byte[] byte_int) {
+ return new Date(decodeInt(byte_int)*86400000l);
+ }
+
+ public Date decodeDateCalendar(byte[] byte_int, Calendar c) {
datetime dt = new datetime(byte_int, null);
return dt.toDate(c);
}
[Non-text portions of this message have been removed]