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