Java : Format about date, time

unchapterd·2021년 12월 19일
0

Java

목록 보기
8/19
post-thumbnail

Format about date, time

본 문서는 2021년 12월 19일 에 기록되었다.

여기서는 날짜 및 시간에 대한 Format 클래스에 대한 이야기를 할 것이다.
따라서 이 문서의 내용은 다음과 같은 경우에 도움이 될 것 같다.

  1. 날짜 및 시간 정보가 필요한 사람
  2. DBMS 와 Java 에서 동질한 날짜 및 시간 Format 이 필요한 사람

더하여 다음과 같은 사람도 이 포스트 내용이 도움이 될 거라고 생각한다.

  1. 숫자에 대한 Format 을 작성해야 하는 사람
  2. 무낮에 대한 Format 을 작성해야 하는 사람

Essentials of Java 에서는 Pacing 까지 다루고 있지만,
아직은 필요하지 않다고 판단되어 본 문서에서는 넘어갔다.


날짜

여기서는 크게 다음과 같은 클래스를 다루고 있다.

  1. Date 클래스 | jdk 1.0 이후
  2. Calendar 클래스 | jdk 1.1 이후
  3. java.time 패키지 | jdk 1.8 이후

이 중 1,2 는 Essesntails of Java 에서 다루는 내용이고
이들 각각 문제점 및 한계를 가지고 있기 때문에 추가로 jdk 1.8 이후의 패키지도 알아보았습니다.

Date

jdk 1.1 에서 Calendar 가 도입됨에 따라서,
jdk 1.0 Date 클래스의 대부분은 @Deprecated 되었다.

그럼에도 불구하고 여전히 Date 를 필요로 하는 메서드들이 있기 때문에,
Calendar 와 Date 를 서로 변환해야 하는 순간들이 온다.

  1. Calendar 를 Date 로 변환
  2. Date 를 Calendar 로 변환
public class Main{
   public static void main(String[] args){
      // 1. Calendar >> Date
      Calendar calendar=Calendar.getInstance();
      Date date=new Date(calendar.getTimeInMillis());
      
      // 2. Date >> Calendar
      Date date=new Date();
      Calendar calendar=Calendar.getInstance();
      calendar.setTime(d);
   }
}

Date 사용 시의 주의사항

Date 는 1월부터 12월이라는 숫자를 사용하고 있다.
하지만 Calendar 는 0부터 11이라는 숫자로 1월부터 12월이라는 숫자를 표시한다.

따라서 Calendar 로부터 현재 날짜 정보를 받아와 Date 안에 넣을 때는 이 부분을 고려해야 한다.

Calendar

jdk 1.1 에서 추가된 Calendar 클래스는 기본적으로 추상 클래스이다.
따라서 생성자를 통해서 객체를 생성할 수 없고 생성을 담당하는 메서드를 호출해야 한다.

Calendar calendar=Calendar.getInstance();

자 그렇다면 왜 이런 방법을 통해서 Calendar 객체를 생성하는 것일까?
그것은 달력 제도 때문인데, 태국에서는 BuddhistCalendar 를 그 외의 국가에서는 GregorianCalendar 를 쓴다. 그런데 접속자의 국가가 어디인지에 따라서 생성자를 호출하는 것은 유연하지 못한 방법이다.

그러나 getInstancae() 를 통해서 Calendar 객체를 만들고
그 안에서 국가와 지역설정을 확인하여 BuddhistCalendar 혹은 GregorianCalendar 를 리턴하는 방식을 사용하면 매우 유연한 구조를 가질 수 있다.

더하여 추가적인 달력제도가 생기면 MoreCalendar 를 만들어서 반영하면 될 뿐이기 때문에, 이러한 getInstance() 와 같은 방법을 사용한다.

이러한 방법을 static factory method 라고 하며 Effective Java 에서나 GOF 디자인 패턴 과 같은 도서에서 가장 처음(혹은 쉽게, 많이) 접하는 것 중 하나이다.

Calendar 사용 시의 주의사항

Calendar 는 0부터 11이라는 숫자로 1월부터 12월이라는 숫자를 표시한다.
따라서 Calendar 객체에서 정보를 받아와서 사용할 때도 이를 고려해야 한다.

java.time

jdk 1.0 Date 와 jdk 1.1 Calendar 는 여러 가지 단점을 가지고 있습니다.
따라서 이를 해결하기 위하여 jdk 1.8 java.time 패키지가 나왔습니다.
이 부분은 다수의 패키지를 포함하고 있으므로 ## java.time 패키지 를 참고해주세요.


java.time 패키지

java.time 패키지는 4개의 하위 패키지를 가지고 있습니다.

  1. java.time.chrono | 표준 ISO 가 아닌 달력 시스템을 위한 클래스들
  2. java.time.format | 날짜와 시간을 Parcing, Format 하기 위한 클래스들
  3. java.time.temporal | 날짜와 시간의 Field, Unit 을 위한 클래스들
  4. java.time.zone | 시간대(time-zone) 을 위한 클래스들

본 문서는 Essentials of Java 를 기반으로 하였기 때문에,
역시나 몇몇 핵심 클래스들만 다루고 넘어가고 차후에 보완하도록 하겠습니다.

핵심 클래스 목록은 다음과 같습니다.

  1. LocalDate
  2. LocalTime
  3. Instant
  4. LocalDateTime
  5. ZonedDateTime
  6. TemporalAdjusters
  7. Period & Duration

부차적으로는 위 클래스들은 어떠한 인터페이스들을 구현한 것입니다.

1번 타입 | Temporal ...

Temporal, TemporalAccessor, TemporalAdjuster
LocalDate, LocalTime, LocalDateTime, Instant 등

2번 타입 | TemporalAmount

TemporalAmount
Period, Duration

또한 마지막에는 Pacing 과 Format 의 내용이 있습니다.

LocalDate

현재 날짜를 얻어오는 클래스.

now()

now() 를 이용해서 손쉽게 현재 날짜를 받을 수 있다.
나는 toString()과 substring() 을 이용하여 정보를 잘라서 쓰는 것을 좋아한다.

LocalDate todayDate=LocalDate.now();
String todayString=todayDate.toString().substring(1,1);

of()

of 메서드를 이용하여 format 으로도 사용할 수 있다.
그러나 겨우 하나의 값을 format 에 맞게 만들기 위하여 객체를 import하고 인스턴스화 하는 것이 맞는 지에 대해서는 회의적이다.

LocalDate todayDate=LocalDate.of(1999,12,31);

get(), getXXX()

get 메서드는 너무 많고 기능은 사실상 동일하다고 생각하여 적지 않겠다.

with(), plus(), minus()

위 메서드는 특정 필드값을 변경하기 위한 메서드이다.

isAfter(), isBefore(), isEqual()

위 메서드는 특정 필드값을 비교하기 위한 메서드이다.
위 내용을 보고 equals()가 있는데 왜 isEqual() 을 만들었는가? 라는 의문이 들 수 있는데,

equal() 은 객체의 모든 주요 필드가 동일해야 하지만,
Date 나 Time 등의 정보는 국가마다 인스턴스가 조금씩 다르기 때문에 사용이 제한적이다.
따라서 isEqual()로 타켓한 필드만 비교하는 것이다.

@Override compareTo()

compareTo 메서드는 잘 @Override 되어 있어서 그대로 사용하면 된다.

LocalTime

현재 시간을 얻어오는 클래스

LocalTime todayTime=LocalTime.now();

아래 항목 모두 LocalDate 와 동일하다.

  1. now(), of()
  2. get(), getXXX()
  3. with(), plus(), minus()
  4. isAfter(), isBefore(), isEqual()
  5. @Override compareTo

LocalDateTime

현재 날짜와 시간을 얻어오는 클래스
LocalDate 와 LocalTime 이 합쳐져있다.

LocalDateTime todayDateTime=LocalDateTime.now();

LocalDate 와 LocalTime 의 메서드들을 모두 그대로 사용한다.
더하여 LocalDate와 LocalTime 으로 타입 변환을 해주는 메서드도 존재한다.

  1. toLocalDate()
  2. toLocalTime()

Offset va Zoned

아래 내용은 Offset Time-Zone 과 Time-Zone 의 차이에 대해서 설명한 것이다.

  1. ZonedDateTime 은 ZonedId 로 구역을 표현한다.
    1.a. ZoneId 는 일광절약시간처럼 시간대와 관련된 규칙들을 포함하고있다.
  2. OffsetDateTime 은 ZoneOffset 을 사용한다.
    2.a. ZoneOffset 은 단순히 시간의 차이만을 기록한다.

국내에서만 사용될 서비스라면 ZonedDateTime 을 써도 되지만
각을 오가는 서비스라면 OffsetDateTIme 을 써야 안전하다.

그러나 실제로 이 둘을 구분하기란 쉽지 않기 때문에,
꼭 필요한 경우가 아니라면 OffsetDateTime 을 쓰는 것이 안전하다.

OffsetDateTime

시간대를 포함하여 현재 날짜와 시간을 얻어오는 클래스
Offset Time-Zone 값과 LocalDate 와 LocalTime 이 합쳐져있다.

OffsetDateTime todayDateTimeOffset=OffsetDateTime.now();

자세한 내용은 해당 기능이 필요한 순간에 구글링을 하거나 API 문서를 확인하자

ZonedDateTime

시간대를 포함하여 현재 날자와 시간을 얻어오는 클래스
Time-Zone 값과 LocalDate 와 LocalTime 이 합쳐져있다.

ZonedDateTime todayDateTimeZoned=ZonedDateTime.now();

자세한 내용은 해당 기능이 필요한 순간에 구글링을 하거나 API 문서를 확인하자

Instant

Instant 는 에포크 타임 (EPOCH TIME, 1970-01-01 00:00:00 UTC) 부터 경과한 시간을 나노초 단위로 표현한다.

역시나 toString() 과 subString() 을 이용해서 특정한 양식을 뽑아낼 때 쓸 수 있다.

개인적으로는 특히 00:00:00 와 관련된 숫자 정보가 필요할 때 사용했다.
예를 들면, 비디오 플레이어의 시간같은 것에서 사용할 수 있는데,
에포크 타임의 객체 생성시 시작시간이 무조건 00:00:00 인 것을 감안하여
javascript 의 setInterval 과 같은 메서드로 1 초마다 1초씩 증가시키고
해당 에포크 타임 객체에 toString 과 subString 을 통해서 시간을 받아올 수 있었다.

이로써,
자리수가 바뀌는 것에 관한 로직을 계산하지 않고 쉽게 비디오 플레이어 테이블을 구현했다.

TemporalAdjusters

plus() 나 minus() 로 날짜를 구할 수 있지만,
이번달의 3번째 금요일과 같은 것들은 하기 난해하고 귀찮다.

그렇기 때문에 이 클래스는 꽤 유용하다.
아래에 사용 예시를 보고 실제로 원하는 값은 몇몇 키워드를 이용해서 메서드 안에서 찾아보자.

LocalDate today=LocalDate.now();
LocalDate nextMonday=today.with(TemporalAdjusters.next(DayOfWeek.MONDAY));

직접 구현하기

만약 해당 기능을 자주 사용하거나
새로운 기능을 필요로 하는 경우 TemporalAdjusters 클래스를 새로 만드는 것이 좋을 수도 있다.

@FunctionalInterface
public interface TemporalAdjuster {
   Temporal adjustInto(Temporal temproal);
}

예를 들면
특정 날짜로부터 2일 후의 날짜를 계산하는 DayAfterTomorrow 는 다음과 같이 작성할 수 있다.

class DayAfterTommorw implements TemporalAdjuster {
   @Override
   public Temporal adjustInto(Temporal temporal) {
      return temporal.plus(2, ChronoUnit.DAYS);
   }
}

Period & Duration

  1. Period 날짜의 차이
  2. Duration 시간의 차이

아래에서는 간단한 예시만 들고 구체적으로 필요한 기능이 있다면 API 를 확인해보자.
아래에서 포함하고 있는 예시는 다음과 같다.

  1. between() | 두 매개변수 값 차이
  2. until() | 한 매개변수까지 남은 거리
  3. of(), with() 등 | 전술한 LocalDate 등과 동일
  4. 사칙연산, 비교연산, 기타 메서드
  5. 변환메서드 toXXX() | 단위를 바꾸기 위한 메서드
// Period 예시
LocalDate date1=LocalDate.of(2014,1,1);
LocalDate date2=LocalDate.of(2015,12,31);
Period result=Period.between(date1, date2);

// Duration 예시
LocalTime time1=LocalTime.of(00,00,00);
LocalTime time2=LocalTime.of(12,34,56);
Duration result=Duration.between(time1,time2);

Format

여기서는 다음과 같은 Format 클래스에 대해서 다루고 있다.
코딩에 명확한 정답은 없고 그저 사용 상의 이점이 있으면 이를 사용하는 것이라고 생각한다.
따라서 아래의 Format 클래스 또한 참고 사항으로 공부하고 필요한 경우에 사용하는 것 이 좋을 것 같다. 물론 본인만의 Format 클래스를 만들어보는 것도 재밌을 것 같다.

  1. DecimalFormat
  2. SimpleDateFormat
  3. ChoiceFormat
  4. MessageFormat

DecimalFormat

DecimalFormat 은 숫자 데이터를 다룬다.
그 중, 정수, 부동소수점, 금액 등의 다양한 형식으로 표현할 수 있다.
또한, 일정한 형식의 텍스트 데이터를 숫자로 쉽게 변환하는 것도 가능하다.

자세한 내용은 구글링을 하고 포스트를 보는 것을 권고해드립니다. 추천

그 이유는 다음과 같습니다.

  1. 단순하게 값의 출력 양식의 경우 System.out.printf() 를 사용하는 것이 좋다.
  2. 어떠한 값의 입출력에 자릿수를 맞추는 경우 Regular Expression 이 훨씬 유연하다.
  3. 어떠한 값의 양식이 필요한 경우 Regular Expression 이 훨씬 유연하다.

SimpleDateFormat

SimpleDateFormat 은 날짜 데이터를 다룬다.
전술한 Date 와 Calendar 가 날짜를 계산하고 원하는 값을 얻는다면,
이 SimpleDateFormat 은 이 값을 원하는 형태로 출력하는 것이다.

자세한 내용은 구글링을 하고 포스트를 하는 것을 권고해드립니다.

그 이유는 다음과 같습니다.

  1. 2021년에서 2만2021년이 되지 않는 이상 크게 자리수가 변하지 않는다.
  2. 2021-1-1 이 아니라 2021-01-01 로 표기되는 입력값의 특성상 크게 자리수가 변하지 않는다.

따라서 다음의 방법을 권고해드립니다.

  1. Regular Expression 을 활용한 방법
  2. String 의 substring(int start, int end) 을 활용한 방법

그러나 다음의 경우에는 SimpleDateFormat 도 좋은 선택이 될 수 있습니다.

  1. Date 나 Calendar 에서 제공해주는 기본 양식과 전혀 다른 양식의 Format 이 필요한 경우
    ex) 2021-01-03 (Date) >> 21년 1월 3일 (첫째주) (SimpeDateFormat)

ChoiceFormat

ChoiceFormat 은 특정 범위에 속하는 값을 문자열로 바꿉니다.
개인적으로는 의미만 보면 GradeFormat 등이 더욱 직관적인 느낌이 듭니다.

해당 클래스는 원래는 if문이나 switch 문을 통해서 분기 처리해야할 성적 정보의 변환을 짧게 만들어줍니다. 또한 날짜나 시간보다는 조금 더 범용적인 기능을 제공하므로 괜찮은 클래스라고 생각합니다.

public class Choice {
   int cutLine[] = { 60, 70, 80, 90 };
   String grades = { "D", "C", "B", "A" };
   ChoiceFormat gradeFormat=new ChoiceFormat(cutLine,grades);
   
   int scores = { 100, 80, 70, 90, 65, 80 };
   
   
   int printValue;
   for (int score : scores) { // adv For 문을 모르면 구글링을 해보자
     scoreFormating=gradeForm.format(score);
     
     System.out.printf("%d : %s",score, scoreFormating);
   }
}

MessageFormat

MessageFormat 은 데이터를 양식에 맞게 출력할 수 있도록 도와줍니다.
최근에 Java 로만 이루어진 가상 서비스를 클론 코딩하는 것을 목표로 삼는 프로젝트를 진행한 바가 있습니다. 그런데 거기서 반복적으로 try-catch 문에 다음과 같은 내용을 적었습니다.

public class Main{
   public static void main(String[] args){
      try{
        // 뭔가 에러를 던져주는 부분이 있다면...
      catch(Exception e){
        System.out.print("Main.java | main.method | "+e);
      }
   }
}

이는 개인적인 습관에서 비롯된 것인데
Java 혹은 Javascript 에서도 어느 정도 자세한 에러의 내용을 출력해줍니다.
하지만 그 에러가 어떤 클래스의 어떤 메서드에서 발생했는지를 알면 더욱 편하게 작업할 수 있습니다.

그래서 저는 특히 try-catch 안에는 저런 내용을 추가하고는 합니다.
단, 이것이 서비스 런칭 상에서 제거되어야 하는 부분인지 는 모르겠습니다.

아래와 같은 방법으로 사용할 수 있습니다.
물론 퍼포먼스 적인 면에서 최악이라고 생각하기 때문에,
아래 방법으로 코딩을 하자! 가 아니라 아래 방법의 예시를 들었을 뿐입니다.

실제로 아래 방법을 사용하게 될지는 잘 모르겠습니다.

public class Main{
   public static void main(String[] args) {
      String printException="{0} | {1} | {2}";
      
      try{
      }catch(Exception e){
         String exceptions[]={ "Main.java", "main.mehotd", e);
         String result=MessageFormat.format(printException, exceptions);
         System.out.println(result);
      }
   }
profile
문제없는 기록

0개의 댓글