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?
- Java SE 8, Java SE 9, Java SE 10, Java SE 11 и более поздние версии - часть стандартного API Java со встроенной реализацией.
- Java 9 добавляет некоторые незначительные функции и исправления.
- Java SE 6 и Java SE 7
- Большая часть функциональности java.time перенесена на Java 6 и 7 в ThreeTen-Backport.
- Android
- Более поздние версии Android связывают реализации классов java.time.
- Для более ранних версий Android (<26) проект ThreeTenABP адаптирует ThreeTen-Backport (упомянутый выше). Смотрите Как использовать ThreeTenABP….
Проект 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);
Меня устраивает.