Розбір рядка з датою та часом у певний момент часу (Java називає це " Instant
") досить складний. Java вирішує це в декількох ітераціях. Найновіший java.time
і java.time.chrono
, охоплює майже всі потреби (крім часового розширення :)).
Однак ця складність приносить багато плутанини.
Ключовим словом для розбору дат є:
Чому у Java існує стільки способів розбору дати
- Існує кілька систем для вимірювання часу. Наприклад, історичні японські календарі були похідні від часових періодів правління відповідного імператора чи династії. Потім є, наприклад, часова мітка UNIX. На щастя, усьому (діловому) світу вдалося використати те саме.
- Історично історичні причини перемикалися з / на з різних причин . Напр. Від Юліанського календаря до григоріанського календаря на 1582 рік. Тому до "західних" дат до цього потрібно ставитися по-різному.
- І звичайно зміна відбулася не відразу. Оскільки календар походив зі штаб-квартири деяких релігій та інших частин Європи, які вірили в інші страждання, наприклад, Німеччина не змінилася до 1700 року.
... і чому це LocalDateTime
, і ZonedDateTime
ін. такий складний
Є часові пояси . Часовий пояс - це, в основному, «смуга» * [1] поверхні Землі, влада якої дотримується тих же правил, коли він має на який час змістити час. Сюди входять літні правила часу.
Часові пояси змінюються з часом для різних областей, в основному залежно від того, хто кого перемагає. І з часом змінюються правила одного часового поясу .
Є компенсації часу. Це не те саме, що часові пояси, оскільки часовий пояс може бути, наприклад, "Прага", але це зміщення літнього часу та зимовий час.
Якщо ви отримаєте позначку часу з часовим поясом, зміщення може змінюватись, залежно від того, в якій частині року він знаходиться. Протягом високосної години часова марка може означати два різні часи, тому без додаткової інформації це неможливо надійно перетворений.
Примітка. Під часовою позначкою я маю на увазі "рядок, що містить дату та / або час, необов'язково з часовим поясом та / або зміщенням часу".
Кілька часових поясів можуть мати спільне зміщення часу на певні періоди. Наприклад, часовий пояс GMT / UTC - такий самий, як часовий пояс "Лондон", коли зміщення літнього часу не діє.
Щоб зробити це трохи складніше (але це не надто важливо для вашої справи використання):
- Вчені спостерігають динаміку Землі, яка змінюється з часом; виходячи з цього, вони додають секунди наприкінці окремих років. (Так
2040-12-31 24:00:00
може бути дійсним дата-час.) Для цього потрібні регулярні оновлення метаданих, якими користуються системи для правильного перетворення дат. Наприклад, в Linux ви отримуєте регулярні оновлення пакетів Java, включаючи ці нові дані.
Оновлення не завжди зберігають попередню поведінку як для історичних, так і для майбутніх часових позначок. Тому може статися, що аналіз двох часових позначок навколо зміни часового поясу, порівнюючи їх, може давати різні результати при роботі на різних версіях програмного забезпечення. Це також стосується порівняння між ураженим часовим поясом та іншим часовим поясом.
Якщо це спричинить помилку у вашому програмному забезпеченні, подумайте про використання часової позначки, яка не має таких складних правил, як часова мітка UNIX .
Через 7 для майбутніх дат ми не можемо точно конвертувати дати точно. Так, наприклад, поточний синтаксичний аналіз 8524-02-17 12:00:00
може бути відключений через пару секунд від майбутнього розбору.
API JDK для цього розвивались із сучасними потребами
- Ранні випуски Java мали лише
java.util.Date
якийсь наївний підхід, припускаючи, що існує лише рік, місяць, день і час. Цього швидко не вистачало.
- Також потреби в базах даних були різними, тому досить рано
java.sql.Date
було введено, з власними обмеженнями.
- Оскільки жодним чином не охоплені різні календарі та часові пояси,
Calendar
API було запроваджено.
- Це все ще не охоплювало складності часових поясів. І все ж, поєднання вищевказаних API було справді нелегким завданням. Оскільки розробники Java почали працювати над глобальними веб-додатками, бібліотеки, націлені на більшість випадків використання, як-от JodaTime, швидко набули популярності. JodaTime був де-факто стандартом протягом декади.
- Але JDK не інтегрувався з JodaTime, тому робота з ним була трохи громіздкою. Отже, після дуже тривалої дискусії про те, як підійти до справи, JSR-310 був створений в основному на базі JodaTime .
Як боротися з цим у Java java.time
Визначте, до якого типу потрібно розібрати часову позначку
Коли ви вживаєте рядок часової позначки, вам потрібно знати, яку інформацію вона містить. Це вирішальний момент. Якщо ви не отримаєте цього права, ви закінчитесь із криптовалютними винятками, такими як "Не вдається створити миттєвий" або "Зсув зони", або "невідомий ідентифікатор зони" тощо.
Чи містить вона дату та час?
Чи має час зміщення часу?
Зсув часу - це +hh:mm
частина. Іноді +00:00
може бути замінено Z
як "зулуський час", UTC
як універсальний координований час або GMT
як середній час Грінвіч. Вони також встановлюють часовий пояс.
Для цих часових позначок ви використовуєте OffsetDateTime
.
Чи є в ньому часовий пояс?
Для цих часових позначок ви використовуєте ZonedDateTime
.
Зону визначає або
- назва ("Прага", "Тихоокеанський стандартний час", "PST"), або
- "ідентифікатор зони" ("Америка / Лос_Ангелес", "Європа / Лондон"), представлений java.time.ZoneId .
Список часових поясів складається за допомогою "бази даних TZ" , підкріпленої ICAAN.
За словами ZoneId
javadoc, ідентифікатор зони також можна якось вказати як Z
і зсув. Я не впевнений, як це відображається до реальних зон. Якщо часова марка, у якої є лише TZ, потрапляє у стрибкову годину зміни зміщення часу, то це неоднозначно, і тлумачення підлягає ResolverStyle
, див. Нижче.
Якщо вона не має жодного , тоді припущенний контекст передбачається або нехтується. І споживач повинен вирішити. Тому його потрібно проаналізувати як LocalDateTime
і перетворити на OffsetDateTime
нього, додавши відсутні дані:
- Можна припустити, що це час UTC. Додайте зміщення за UTC за 0 годин.
- Можна припустити, що це час місця, де відбувається перетворення. Перетворіть його, додавши часовий пояс системи.
- Можна знехтувати і просто використовувати його як є. Це корисно, наприклад, для порівняння або підведення підсумків у два рази (див.
Duration
), Або коли ви цього не знаєте, і це не має значення (наприклад, розклад місцевих автобусів).
Часткова інформація про час
- На підставі того, що містить штамп часу , ви можете прийняти
LocalDate
, LocalTime
, OffsetTime
, MonthDay
, Year
, або YearMonth
з нього.
Якщо у вас є повна інформація, ви можете отримати java.time.Instant
. Це також використовується для перетворення між OffsetDateTime
та ZonedDateTime
.
З’ясуйте, як його розібрати
Існує велика документація, за DateTimeFormatter
якою можна як проаналізувати рядок часової позначки, так і відформатувати її до рядка.
У заздалегідь створених DateTimeFormatter
s повинні охоплювати большеменьше всі стандартні формати тимчасових міток. Наприклад, ISO_INSTANT
може розбирати 2011-12-03T10:15:30.123457Z
.
Якщо у вас є спеціальний формат, ви можете створити свій власний DateTimeFormatter (який також є аналізатором).
private static final DateTimeFormatter TIMESTAMP_PARSER = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SX"))
.toFormatter();
Рекомендую переглянути вихідний код DateTimeFormatter
та надихнутися, як створити його за допомогою DateTimeFormatterBuilder
. Поки ви там, також погляньте на те, ResolverStyle
який визначає, чи аналізатор LENIENT, SMART або STRICT для форматів та неоднозначної інформації.
TemporalAccessor
Зараз частою помилкою є втручання в складність TemporalAccessor
. Це походить від того, як розробники звикли працювати SimpleDateFormatter.parse(String)
. Правильно, DateTimeFormatter.parse("...")
дає вам TemporalAccessor
.
// No need for this!
TemporalAccessor ta = TIMESTAMP_PARSER.parse("2011-... etc");
Але, оснащені знаннями з попереднього розділу, ви можете зручно проаналізувати потрібний тип:
OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z", TIMESTAMP_PARSER);
Вам насправді і не потрібно DateTimeFormatter
. Типи, які потрібно розібрати, мають parse(String)
методи.
OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z");
Що стосується TemporalAccessor
, ви можете використовувати його, якщо маєте розпливчасте уявлення про те, яка інформація є в рядку, і хочете вирішити під час виконання.
Я сподіваюся, що я пролити трохи розуміння на вашу душу :)
Примітка. Є підтримка java.time
Java 6 і 7: ThreeTen-Backport . Для Android у нього є ThreeTenABP .
[1] Мало того, що вони не є смужками, але є і деякі дивні крайнощі. Наприклад, деякі сусідні тихі острови мають часові пояси +14: 00 та -11: 00. Це означає, що хоча на одному острові є 1 травня 3 вечора, на іншому острові не так далеко, це ще 30 квітня 12 вечора (якщо я правильно порахував :))
ZonedDateTime
скоріше, ніж аLocalDateTime
. Назва є протиінтуїтивно зрозумілою;Local
означає будь-яку місцевість в цілому , а не по часу конкретної зони. Як такий,LocalDateTime
об’єкт не прив’язаний до часової лінії. Щоб мати значення, щоб отримати вказаний момент на часовій лінії, потрібно застосувати часовий пояс.