ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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는 선택이 아닌 필수..)

    Instant의 하드웨어에 따른 시간 차이 문제

     

    자바스크립트에서의 시간 다루기

    • 자바스크립트에서는 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년 기준인 이유 아티클

    반응형
Designed by Tistory.