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

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

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 object?

Ответ 1

Я изменил часовой пояс своих компьютеров на Европу/Бухарест для эксперимента. Это 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, мы можем видеть, что он слишком рано, 04:16:32 UTC, когда Instant - 06:16:32 UTC. Таким образом, этот метод обманчив, похоже, что он работает, но он этого не делает.

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

Ответ 2

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

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 когда вы имеете в виду определенный момент, конкретную точку на временной шкале. Вместо этого используйте:

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

Dont.

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

Table of date-time types in Java (both modern and legacy) and in standard SQL.

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

Чтобы зафиксировать текущий момент в 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. И поиск для многих примеров и объяснений. Спецификация JSR 310.

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

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

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

Ответ 3

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

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());
}   

Ответ 4

При сохранении записи в БД SQL Server я столкнулся с той же проблемой. Я использовал java.sql.Timestamp.valueOf(String s) чтобы получить 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);

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