Chapter 10 날짜와 시간 & 형식화

1.날짜와 시간

  • 대표적인 2개의 Api
    • java.util.Date (Deprecated)
    • java.util.Calendar
  • 사용하기 매우 불편해 대체되는 오프소스 라이브러리
  • 결국, jdk8에서 joda-time 라이브러리를 수용

Joda-Time provides a quality replacement for the Java date and time classes. Joda-Time is the de facto standard date and time library for Java prior to Java SE 8. Users are now asked to migrate to java.time (JSR-310).

Calendar

Calendar cal = Calendar.getInstance(); //Singleton&Factory pattern

private static Calendar createCalendar(TimeZone zone, Locale aLocale){
        ...
        ...

        if (aLocale.hasExtensions()) {
            String caltype = aLocale.getUnicodeLocaleType("ca");
            if (caltype != null) {
                switch (caltype) {
                case "buddhist":
                cal = new BuddhistCalendar(zone, aLocale);
                    break;
                case "japanese":
                    cal = new JapaneseImperialCalendar(zone, aLocale);
                    break;
                case "gregory":
                    cal = new GregorianCalendar(zone, aLocale);
                    break;
                }
            }
        }
        ...
        ...

        return cal;
    }
  • 구현체
    • GregorianCalendar
    • JapaneseImperialCalendar
    • BuddhistCalendar

날짜 설정

Calendar date = Calendar.getInstance();

//2016.3.7 로 설정 month   the month between 0-11.
//Mutable
date.set(2016, 2, 7);

date.add(Calendar.YEAR, 2); //date.add(Calendar.JUNE, 2) compile ok.

Date <-> Calendar

//1.Calenter -> Date
Calendar cal = Calendar.getInstance();
...
Date d = new Date(cal.getTimeInMillis());

//2.Date -> Calendar
Date d = new Date();
...
Calendar cal = Calendar.getInstance();
cal.setTime(d);

Apache Common Lang (DateUtils)

Date now = new Date();
Date tomorrow = DateUtils.addDays(now, 1);
Date tomorrowAnd2Min = DateUtils.addSeconds(tomorrow, 120);

Extra

  • 본인(Clint)는 주로 날짜/시간을 저장할 경우 Long 타입으로 저장합니다.
public class Connection {
    ...

    @Column(updatable = false)
    private Long created;

    ...

    @PrePersist
    public void onCreate() {
        this.created = System.currentTimeMillis();
    }
}
  • 이유는 Zone, Parsing 등 편하기 때문입니다.
long today = System.currentTimeMillis(); // long 형의 현재시간

DateFormat df = new SimpleDateFormat("HH:mm:ss"); // HH=24h, hh=12h
String str = df.format(today);

Date date = new Date(today);
  • api 제공시, javascript에서 변환이 편함.
var date = new Date(1324339200000);

date.toString("MMM dd");
<!-- AngularJS -->
<td>{{ conn.created | date : 'yyyy.MM.dd HH:mm:ss' }}</td>

2.형식화 클래스

  • DecimalFormat
  • SimpleDateFormat
  • ChoiceFormat
  • MessageFormat

DeciamlFormat

숫자를 형식화

DecimalFormat df = new DecimalFormat("#,###.##");

Number num = df.parse("1,234,567.89");
//1234567.89

df.format(1234567.89);
//1,234,567.89

SimpleDateFormat

날짜를 형식화

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd");

String result = sdf.format(new Date());

SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy년MM월dd일");
Date result2 = sdf2.parse("2016년3월8일");

ChoiceFormat

특정범위에 속하는 값을 형식화

String pattern = "60#D|70#C|80<B|90#A";
int[] scores = {91, 90, 80, 88, 70, 52, 60};

ChoiceFormat cf = new ChoiceFormat(pattern);

for(int score : scores){
  System.out.println(cf.format(score));
}

MessageFormat

데이터를 정해진 양식에 맞게 형식화

String format = "Name : {0}, Tel : {1}, Loc : {2}";
String[] params = {"Clint.cho", "010.1234.5789", "PanGyo"};

MessageFormat messageFormat = new MessageFormat(format);
String result = messageFormat.format(params);        

System.out.println(String.format("Hello %s", "Java Study"));

3. java.time패키지

  • JDK1.8 부터 추가 되었으며 다음과 같이 4개의 하위 패키지를 가지고 있다.
파키지 설명
java.time 날짜와 시간을 다루는데 필요한 핵심 클래스들을 제공
java.time.chrono 표준(ISO)가 아닌 달력 시스템을 위한 클래스들을 제공
java.time.format 날짜와 시간을 파싱하고, 형식화하기 위한 클래스들을 제공
java.time.temporal 날짜와 시간의 필드(field)와 단위(unit)를 위한 클래스들을 제공
java.time.zone 시간대(time-zone)와 관련된 클래스들을 제공

3.1 java.time패키지의 핵심 클래스

클래스 설명
LocalTime 시간
LocalDate 날짜
LocalDateTime 날짜 + 시간
ZoneDateTime 시간대 + 날찌 + 시간
Instant 시간을 나노초로 표현
Period 두 날짜간의 차이
Duration 두 시간의 차이

객체 생성

  • 객체 생성은 now(), of() 두개의 static mehtod를 사용한다.

Temporal과 TemporalAmount

  • 날짜와 시간을 표한하기 위한 클래스 들은 모두 Temporal, TemporalAccessor, TemporalAdjuster 인터페이스를 구현 Temporal 이 TemporalAccessor를 상속
  • 날짜외 시간의 간격을 표현하기 위한 클래스 들은 TemporalAmount를 구현

TemporalUnit과 TemporalField

  • 날짜와 시간의 단위를 정해 놓은 것이 TemporalUnit인터페이스 이고 이것을 구현한 것이 열거형 ChronoUnit이다
  • 날짜와 시간의 필드를 정해 놓은 것이 TemporalField인터페이스 이고 이것을 구현한 것이 열거형 ChronoField이다.

3.2 LocalDate와 LocalTime

  • LocalDate와 LocalTime은 java.time패키지의 가장 기본이 되는 클래스이며 나머지 클래스들은 이들의 확장이다.
  • 객체 생성 : now(), of()
  • 특정 필드의 값 가져오기 get(), getXXX(), 매개변수 등은 p.556 참조
  • 필드의 값 변경하기 with(), plus(), minus()
  • LocalTime 의 tuncatedTo()는 지정된 필드보단 작은 단위의 필드 값을 0으로 변경.
          LocalTime time = LocalTime.of(12, 34, 56); // 12시 34분 56초
          time = time.truncatedTo(ChronoUnit.HOURS); // 시(hour)보다 작은 단위를 0으로.
          System.out.println(time);
    
  • 날짜와 시간의 비교 isAfter(), isBefore(), isEqual()

3.3 Instant

  • Instant는 에포크 타임(EPOCH TIME, 1970-01-01 00:00:00 UTC)
    쉬어가기
    협정 세계시(協定世界時, 프랑스어: Temps Universel Coordonné, 영어: Coordinated Universal Time, UTC)
    국제 전기 통신 연합은 협정 세계시에 대한 통일된 약자를 원했으나, 영어권의 사람들과 프랑스어권의 사람들은 각각 자신의 언어로 된 약자인 CUT(Coordinated Universal Time)와 TUC(Temps Universel Coordonné)를 사용하길 원했습니다. 이 분쟁은 결국 두 언어 모두 C, T, U로 구성되어 있다는 것에 착안하여 UTC라는 약어를 탄생시켰습니다. 
    "UTC"는 보통 "Universal Time Code"이나 "Universal Time Convention"의 약어라 알려지기도 하는데 이는 틀린 것입니다. 
    대한민국의 시간대(Korea Standard Time, KST)는 UTC +9에 속합니다.
    
  • Instant를 생성할떄는 now()와 ofEpochSecond()를 사용
  • Instant는 기존의 java.util.Date를 대체하기 위한 것이며 변환 메서드가 추가 되었다.

3.4 LocalDateTime과 ZonedDateTime

LocalDate + LocalTime -> LocalDateTime
LocalDateTime + 시간데 -> ZoneDateTime
  • 기본적인거는 LocalDate와 LocalTime과 동일
  • 시간대는 기존에는 TimeZone클래스를 사용했으나 ZoneId라는 클래스가 생김
  • ZoneId는 일광 절약시간(DST, Daylight Saving Time)을 자동적으로 처리해 주므로 더 편리하다. 일광 절약시간 = summer time 낮 시간이 길어지는 봄부터 시곗바늘을 1시간 앞당겼다가 낮 시간이 짧아지는 가을에 되돌리는 제도
  • ZoneOffset은 UTC로부터 얼마만큼 떨어져 있는지를 표현한다. 서울은 '+9'이다.
  • OffsetDateTime은 시간대를 시간의 차이로만 구분하며 서로 다른 시간대에서 데이터를 주고받을때 시간을 표현하기에 적합하다.

3.5 TemporalAdjusters

  • 자주 쓰일만한 날짜 계산들을 대신해주는 메서드를 정의해 놓은 클래스
          LocalDate today = LocalDate.now();
          LocalDate nextMonday = today.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
    
  • TemporalAdjuster의 adjustInto()를 구현함으로써 직접 만들 수 있으나 사용은 클래스의 with()를 통해서 사용한다 with() 내부에서 adjustInto() 호출

    @Override
    public LocalDate with(TemporalAdjuster adjuster) {
        // optimizations
        if (adjuster instanceof LocalDate) {
            return (LocalDate) adjuster;
        }
        return (LocalDate) adjuster.adjustInto(this);
    }

3.6 Period와 Duration

날짜 - 날짜 = Period
시간 - 시간 = Duration
  • Period.between()

    public static Period between(LocalDate startDateInclusive, LocalDate endDateExclusive) {
        return startDateInclusive.until(endDateExclusive);
    }

    @Override
    public Period until(ChronoLocalDate endDateExclusive) {
        LocalDate end = LocalDate.from(endDateExclusive);
        long totalMonths = end.getProlepticMonth() - this.getProlepticMonth();  // safe
        int days = end.day - this.day;
        if (totalMonths > 0 && days < 0) {
            totalMonths--;
            LocalDate calcDate = this.plusMonths(totalMonths);
            days = (int) (end.toEpochDay() - calcDate.toEpochDay());  // safe
        } else if (totalMonths < 0 && days > 0) {
            totalMonths++;
            days -= end.lengthOfMonth();
        }
        long years = totalMonths / 12;  // safe
        int months = (int) (totalMonths % 12);  // safe
        return Period.of(Math.toIntExact(years), months, days);
    }
  • between과 until을 위에 있는거 처럼 같은일을 하나 between()은 static메서드 이고, until()은 인스턴스 메서드라는 차이가 있다.

  • Duration.between()

    public static Duration between(Temporal startInclusive, Temporal endExclusive) {
        try {
            return ofNanos(startInclusive.until(endExclusive, NANOS));
        } catch (DateTimeException | ArithmeticException ex) {
            long secs = startInclusive.until(endExclusive, SECONDS);
            long nanos;
            try {
                nanos = endExclusive.getLong(NANO_OF_SECOND) - startInclusive.getLong(NANO_OF_SECOND);
                if (secs > 0 && nanos < 0) {
                    secs++;
                } else if (secs < 0 && nanos > 0) {
                    secs--;
                }
            } catch (DateTimeException ex2) {
                nanos = 0;
            }
            return ofSeconds(secs, nanos);
        }
    }
  • of(), with(), plus(), minus(), negate(), abs(), toXXX()

3.7 파싱과 포맷

  • 형식화와 관련된 클래스들은 java.time.format패키지에 들어있는데 그중에서 DataTimeFormatter가 핵심이다 p.572참고

로케일에 종속된 형식화

  • DateTimeFormatter의 static메서드 ofLocalizeDate(), ofLocalizedTime(), ofLocalizedDateTime()은 로케일(locale)에 종속적인 포맷터를 생성한다.
        DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
        String shortFormat = formatter.format(LocalDate.now());
FormatStyle 날짜 시간
FULL 2015년 11월 28일 토요일 N/A
LONG 2015년 11월 28일 (토) 오후 9시 15분 13초
MEDIUM 2015. 11. 28 오후 9:15:13
SHORT 15. 11. 28 오후 9:15

출력형식 직접 정의하기

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");

문자열을 날짜와 시간으로 파싱하기


    public static LocalDate parse(CharSequence text) {
        return parse(text, DateTimeFormatter.ISO_LOCAL_DATE);
    }

    public static LocalDate parse(CharSequence text, DateTimeFormatter formatter) {
        Objects.requireNonNull(formatter, "formatter");
        return formatter.parse(text, LocalDate::from);
    }

    public static DateTimeFormatter ofPattern(String pattern) {
        return new DateTimeFormatterBuilder().appendPattern(pattern).toFormatter();
    }