Как правильно обрабатывать дату и время в формате UTC, используя Java, iBatis и Oracle? - PullRequest
1 голос
/ 31 октября 2009

Я столкнулся с неожиданной проблемой перехода на летнее время в коде, который я считал чисто UTC. Я использую Java 1.6, iBatis SQL mapper (2.3.3) и Oracle XE (eval-версия Oracle 10.2) с тонким драйвером Oracle.

База данных содержит таблицу, которая представляет расписание телевизионного вещания. Каждый «актив» (программа) имеет время начала и время окончания. Вот соответствующий срез:

create table Asset
(
 asset_id      integer not null, -- The unique id of the Asset.
 [...] 
 start_time    timestamp,        -- The start time.
 end_time      timestamp,        -- The end time.
 [...] 

 constraint asset_primary_key    primary key (asset_id),
 constraint asset_time           check (end_time >= start_time)
);

Ограничение оракула asset_time действует для программ, которые выходят за рамки централизованного регулирования летнего времени США в это предстоящее воскресное утро, 11 января 2009 года.

У меня есть этот объект передачи данных (Даты - java.util.Dates):

public class Asset 
{
 protected Long    asset_id;
 [...]
 protected Date    start_time;
 protected Date    end_time; 

 public Date       getStart_time()     { return start_time; }
 public Date       getEnd_time()       { return end_time; }

 public void setStart_time(Date start_time) { this.start_time = start_time; }
 public void setEnd_time(Date end_time)     { this.end_time = end_time; }
 [...]
}

И на карте iBatis SQL у меня есть такой оператор, который вставляет DTO Asset в таблицу Oracle Asset:

<insert id="Asset.insert" parameterClass="com.acme.Asset">
    insert into Asset 
        ( asset_id, [...] start_time, end_time )
    values
        ( #asset_id#, [...] #start_time#, #end_time# )
</insert>

На стороне Java я убедился, что я даю iBatis правильный ввод даты UTC через это утверждение перед вставкой, которое не брошено:

System.err.println("Inserting asset " + program_id);
System.err.println("  "+asset.getStart_time_str()+"--"+asset.getEnd_time_str());
if ( !asset.getEnd_time().after(asset.getStart_time())) {
 System.err.println("Invalid datetime range in asset.");
 throw new AssertionError("Invalid datetime range in asset.");
}

Непосредственно перед ошибкой ограничения Oracle печатается приведенный выше код:

Inserting asset EP011453960004
  2009-11-01T06:30:00Z--2009-11-01T07:00:00Z

Я нахожусь в центральном часовом поясе США, GMT -5: 00, поэтому эта программа начинается в 1:30 и заканчивается в 2:00. Изменение летнего времени наступает в 2:00 утра и переводит часы обратно в 1:00 утра.

iBatis сообщает об ошибке ограничения Oracle (отредактировано):

2009-10-30 22:58:42,238  [...] Executing Statement:
    insert into Asset ( asset_id, [...] start_time, end_time )
         values       ( ?, [...] ?, ? )  
2009-10-30 22:58:42,238  [...] Parameters: 
    [EP011453960004, [...] 2009-11-01 01:30:00.0, 2009-11-01 01:00:00.0]
2009-10-30 22:58:42,238  [..] Types: 
    [java.lang.Long, [...] java.sql.Timestamp, java.sql.Timestamp]
2009-10-30 22:58:42,285  [...] - Failed with a SQLException:   
--- The error occurred in com/acme/data/dao/Asset-Write.xml.  
--- The error occurred while applying a parameter map.  
--- Check the Asset.insert-InlineParameterMap.  
--- Check the statement (update failed).  
--- Cause: java.sql.SQLException: ORA-02290: check constraint (ACME.ASSET_TIME)
                                             violated

Вы заметите, что на стороне Oracle он видит start_time / end_time с настройкой перехода на летнее время, поэтому что-то в логике отображения iBatis или в драйвере Oracle не выполняет то, что я ожидал. Драйвер ojdbc14.jar, тонкий драйвер:

JDBCReadWrite.Driver        = oracle.jdbc.OracleDriver
JDBCReadWrite.ConnectionURL = jdbc:oracle:thin:@localhost:1521:XE

Какой правильный способ гарантировать, что этот код является чисто UTC?

Заранее спасибо!

Ответы [ 2 ]

6 голосов
/ 03 ноября 2009

У меня есть решение, которое, кажется, делает свое дело. Несмотря на то, что приложение и база данных использовали типы, которые хранят смещения времени с полуночи 01.01.1970 по Гринвичу, спецификация JDBC требует применения корректировки из / в часовой пояс JVM по умолчанию, входящий / выходящий. И iBatis отображает даты, используя JDBC по умолчанию. Корректировки всегда были симметричными и, следовательно, безвредными, если данные не пересекали границу перехода на летнее время или если для машины или JVM было установлено значение по умолчанию GMT.

В качестве эксперимента я переключил часовой пояс JVM по умолчанию на GMT:

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

и это решило проблему, хотя и очень жестко (другой код в JVM может этого не ожидать).

Но iBatis позволяет вам переопределить обработку типов по умолчанию на любом уровне детализации. Я написал обработчик типа с сохранением GMT и зарегистрировал его для всех моих java.util.Dates:

<typeHandler callback="com.acme.GMTDateTypeHandler" javaType="java.util.Date"/>

Мой обработчик типов выглядит так:

public class GMTDateTypeHandler implements TypeHandlerCallback
{     
    @Override
    public void setParameter(ParameterSetter setter, Object parameter) 
        throws SQLException
    {
        java.util.Date date = (java.util.Date) parameter;
        if ( date == null )
            setter.setNull(Types.TIMESTAMP);
        else
        {
            Timestamp timestamp = new Timestamp(date.getTime());
            Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
            setter.setTimestamp(timestamp, calendar);
        }
    }

    @Override
    public Object getResult(ResultGetter getter) throws SQLException
    {
        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
        return getter.getTimestamp(calendar);
    }

    @Override
    public Object valueOf(String s)
    {
        throw new UnsupportedOperationException(
            "GMTDateTypeHandler.valueOf() is not supported.");
    }
}
0 голосов
/ 01 ноября 2009

Обычно Oracle преобразует значения даты / времени из часового пояса клиента в часовой пояс сервера при хранении данных. И наоборот, при повторном чтении.

Если вы хотите, чтобы значения даты / времени были сохранены без изменений, вы можете использовать вариант типа данных часового пояса, «Тип данных TIMESTAMP WITH TIME ZONE», который позволяет сохранять часовой пояс со значением , Вы можете найти некоторую информацию здесь в Тип данных Oracle SQL doc . Просто найдите часть "с часовым поясом".

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...