Дата и временная дата Java из экземпляра ZonedDateTime UTC

У меня есть Java-приложение, в котором я хотел бы время в UTC. В настоящее время код использует сочетание java.util.Date и java.sql.Timestamp. Чтобы получить время в UTC, программист передо мной использовал:

Дата:

 Date.from(ZonedDateTime.now(ZoneOffset.UTC)).toInstant();

Для отметки времени:

 Timestamp.from(ZonedDateTime.now(ZoneOffset.UTC).toInstant());

Однако я сам выполнял несколько тестов с этим кодом, и обе эти строки возвращают текущую дату/время (в моем текущем часовом поясе). Из всего, что я прочитал, появляется, что Date/Timestamp не имеет значения zoneOffset, но я не могу найти конкретную формулировку этого.

Есть ли способ сохранить timeZone (UTC) в объектах Date или Timestamp, или мне нужно сделать некоторые рефакторинг и использовать фактический объект ZonedDateTime во время моего приложения? Также будет ли этот объект ZonedDateTime совместим с текущим объектом Timestamp для sql?

Пример:

public static void main (String args[])
{
    ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneOffset.UTC);
    Timestamp timestamp = Timestamp.from(ZonedDateTime.now(ZoneOffset.UTC).toInstant());
    Date date = Date.from(ZonedDateTime.now(ZoneOffset.UTC).toInstant());
    System.out.println("ZonedDateTime: " + zonedDateTime);
    System.out.println("Timestamp: " + timestamp);
    System.out.println("Date: " + date);
}

Выход:

 ZonedDateTime: 2017-04-06T15:46:33.099Z
 Timestamp: 2017-04-06 10:46:33.109
 Date: Thu Apr 06 10:46:33 CDT 2017

Ответ 1

ТЛ; др

Instant.now()  // Capture the current moment in UTC with a resolution up to nanoseconds.

Используйте только классы java.time. Избегайте проблемных старых устаревших классов времени, добавленных до Java 8.

Использование java.time

Программист, прежде чем вы использовали новые современные классы java.time, которые теперь вытесняют печально известные старые устаревшие классы времени, такие как Date, Calendar, Timestamp.

Instant

Instant класс представляет момент на временной шкале в UTC с разрешением наносекунд (до девяти (9) цифр десятичной дроби). Получить текущий момент в UTC предельно просто: Instant.now.

Instant instant = Instant.now();

преобразование

Вы должны придерживаться классов java.time и избегать устаревших классов. Но если это абсолютно необходимо, например, сопряжение со старым кодом, еще не обновленным для java.time, вы можете конвертировать в/из java.time. Посмотрите на новые методы для старых классов. Унаследованный класс java.util.Date эквивалент Instant.

java.util.Date d = java.util.Date.from( myInstant); // To legacy from modern.
Instant instant = myJavaUtilDate.toInstant();  // To modern from legacy.

JDBC

Избегайте устаревших классов времени. Вместо этого используйте классы java.time.

Ваш JDBC 4.2 совместимый драйвер может напрямую обращаться к типам java.time, вызывая PreparedStatement::setObject и ResultSet::getObject.

myPreparedStatement.setObject( … , instant ) ;

… а также …

Instant instant = myResultSet.getObject( … , Instant.class ) ;

Если нет, вернитесь к использованию типов java.sql, но как можно короче. Используйте новые методы преобразования, добавленные к старым классам.

myPreparedStatement.setTimestamp( … , java.sql.Timestamp.from( instant ) ) ;

… а также …

Instant instant = myResultSet.getTimestamp( … ).toInstant() ;

Нет необходимости в ZonedDateTime

Обратите внимание, что мы не нуждались в вашем упомянутом ZonedDateTime поскольку вы сказали, что вас интересует только UTC. Instant объекты всегда находятся в UTC. Это означает, что исходный код, который вы цитировали:

Date.from(ZonedDateTime.now(ZoneOffset.UTC)).toInstant();

... можно было бы просто сократить:

Date.from( Instant.now() ) ;

Обратите внимание, что java.util.Date всегда находится в UTC. Однако его toString к сожалению, неявно применяет текущий часовой пояс JVM по умолчанию при создании String. Эта анти-функция не создает никакой путаницы, как вы можете видеть, просматривая Stack Overflow.

Если вы хотите увидеть значение UTC Instant объектов через объектив области настенных часов, назначьте часовой пояс ZoneId получить ZoneDateTime.

Укажите правильное название часового пояса в формате continent/region, например, America/Montreal, Africa/Casablanca или Pacific/Auckland. Никогда не используйте аббревиатуру 3-4 букв, такую как CDT или EST или IST поскольку они не являются настоящими часовыми поясами, а не стандартизированы и даже не уникальны (!).

ZoneId z = ZoneId.of( "America/Chicago" );
ZonedDateTime zdt = instant.atZone( z );

О java.time

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

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

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

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

Где можно получить классы java.time?

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

Ответ 2

В Java Date представляет собой момент времени. Это не связано с меткой времени. Когда вы вызываете метод toString() объекта Date, он преобразует это время в стандартную временную метку платформы, например. Следующее будет печатать дату/время в UTC (поскольку он устанавливает часовой пояс по умолчанию для UTC):

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneOffset.UTC);
Timestamp timestamp = Timestamp.from(ZonedDateTime.now(ZoneOffset.UTC).toInstant());
Date date = Date.from(ZonedDateTime.now(ZoneOffset.UTC).toInstant());
System.out.println("ZonedDateTime: " + zonedDateTime);
System.out.println("Timestamp: " + timestamp);
System.out.println("Date: " + date);

Ответ 3

Я создал класс SimpleJdbcUpdate, показанный ниже:

public class SimpleJdbcUpdate {

private final JdbcTemplate jdbcTemplate;
private DataSource dataSource;
private String tableName;
private final TableMetaDataContext tableMetaDataContext = new TableMetaDataContext();
Map<String, ColumnInfo> propertyToColumnMap = new HashMap<>();
private boolean compiled;

public SimpleJdbcUpdate(DataSource dataSource) {
    jdbcTemplate = new JdbcTemplate(dataSource);
    this.dataSource = dataSource;
}

private List<String> getColumnNames() {
    return Collections.emptyList();
}

private String[] getGeneratedKeyNames() {
    return new String[0];
}

public SimpleJdbcUpdate withTableName(String tableName) {
    this.tableName = tableName;
    return this;
}

public int execute(BeanPropertySqlParameterSource parameterSource, String[] keys) {

    if (!compiled) {
        compile();
    }

    return doExecute(parameterSource, keys);
}

private int doExecute(BeanPropertySqlParameterSource parameterSource, String[] keys) {
    String[] propertyNames = parameterSource.getParameterNames();

    List<Object> values = new ArrayList<>();
    String updateSql = updateSql(parameterSource, propertyNames, values);
    String wherePart = wherePart(parameterSource, keys, values);

    String updateSqlWithWhere = updateSql + wherePart;
    return jdbcTemplate.update(updateSqlWithWhere, values.toArray());
}

private String updateSql(BeanPropertySqlParameterSource parameterSource, String[] propertyNames, List<Object> values) {
    StringBuilder updateSqlBuilder = new StringBuilder("update " + tableName + " set ");
    boolean first = true;
    for (String propertyName : propertyNames) {
        ColumnInfo columnInfo = propertyToColumnMap.get(propertyName);
        if (columnInfo == null) {
            continue;
        }

        addValue(parameterSource, values, propertyName);

        if (!first) {
            updateSqlBuilder.append(", ");
        }
        updateSqlBuilder.append(columnInfo.columnName + " = ?");
        first = false;
    }
    return updateSqlBuilder.toString();
}

private String wherePart(BeanPropertySqlParameterSource parameterSource, String[] keys, List<Object> values) {
    StringBuilder wherePartBuilder = new StringBuilder();
    boolean first = true;
    for (String key : keys) {
        ColumnInfo columnInfo = propertyToColumnMap.get(key);
        if (columnInfo == null) {
            continue;
        }

        addValue(parameterSource, values, key);

        if (first) {
            wherePartBuilder.append(" WHERE ");
        } else {
            wherePartBuilder.append(" AND ");
        }
        wherePartBuilder.append(columnInfo.columnName + " = ?");

        first = false;
    }
    return wherePartBuilder.toString();
}

private void addValue(BeanPropertySqlParameterSource parameterSource, List<Object> values, String propertyName) {
    if (parameterSource.hasValue(propertyName)) {
        Object typedValue = SqlParameterSourceUtils.getTypedValue(parameterSource, propertyName);
        if (typedValue.getClass().isEnum()) {
            typedValue = new SqlParameterValue(Types.VARCHAR, ((Enum) typedValue).name());
        }
        values.add(typedValue);
    }
}

private void compile() {
    tableMetaDataContext.setTableName(tableName);
    this.tableMetaDataContext.processMetaData(dataSource, getColumnNames(), getGeneratedKeyNames());

    List<String> tableColumns = tableMetaDataContext.getTableColumns();
    for (int i = 0; i < tableColumns.size(); i++) {
        String column = tableColumns.get(i);
        String propertyName = JdbcUtils.convertUnderscoreNameToPropertyName(column);
        propertyToColumnMap.put(propertyName, new ColumnInfo(column, i));
    }

    compiled = true;
}

private static class ColumnInfo {
    String columnName;
    Integer index;

    public ColumnInfo(String columnName, Integer index) {
        this.columnName = columnName;
        this.index = index;
    }
  }
}