Java - конвертировать java.time.Instant в java.sql.Timestamp без смещения зоны

В приложении, которое я разрабатываю, мне нужно конвертировать объект java.time.Instant в java.sql.Timestamp. Когда я создаю Мгновенный объект, например

Instant now = Instant.now();

и осмотрите элемент. Я получаю что-то вроде 2017-03-13T14:28:59.970ZЗатем я пытаюсь создать объект Timestamp следующим образом:

Timestamp current = Timestamp.from(now);

И когда я проверяю элемент. Я получаю что-то вроде 2017-03-13T16:28:59.970ZТот же результат, но с добавленным смещением на 2 часа. Может кто-нибудь объяснить, что это происходит, и дать мне ответ, как решить эту проблему без компенсации?

Когда я создал так:

LocalDateTime ldt = LocalDateTime.ofInstant(Instnant.now(), ZoneOffset.UTC);
Timestamp current = Timestamp.valueOf(ldt);

Все отлично работает Но я стараюсь избегать конверсий. Есть ли способ сделать это только с помощью объекта Instant?

5 ответов

Решение

Я сменил часовой пояс своего компьютера на Европу / Бухарест для эксперимента. Это UTC + 2 часа, как ваш часовой пояс.

Теперь, когда я копирую ваш код, я получаю результат, похожий на ваш:

    Instant now = Instant.now();
    System.out.println(now); // prints 2017-03-14T06:16:32.621Z
    Timestamp current = Timestamp.from(now);
    System.out.println(current); // 2017-03-14 08:16:32.621

Выход дан в комментариях. Тем не менее, я продолжаю:

    DateFormat df = DateFormat.getDateTimeInstance();
    df.setTimeZone(TimeZone.getTimeZone("UTC"));
    // the following prints: Timestamp in UTC: 14-03-2017 06:16:32
    System.out.println("Timestamp in UTC: " + df.format(current));

Теперь вы можете видеть, что Timestamp действительно согласен с Instant мы начали с (только миллисекунды не печатаются, но я верю, что они тоже там). Таким образом, вы все сделали правильно и только запутались, потому что, когда мы напечатали Timestamp мы неявно называли его toString метод, и этот метод, в свою очередь, захватывает настройку часового пояса компьютера и отображает время в этой зоне. Только из-за этого дисплеи разные.

Другая вещь, которую вы пытались, используя LocalDateTime, кажется, работает, но это действительно не дает вам то, что вы хотите:

    LocalDateTime ldt = LocalDateTime.ofInstant(Instant.now(), ZoneOffset.UTC);
    System.out.println(ldt); // 2017-03-14T06:16:32.819
    current = Timestamp.valueOf(ldt);
    System.out.println(current); // 2017-03-14 06:16:32.819
    System.out.println("Timestamp in UTC: " + df.format(current)); // 14-03-2017 04:16:32

Теперь, когда мы печатаем Timestamp используя наш UTC DateFormatмы видим, что это на 2 часа раньше, 04:16:32 UTC, когда Instant Это 06:16:32 UTC. Так что этот метод обманывает, похоже, что он работает, но это не так.

Это показывает проблему, которая приводит к созданию классов даты и времени Java 8 для замены старых. Поэтому реальным и хорошим решением вашей проблемы, вероятно, было бы получить себе драйвер JDBC 4.2, который может принимать Instant объект легко, так что вы можете избежать преобразования в Timestamp в целом. Я пока не знаю, доступно ли это вам, но я уверен, что так и будет.

Неправильные классы для использования

LocalDateTime ldt = LocalDateTime.ofInstant(Instnant.now(), ZoneOffset.UTC);
Timestamp current = Timestamp.valueOf(ldt);

Две проблемы с этим кодом.

Во-первых, никогда не смешивайте современные классы java.time (LocalDateTime здесь) с ужасными старыми унаследованными классами даты и времени (java.sql.Timestamp Вот). Инфраструктура java.time полностью вытесняет ужасные старые классы, начиная с принятия JSR 310. Вам никогда не нужно использовать Timestamp еще раз: Начиная с JDBC 4.2 мы можем напрямую обмениваться объектами java.time с базой данных.

Другая проблема заключается в том, что LocalDateTime класс по определению не может представлять момент. Это преднамеренно испытывает недостаток в часовом поясе или смещении от UTC. использование LocalDateTime только когда вы имеете в виду дату со временем суток везде или где-либо, другими словами, любой / все многие другие моменты в диапазоне около 26-27 часов (текущие крайности часовых поясов по всему миру).

Не использовать LocalDateTime когда вы имеете в виду определенный момент, конкретную точку на временной шкале. Вместо этого используйте:

  • Instant (всегда в UTC)
  • OffsetDateTime (дата со временем суток и со смещением от UTC)
  • ZonedDateTime (дата с указанием времени суток и часового пояса).

Затем я пытаюсь создать объект Timestamp

Не.

Никогда не используйте java.sql.Timestamp, Заменен на java.time.Instant, Смотрите ниже для получения дополнительной информации.

Текущий момент

Чтобы зафиксировать текущий момент в UTC, используйте любой из них:

Instant instant = Instant.now() ;

…или же…

OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC );

Оба представляют одно и то же, момент в UTC.

База данных

Вот несколько примеров SQL и кода Java для передачи текущего момента в базу данных.

В примере используется ядро базы данных H2, встроенное в Java.

sql = "INSERT INTO event_ ( name_ , when_ ) " + "VALUES ( ? , ? ) ;";
try ( PreparedStatement preparedStatement = conn.prepareStatement( sql ) ; ) {
    String name = "whatever";
    OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC );

    preparedStatement.setString( 1 , name );
    preparedStatement.setObject( 2 , odt );
    preparedStatement.executeUpdate();
}

Вот полный пример приложения, использующего этот код.

package com.basilbourque.example;

import java.sql.*;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.UUID;

public class MomentIntoDatabase {

    public static void main ( String[] args ) {
        MomentIntoDatabase app = new MomentIntoDatabase();
        app.doIt();
    }

    private void doIt ( ) {
        try {
            Class.forName( "org.h2.Driver" );
        } catch ( ClassNotFoundException e ) {
            e.printStackTrace();
        }

        try (
                Connection conn = DriverManager.getConnection( "jdbc:h2:mem:moment_into_db_example_" ) ;
                Statement stmt = conn.createStatement() ;
        ) {
            String sql = "CREATE TABLE event_ (\n" +
                    "  id_ UUID DEFAULT random_uuid() PRIMARY KEY ,\n" +
                    "  name_ VARCHAR NOT NULL ,\n" +
                    "  when_ TIMESTAMP WITH TIME ZONE NOT NULL\n" +
                    ") ; ";
            System.out.println( sql );
            stmt.execute( sql );

            // Insert row.
            sql = "INSERT INTO event_ ( name_ , when_ ) " + "VALUES ( ? , ? ) ;";
            try ( PreparedStatement preparedStatement = conn.prepareStatement( sql ) ; ) {
                String name = "whatever";
                OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC );

                preparedStatement.setString( 1 , name );
                preparedStatement.setObject( 2 , odt );
                preparedStatement.executeUpdate();
            }

            // Query all.
            sql = "SELECT * FROM event_ ;";
            try ( ResultSet rs = stmt.executeQuery( sql ) ; ) {
                while ( rs.next() ) {
                    //Retrieve by column name
                    UUID id = ( UUID ) rs.getObject( "id_" );  // Cast the `Object` object to UUID if your driver does not support JDBC 4.2 and its ability to pass the expected return type for type-safety.
                    String name = rs.getString( "name_" );
                    OffsetDateTime odt = rs.getObject( "when_" , OffsetDateTime.class );

                    //Display values
                    System.out.println( "id: " + id + " | name: " + name + " | when: " + odt );
                }
            }
        } catch ( SQLException e ) {
            e.printStackTrace();
        }
    }
}

Разбор строки

Что касается соответствующего комментария Мельника, вот еще один пример, основанный на приведенном выше примере кода. Вместо того, чтобы захватить текущий момент, этот код анализирует строку.

Во входной строке отсутствует какой-либо индикатор часового пояса или смещения от UTC. Таким образом, мы разбираем как LocalDateTime имея в виду, что это не является моментом, это не точка на временной шкале.

String input = "22.11.2018 00:00:00";
DateTimeFormatter f = DateTimeFormatter.ofPattern( "dd.MM.uuuu HH:mm:ss" );
LocalDateTime ldt = LocalDateTime.parse( input , f );

ldt.toString (): 2018-11-22T00: 00

Но нам сообщили, что строка должна была представлять момент в UTC, но отправитель облажался и не смог включить эту информацию (такую ​​как Z или же +00:00 в конце означает UTC). Таким образом, мы можем применить смещение от UTC, равное нулю часов, минут и секунд, чтобы определить фактический момент, конкретную точку на временной шкале. Результат как OffsetDateTime объект.

OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC );

odt.toString (): 2018-11-22T00: 00Z

Z в конце означает UTC и произносится как "зулу". Определено в стандарте ISO 8601.

Теперь, когда у нас есть момент, мы можем отправить его в базу данных в столбце типа SQL-стандарта TIMESTAMP WITH TIME ZONE,

preparedStatement.setObject( 2 , odt );

Когда затем получить это сохраненное значение.

 OffsetDateTime odt = rs.getObject( "when_" , OffsetDateTime.class );

2018-11-22T00: 00Z

Вот полное приложение для этого примера.

package com.basilbourque.example;

import java.sql.*;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.UUID;

public class MomentIntoDatabase {

    public static void main ( String[] args ) {
        MomentIntoDatabase app = new MomentIntoDatabase();
        app.doIt();
    }

    private void doIt ( ) {
        try {
            Class.forName( "org.h2.Driver" );
        } catch ( ClassNotFoundException e ) {
            e.printStackTrace();
        }

        try (
                Connection conn = DriverManager.getConnection( "jdbc:h2:mem:moment_into_db_example_" ) ;
                Statement stmt = conn.createStatement() ;
        ) {
            String sql = "CREATE TABLE event_ (\n" +
                    "  id_ UUID DEFAULT random_uuid() PRIMARY KEY ,\n" +
                    "  name_ VARCHAR NOT NULL ,\n" +
                    "  when_ TIMESTAMP WITH TIME ZONE NOT NULL\n" +
                    ") ; ";
            System.out.println( sql );
            stmt.execute( sql );

            // Insert row.
            sql = "INSERT INTO event_ ( name_ , when_ ) " + "VALUES ( ? , ? ) ;";
            try ( PreparedStatement preparedStatement = conn.prepareStatement( sql ) ; ) {
                String name = "whatever";
                String input = "22.11.2018 00:00:00";
                DateTimeFormatter f = DateTimeFormatter.ofPattern( "dd.MM.uuuu HH:mm:ss" );
                LocalDateTime ldt = LocalDateTime.parse( input , f );
                System.out.println( "ldt.toString(): " + ldt );
                OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC );
                System.out.println( "odt.toString(): " + odt );

                preparedStatement.setString( 1 , name );
                preparedStatement.setObject( 2 , odt );
                preparedStatement.executeUpdate();
            }

            // Query all.
            sql = "SELECT * FROM event_ ;";
            try ( ResultSet rs = stmt.executeQuery( sql ) ; ) {
                while ( rs.next() ) {
                    //Retrieve by column name
                    UUID id = ( UUID ) rs.getObject( "id_" );  // Cast the `Object` object to UUID if your driver does not support JDBC 4.2 and its ability to pass the expected return type for type-safety.
                    String name = rs.getString( "name_" );
                    OffsetDateTime odt = rs.getObject( "when_" , OffsetDateTime.class );

                    //Display values
                    System.out.println( "id: " + id + " | name: " + name + " | when: " + odt );
                }
            }
        } catch ( SQLException e ) {
            e.printStackTrace();
        }
    }
}

О java.time

Инфраструктура java.time встроена в Java 8 и более поздние версии. Эти классы вытесняют проблемные старые классы даты и времени, такие как java.util.Date, Calendar & SimpleDateFormat,

Проект Joda-Time, находящийся сейчас в режиме обслуживания, рекомендует перейти на классы java.time.

Чтобы узнать больше, смотрите Oracle Tutorial. И поиск переполнения стека для многих примеров и объяснений. Спецификация JSR 310.

Вы можете обмениваться объектами java.time напрямую с вашей базой данных. Используйте драйвер JDBC, соответствующий JDBC 4.2 или более поздней версии. Нет необходимости в строках, нет необходимости в java.sql.* классы.

Где взять классы java.time?

Проект ThreeTen-Extra расширяет java.time дополнительными классами. Этот проект является полигоном для возможных будущих дополнений к java.time. Вы можете найти некоторые полезные классы здесь, такие как Interval, YearWeek, YearQuarter и многое другое.

Instant всегда показывает время в формате UTC, тогда как Timestamp дает время в вашей локальной зоне. Поэтому, если вас не беспокоит какой-либо конкретный часовой пояс, вы можете использовать Instant. Также рекомендуется сохранять записи в формате UTC, чтобы ваше приложение не пострадало в случае его развертывания в любом другом часовом поясе.

Если вам нужна текущая временная метка, почему бы не использовать следующую функцию, я использовал ее в различных проектах и ​​отлично работает:

public static Timestamp getTimeStamp()
{
    // Calendar information
    Calendar calendar       = Calendar.getInstance();
    java.util.Date now      = calendar.getTime();
    Timestamp dbStamp       = new Timestamp(now.getTime());
    return dbStamp;
}   

Пример:

System.out.println( getTimeStamp() );

Выходные данные: 2017-03-13 15: 01: 34.027

РЕДАКТИРОВАТЬ

Используя Java 8 LocalDateTime:

public static Timestamp getTimeStamp()
{
    return Timestamp.valueOf(LocalDateTime.now());
}   

При сохранении записи в БД SQL Server я столкнулся с той же проблемой. Я использовал java.sql.Timestamp.valueOf(String s) чтобы получить метку времени в UTC:

import java.time.Instant; import java.time.format.DateTimeFormatter; .... .... DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(UTC); String dateTime = dateTimeFormatter.format(Instant date); Timestamp timestamp = Timestamp.valueOf(dateTime);

Меня устраивает.

Другие вопросы по тегам