LocalDateTime으로 날짜를 구문 분석 / 형식화하는 방법은 무엇입니까? (자바 8)


341

Java 8 은 날짜 및 시간 작업을위한 새로운 java.time API를 추가했습니다 ( JSR 310 ).

날짜와 시간이 문자열로 있습니다 (예 :) "2014-04-08 12:30". LocalDateTime주어진 문자열에서 인스턴스를 어떻게 얻을 수 있습니까?

LocalDateTime객체 작업을 마친 후 : LocalDateTime인스턴스를 위에 표시된 것과 같은 형식의 문자열로 다시 변환하려면 어떻게 해야합니까?


11
참고로, 대부분의 시간을 대부분의 사람들이 원하는 것이 ZonedDateTime오히려 이상을 LocalDateTime. 이름은 반 직관적입니다. 는 Local의미 있는 일반 지역이 아닌 특정 시간대를. 따라서 LocalDateTime개체는 타임 라인에 연결되지 않습니다. 타임 라인에서 특정 순간을 얻으려면 의미를 가지려면 시간대를 적용해야합니다.
Basil Bourque

에 대한 설명은 내 대답을 참조 LocalDateTimeZonedDateTimeOffsetDateTimeInstantLocalDateLocalTime, 그것은 너무 복잡하고 어떻게 바로 첫 번째 촬영에서 그것을 할 수있어 이유에 대해 침착을 유지하는 방법에 대해 설명합니다.
Ondra Žižka

1
비실용적으로 길지 LocalDateTime않았다면 아마도 이름이되었을 것입니다 ZonelessOffsetlessDateTime.
Ondra Žižka 2016 년

답변:


534

구문 분석 날짜 및 시간

LocalDateTime문자열에서 객체 를 만들려면 정적 LocalDateTime.parse()메서드를 사용할 수 있습니다 . 문자열과 DateTimeFormatter매개 변수로 사용됩니다. 는 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인스턴스의 형식 을 지정하면 string이 "1986-04-08T12:30:00"됩니다.

parse()format()방법은 모든 날짜 / 시간 관련 개체 사용할 수 있습니다 (예를 들어, LocalDate또는 ZonedDateTime)


77
DateTimeFormatter는 변경 불가능하고 스레드로부터 안전하므로 가능한 한 정적 상수로 저장하는 것이 좋습니다.
JodaStephen

@micha이 날짜가 "2016-12-31T07 : 59 : 00.000Z"인 경우 어떻게해야합니까?
Dawood Ahmed

14
@DawoodAbbasi tryDateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")
Ray Hulha

1
@Loenix format()는 인스턴스 대신 LocalDateTime 클래스 를 호출하려고하기 때문일 수 있습니다 . 적어도 내가 한 일입니다. 위의 예에서 혼동 DateTime되었습니다 dateTime.
glaed

2
MM의 대문자를 잊지 마십시오
Wesos de Queso 2006 년

159

ISO-8601 형식 인 경우 패턴을 제공하지 않고 LocalDate.parse()또는 LocalDateTime.parse()에 사용할 수 있습니다 .StringString

예를 들어

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 는 월의 일 (2 자리), 월 이름의 3 자 (1 월, 2 월, 3 월, ...) 및 4 자리 연도를 나타냅니다.

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

(완전한 참조 포맷 및 구문 분석 DateFormatter에 대한 패턴의 목록 )

  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 형식의 사용을 최대화하려고하면 장기적으로 배당금을 지불하게됩니다.
Christopher Yang

유효성 검사를 위해 날짜를 구문 분석하고 싶지만 구문 분석 2018-08-09 12:00:08할 때 T필요하지 않은 추가가 표시됩니다. 그것을 할 수있는 방법이 있습니까?
Raghuveer

@Raghuveer T는 날짜와 시간 사이의 ISO-8061 구분자입니다. 형식에 공백이 있으면 yyyy-MM-dd hh:mm:ss구문 분석 및 형식화에 패턴 을 사용할 수 있습니다 . T는 항상 기본 (ISO-8061) 형식으로 표시되지만 고유 한 패턴을 사용할 수 있습니다.
Egor Hans

39

위의 두 답변 모두 문자열 패턴에 관한 질문을 잘 설명합니다. 그러나 ISO 8601 로 작업하는 경우 DateTimeFormatterLocalDateTime이 이미 준비되어 있으므로 적용 할 필요가 없습니다 .

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.timejava.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향후 구문 분석에서 몇 초가 걸릴 수 있습니다.

이를위한 JDK의 API는 현대의 요구와 함께 발전했습니다.

  • 초기 Java 릴리스에는 java.util.Date연도, 월, 일 및 시간이 있다고 가정하고 약간 순진한 접근 방식이있었습니다. 이것은 빨리 충분하지 않았다.
  • 또한 데이터베이스의 요구 사항이 다르기 때문에 java.sql.Date자체 제한 사항이 있기 때문에 매우 초기 에 도입되었습니다.
  • 다른 캘린더와 시간대를 잘 다루지 않았으므로 CalendarAPI가 도입되었습니다.
  • 이것은 여전히 ​​표준 시간대의 복잡성을 다루지 않았습니다. 그러나 위 API의 혼합은 실제로 작업하기가 어려웠습니다. 따라서 Java 개발자가 글로벌 웹 응용 프로그램 작업을 시작하면서 JodaTime과 같은 대부분의 사용 사례를 대상으로하는 라이브러리가 빠르게 인기를 얻었습니다. JodaTime은 약 10 년 동안 사실상의 표준이었습니다.
  • 그러나 JDK는 JodaTime과 통합되지 않았으므로 작업이 약간 번거로 웠습니다. 따라서 JSR-310 은이 문제에 접근하는 방법에 대한 매우 긴 토론 끝에 주로 JodaTime을 기반 으로 만들어졌습니다 .

Java에서 처리하는 방법 java.time

타임 스탬프를 구문 분석 할 유형 결정

타임 스탬프 문자열을 사용할 때는 어떤 정보가 들어 있는지 알아야합니다. 이것이 중요한 포인트입니다. 이 권한을 얻지 못하면 "즉시 만들 수 없음"또는 "영역 오프셋 누락"또는 "알 수없는 영역 ID"등과 같은 예외적 인 예외가 발생합니다.

날짜와 시간이 포함되어 있습니까?

  1. 시간 오프셋이 있습니까?
    시간 오프셋이 +hh:mm부품입니다. 경우에 따라 '줄루 시간', 표준시 또는 그리니치 표준시 +00:00로 대체 될 수 있습니다 . 또한 시간대를 설정합니다. 이 타임 스탬프에는을 사용 합니다.ZUTCGMT
    OffsetDateTime

  2. 시간대가 있습니까?
    이 타임 스탬프에는을 사용 ZonedDateTime합니다.
    영역은 다음 중 하나로 지정됩니다.

    • 이름 ( "프라하", "태평양 표준시", "PST") 또는
    • "zone ID"( "America / Los_Angeles", "Europe / London")는 java.time.ZoneId로 표시됩니다 .

    시간대 목록은 ICAAN이 지원 하는 "TZ 데이터베이스"에 의해 컴파일됩니다 .

    ZoneId의 javadoc 에 따르면 영역 ID를 어떻게 든 지정 Z하고 오프셋 으로 지정할 수 있습니다 . 이것이 실제 영역에 어떻게 매핑되는지 잘 모르겠습니다. TZ 만있는 타임 스탬프가 윤초 시간 오프셋 변경에 속하면 모호하며 해석이 적용 ResolverStyle됩니다 (아래 참조).

  3. 이없는 경우 누락 된 컨텍스트가 가정되거나 무시됩니다. 그리고 소비자는 결정해야합니다. 따라서 누락 된 정보를 추가하여 구문 분석 LocalDateTime하고 변환해야 OffsetDateTime합니다.

    • UTC 시간 이라고 가정 할 수 있습니다 . UTC 오프셋을 0 시간으로 추가하십시오.
    • 전환이 발생한 시점 이라고 가정 할 수 있습니다 . 시스템 시간대를 추가하여 변환하십시오.
    • 무시 하고 그대로 사용할 수 있습니다 . 예를 들어 두 번 비교 또는 빼거나 () 참조 Duration하거나 모르거나 실제로 중요하지 않은 경우 (예 : 로컬 버스 일정)에 유용합니다 .

부분 시간 정보

  • 타임 스탬프가 포함 된 내용을 바탕으로, 당신이 취할 수 LocalDate, LocalTime, OffsetTime, MonthDay, Year, 또는 YearMonth그것에서.

전체 정보가 있으면를 얻을 수 있습니다 java.time.Instant. 또한 내부적으로 OffsetDateTime와 사이를 변환하는 데 사용됩니다 ZonedDateTime.

그것을 파싱하는 방법을 알아 내십시오.

DateTimeFormatter타임 스탬프 문자열과 형식을 문자열로 구문 분석 할 수 있는 광범위한 설명서 가 있습니다.

미리 만들어진 DateTimeFormatter 더보기 간략히 모든 표준 타임 스탬프 형식을 포함해야한다. 예를 들어, 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. 개발자가 작업하는 방식에서 비롯된 것입니다 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 : BackTen-Backport 백 포트가 있습니다 . 안드로이드에는 ThreeTenABP가 있습니다.

[1] 그것들은 줄무늬가 아니라 이상한 극단도 있습니다. 예를 들어, 일부 인접한 태평양 섬 에는 +14 : 00 및 -11 : 00 시간대가 있습니다. 즉, 한 섬에는 5 월 1 일 오후 3시, 다른 섬에는 아직 4 월 12 일 오후 4시 30 분이 있습니다 (정확하게 계산 된 경우).


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
```public final static DateTimeFormatter TIMESTAMP_XX = new 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.HOUR_OF_DAY, 0) .parseDefaulting (ChronoField.HOUR_OF_DAY, 0) .parseDefaulting (ChronoField.HOUR_OF_DAY, 0). , 0) .parseDefaulting (ChronoField.NANO_OF_SECOND, 0) .toFormatter (); ```
Alan Stewart
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.