Як розібрати / відформатувати дати за допомогою LocalDateTime? (Java 8)


341

Java 8 додала новий API Java.time для роботи з датами та часом ( JSR 310 ).

У мене є дата і час як рядки (наприклад "2014-04-08 12:30"). Як я можу отримати LocalDateTimeекземпляр із заданого рядка?

Після того як я закінчив роботу з LocalDateTimeоб'єктом: Як я можу потім перетворити LocalDateTimeекземпляр назад у рядок у тому ж форматі, що показано вище?


11
FYI, більшість людей більшість часу хотіли б ZonedDateTimeскоріше, ніж а LocalDateTime. Назва є протиінтуїтивно зрозумілою; Localозначає будь-яку місцевість в цілому , а не по часу конкретної зони. Як такий, LocalDateTimeоб’єкт не прив’язаний до часової лінії. Щоб мати значення, щоб отримати вказаний момент на часовій лінії, потрібно застосувати часовий пояс.
Василь Бурк

Дивіться моя відповідь для пояснення LocalDateTimeпроти ZonedDateTimeпроти OffsetDateTimeпроти Instantпроти LocalDateпроти LocalTime, як зберегти спокій про те, чому це так складно і як зробити це правильно з першого пострілу.
Ондра Жижка

1
Якби це було непрактично довго, LocalDateTimeнапевно, його назвали б ZonelessOffsetlessDateTime.
Ondra Žižka

Відповіді:


534

Дата та час розбору

Для створення LocalDateTimeоб'єкта з рядка ви можете використовувати статичний LocalDateTime.parse()метод. Він приймає рядок та DateTimeFormatterпараметр a . DateTimeFormatterВикористовується для вказівки шаблону дати / часу.

String str = "1986-04-08 12:30";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.parse(str, formatter);

Форматування дати та часу

Для створення відформатованого рядка з LocalDateTimeоб'єкта можна скористатися format()методом.

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.of(1986, Month.APRIL, 8, 12, 30);
String formattedDateTime = dateTime.format(formatter); // "1986-04-08 12:30"

Зауважте, що є деякі часто використовувані формати дати / часу, визначені як константи в DateTimeFormatter. Наприклад: Використання DateTimeFormatter.ISO_DATE_TIMEдля форматування LocalDateTimeпримірника зверху призведе до рядка "1986-04-08T12:30:00".

В parse()і format()методи доступні для всіх дата / час , пов'язані з об'єктами (наприклад , LocalDateчи ZonedDateTime)


77
Зауважимо лише, що DateTimeFormatter є незмінним і безпечним для потоків, і тому рекомендований підхід полягає в тому, щоб зберігати його в статичній константі, де це можливо.
JodaStephen

@micha, що якщо у мене "2016-12-31T07: 59: 00.000Z" цей формат дати?
Давуд Ахмед

14
Спробуйте @DawoodAbbasiDateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")
Рей Хулха

1
@Loenix, можливо, це тому, що ви намагаєтесь зателефонувати format()на клас LocalDateTime, а не на екземпляр? По крайней мере, це те, що я зробив: я плутати DateTimeз dateTimeв наведеному вище прикладі.
glaed

2
Не забувайте про великі
регістри

159

Ви також можете використовувати LocalDate.parse()або LocalDateTime.parse()на Stringбез надання його з малюнком, якщо Stringв форматі ISO-8601 .

наприклад,

String strDate = "2015-08-04";
LocalDate aLD = LocalDate.parse(strDate);
System.out.println("Date: " + aLD);

String strDatewithTime = "2015-08-04T10:11:30";
LocalDateTime aLDT = LocalDateTime.parse(strDatewithTime);
System.out.println("Date with Time: " + aLDT);

Вихід ,

Date: 2015-08-04
Date with Time: 2015-08-04T10:11:30

і використовувати його, DateTimeFormatterлише якщо вам доведеться мати справу з іншими моделями дат.

Наприклад, у наступному прикладі dd MMM uuuu позначає день місяця (дві цифри), три літери назви місяця (січень, лютий, березень ...) та чотиризначний рік:

DateTimeFormatter dTF = DateTimeFormatter.ofPattern("dd MMM uuuu");
String anotherDate = "04 Aug 2015";
LocalDate lds = LocalDate.parse(anotherDate, dTF);
System.out.println(anotherDate + " parses to " + lds);

Вихідні дані

04 Aug 2015 parses to 2015-08-04

також пам’ятайте, що DateTimeFormatterоб’єкт двосторонній; він може аналізувати вхід і форматувати вихід.

String strDate = "2015-08-04";
LocalDate aLD = LocalDate.parse(strDate);
DateTimeFormatter dTF = DateTimeFormatter.ofPattern("dd MMM uuuu");
System.out.println(aLD + " formats as " + dTF.format(aLD));

Вихідні дані

2015-08-04 formats as 04 Aug 2015

(див. повний перелік шаблонів форматування та розбору датиFormatter )

  Symbol  Meaning                     Presentation      Examples
  ------  -------                     ------------      -------
   G       era                         text              AD; Anno Domini; A
   u       year                        year              2004; 04
   y       year-of-era                 year              2004; 04
   D       day-of-year                 number            189
   M/L     month-of-year               number/text       7; 07; Jul; July; J
   d       day-of-month                number            10

   Q/q     quarter-of-year             number/text       3; 03; Q3; 3rd quarter
   Y       week-based-year             year              1996; 96
   w       week-of-week-based-year     number            27
   W       week-of-month               number            4
   E       day-of-week                 text              Tue; Tuesday; T
   e/c     localized day-of-week       number/text       2; 02; Tue; Tuesday; T
   F       week-of-month               number            3

   a       am-pm-of-day                text              PM
   h       clock-hour-of-am-pm (1-12)  number            12
   K       hour-of-am-pm (0-11)        number            0
   k       clock-hour-of-am-pm (1-24)  number            0

   H       hour-of-day (0-23)          number            0
   m       minute-of-hour              number            30
   s       second-of-minute            number            55
   S       fraction-of-second          fraction          978
   A       milli-of-day                number            1234
   n       nano-of-second              number            987654321
   N       nano-of-day                 number            1234000000

   V       time-zone ID                zone-id           America/Los_Angeles; Z; -08:30
   z       time-zone name              zone-name         Pacific Standard Time; PST
   O       localized zone-offset       offset-O          GMT+8; GMT+08:00; UTC-08:00;
   X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
   x       zone-offset                 offset-x          +0000; -08; -0830; -08:30; -083015; -08:30:15;
   Z       zone-offset                 offset-Z          +0000; -0800; -08:00;

   p       pad next                    pad modifier      1

   '       escape for text             delimiter
   ''      single quote                literal           '
   [       optional section start
   ]       optional section end
   #       reserved for future use
   {       reserved for future use
   }       reserved for future use

11
Ця відповідь торкнулася важливої ​​теми: використовуйте заздалегідь задані формати, де це можливо, наприклад НЕ створюйте основу форматування на "yyyy-MM-dd", використовуйте DateTimeFormatter.ISO_LOCAL_DATE замість цього. Це зробить ваш код виглядати в цілому чистіше. Крім того, намагайтеся максимально використовувати формат ISO8061, це виплатить дивіденди в довгостроковій перспективі.
Крістофер Ян

Я хочу проаналізувати дату перевірки на зразок, 2018-08-09 12:00:08але коли я розбираю, я бачу T, що додається, яка мені не потрібна. Чи є спосіб це зробити?
Raghuveer

@Raghuveer T - це просто роздільник ISO-8061 між датою та часом. Якщо натомість у вашому форматі є пробіл, ви можете просто використовувати шаблон yyyy-MM-dd hh:mm:ssдля розбору та форматування. T завжди відображатиметься у форматі за замовчуванням (ISO-8061), але ви можете використовувати власні шаблони.
Егор Ганс

39

Обидві відповіді вище добре пояснюють питання щодо рядкових моделей. Однак на всякий випадок, коли ви працюєте з ISO 8601 , не потрібно застосовувати заявки, DateTimeFormatterоскільки LocalDateTime вже готовий до цього:

Перетворити LocalDateTime у часовий пояс рядка ISO8601

LocalDateTime ldt = LocalDateTime.now(); 
ZonedDateTime zdt = ldt.atZone(ZoneOffset.UTC); //you might use a different zone
String iso8601 = zdt.toString();

Перетворити рядок ISO8601 в LocalDateTime

String iso8601 = "2016-02-14T18:32:04.150Z";
ZonedDateTime zdt = ZonedDateTime.parse(iso8601);
LocalDateTime ldt = zdt.toLocalDateTime();

20

Розбір рядка з датою та часом у певний момент часу (Java називає це " Instant") досить складний. Java вирішує це в декількох ітераціях. Найновіший java.timeі java.time.chrono, охоплює майже всі потреби (крім часового розширення :)).

Однак ця складність приносить багато плутанини.

Ключовим словом для розбору дат є:

Чому у Java існує стільки способів розбору дати

  1. Існує кілька систем для вимірювання часу. Наприклад, історичні японські календарі були похідні від часових періодів правління відповідного імператора чи династії. Потім є, наприклад, часова мітка UNIX. На щастя, усьому (діловому) світу вдалося використати те саме.
  2. Історично історичні причини перемикалися з / на з різних причин . Напр. Від Юліанського календаря до григоріанського календаря на 1582 рік. Тому до "західних" дат до цього потрібно ставитися по-різному.
  3. І звичайно зміна відбулася не відразу. Оскільки календар походив зі штаб-квартири деяких релігій та інших частин Європи, які вірили в інші страждання, наприклад, Німеччина не змінилася до 1700 року.

... і чому це LocalDateTime, і ZonedDateTimeін. такий складний

  1. Є часові пояси . Часовий пояс - це, в основному, «смуга» * [1] поверхні Землі, влада якої дотримується тих же правил, коли він має на який час змістити час. Сюди входять літні правила часу.
    Часові пояси змінюються з часом для різних областей, в основному залежно від того, хто кого перемагає. І з часом змінюються правила одного часового поясу .

  2. Є компенсації часу. Це не те саме, що часові пояси, оскільки часовий пояс може бути, наприклад, "Прага", але це зміщення літнього часу та зимовий час.
    Якщо ви отримаєте позначку часу з часовим поясом, зміщення може змінюватись, залежно від того, в якій частині року він знаходиться. Протягом високосної години часова марка може означати два різні часи, тому без додаткової інформації це неможливо надійно перетворений.
    Примітка. Під часовою позначкою я маю на увазі "рядок, що містить дату та / або час, необов'язково з часовим поясом та / або зміщенням часу".

  3. Кілька часових поясів можуть мати спільне зміщення часу на певні періоди. Наприклад, часовий пояс GMT / UTC - такий самий, як часовий пояс "Лондон", коли зміщення літнього часу не діє.

Щоб зробити це трохи складніше (але це не надто важливо для вашої справи використання):

  1. Вчені спостерігають динаміку Землі, яка змінюється з часом; виходячи з цього, вони додають секунди наприкінці окремих років. (Так 2040-12-31 24:00:00може бути дійсним дата-час.) Для цього потрібні регулярні оновлення метаданих, якими користуються системи для правильного перетворення дат. Наприклад, в Linux ви отримуєте регулярні оновлення пакетів Java, включаючи ці нові дані.
  2. Оновлення не завжди зберігають попередню поведінку як для історичних, так і для майбутніх часових позначок. Тому може статися, що аналіз двох часових позначок навколо зміни часового поясу, порівнюючи їх, може давати різні результати при роботі на різних версіях програмного забезпечення. Це також стосується порівняння між ураженим часовим поясом та іншим часовим поясом.

    Якщо це спричинить помилку у вашому програмному забезпеченні, подумайте про використання часової позначки, яка не має таких складних правил, як часова мітка UNIX .

  3. Через 7 для майбутніх дат ми не можемо точно конвертувати дати точно. Так, наприклад, поточний синтаксичний аналіз 8524-02-17 12:00:00може бути відключений через пару секунд від майбутнього розбору.

API JDK для цього розвивались із сучасними потребами

  • Ранні випуски Java мали лише java.util.Dateякийсь наївний підхід, припускаючи, що існує лише рік, місяць, день і час. Цього швидко не вистачало.
  • Також потреби в базах даних були різними, тому досить рано java.sql.Dateбуло введено, з власними обмеженнями.
  • Оскільки жодним чином не охоплені різні календарі та часові пояси, CalendarAPI було запроваджено.
  • Це все ще не охоплювало складності часових поясів. І все ж, поєднання вищевказаних API було справді нелегким завданням. Оскільки розробники Java почали працювати над глобальними веб-додатками, бібліотеки, націлені на більшість випадків використання, як-от JodaTime, швидко набули популярності. JodaTime був де-факто стандартом протягом декади.
  • Але JDK не інтегрувався з JodaTime, тому робота з ним була трохи громіздкою. Отже, після дуже тривалої дискусії про те, як підійти до справи, JSR-310 був створений в основному на базі JodaTime .

Як боротися з цим у Java java.time

Визначте, до якого типу потрібно розібрати часову позначку

Коли ви вживаєте рядок часової позначки, вам потрібно знати, яку інформацію вона містить. Це вирішальний момент. Якщо ви не отримаєте цього права, ви закінчитесь із криптовалютними винятками, такими як "Не вдається створити миттєвий" або "Зсув зони", або "невідомий ідентифікатор зони" тощо.

Чи містить вона дату та час?

  1. Чи має час зміщення часу?
    Зсув часу - це +hh:mmчастина. Іноді +00:00може бути замінено Zяк "зулуський час", UTCяк універсальний координований час або GMTяк середній час Грінвіч. Вони також встановлюють часовий пояс.
    Для цих часових позначок ви використовуєте OffsetDateTime.

  2. Чи є в ньому часовий пояс?
    Для цих часових позначок ви використовуєте ZonedDateTime.
    Зону визначає або

    • назва ("Прага", "Тихоокеанський стандартний час", "PST"), або
    • "ідентифікатор зони" ("Америка / Лос_Ангелес", "Європа / Лондон"), представлений java.time.ZoneId .

    Список часових поясів складається за допомогою "бази даних TZ" , підкріпленої ICAAN.

    За словами ZoneIdjavadoc, ідентифікатор зони також можна якось вказати як Zі зсув. Я не впевнений, як це відображається до реальних зон. Якщо часова марка, у якої є лише TZ, потрапляє у стрибкову годину зміни зміщення часу, то це неоднозначно, і тлумачення підлягає ResolverStyle, див. Нижче.

  3. Якщо вона не має жодного , тоді припущенний контекст передбачається або нехтується. І споживач повинен вирішити. Тому його потрібно проаналізувати як LocalDateTimeі перетворити на OffsetDateTimeнього, додавши відсутні дані:

    • Можна припустити, що це час UTC. Додайте зміщення за UTC за 0 годин.
    • Можна припустити, що це час місця, де відбувається перетворення. Перетворіть його, додавши часовий пояс системи.
    • Можна знехтувати і просто використовувати його як є. Це корисно, наприклад, для порівняння або підведення підсумків у два рази (див. Duration), Або коли ви цього не знаєте, і це не має значення (наприклад, розклад місцевих автобусів).

Часткова інформація про час

  • На підставі того, що містить штамп часу , ви можете прийняти LocalDate, LocalTime, OffsetTime, MonthDay, Year, або YearMonthз нього.

Якщо у вас є повна інформація, ви можете отримати java.time.Instant. Це також використовується для перетворення між OffsetDateTimeта ZonedDateTime.

З’ясуйте, як його розібрати

Існує велика документація, за DateTimeFormatterякою можна як проаналізувати рядок часової позначки, так і відформатувати її до рядка.

У заздалегідь створених DateTimeFormatters повинні охоплювати большеменьше всі стандартні формати тимчасових міток. Наприклад, 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.timeJava 6 і 7: ThreeTen-Backport . Для Android у нього є ThreeTenABP .

[1] Мало того, що вони не є смужками, але є і деякі дивні крайнощі. Наприклад, деякі сусідні тихі острови мають часові пояси +14: 00 та -11: 00. Це означає, що хоча на одному острові є 1 травня 3 вечора, на іншому острові не так далеко, це ще 30 квітня 12 вечора (якщо я правильно порахував :))


3

НАДАЙТЕ ТОЧНИЙ ЧАС UTC В ПОТРІБНОМУ ФОРМАті

// Current UTC time
        OffsetDateTime utc = OffsetDateTime.now(ZoneOffset.UTC);

        // GET LocalDateTime 
        LocalDateTime localDateTime = utc.toLocalDateTime();
        System.out.println("*************" + localDateTime);

        // formated UTC time
        DateTimeFormatter dTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        System.out.println(" formats as " + dTF.format(localDateTime));

        //GET UTC time for current date
        Date now= new Date();
        LocalDateTime utcDateTimeForCurrentDateTime = Instant.ofEpochMilli(now.getTime()).atZone(ZoneId.of("UTC")).toLocalDateTime();
        DateTimeFormatter dTF2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        System.out.println(" formats as " + dTF2.format(utcDateTimeForCurrentDateTime));

0

Я вважав, що це чудово, щоб охопити кілька варіантів формату часу таким чином:

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);

1
`` `публічний кінцевий статичний DateTimeFormatter TIMESTAMP_XX = новий DateTimeFormatterBuilder (). appendPattern (" [[uuuu] [- MM] [- dd]] [[HH] [: mm] [: ss] [. SSS]] "). parseDefaulting (ChronoField.YEAR, 2020) .parseDefaulting (ChronoField.MONTH_OF_YEAR, 1) .parseDefaulting (ChronoField.DAY_OF_MONTH, 1) .parseDefaulting (ChronoField.HOUR_OF_DAY, 0) .parseDefaulting (ChronoField.MINUTE_OF_HOUR, 0) .parseDefaulting (ChronoField.SECOND_OF_MINUTE , 0) .parseDefaulting (ChronoField.NANO_OF_SECOND, 0) .toFormatter (); `` `
Алан Стюарт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.