-
DB의 시간, Java의 시간, Javascript의 시간 (Data, LocalDate, LocalDateTime, Y2K38 Problem)JAVA/자바공부 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년 기준인 이유 아티클
반응형'JAVA > 자바공부' 카테고리의 다른 글
자바 LocalDateTime 요일, 오전/오후 한국어로 변경 (0) 2023.08.03 (다시 보는) SOLID 원칙이란? (0) 2023.05.29 자바 (0) 2023.02.20 정규식 표현 (자바, 코틀린) - 전화번호, 우편번호, 이메일 (0) 2022.10.14 자바 17로 바꿔야 할까? (자바 JDK 10 ~ 17 정리) (1) 2022.10.01