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