Posted by: Serge Rielau
In my job I deal daily with colleagues, customers and partners around the world and even by family is spread out across three continents.
So dealing with timezones is familiar. But things got even more complicated when a few years back Europe and the US diverged on dated when to switch to and from DST.
I'm certainly not special and any global application needs to be able to translate time stamps from one timezone to another while obeying the latest rules
(such as jumping forward an entire day in Samoa at the end of 2011!)
For SQL and DB2 luckily we are not forced to keep track of these rules. Instead we can refer to readily available libraries to expose timezone mapping.
Below I provide timezone mapping using Java.
Let's get started:
- Ensure javac the java compiler is on your path.
It's normally on sqllib\java\jdk\bin
- Create a java file named TIMEZONEUDF.java
import java.lang.*; // for String class
import COM.ibm.db2.app.*; // UDF and associated classes
import java.sql.*;
import java.math.*;
import java.io.*;
import java.util.TimeZone;
public class TIMEZONEUDF extends UDF
{
public static int tzOffset(String timeZoneID, Timestamp tstamp)
throws Exception
{
return TimeZone.getTimeZone(timeZoneID).getOffset(tstamp.getTime());
}
public void listtz(String timezone)
throws Exception
{
String[] ids;
int id = 0;
byte[] scratchpad = getScratchpad();
// variables to read from SCRATCHPAD area
ByteArrayInputStream
byteArrayIn = new ByteArrayInputStream(scratchpad);
DataInputStream
dataIn = new DataInputStream(byteArrayIn);
// variables to write into SCRATCHPAD area
byte[] byteArrayRow;
int i;
ByteArrayOutputStream
byteArrayOut = new ByteArrayOutputStream(10);
DataOutputStream
dataOut = new DataOutputStream(byteArrayOut);
switch (getCallType())
{
case SQLUDF_TF_FIRST:
// do initialization for the whole statement
// (the statement may invoke litstz more than once)
break;
case SQLUDF_TF_OPEN:
// do initialization valid for this invokation of thd function
id = 0;
// save data in SCRATCHPAD area
dataOut.writeInt(id);
byteArrayRow = byteArrayOut.toByteArray();
for(i = 0; i < byteArrayRow.length; i++)
{
scratchpad[i] = byteArrayRow[i];
}
setScratchpad(scratchpad);
break;
case SQLUDF_TF_FETCH:
// get data from SCRATCHPAD area
id = dataIn.readInt();
ids = TimeZone.getAvailableIDs();
// work with data
if(id >= ids.length)
{
// Set end-of-file signal and return
setSQLstate ("02000");
}
else
{
// Set the current output row and increment the row number
set(1, ids[id]);
id++;
}
// save data in SCRATCHPAD area
dataOut.writeInt(id);
byteArrayRow = byteArrayOut.toByteArray();
for(i = 0; i < byteArrayRow.length; i++)
{
scratchpad[i] = byteArrayRow[i];
}
setScratchpad(scratchpad);
break;
case SQLUDF_TF_CLOSE:
break;
case SQLUDF_TF_FINAL:
break;
}
} // tableUDF
}
- Compile the program from your shell
javac TIMEZONEUDF.java
- Produce a jar file
jar cvf TIMEZONEUDF.jar TIMEZONEUDF.class
- Now, from the same directory we start the DB2 shell CLP
db2 -t
- Connect to the database
Connect to <dbname>;
- Register the jar file with the database
call sqlj.install_jar('file:.\TIMEZONEUDF.jar', 'TIMEZONEUDFJAR');
You may want to move the JAR file to a safe place and adjust the path above accordingly.
In a multi-member environment, make sure the file is accessible from all members.
- Create the functions:
CREATE OR REPLACE FUNCTION
TZ_OFFSET_MSEC
(zone VARCHAR(64),
stamp TIMESTAMP DEFAULT (CURRENT TIMESTAMP))
RETURNS INTEGER
SPECIFIC TZ_OFFSET_MSEC
LANGUAGE JAVA RETURNS NULL ON NULL INPUT
PARAMETER STYLE JAVA
DETERMINISTIC NO EXTERNAL ACTION NO SQL
EXTERNAL NAME 'TIMEZONEUDFJAR:TIMEZONEUDF.tzOffset' ;
CREATE OR REPLACE FUNCTION
FROM_TZ
(fromz VARCHAR(64),
toz VARCHAR(64),
stamp TIMESTAMP DEFAULT(CURRENT TIMESTAMP))
RETURNS TIMESTAMP
SPECIFIC FROM_TZ
DETERMINISTIC NO EXTERNAL ACTION CONTAINS SQL
RETURN stamp +((tz_offset_msec(toz, stamp)
- tz_offset_msec(fromz, stamp)) / 1000) SECONDS;
CREATE OR REPLACE FUNCTION
TZ_OFFSET
(zone VARCHAR(64),
stamp TIMESTAMP DEFAULT (CURRENT TIMESTAMP))
RETURNS VARCHAR(6)
SPECIFIC TZ_OFFSET
DETERMINISTIC NO EXTERNAL ACTION CONTAINS SQL
RETURN TO_CHAR(TZ_OFFSET_MSEC(zone, stamp) / 1000 / 60 / 60, 'S00')
|| ':' || LTRIM(TO_CHAR(MOD(TZ_OFFSET_MSEC(zone, stamp) / 1000 / 60, 60),'00'));
CREATE OR REPLACE FUNCTION
TIMEZONE_NAMES
() RETURNS TABLE(TZNAME VARCHAR(64))
SPECIFIC TIMEZONE_NAMES
LANGUAGE JAVA
PARAMETER STYLE DB2GENERAL NO FINAL CALL
DETERMINISTIC NO EXTERNAL ACTION NO SQL
DISALLOW PARALLEL SCRATCHPAD 100
EXTERNAL NAME 'TIMEZONEUDFJAR:TIMEZONEUDF.listtz';
We can now test the functions
VALUES TZ_OFFSET_MSEC('America/New_York');
1
-----------
-18000000
1 record(s) selected.
This is the current (wall clock time) offset of the New York timezone to UTC. So new York is 18,000,000 milliseconds behind UTC.
In June however new York was in Daylight Savings Time (DST):
VALUES TZ_OFFSET_MSEC('America/New_York', '2011-06-01');
1
-----------
-14400000
1 record(s) selected.
Milliseconds are a bit clumsy, so we provide also a version that provides a more readable output.
VALUES TZ_OFFSET('America/New_York');
1
------
-05:00
1 record(s) selected.
New York is trailing UTC by 5 hours. St. Johns Newfoundland likes to trail by fractions of an hour:
VALUES TZ_OFFSET('America/St_Johns', '2011-06-01');
1
------
-03:30
1 record(s) selected.
When you know the relation between any timezone and UTC, then mapping a time from any timezone to any other is just simple math.
VALUES FROM_TZ('America/New_York', 'Europe/Berlin');
1
--------------------------
2012-01-07-23.45.32.859000
1 record(s) selected.
It's just before midnight in Germany. They are 6 hours ahead.
But at the beginning of November things looked different. Due to the lag in switching back from DST it's only 5 hours.
VALUES FROM_TZ('America/New_York', 'Europe/Berlin', '2011-11-01-18.00.00');
1
--------------------------
2011-11-01-23.00.00.000000
1 record(s) selected.
These are handy functions. But which names can I provide for timezones?
The following table function lists the options:
SELECT * FROM TABLE(TIMEZONE_NAMES()) WHERE TZNAME LIKE 'America/C%';
TZNAME
----------------------------------------------------------------
America/Cambridge_Bay
America/Chihuahua
America/Cancun
America/Chicago
America/Costa_Rica
America/Cayman
America/Coral_Harbour
America/Caracas
America/Campo_Grande
America/Cuiaba
America/Curacao
America/Catamarca
America/Cayenne
America/Cordoba
14 record(s) selected.
#Db2