Map Database timestamp column to UTC Calendar (JPA) and pass it as UTC date via WebService (jax-ws)

StackOverflow https://stackoverflow.com/questions/1821104

  •  10-07-2019
  •  | 
  •  

Question

This sounds like a simple task.
Get UTC timestamp value from DB and pass it as UTC date via Web Service.

We have timestamp column DATE_COLUMN and store there time in UTC time zone.

With JPA we get this time with

@Column(name = "DATE_COLUMN")
private java.sql.Timestamp dateValue;

And as we have to pass this time via Web Service in UTC (Jax-ws 2.0) we have getDate and setDate methods.
We are interested in getDate.

public Calendar getDate()
{
   Calendar calendar = Calendar.getInstance(utcTimeZone);
   calendar.setTimeInMillis(dateValue.getTime());

   return calendar;
}

This doesn't work as you may think it should.
And this is because application's default time zone is not 'UTC'.

Here is an example for clarification.
Value in the DATE_COLUMN equals to "30.11.09 16:34:48,833045000", when I translate it to UTC I get "2009-11-30T14:34:48.833Z".
The difference is 2 hours. And this is because my default time zone is "Europe/Helsinki".

Same problem if you just want to map 'DATE_COLUMN' to Calendar

@Column(name = "DATE_COLUMN")
@Temporal(TemporalType.TIMESTAMP)
private Calendar dateValue;

public Calendar getDate()
{
   calendar.setTimeZone(utcTimeZone);
   return calendar;
}

I don't want to change application's time zone because it doesn't look like the solution.

By now we have only two options.

First. Calculate offset between application's time zone and UTC and add it manually after automatic subtraction in the calendar.setTimeZone.

public Calendar getDate()
{
   Calendar calendar = Calendar.getInstance(utcTimeZone);
   calendar.setTimeInMillis(dateValue.getTime());

   int offset = TimeZone.getDefault().getOffset(dateValue.getTime());

   calendar.add(Calendar.MILLISECOND, offset);

   return calendar;
}

Second. Pass dateValue as Long via Web Service. Which is not bad except that we lose real type of the field in wsdl.

My imaginary solution is

@Column(name = "DATE_COLUMN")
@Temporal(type = TemporalType.TIMESTAMP, timezone = 'UTC')
private Calendar dateValue;

But I tend to think that there is the real one somewhere. And I hope you can point it out.

Was it helpful?

Solution

We decided to use following solution.
Use Date for retrieving date from database. It is because Date is timezoneless type.

@Column(name = "DATE_COLUMN")
@Temporal(TemporalType.TIMESTAMP)
private Date dateValue;

public Date getDate()
{
   return dateValue;
}

And to send it via WebService in UTC (jax-ws) we created UtcTimestampAdapter to change zone from application's default to UTC in the marshaling phase.

public class UtcTimestampAdapter extends XmlAdapter<XMLGregorianCalendar, Date>
{
   @Override
   public XMLGregorianCalendar marshal(Date date) throws Exception
   {
      GregorianCalendar calendar = new GregorianCalendar();
      calendar.setTime(date);

      DatatypeFactory dataTypeFactory = DatatypeFactory.newInstance();
      XMLGregorianCalendar xmlCalendar = 
         dataTypeFactory.newXMLGregorianCalendar(calendar);

      //Reset time zone to UTC
      xmlCalendar.setTimezone(0);

      return xmlCalendar;
   }

   @Override
   public Date unmarshal(XMLGregorianCalendar calendar) throws Exception
   {
      return calendar.toGregorianCalendar().getTime();
   }
}

Then to enable this rule to all Datas fields in the module we added package specific setting like so.

@XmlJavaTypeAdapter(value = UtcTimestampAdapter.class, type = Date.class)
@XmlSchemaType(name = "dateTime", type = XMLGregorianCalendar.class)
package com.companyname.modulename;

That's it. Now we have generic solution which encapsulate all logic in one place. And if we want to send timezoneless dates as UTC via web service in some other module we will just annotate certain package.

OTHER TIPS

If you need the java process to run at the UTC timezone, the easiest way to do so is by adding the following JVM parameter:

-Duser.timezone=UTC

TimeZone.setDefault(TimeZone.getTimeZone("UTC")); seems to be affecting the entire JVM.

This will cause other applications fail if they were expecting local time

Another solution is to set the default timezone for the application only in a @StartupBean:

import java.util.TimeZone;
import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import javax.ejb.Startup;

@Startup
@Singleton
public class StartupBean {

    @PostConstruct
    public void initializeTheServer() {
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
    }
}

From then on, all interpretation of Date objects will be based on UTC. This includes XML marshalling.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top