Вопрос-ответ

Java date parsing with microsecond or nanosecond accuracy

Синтаксический анализ даты Java с точностью до микросекунды или наносекунды

Согласно документации по классу SimpleDateFormat, Java не поддерживает детализацию по времени выше миллисекунд в своих шаблонах даты.

Итак, строка даты типа


  • 2015-05-09 00:10:23.999750900 // Последние 9 цифр обозначают наносекунды

при анализе с помощью шаблона


  • гггг-ММ-дд ЧЧ:мм: ss.SSSSSSSSSS // 9 символов 'S'

фактически интерпретирует целое число после . символа как (почти 1 миллиард!) миллисекунды, а не как наносекунды, в результате чего получается дата


  • 2015-05-20 21:52:53 UTC

т. е. более чем на 11 дней вперед. Удивительно, но использование меньшего количества S символов по-прежнему приводит к анализу всех 9 цифр (вместо, скажем, крайних левых 3 для .SSS).

Есть 2 способа правильно решить эту проблему:


  • Используйте предварительную обработку строк

  • Используйте пользовательскую реализацию SimpleDateFormat

Есть ли какой-либо другой способ получить правильное решение, просто добавив шаблон в стандартную SimpleDateFormat реализацию, без каких-либо других модификаций кода или манипулирования строками?

Переведено автоматически
Ответ 1

tl;dr

LocalDateTime.parse(                 // With resolution of nanoseconds, represent the idea of a date and time somewhere, unspecified. Does *not* represent a moment, is *not* a point on the timeline. To determine an actual moment, place this date+time into context of a time zone (apply a `ZoneId` to get a `ZonedDateTime`). 
"2015-05-09 00:10:23.999750900" // A `String` nearly in standard ISO 8601 format.
.replace( " " , "T" ) // Replace SPACE in middle with `T` to comply with ISO 8601 standard format.
) // Returns a `LocalDateTime` object.

Нет

Нет, вы не можете использовать SimpleDateFormat для обработки наносекунд.

Но ваша предпосылка, что…


Java не поддерживает детализацию по времени выше миллисекунд в своих шаблонах дат


... больше не соответствует действительности начиная с Java 8, 9, 10 и более поздних версий со встроенными классами java.time. И это не совсем верно для Java 6 и Java 7, поскольку большая часть функций java.time перенесена обратно.

java.time

SimpleDateFormat и связанные с ними java.util.Date/.Calendar классы теперь устарели благодаря новому пакету java.time, который содержится в Java 8 (Учебное пособие).

Новые классы java.time поддерживают наносекундное разрешение. Эта поддержка включает синтаксический анализ и генерацию девяти цифр, состоящих из долей секунды. Например, когда вы используете java.time.format DateTimeFormatter API, S буква шаблона обозначает "долю секунды", а не "миллисекунды", и он может обрабатывать значения в наносекунды.

Instant

В качестве примера, Instant класс представляет момент времени в UTC. Его toString метод генерирует String объект, используя стандартный формат ISO 8601. Z В конце означает UTC, произносится “Zulu”.

instant.toString()  // Generate a `String` representing this moment, using standard ISO 8601 format.

2013-08-20T12:34:56.123456789Z


Обратите внимание, что запись текущего момента в Java 8 ограничена разрешением в миллисекунду. Классы java.time могут хранить значение в наносекундах, но могут определять текущее время только в миллисекундах. Это ограничение связано с реализацией Clock. В Java 9 и более поздних версиях новая Clock реализация может отображать текущий момент с более высоким разрешением, в зависимости от ограничений вашего аппаратного обеспечения и операционной системы, обычно, по моему опыту, это микросекунды.

Instant instant = Instant.now() ;  // Capture the current moment. May be in milliseconds or microseconds rather than the maximum resolution of nanoseconds.

LocalDateTime

В вашем примере строки ввода 2015-05-09 00:10:23.999750900 отсутствует индикатор часового пояса или смещения от UTC. Это означает, что он не представляет момент, не является точкой на временной шкале. Вместо этого он представляет потенциальные моменты в диапазоне примерно 26-27 часов, диапазон часовых поясов по всему миру.

Обрабатывает такие входные данные как LocalDateTime объект. Сначала замените ПРОБЕЛ посередине на T в соответствии с форматом ISO 8601, используемым по умолчанию при синтаксическом анализе / генерации строк. Таким образом, нет необходимости указывать шаблон форматирования.

LocalDateTime ldt = 
LocalDateTime.parse(
"2015-05-09 00:10:23.999750900".replace( " " , "T" ) // Replace SPACE in middle with `T` to comply with ISO 8601 standard format.
)
;

java.sql.Timestamp

Класс java.sql.Timestamp также обрабатывает наносекундное разрешение, но неудобным способом. Обычно лучше выполнять свою работу внутри классов java.time. Больше никогда не нужно использовать Timestamp начиная с JDBC 4.2 и более поздних версий.

myPreparedStatement.setObject( … , instant ) ;

И извлечение данных.

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

OffsetDateTime

Поддержка Instant не предусмотрена спецификацией JDBC, но OffsetDateTime есть. Поэтому, если приведенный выше код не работает с вашим драйвером JDBC, используйте следующий.

OffsetDateTime odt = instant.atOffset( ZoneOffset.UTC ) ; 
myPreparedStatement.setObject( … , odt ) ;

И извлечение данных.

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

При использовании более старого драйвера JDBC, выпущенного до версии 4.2, вы можете использовать методы toInstant и from для перехода между java.sql.Timestamp и java.time. Эти новые методы преобразования были добавлены к старым устаревшим классам.

Таблица типов даты и времени в Java (как устаревшей, так и современной) и в стандартном SQL


О java.time

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

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

Чтобы узнать больше, ознакомьтесь с Руководством по Oracle. И найдите в Stack Overflow множество примеров и пояснений. Спецификация - JSR 310.

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

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

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

Ответ 2

Я нашел замечательным возможность использовать несколько вариантов формата даты и времени, подобных этому:

final DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder();
dtfb.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S"))
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0);
Ответ 3

java.time

java.util API даты и времени и их API форматирования, SimpleDateFormat устарели и подвержены ошибкам. Рекомендуется полностью прекратить их использование и переключиться на современный API определения даты и времени.

Вы можете создать, DateTimeFormatter используя DateTimeFormatterBuilder, как показано ниже:

DateTimeFormatter parser = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral(' ')
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.toFormatter(Locale.ENGLISH);

ДЕМОНСТРАЦИЯ:

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.Locale;
import java.util.stream.Stream;

public class Main {
public static void main(String[] args) {
DateTimeFormatter parser = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral(' ')
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.toFormatter(Locale.ENGLISH);

// Test
Stream.of(
"2015-05-09 00:10:23.123456789",
"2015-05-09 00:10:23.12345678",
"2015-05-09 00:10:23.1234567",
"2015-05-09 00:10:23.123456",
"2015-05-09 00:10:23.12345",
"2015-05-09 00:10:23.1234",
"2015-05-09 00:10:23.123",
"2015-05-09 00:10:23.12",
"2015-05-09 00:10:23.1",
"2015-05-09 00:10:23.1",
"2015-05-09 00:10:23",
"2015-05-09 00:10"
)
.map(s -> LocalDateTime.parse(s, parser))
.forEach(System.out::println);
}
}

Вывод:

2015-05-09T00:10:23.123456789
2015-05-09T00:10:23.123456780
2015-05-09T00:10:23.123456700
2015-05-09T00:10:23.123456
2015-05-09T00:10:23.123450
2015-05-09T00:10:23.123400
2015-05-09T00:10:23.123
2015-05-09T00:10:23.120
2015-05-09T00:10:23.100
2015-05-09T00:10:23.100
2015-05-09T00:10:23
2015-05-09T00:10

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

DateTimeFormatter parser = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.optionalStart()
.appendLiteral(' ')
.appendOptional(DateTimeFormatter.ISO_LOCAL_TIME)
.optionalEnd()
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.toFormatter(Locale.ENGLISH);

ДЕМОНСТРАЦИЯ:

public class Main {
public static void main(String[] args) {
DateTimeFormatter parser = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.optionalStart()
.appendLiteral(' ')
.appendOptional(DateTimeFormatter.ISO_LOCAL_TIME)
.optionalEnd()
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.toFormatter(Locale.ENGLISH);

// Test
Stream.of(
"2015-05-09 00:10:23.123456789",
"2015-05-09 00:10:23.12345678",
"2015-05-09 00:10:23.1234567",
"2015-05-09 00:10:23.123456",
"2015-05-09 00:10:23.12345",
"2015-05-09 00:10:23.1234",
"2015-05-09 00:10:23.123",
"2015-05-09 00:10:23.12",
"2015-05-09 00:10:23.1",
"2015-05-09 00:10:23.1",
"2015-05-09 00:10:23",
"2015-05-09 00:10",
"2015-05-09"
)
.map(s -> LocalDateTime.parse(s, parser))
.forEach(System.out::println);
}
}

Вывод:

2015-05-09T00:10:23.123456789
2015-05-09T00:10:23.123456780
2015-05-09T00:10:23.123456700
2015-05-09T00:10:23.123456
2015-05-09T00:10:23.123450
2015-05-09T00:10:23.123400
2015-05-09T00:10:23.123
2015-05-09T00:10:23.120
2015-05-09T00:10:23.100
2015-05-09T00:10:23.100
2015-05-09T00:10:23
2015-05-09T00:10
2015-05-09T00:00

Узнайте больше о современном API для определения даты и времени из Trail: Date Time.

Ответ 4

ТЕСТОВЫЙ КОД:

@Test
void contextLoads() throws ParseException {
DateTimeFormatter ISO_LOCAL_DATE = new >DateTimeFormatterBuilder()
.appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
.optionalStart().appendLiteral('/').optionalEnd()
.optionalStart().appendLiteral('-').optionalEnd()
.optionalStart().appendValue(MONTH_OF_YEAR, 2)
.optionalStart().appendLiteral('/').optionalEnd()
.optionalStart().appendLiteral('-').optionalEnd()
.optionalStart().appendValue(DAY_OF_MONTH, 2)
.toFormatter();

DateTimeFormatter ISO_LOCAL_TIME = new DateTimeFormatterBuilder()
.appendValue(HOUR_OF_DAY, 2)
.optionalStart().appendLiteral(':').appendValue(MINUTE_OF_HOUR, 2)
.optionalStart().appendLiteral(':').appendValue(SECOND_OF_MINUTE, 2)
.optionalStart().appendFraction(NANO_OF_SECOND, 0, 9, true)
.optionalStart().appendZoneId()
.toFormatter();

DateTimeFormatter dateTimeFormatter = new DateTimeFormatterBuilder()
.append(ISO_LOCAL_DATE)
.optionalStart().appendLiteral(' ').optionalEnd()
.optionalStart().appendLiteral('T').optionalEnd()
.optionalStart().appendOptional(ISO_LOCAL_TIME).optionalEnd()
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
.toFormatter(Locale.SIMPLIFIED_CHINESE);

Stream.of("2015-05-09T00:10:23.934596635Z",
"2015-05-09 00:10:23.123456789UTC",
"2015/05/09 00:10:23.123456789",
"2015-05-09 00:10:23.12345678",
"2015/05/09 00:10:23.1234567",
"2015-05-09T00:10:23.123456",
"2015-05-09 00:10:23.12345",
"2015/05-09T00:10:23.1234",
"2015-05-09 00:10:23.123",
"2015-05-09 00:10:23.12",
"2015-05-09 00:10:23.1",
"2015-05-09 00:10:23",
"2015-05-09 00:10",
"2015-05-09 01",
"2015-05-09"
).forEach(s -> {
LocalDateTime date = LocalDateTime.parse(s, dateTimeFormatter);
System.out.println(s + " ==> " + date);
});
}

ВЫХОД:

2015-05-09T00:10:23.934596635Z ==> 2015-05-09T00:10:23.934596635
2015-05-09 00:10:23.123456789UTC ==> 2015-05-09T00:10:23.123456789
2015/05/09 00:10:23.123456789 ==> 2015-05-09T00:10:23.123456789
2015-05-09 00:10:23.12345678 ==> 2015-05-09T00:10:23.123456780
2015/05/09 00:10:23.1234567 ==> 2015-05-09T00:10:23.123456700
2015-05-09T00:10:23.123456 ==> 2015-05-09T00:10:23.123456
2015-05-09 00:10:23.12345 ==> 2015-05-09T00:10:23.123450
2015/05-09T00:10:23.1234 ==> 2015-05-09T00:10:23.123400
2015-05-09 00:10:23.123 ==> 2015-05-09T00:10:23.123
2015-05-09 00:10:23.12 ==> 2015-05-09T00:10:23.120
2015-05-09 00:10:23.1 ==> 2015-05-09T00:10:23.100
2015-05-09 00:10:23 ==> 2015-05-09T00:10:23
2015-05-09 00:10 ==> 2015-05-09T00:10
2015-05-09 01 ==> 2015-05-09T01:00
2015-05-09 ==> 2015-05-09T00:00
java