JAVA/자바공부
DB의 시간, Java의 시간, Javascript의 시간 (Data, LocalDate, LocalDateTime, Y2K38 Problem)
sendkite
2023. 3. 7. 23:22
시간에 대해 문제의식을 가진 것은 MySQL의 datetime과 timestamp의 차이를 인지하면서 부터이다.
timestamp를 사용하면 2038년이 지나고 1970년으로 시간이 바뀐다고 한다.(Y2K38 버그라고 이름도 있음)
MySQL에서는 8.0.28버전 부터는이 문제를 해결했다고 하는데 왜 1970년으로 회귀하는지와 자바, 자바스크립트에서는 무엇을 해야하는지 알아보자.
컴퓨터가 시간을 정의하는 방법
- 1970년 1월 1일 0시 0초를 기준으로 흐르는 시간을 밀리세컨 단위 정수로 측정하고 표현한다.
- 측정한 시간을 Unix time, Unix Epoch Time, Unix Timestamp로 부른다.
- 2023년 3월 7일, 11시 현재 Unix Time은 1678197732 이다.
- Bell 연구소에서 근무했던 Dennis Ritche의 말에 따르면 1970년 정도로 정해두자라고 합의했다고 한다.
- 컴퓨터에서 시간은 32비트 범위로 표현되는데 정수의 범위가 초과되면 overflow가 발생해서 시간이 1970년으로 되돌아간다.
- 2038년이 되면 32비트 정수 범위 초과 상태가 발생하는데 이로 인한 버그를 Y2K38 Problem이라고 한다.
Unix Time을 사람이 읽을 수 있는 시간으로 변환 방법
- 컴퓨터에서 시간을 사람의 시간으로 변환하는 방법은 다음과 같다.
- 초 -> 시간
| 1 Hour | 3600 Seconds |
| 1 Day | 86400 Seconds |
| 1 Week | 604800 Seconds |
| 1 Month (30.44 days) | 2629743 Seconds |
| 1 Year (365.24 days) | 31556926 Seconds |
- 국제 표기법 기준
| 03/07/2023 @ 2:00pm | UTC |
| 2023-03-07T14:00:14+00:00 | ISO 8601 |
| Tue, 07 Mar 2023 14:00:14 +0000 | RFC 822, 1036, 1123, 2822 |
| Tuesday, 07-Mar-23 14:00:14 UTC | RFC 2822 |
| 2023-03-07T14:00:14+00:00 | RFC 3339 |
- UTC(Universal Time Coordinated)는 세계 협정 시로 세슘 원자의 진동수를 기준으로 초를 정의한 것이다. 세계 어디에서 측정해도 같은 시간을 나타낸다. (서버에서는 UTC를 따른다.)
- 우리 일상에서는 GMT : 영국에 그리니치 천문대 기준 시간 사용한다.
- GMT와 한국의 시간 차이를 표현한 것을 Offset이라 한다.
- 지구의 위도, 경도 위치에 따라 TimeZone에 따라 시간이 달라진다.
"결국 우리가 할 일은 Unix Time <-> 인간이 읽는 시간 변환이다."
자바에서의 시간 다루기
우선 자바 8 이전의 시간 객체는 다음과 같다.
많은 문제가 있어 그 시절 선배 프로그래머들은 Joda Time이라는 라이프러리를 필수로 사용했다고 한다.
- Date (Unix Epoch Time)
- setTime 하면 인스턴스의 시간 변경할 수 있다. (가변적이다.)
- 멀티스레스에서 안전하게 사용할 수 없다.
- 스레스 세이프하지 않다. (버그 발생할 여지가 많다.)
- Calender (GregorianCalender)
- month가 0부터 시작하기 때문에 실수하기 쉽다.
- 음수가 올 수 도 있다. Type 안정성이 없다.
- SimpleDateFormat
자바 8 이후에는 JSR-310 기준 DateTime API 구현체의 등장으로 컴퓨터 <-> 사람 간의 시간을 불변으로, 실수가 적게 다룰 수 있게 되었다. 핵심 문법은 아래와 같다.
// 시간 API
// 01-1 기계에서 사용하는 시간
Instant instant = Instant.now(); // 기준시 UTC, GMT
instant.atZone(ZoneId.of("UTC"));
// 01-2 서울로 보는법 Zone 추가
ZoneId zone = ZoneId.systemDefault();
ZonedDateTime zonedDateTime = instant.atZone(zone);
// 02 휴먼이 보는 시간
// 02-1 서버 Zone 참고해서 시간 가져옴
LocalDateTime now = LocalDateTime.now();
LocalDateTime birthday = LocalDateTime.of(1992, Month.AUGUEST, 7, 0, 0, 0)
// 02-2 휴먼이 보는 시간
ZonedDateTime nowInKorea = ZonedDateTime.now(ZoneId.of("Asia/Seoul"));
nowInKorea.toInstant();
// 03 기간을 표현하는 방법
LocalDate today = LocalDate.now();
LocalDate thisYearBirthday = LocalDate.of(2023, Month.AUGUEST, 7);
Period period = Period.between(today, thisYearBirthday);
period.getDays(); // 오늘부터 몇일 남았는지 표현
Period until = today.until(thisYearBirthDay);
until.get(ChronoUnit.DAYS); // 오늘부터 몇일 남았는지 표현
// 기계용 시간 비교
Instant now = Instant.now();
Instant plus = now.plus(10, ChronoUnit.SECONDS);
Duration between = Duration.between(now, plus);
between.getSeconds(); // 인스턴트간 시간 차이 표시
// 포멧터 사용
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter.ofPattern("MM/dd/yyyy");
now.format(MMddyyyy);
// 파싱 사용
LocalDate.parse("08/07/1992", MMddyyyy);
// 레거시인 Date와 호환가능
Date date = new Date();
Instant instant = date.toInstant();
Date newDate = Date.from(instant);
GregorianCalendar gregorianCalendar = new GregorianCalendar();
LocalDateTime dateTime = gregorianCalendar.toInstant().atZone(ZoneId.systemDefault())
.toLocalDateTime();
- 특이사항으로 Instant는 기계의 시간이기 때문에 말 그대로 하드웨어에 따라서 값이 달라진다.
- Mac과 Window가 값을 다르게 인식해서 테스트가 깨지는 일이 있었다. (Formatter는 선택이 아닌 필수..)

자바스크립트에서의 시간 다루기
- 자바스크립트에서는 Data 객체만 있다.
- 브라우저, 서버, 지역, 국가에 따라서 컴퓨터에서는 같은 시간인데, 인간이 읽는 시간은 다르게 출력되는 오류가 있다.
- 자바스트립트 ZONE_TIME, UTC 시간 다루기 위해 라이브러리를 필수로 사용해줘야 한다.
- (day.js, luxon, js-joda, data-fns….) js-joda <- 자바 8 이전의 그것이 JS로.. 현재 자바 8의 시간 api 대부분이 joda 라이프러리 기준으로 만들어져서 Java 시간 사용이랑 똑같이 쓸 수 있다. (출처 : https://jojoldu.tistory.com/600)
import { DateTimeFormatter, LocalDate, LocalDateTime, nativeJs } from '@js-joda/core';
export class DateTimeUtil {
private static DATE_FORMATTER = DateTimeFormatter.ofPattern('yyyy-MM-dd');
private static DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(
'yyyy-MM-dd HH:mm:ss',
);
static toString(localDate: LocalDate | LocalDateTime): string {
if (!localDate) {
return '';
}
if (localDate instanceof LocalDate) {
return localDate.format(this.DATE_FORMATTER);
}
return localDate.format(this.DATE_TIME_FORMATTER);
}
static toDate(localDate: LocalDate | LocalDateTime): Date | null {
if (!localDate) {
return null;
}
return new Date(this.toString(localDate));
}
static toLocalDate(date: Date): LocalDate | null {
if (!date) {
return null;
}
return LocalDate.from(nativeJs(date));
}
static toLocalDateTime(date: Date): LocalDateTime | null {
if (!date) {
return null;
}
return LocalDateTime.from(nativeJs(date));
}
static toLocalDateBy(strDate: string): LocalDate | null {
if (!strDate) {
return null;
}
return LocalDate.parse(strDate, DateTimeUtil.DATE_FORMATTER);
}
static toLocalDateTimeBy(strDate: string): LocalDateTime | null {
if (!strDate) {
return null;
}
return LocalDateTime.parse(strDate, DateTimeUtil.DATE_TIME_FORMATTER);
}
}
아참... mySQL에서 Y2K38문제는 8.0.28 버전부터 timstamp 함수 관련 에러는 해결했으나 여전히 38년 이후 범위는 사용 못한다.
https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-28.html
Reference
자바스크립트 시간 관련 아티클
Y2K38 관련 아티클
유닉스 타임이 1970년 기준인 이유 아티클
반응형