java 날짜객체!
Date
Date
- jdk 1.0 제공한 클래스, java.util
패키지 안에 있다.
Date
출력 함수
Date now = new Date();
System.out.println(now); // Wed Jan 30 10:24:46 KST 2019
System.out.println(now.getTime()); // 1548812137967
System.out.println(now.getDate()); // 30
System.out.println(now.getDay()); // 3, 일요일=0 수요일=3
System.out.println(now.getMonth()+1); // 1, 0~11 값으로 표현
System.out.println(now.getYear()+1900); // 2019, 1900 더해줘야함
System.out.println(now.getHours()); // 4
System.out.println(now.getMinutes()); // 47
System.out.println(now.toString()); // Mon May 31 16:50:40 KST 2010
System.out.println(now.toGMTString()); // 31 May 2010 07:50:40 GMT
System.out.println(now.toLocaleString()); // 1. 5. 31 오후 4:50:40
String d = String.format("%d년 %d월 %d일 %d:%d:%d (%c)"
, 1900 + now.getYear()
, now.getMonth() + 1
, now.getDate()
, now.getHours()
, now.getMinutes()
, now.getSeconds()
, "일월화수목금토".charAt( now.getDay() ) // 3
);
System.out.println(d); // 2010년 5월 31일 22:2:27 (월)
Date
수정 및 비교 함수
Date when = new Date(2019-1900, 1-1, 29); // 현재 30일
Date past = new Date(2010-1900, 5-1, 1); // 2010 5 1 설정
System.out.println(now.before(past)); // false
System.out.println(now.after(past)); // true
now.setYear(2010-1900);
now.setMonth(5-1);
now.setDate(1);
설정되지 않은 시, 분, 초는 현재시, 분, 초로 들어감…
이렇게 자투리로 남는 시간값 때문에 오차가 생길 수 도 있다.
날짜끼리 차이 계산할때에는 시간은 모두 0
으로 세팅하거나 clone
메서드를 사용해서 서로 같은 시간을 가리키는 식으로 오차를 없애야함.
SimpleDateFormat
날짜를 다양하게 다루기 위해서 사용되는 클래스 Format클래스중 가장 많이 사용된다.
다양한 형식을 날짜를 Calendar
클래스로 저장하고
Calendar
나 Date
클래스의 날짜를 다양한 형식으로 변환 출력한다.
String pattern = "G"; // BC AD
String pattern = "y"; // 년도
String pattern = "M"; // 월
String pattern = "w"; // 주(월단위)
String pattern = "W"; // 주(년단위)
String pattern = "d"; // 일(월단위)
STring pattern = "D"; // 일(년단위)
STring pattern = "H"; // 시(24시)
STring pattern = "h"; // 시(12시)
String pattern = "E"; // 요일
Date now = new Date();
String pattern = "yyyy년 MM월 dd일 HH'h' mm'm' ss's' (E)";
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
System.out.println( sdf.format(now) ); // 2019년 02월 02일 03h 48m 21s (토)
날짜 객체를 문자열로 변환해서 출력도 가능하지만 문자열로 날짜 객체를 만들 수 있다.
String strDate = "2019년 02월 02일";
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy년 M월 d일"); //부모클레스인 DateFormat에 UpCasting
Date date = sdf2.parse(strDate);
System.out.println(date.toLocaleString()); // 1. 2. 2 오전 12:00:00
System.out.println(sdf2.format(date)); // 2018년 2월 2일
만약 문자열이 잘못된 포멧이면 ParseException
예외를 발생한다.
SimpleDateFormat
의 parse
메서드로 생성되는 날짜 객체 역시 설정되지 않은 시, 분, 초는 0으로 세팅된다.
String pattern = "y/M/d";
Scanner sc = new Scanner(System.in);
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
Date inDate = null;
System.out.print("날짜입력 y/M/d: ");
while (true) {
try {
inDate = sdf.parse(sc.nextLine());
break;
} catch (ParseException e) {
System.out.print("잘못된 날짜 포멧... 다시입력: (y/M/d): ");
}
}
/*
날짜입력 y/M/d: 2019.01.1
잘못된 날짜 포멧... 다시입력: (y/M/d): 2019/1/01
1. 1. 1 오전 12:00:00
날짜입력 y/M/d: 2019/13/41
1. 2. 10 오전 12:00:00
*/
// (13월 41일) 을 자동으로 이뤌한다.
Calendar
Calendar
는 추상클래스로 new
를 통해 객체생성 불가능하다.
Calendar.getInstance()
정적 메서드로 인스턴스를 가져올 수 있음.
Calendar cal = Calendar.getInstance(); //현재날짜....생성
추상클래스인 Calender
를 구현하는 클래스는 아래 3개, 기본 GregorianCalendar
를 사용한다.
BuddhistCalendar
JapaneseImperialCalendar
GregorianCalendar
다음과 같이 GregorianCalendar
생성자에 날짜, 시간을 설정해 생성 가능.
설정하지 않은 시간, 초, 밀리초는 모두 0
으로 초기화된다.
Calendar cal = new GregorianCalendar(2018,5-1,20);
System.out.println(cal.get(Calendar.YEAR)); // 2019
System.out.println(cal.get(Calendar.MONTH)+1); // 2, 0~11
System.out.println(cal.get(Calendar.DATE)); //
System.out.println( cal.get(Calendar.DAY_OF_WEEK) ); // 1, /*일(1) 월(2) 화(3) 수(4) 목(5) 금(6) 토(7)*/
System.out.println("일월화수목금토".charAt(cal.get(Calendar.DAY_OF_WEEK)-1)); // 일
System.out.println(cal.get(Calendar.HOUR)); // 9, 12시 기준
System.out.println(cal.get(Calendar.HOUR_OF_DAY)); // 21, 24시기준
System.out.println(cal.get(Calendar.MINUTE)); // 59
System.out.println(cal.get(Calendar.SECOND)); // 27
cal.set(Calendar.YEAR, 2019); // 년만 변경
cal.set(Calendar.MONTH, 1-1); // 월만 변경
cal.set(Calendar.DATE, 1); // 일만 변경
cal.set(2018, 12-1, 21); // 한꺼번에 변경
cal.add(Calendar.DATE, 100); // 100일 후 날짜
cal.add(Calendar.DATE, -100); // 100일 전 날짜
2월의 경우 28일까지 밖에 없음으로 set
메서드로 달 설정시 28일을 넘지 않도록 주의.
마지막 날짜를 설정하고 싶다면 Calendar.DAY_OF_MONTH
값을 사용
설령 2월에 30일을 설정한다 하더라도 내부 validation 을 통해 자동 이월된다.
Calendar cal = Calendar.getInstance();
cal.set(2018, 1-1, 1);
for (int i = 0; i < 12; i++) {
cal.set(Calendar.MONTH, i);
System.out.println(cal.getActualMaximum(Calendar.DAY_OF_MONTH));
// 31 28 31 30 31 30 31 31 30 31 30 31
}
cal = new GregorianCalendar(2019, Calendar.FEBRUARY, 1); // 2024년 2월 1일 설정
System.out.println("Original Date: " + cal.getTime()); // Original Date: Fri Feb 01 00:00:00 KST 2019
cal.set(Calendar.DATE, 30); // 2월에 30일을 설정
System.out.println("Updated Date: " + cal.getTime()); // Updated Date: Sat Mar 02 00:00:00 KST 2019
Date -> Calender, Calender -> Date
변환 함수
public static Calendar date2cal(Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
return cal;
}
public static Date cal2date(Calendar cal) {
return cal.getTime();
// return new Date( cal.getTimeInMillis() );
}
날짜사이의 차이 구하기
getTimeInMillis()
메서드를 통해 두 날짜 사이의 ‘밀리초’를 가질고 날짜 차이를 얻기 때문에 ‘시’ 이하의 시간단위로 인해 미세한 오차가 생길수 있음으로 clone
을 통해 ‘시’이하 단위를 일치화.
GregorianCalendar
으로 생성하면 시, 분, 초 모두 0으로 설정되기 때문에 clone()
으로 복제할 필요가 없으니
Calendar
로 차이를 구해야 한다면 GregorianCalendar
로 생성하자.
Calendar openingDay = new GregorianCalendar(2018, Calendar.DECEMBER, 21);
Calendar now = (Calendar) openingDay.clone();
now.add(Calendar.DATE, 25); // 25 일 뒤로 설정
// 두 날짜 간의 시간 차이를 밀리초 단위로 계산
long gapInMillis = now.getTimeInMillis() - openingDay.getTimeInMillis();
System.out.println(gapInMillis + " ms"); // 2160000000 ms
System.out.println((gapInMillis / 1000) + " s"); // 2160000 s
System.out.println((gapInMillis / 1000 / 60) + " m"); // 36000 m
System.out.println((gapInMillis / 1000 / 60 / 60) + " h"); // 600 h
System.out.println((gapInMillis / 1000 / 60 / 60 / 24) + " d"); // 25 일
Calendar객체를 사용한 달력그리기
...
int year = 2019;
int month = 3;
//달 1일 의 요일 필요, 마지막 일자 필요
printCalendar(year, month);
private static void printCalendar(int year, int month) {
int week = 0;
int endDay = 0;
System.out.printf("%d년 %d월\n", year, month);
String weeks = "일월화수목금토";
for (int i = 0; i < weeks.length(); i++) {
System.out.printf("%c\t", weeks.charAt(i));
}
System.out.println();
Calendar cal = Calendar.getInstance();
cal.set(year, month-1, 1);
week = cal.get(Calendar.DAY_OF_WEEK);
cal.add(Calendar.DATE, -week+1); //전달의 일요일 부터 출력.
for (int i = 0; i < 42; i++) {
System.out.printf("%d\t", cal.get(Calendar.DATE));
cal.add(Calendar.DATE, 1);
if(i%7==6)
System.out.println();
}
}
%
(데이터형) 따로 형식을 붙여줄 필요없이 MessageFormat
을 사용하면 몇번째 인자인지만 알려주면 된다.
출력할 변수 타입이 뭔지 모를때 사용하면 편할듯 함.
LocalDate, LocalTime, LocalDateTime 클래스
jdk 1.8
부터 추가된 java.time
패키지.
Date, Calendar
부족한 메서드들을 추가하여 시간 관련 연산을 더욱 편하게 하기 위해 만들어짐.
java.time
- 날짜, 시간 다루는 핵심 클래스 대거 포함되어 있다.
java.time.chrono
- 표준이 아닌 달력 시스템을 위한 클래스 제공.java.time.format
- 날짜 시간을 파싱해서 원하는 포멧으로 변환 출력.java.time.temporal
- 날짜 시간 필드와 단위를 위한 클래스 제공java.time.zone
- 시간대(timezone)과 관련된 클래스 제공
java.time
클래스들은 String
클래스 처럼 불변하다.
Calendar cal = Calendar.getInstance();
cal.set(field, value); //cal객체의 정보가 바뀜
LocalDate now = LocalDate.now();
now = now.plusDays(1); //객체가 바뀌지 않아 다시 대입
System.out.println(now); //2019-02-01
Calendar는 기존 인스턴스의 정보를 바꾸지만 java.time패키지의 객체들은 기존의 인스턴스를 버리고 새 인스턴스를 참조한다.
now.plusDays(1)
하루 증가후 꼭 반환하는 값을 재참조 해야한다.
java.time
에서는 날짜를 다루는 클래스와 시간을 다루는 클래스들 분리해두었다.
날짜 - LocalDate
시간 - LocalTime
날짜시간 - LocalDateTime
LocalDate
날짜 생성
LocalDate now = LocalDate.now();
LocalDate dday = LocalDate.of(2019, 2, 4);
// 2010년도에서 daysOfYear 37일이 지난 날짜 객체를 반환한다.
// daysOfYear 가 366 일 경우 DateTimeException 에러발생
LocalDate date = LocalDate.ofYearDay(2010, 37);
System.out.println(date); //2010-02-06 출력
날짜 읽기
/* public final class LocalDate extends Object
implements Temporal, TemporalAdjuster, ChronoLocalDate, Serializable */
LocalDate now = LocalDate.of(2019, 2, 4);
System.out.println(now); //2019-02-04
// java.time.temporal.ChronoField 를 사용한 get
System.out.println(now.get(ChronoField.YEAR)); //2019
System.out.println(now.get(ChronoField.MONTH_OF_YEAR)); //2
System.out.println(now.get(ChronoField.DAY_OF_MONTH)); //4
System.out.println(now.get(ChronoField.DAY_OF_WEEK)); //1, 0(일) ~ 6(토)
System.out.println(now.getYear()); // 2019
System.out.println(now.getMonthValue()); // 2
System.out.println(now.getDayOfMonth()); // 4
System.out.println(now.lengthOfMonth()); // 28, 마지막날짜 반환
System.out.println(now.lengthOfYear()); // 365, 년도의 일 수 반환
// 자매품 lengthOfYear 메서드 - 해당 년도의 일 수 를 반환
날짜 변경
now = now.plusDays(1);
System.out.println(now); //2019-02-01
now = now.plusDays(-1);
System.out.println(now); //2019-01-31
now = now.minusDays(1);
System.out.println(now); //2019-01-30
now.plusMonths(long time);
now.plusWeeks(long time);
now.plusYears(long time);
now = now.withMonth(9);
now = now.withDayOfMonth(11);
System.out.println(now); //2019-09-11
now = now.with(ChronoField.MONTH_OF_YEAR, 9);
now = now.with(ChronoField.DAY_OF_MONTH, 11);
System.out.println(now); //2019-09-11
날짜 비교
LocalDate now = LocalDate.of(2019, 2, 5);
LocalDate dday = LocalDate.of(2019, 2, 4);
System.out.println(now.compareTo(dday)); // 피연산자(now)가 같으면 0, 작으면 -1, 크면 1
System.out.println(now.isEqual(dday)); // false
System.out.println(now.isAfter(dday)); // true
System.out.println(now.isBefore(dday)); // false
LocalTime
시간 생성
/* public final class LocalTime extends Object
implements Temporal, TemporalAdjuster, Comparable<LocalTime>, Serializable */
LocalTime now = LocalTime.now();
System.out.println(LocalTime.now()); // 16:30:48.239, 시:분:초.밀리초
System.out.println(LocalTime.of(14, 52)); // 14:52
System.out.println(LocalTime.of(14, 52, 10, 999999999)); // 14:52:10.999999999
시간 읽기
LocalTime now = LocalTime.now();
int time = now.getHour();
int min = now.getMinute();
int second = now.getSecond();
int mils = now.getNano();
System.out.printf("%d시 %d분 %d.%d초", time, min, second, mils); // 14시 52분 10.999999999초
time = now.get(ChronoField.HOUR_OF_DAY);
min = now.get(ChronoField.MINUTE_OF_HOUR);
second = now.get(ChronoField.SECOND_OF_MINUTE);
mils = now.get(ChronoField.MILLI_OF_SECOND);
System.out.printf("%d시 %d분 %d.%d초", time, min, second, mils); // 14시 52분 10.999초
시간 변경
LocalTime now = LocalTime.now();
System.out.println(now); // 16:40:37.660521
System.out.println(now.truncatedTo(ChronoUnit.HOURS)); // 16:00
now = now.withHour(10); // 시(hour)를 10으로 변경
now = now.withMinute(30); // 분(minute)을 30으로 변경
now = now.withSecond(45); // 초(second)를 45로 변경
now = now.withNano(500); // 나노초(nano)를 500으로 변경
System.out.println(now); // 10:30:45.000000500
now = now.with(ChronoField.HOUR_OF_DAY, 10); // 시(hour)를 10으로 변경
now = now.with(ChronoField.MINUTE_OF_HOUR, 30); // 분(minute)을 30으로 변경
now = now.with(ChronoField.SECOND_OF_MINUTE, 45); // 초(second)를 45로 변경
now = now.with(ChronoField.NANO_OF_SECOND, 500); // 나노초(nano)를 500으로 변경
System.out.println(now); // 10:30:45.000000500
LocalDateTime
날짜와 시간정보 모두 포함
LocalDate, LocalTime, LocalDateTime
모두 비슷한 상속관계를 가진다.
날짜 생성
/* public final class LocalDateTime extends Object
implements Temporal, TemporalAdjuster, ChronoLocalDateTime<LocalDate>, Serializable */
LocalDateTime ldt1 = LocalDateTime.of(2019, 8, 2, 14, 30);
LocalDateTime ldt2 = LocalDateTime.of(2019, 8, 2, 14, 30, 45);
LocalDateTime ldt3 = LocalDateTime.of(2019, 8, 2, 14, 30, 45, 123456789);
LocalDateTime ldt4 = LocalDateTime.of(2019, Month.AUGUST, 2, 14, 30);
LocalDate d = LocalDate.of(2015, 12, 31);
LocalTime t = LocalTime.of(12, 34, 56);
LocalDateTime ldt5 = LocalDateTime.of(d, t);
System.out.println(ldt1); // 2019-08-02T14:30
System.out.println(ldt2); // 2019-08-02T14:30:45
System.out.println(ldt3); // 2019-08-02T14:30:45.123456789
System.out.println(ldt4); // 2019-08-02T14:30
System.out.println(ldt5); // 2015-12-31T12:34:56
parse, format, DateTimeFormatter
parse
:String
->[LocalDate, LocalTime, LocalDateTime]
변환format
:[LocalDate, LocalTime, LocalDateTime]
->String
변환
LocalDate 파싱
LocalDate.parse
메서드의 기본 문자열 패턴은 yyyy-MM-dd
이다.
DateTimeFormatter.ofPattern
메서드로 문자열 패턴 변경 가능하다.
// String -> LocalDate
LocalDate d1 = LocalDate.parse("2018-09-18");
LocalDate d2 = LocalDate.parse("2018년 09월 19일",
DateTimeFormatter.ofPattern("yyyy년 MM월 dd일"));
System.out.println(d1); // 2018-09-18
System.out.println(d2); // 2018-09-19
// LocalDate -> String
String date_str = d1.format(s, DateTimeFormatter.ofPattern("yyyy년 MM월 dd일"));
System.out.println(date_str); // 2018년 11월 11일
LocalTime.parse
메서드의 기본 문자열 패턴은 hh:mm:ss
이다(설정하지 않은 초 이하는 다 0으로 설정됨).
LocalTime
역시 DateTimeFormatter
를 사용해서 문자열 패턴 변경이 가능하다.
// String -> LocalTime
LocalTime t1 = LocalTime.parse("10:10:10");
LocalTime t2 = LocalTime.parse("10.10.10",
DateTimeFormatter.ofPattern("HH.mm.ss"));
System.out.println(t1); //10:10:10
System.out.println(t2); //10:10:10
// LocalTime -> String
String time_str = time.format(DateTimeFormatter.ofPattern("HH.mm.ss"));
System.out.println(time_str); //10.10.10
TemporalAdjusters - 시간조정자
해당일자로 다음주 토요일, 다음달 2번째 월요일 등, 까다로운 날짜 계산이 필요할때 TemporalAdjusters
를 사용하면 편하다.
위같은 연산을 plus, minus, with
메서드 로만 진행하면 분명 복잡한 연산이 필요하겠지만 TemporalAdjusters
에는 이미 해당 메서드들이 다 정의 되어있다.
LocalDate today = LocalDate.now();
System.out.println(today);
System.out.println(today.with(TemporalAdjusters.firstDayOfMonth())); //첫째날 반환
System.out.println(today.with(TemporalAdjusters.lastDayOfMonth())); //마지막날 반환
System.out.println(today.with(TemporalAdjusters.firstInMonth(DayOfWeek.TUESDAY))); //첫 화요일 반환
System.out.println(today.with(TemporalAdjusters.lastInMonth(DayOfWeek.TUESDAY))); //마지막 화요일 반환
System.out.println(today.with(TemporalAdjusters.previous(DayOfWeek.TUESDAY))); // 저번 화요일
System.out.println(today.with(TemporalAdjusters.next(DayOfWeek.TUESDAY))); //다음 화요일
/*
2019-02-07
--------------
2019-02-01
2019-02-28
2019-02-05
2019-02-26
2019-02-05
2019-02-12
*/
Period, Duration - 두 시간, 날짜 차이를 구하는 클래스
날짜 차이가 얼마나 나는지 구하고 싶을땐 Period
메서드를 사용하면 편하다.
LocalDate d1 = LocalDate.of(2014, 1, 1);
LocalDate d2 = LocalDate.of(2015, 12, 31);
Period pe = Period.between(d1, d2);
System.out.println(pe.getYears()); // 1
System.out.println(pe.getMonths()); // 11
System.out.println(pe.getDays()); // 30
총 1년 11개월 30일 차이난다는것을 알 수 있다.
시간 차이가 얼마나 나는지 구하고 싶을땐 Duration
LocalTime t1 = LocalTime.of(00, 00, 10);
LocalTime t2 = LocalTime.of(12, 34, 56);
Duration du = Duration.between(t1, t2);
System.out.printf("%d시 %d분 %d초\n",
du.toHours(),
du.toMinutes()%60,
du.getSeconds()%60
); // 12시 34분 46초
특정 값 하나만 얻어와도 된다면 until
메서드를 사용하면 편하다.
LocalDate endDay = LocalDate.of(2019, 7, 19);
LocalDate today = LocalDate.now();
long dday = today.until(endDay, ChronoUnit.DAYS);
System.out.println(dday); // 162
Zone
여러 국가에서 지원하는 서비스의 경우 서버가 위치한 Local Time
보다는
Universal Time Coordinated(UTC: 세계 협정시)
을 지원해야 한다.
그리니치 표준시라고도 하는데 런던 웰링턴의 그리니치 시계탑을 기준으로 표준시를 결정했기 때문
Zulu time 이라고도 하는데 군에서 UTC 를 뜻하는 단어이다.
ISO 8601 의 마지막 특수문자
Z
가 Zulu time 을 뜻한다.
2011-08-12T20:17:46.384Z
- 뒤에 Z(Zulu Time) 특수문자가 붙어서 표준시를 뜻함.
대표적인 나라 도시의 UTC Time Zone
은 아래와 같다.
0:00 GMT/LON(런던) GMT+0
1:00 PAR(파리) GMT+1
2:00 CAI/JRS(카이로) GMT+2
3:00 JED(제다) GMT+3
3:30 THR(테헤란) GMT+3.5
4:00 DXB(두바이) GMT+4
4:30 KBL(카불) GMT+4.5
5:00 KHI(카라치) GMT+5
5:30 DEL(델리) GMT+5.5
6:00 DAC(다카) GMT+6
6:30 RGN(양곤) GMT+6.5
7:00 BKK(방콕) GMT+7
8:00 HKG(홍콩) GMT+8
9:00 SEL(서울) GMT+9
9:30 ADL(다윈) GMT+9.5
10:00 SYD(시드니) GMT+10
11:00 NOU(누메아) GMT+11
12:00 WLG(웰링턴) GMT+12
더많은 도시의 타임존 을 확인하고 싶다면 아래 url 참고
https://jp.cybozu.help/general/en/admin/list_systemadmin/list_localization/timezone.html
UTC
를 위한 Formatter
는 아래 참고
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
format.setTimeZone(TimeZone.getTimeZone("UTC"));
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX").withZone(ZoneId.of("UTC"));
'Z'
는 일반 문자열, 그리고 TimeZone 을 UTC 로 설정해서 Formatter 를 구현하면 된다.
2020-09-10T10:58:19+09:00
- UTC+9 를 뜻하며 서울이나 도쿄 등의 도시에서 사용한다.
yyyy-MM-dd'T'HH:mm:ssXXX
를 Formatter 의 format 문자열로 정의하면 된다.
ZoneDateTime
java8 에서 시간 표기를 위한 클래스는 아래와 같다.
- LocalDateTime
- OffsetDateTime
- ZoneDateTime
- Instant
OffsetDateTime
보다 ZoneDateTime
이 더 많은 정보를 가지고 있는데
ZoneDateTime
에는 국가와 같은 ZoneId
Asia/seoul
같은 정보도 가지고 있을 수 있다.
ZoneDateTime
이라 하더라도 반드시 ZoneId
를 넣을 필요는 없기에
가장 범위가 작은 LocalDateTime
, 가장 범위가 큰 ZoneDateTime
둘중 하나를 자주 사용한다.
DateTimeFormatter
에 이미 여러가지 형식을 지정해두었는데
어떻게 출력되는지 알아보자.
ZonedDateTime zdt = ZonedDateTime.parse("2019-03-10T02:30:00Z").withZoneSameInstant(ZoneId.of("Asia/Seoul"));
System.out.println(zdt.format(DateTimeFormatter.ISO_LOCAL_DATE)); // 2019-03-10
System.out.println(zdt.format(DateTimeFormatter.ISO_OFFSET_DATE)); // 2019-03-10+09:00
System.out.println(zdt.format(DateTimeFormatter.ISO_DATE)); // 2019-03-10+09:00
System.out.println(zdt.format(DateTimeFormatter.ISO_LOCAL_TIME)); // 11:30:00
System.out.println(zdt.format(DateTimeFormatter.ISO_OFFSET_TIME)); // 11:30:00+09:00
System.out.println(zdt.format(DateTimeFormatter.ISO_TIME)); // 11:30:00+09:00
System.out.println(zdt.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); // 2019-03-10T11:30:00
System.out.println(zdt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)); // 2019-03-10T11:30:00+09:00
System.out.println(zdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)); // 2019-03-10T11:30:00+09:00[Asia/Seoul]
System.out.println(zdt.format(DateTimeFormatter.ISO_DATE_TIME)); // 2019-03-10T11:30:00+09:00[Asia/Seoul]
System.out.println(zdt.format(DateTimeFormatter.ISO_ORDINAL_DATE)); // 2019-069+09:00
System.out.println(zdt.format(DateTimeFormatter.ISO_WEEK_DATE)); // 2019-W10-7+09:00
System.out.println(zdt.format(DateTimeFormatter.ISO_INSTANT)); // 2019-03-10T02:30:00Z
System.out.println(zdt.format(DateTimeFormatter.BASIC_ISO_DATE)); // 20190310+0900
System.out.println(zdt.format(DateTimeFormatter.RFC_1123_DATE_TIME)); // Sun, 10 Mar 2019 11:30:00 +0900
ISO_ZONED_DATE_TIME
ISO_DATE_TIME
두개의 포멧 차이가 없는데
구현부를 보면 offsetId
가 optional
한지 아닌지 정도 차이이다.
ISO_ZONED_DATE_TIME = (new DateTimeFormatterBuilder())
.append(ISO_OFFSET_DATE_TIME)
.optionalStart()
.appendLiteral('[')
.parseCaseSensitive()
.appendZoneRegionId() // Asia/seoul
.appendLiteral(']')
.toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
ISO_DATE_TIME = (new DateTimeFormatterBuilder())
.append(ISO_LOCAL_DATE_TIME)
.optionalStart()
.appendOffsetId()
.optionalStart()
.appendLiteral('[')
.parseCaseSensitive()
.appendZoneRegionId()
.appendLiteral(']')
.toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
ZoneId
문자열을 deseiralize
하는 경우는 많이 없기 때문에
가장 많이 사용하는 것은 오프셋을 출력하지 않는 ISO_LOCAL_DATE_TIME
, 오프셋을 출력하는 ISO_DATE_TIME
정도.
ISO_DATE_TIME
가 optional
설정에 묶인 정보가 가장 많기 때문에 웬만한 포멧은 다 처리 가능하다.
ZondId
가 지정되어 있지 않은 ZonedDateTime
의 경우 ZondId
가 출력되지 않는다.
ZonedDateTime zdt1 = ZonedDateTime.parse("2019-03-10T02:30:00Z").withZoneSameInstant(ZoneId.of("Asia/Seoul"));
ZonedDateTime zdt2 = ZonedDateTime.parse("2019-03-10T02:30:00Z");
System.out.println(zdt1.format(DateTimeFormatter.ISO_DATE_TIME)); // 2019-03-10T11:30:00+09:00[Asia/Seoul]
System.out.println(zdt2.format(DateTimeFormatter.ISO_DATE_TIME)); // 2019-03-10T02:30:00Z
withZoneSameInstant() vs withZoneSameLocal
withZoneSameInstant()
은 시간과 함께 영역을 변경withZoneSameLocal()
은 영역만 변경
ZonedDateTime zdt = ZonedDateTime.parse("2019-03-10T02:30:00Z");
ZoneId zoneId = ZoneId.of("Asia/Seoul");
System.out.println(zdt.withZoneSameInstant(zoneId)); // 2019-03-10T11:30+09:00[Asia/Seoul]
System.out.println(zdt.withZoneSameLocal(zoneId)); // 2019-03-10T02:30+09:00[Asia/Seoul]
Instant
UTC 기준으로 초와 나노초를 표현하는 객체.
객체만으로 명확한 UTC 기준을 알수 있고 문자열 변환도 기본 ISO-8601 변환방식을 바로 사용할 수 있어 많이 사용한다.
// 현재시간 10:58 분
Instant now = Instant.now();
System.out.println(now.toString()) // 2024-06-29T01:58:17.575975Z
long seconds = instant.getEpochSecond(); // 초
int nanos = instant.getNano(); // 나노초
long millis = instant.toEpochMilli(); // 밀리초
// UNIX Epoch Time(1970-01-01T00:00:00Z)을 기준으로 초 단위로 생성
System.out.println(Instant.ofEpochSecond(1609459200L)); // 2021-01-01T00:00:00Z
System.out.println(Instant.ofEpochSecond(1609459200L, 123456789L)); // 2021-01-01T00:00:00.123456789Z
// 문자열로 Instant 생성
Instant i = Instant.parse("2024-11-29T01:58:17.575975Z")
// 시간계산
Instant now = Instant.now();
Instant later = now.plusSeconds(3600); // 1시간 후
Instant earlier = now.minusMillis(5000); // 5초 전
// 시간비교
Instant instant1 = Instant.now();
Instant instant2 = instant1.plusSeconds(10);
boolean isBefore = instant1.isBefore(instant2); // true
boolean isAfter = instant1.isAfter(instant2); // false
// UTC 로 생성된 시간객체를 LocalDateTime, ZoneDateTime 으로 변환
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
ZonedDateTime zonedDateTime = instant.atZone(ZoneId.of("Asia/Seoul"));
ZonedDateTime utcZonedDateTime = instant.atZone(ZoneOffset.UTC);
달려그리기
윤년확인하기
먼저 윤년인지 여부를 구하는 함수를 작성, 내장함수와 직접구현 가능.
LocalDate now = LocalDate.now();
now.isLeapYear(); //객체의 년도가 윤년인지 true false반환
윤년을 구하는 공식을 간단하게 java코드로 만들어보자.
다른 언어에서도 마찬가지로 적용 가능할 것이다.
- 서력 기원 연수가 4로 나누어떨어지는 해는 윤년으로 한다.
- 서력 기원 연수가 4, 100으로 나누어떨어지는 해는 평년으로 한다.
- 서력 기원 연수가 4, 100, 400으로 나누어떨어지는 해는 윤년으로 둔다.
즉 년도가 4로 나누어 떨어지면서 100으로 나뉘지 않으면서 그중에 400으로 나뉘어지면 윤년으로 본다!
public static int getLastDay(int year, int month) {
int[] days = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
return isleapyear(year) && month==2 ? 29 : days[month-1];
}
public static boolean isleapyear(int year) {
return year%100!=0 && year%4==0 || year%400==0 ? true : false;
}
시작요일 구하기
달의 시작요일이 무슨요일인지, 마지막일이 몇일인지 알아야한다.
마지막일은 윤년일 제외하곤 고정값이다(30, 31 반복).
public static int getLastDay(int year, int month) {
int[] days = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
return isleapyear(year) && month==2 ? 29 : days[month-1];
}
시작요일을 구하는 공식은 1년 1월 1일이 월요일임으로, 원하는 날짜까지의 일 수 를 구한후 7로 나눈 나머지값이 요일이 된다.
그럼 2019-4-1
일의 요일을 구하고 싶다면 1-1-1
부터 2019-4-1
까지의 일 수 를 구하면 된다.
먼저 1년 ~ 2018년 까지의 일수를 구한다.
365 곱한것 뒤의 + (year-1)/4 - (year-1)/100 + (year-1)/400
는 윤년에 해당하는 일수를 추가로 더해주기 위한것, 윤년의 경우 366일이니까!
그리고 4월 1일의 까지의 일수를 for
문을 통해 추가로 더해준다.
public static int getDayOfWeek(int year, int month) {
int totDays = (year-1)*365 + (year-1)/4 - (year-1)/100 + (year-1)/400;
for (int i = 1; i < month; i++) {
totDays+=getLastDay(year, i);
}
totDays+=1;
return totDays%7;
}
미리 마지막일을 저장해 놓은 배열을 정의해두었다. (2월은 윤년의 경우 29일로 반환)
이제 달력 그리기를 위한 모든 값이 구해졌으니 달력을 그려보자!
public class Calender {
public static void main(String[] args) throws IOException {
int year, month;
System.out.print("년 월 입력하세요: "); //2010 5
Scanner sc = new Scanner(System.in);
year = sc.nextInt();
month = sc.nextInt();
createCalendar(year, month);
}
//시작 요일과 마지막 날짜를 구하는 메서드
public static void createCalendar(int year, int month) {
int dayOfWeek = getDayOfWeek(year, month);
int lastDay = getLastDay(year, month);
printCalender(year, month, dayOfWeek, lastDay);
}
//단순 선 긋는 함수
public static void drawLine(int n) {
for (int i = 0; i < n; i++)
System.out.print('-');
System.out.println();
}
public static void printCalender(int year, int month, int dayOfWeek, int lastDay) {
System.out.printf("\t\t%d년 %d월\n",year,month);
String week = "일월화수목금토";
drawLine(50);
for (int i = 0; i < 7; i++)
System.out.printf("%c \t", week.charAt(i));
System.out.println();
drawLine(50);
drawLine(50);
int cnt=0;
for (int i = 0; i < dayOfWeek; i++) {
System.out.print('\t');
cnt++;
} //시작 요일 수만큼 공백을 준다.
for (int d = 1; d <= lastDay; d++) {
System.out.printf("%d \t", d);
if(cnt%7==6)
System.out.println();
cnt++;
} //차례대로 요일 출력 1~30
}
public static int getDayOfWeek(int year, int month) {
int totDays = (year-1)*365 + (year-1)/4 - (year-1)/100 + (year-1)/400;
for (int i = 1; i < month; i++) {
totDays+=getLastDay(year, i);
}
totDays+=1;
return totDays%7;
}
public static int getLastDay(int year, int month) {
int[] days = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
return isleapyear(year) && month==2 ? 29 : days[month-1];
}
public static boolean isleapyear(int year) {
return year%100!=0 && year%4==0 || year%400==0 ? true : false;
}
}
출력
년 월 입력하세요: 2019 4
2019년 4월
--------------------------------------------------
일 월 화 수 목 금 토
--------------------------------------------------
--------------------------------------------------
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
모든 2019년에 해당하는 달력을 출력해보자(1~12 모두 출력)
3차원 배열을 사용해 출력해보자.
위 그림의 달력 형식을 보면 총 6행 7열까지 나올 수 있다.
위 사진의 경우 5행 7열이지만 1일이 일요일부터 시작한다던가 하는 경우 6줄이 출력될 수 있다.
그럼으로 int형 배열 [6][7]
짜리 2차원 배열을 사용해야 1달을 출력할 수 있다.
12달을 출력하려면 [12][6][7]
3차월 배열이 필요
import java.util.Scanner;
public class Calender {
public static void main(String[] args) {
//달력 출력시 필요한것, 마지막 날짜, 시작 요일
Scanner sc = new Scanner(System.in);
int year = sc.nextInt();
int[][][] dal = new int[12][6][7];
createDaliek(dal, year);
printDaliek(dal, year, 4); //4행으로 출력
}
private static void createDaliek(int[][][] dal, int year) {
int[] lastDays = new int[12];
int[] dayOfWeek = new int[12];
int day = 1;
//먼저 매 달의 시작요일, 마지막일을 저장할 배열을 초기화
for (int i = 0; i < lastDays.length; i++) {
lastDays[i] = getLastDay(year, i);
dayOfWeek[i] = getDayOfWeek(year, i);
}
for (int i = 0; i < dal.length; i++) { //12개월
day = 1;
for (int j = 0; j < dal[0].length; j++) { //6반복
for (int k = 0; k < dal[0][0].length; k++) { //7반복
// 시작요일이 배열 인덱스 j*7+k보다 크다면 -1로 설정
// day가 마지막일보다 커진다면 -1로 설정, -1은 출력되지 않는다.
if (j * 7 + k < dayOfWeek[i] || day > lastDays[i])
dal[i][j][k] = -1;
else
dal[i][j][k] = day++;
}
}
} //end first for
}
private static void printDaliek(int[][][] dal, int year, int num) {
String week = "일월화수목금토";
for (int z = 0; z < 12 / num; z++) {
for (int i = 0; i < num; i++) {
System.out.printf("\t%d년 %d월\t\t", year, z * num + i + 1);
}
System.out.println();
for (int i = 0; i < num; i++) {
drawLine(27);
}
System.out.println();
for (int i = 0; i < num; i++) {
for (int p = 0; p < week.length(); p++) {
System.out.printf("%c ", week.charAt(p));
}
System.out.print("\t");
}
System.out.println();
for (int j = 0; j < 2; j++) {
for (int i = 0; i < num; i++) {
drawLine(27);
}
System.out.println();
}
//눈을 크게 뜨고 확인하자....
for (int i = 0; i < dal[0].length; i++) {
for (int j = num * z; j < num + num * z; j++) {
for (int k = 0; k < dal[0][0].length; k++) {
if (dal[j][i][k] == -1)
System.out.print(" ");
else
System.out.printf("%2d ", dal[j][i][k]);
}
System.out.print("\t");
}
System.out.println();
}
System.out.println();
}
}
public static void drawLine(int n) {
for (int i = 0; i < n; i++)
System.out.print('-');
System.out.print("\t");
}
public static int getDayOfWeek(int year, int month) {
int totDays = (year - 1) * 365 + (year - 1) / 4 - (year - 1) / 100 + (year - 1) / 400;
for (int i = 0; i < month; i++) //0~11
totDays += getLastDay(year, i);
totDays += 1;
return totDays % 7;
}
public static int getLastDay(int year, int month) {
int[] days = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
return isleapyear(year) && month == 1 ? 29 : days[month]; //0 = 1월, 11=12월
}
public static boolean isleapyear(int year) {
return year % 100 != 0 && year % 4 == 0 || year % 400 == 0 ? true : false;
}
}
일단 3차원 배열로 저장해 놓았다면 출력에서 for문막 약간 변경시켜주면 된다.
출력
2019
2019년 1월 2019년 2월 2019년 3월 2019년 4월
--------------------------- --------------------------- --------------------------- ---------------------------
일 월 화 수 목 금 토 일 월 화 수 목 금 토 일 월 화 수 목 금 토 일 월 화 수 목 금 토
--------------------------- --------------------------- --------------------------- ---------------------------
--------------------------- --------------------------- --------------------------- ---------------------------
1 2 3 4 5 1 2 1 2 1 2 3 4 5 6
6 7 8 9 10 11 12 3 4 5 6 7 8 9 3 4 5 6 7 8 9 7 8 9 10 11 12 13
13 14 15 16 17 18 19 10 11 12 13 14 15 16 10 11 12 13 14 15 16 14 15 16 17 18 19 20
20 21 22 23 24 25 26 17 18 19 20 21 22 23 17 18 19 20 21 22 23 21 22 23 24 25 26 27
27 28 29 30 31 24 25 26 27 28 24 25 26 27 28 29 30 28 29 30
31
2019년 5월 2019년 6월 2019년 7월 2019년 8월
--------------------------- --------------------------- --------------------------- ---------------------------
일 월 화 수 목 금 토 일 월 화 수 목 금 토 일 월 화 수 목 금 토 일 월 화 수 목 금 토
--------------------------- --------------------------- --------------------------- ---------------------------
--------------------------- --------------------------- --------------------------- ---------------------------
1 2 3 4 1 1 2 3 4 5 6 1 2 3
5 6 7 8 9 10 11 2 3 4 5 6 7 8 7 8 9 10 11 12 13 4 5 6 7 8 9 10
12 13 14 15 16 17 18 9 10 11 12 13 14 15 14 15 16 17 18 19 20 11 12 13 14 15 16 17
19 20 21 22 23 24 25 16 17 18 19 20 21 22 21 22 23 24 25 26 27 18 19 20 21 22 23 24
26 27 28 29 30 31 23 24 25 26 27 28 29 28 29 30 31 25 26 27 28 29 30 31
30
2019년 9월 2019년 10월 2019년 11월 2019년 12월
--------------------------- --------------------------- --------------------------- ---------------------------
일 월 화 수 목 금 토 일 월 화 수 목 금 토 일 월 화 수 목 금 토 일 월 화 수 목 금 토
--------------------------- --------------------------- --------------------------- ---------------------------
--------------------------- --------------------------- --------------------------- ---------------------------
1 2 3 4 5 6 7 1 2 3 4 5 1 2 1 2 3 4 5 6 7
8 9 10 11 12 13 14 6 7 8 9 10 11 12 3 4 5 6 7 8 9 8 9 10 11 12 13 14
15 16 17 18 19 20 21 13 14 15 16 17 18 19 10 11 12 13 14 15 16 15 16 17 18 19 20 21
22 23 24 25 26 27 28 20 21 22 23 24 25 26 17 18 19 20 21 22 23 22 23 24 25 26 27 28
29 30 27 28 29 30 31 24 25 26 27 28 29 30 29 30 31