๋ฐ˜์‘ํ˜•

๐Ÿ™ ์„œ๋ก 


ํ˜„์žฌ ์šฐ๋ฆฌ ํšŒ์‚ฌ์—์„œ๋Š” ๋ชจ๋“  ๋‚ ์งœ๋Š” UTC ํƒ€์ž„์กด์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ๋‹ค.

์ด ๊ณผ์ •์—์„œ ํƒ€์ž„์กด์— ๋Œ€ํ•œ ์ดํ•ด๊ฐ€ ์ œ๋Œ€๋กœ ์žกํžŒ ๋ถ„๋“ค๊ณผ ์žกํžˆ์ง€ ์•Š๋Š” ๋ถ„๋“ค์ด ์žˆ๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด, LocalDateTime๊ณผ Instant๊ฐ€ ์„ž์—ฌ์„œ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋‹ค. ๊ฐ™์€ ๋กœ์ง์ž„์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ .

๋‚˜๋„ ์ œ๋Œ€๋กœ ์žกํžˆ์ง€ ์•Š๋Š” ์‚ฌ๋žŒ ์ค‘ ํ•œ ๋ช…์ด๋‹ค. ํƒ€์ž„์กด์— ๋Œ€ํ•ด์„œ ํ•œ ๋ฒˆ ์ •๋ฆฌ๋ฅผ ํ–ˆ์ง€๋งŒ,

์•„์ง๋„ UTC๋ฅผ ์ฑ„ํƒํ•˜๋Š” ์ด์œ ์— CTO๋‹˜๊ป˜ ์„ค๋ช…์„ ๋“ค์—ˆ์Œ์—๋„ ๊ณต๊ฐ์„ ๋– ๋‚˜์„œ ์ดํ•ด๋ฅผ ์ œ๋Œ€๋กœ ํ•˜์ง€ ๋ชปํ•œ ์ƒํ™ฉ์ด๋‹ค.

์ด๋ฒˆ ๊ธฐํšŒ์— LocalDateTime๊ณผ Instant๋กœ ํƒ€์ž„์กด์— ๋Œ€ํ•œ ์ดํ•ด๋„๋ฅผ ์กฐ๊ธˆ ๋” ๋†’์ผ ๋ชฉ์ ์ด๋‹ค.

๐Ÿ™ Instant, LocalDateTime, ZoneDateTime ์ฐจ์ด์  ๋ฐ ํŠน์ง•


LocalDateTime

  • Java 1.8
  • ๋กœ์ปฌ ํƒ€์ž„์กด์„ ๋”ฐ๋ฅธ๋‹ค.
  • ํƒ€์ž„์กด์„ ํฌํ•จํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • atZone() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํƒ€์ž„์กด ๋ณ€ํ™˜์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

Instatnt ํด๋ž˜์Šค

  • Java 1.8
  • UTC ํƒ€์ž„์กด์œผ๋กœ ์‚ฌ์šฉ๋˜๋ฉฐ, 1970๋…„ 1์›” 1์ผ 00:00:00(GMT)๋ถ€ํ„ฐ ๊ฒฝ๊ณผํ•œ ์‹œ๊ฐ„์„ ์ดˆ ๋‹จ์œ„๋กœ ํ‘œํ˜„ํ•œ๋‹ค.
  • ํƒ€์ž„์กด์„ ํฌํ•จํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • ๋ฌด์กฐ๊ฑด UTC ํƒ€์ž„์กด์ด๋‹ค.
  • atZone() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค๋ฅธ ํƒ€์ž„์กด์œผ๋กœ ๋ณ€ํ™˜์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

ZoneDateTime

  • Java 1.8

  • ํƒ€์ž„์กด์„ ํฌํ•จํ•œ๋‹ค.
    ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ํƒ€์ž„์กด์„ ํฌํ•จํ•œ๋‹ค.

    A date-time with a time-zone in the ISO-8601 calendar system, such as 2007-12-03T10:15:30+01:00 Europe/Paris.

๊ฐ ํƒ€์ž…์˜ ํฌ๋งท

์•„๋ž˜ ์ฝ”๋“œ๋Š” ๊ฐ ํƒ€์ž…์˜ ํฌ๋งท์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ์‚ดํŽด๋ณด๋Š” ์ฝ”๋“œ์ด๋‹ค.

  • ์œ ์ผํ•˜๊ฒŒ ZoneDateTime๋งŒ ํƒ€์ž„์กด์ด ์กด์žฌํ•œ๋‹ค.
  • Instant๋Š” ํƒ€์ž„์กด์€ ์—†์ง€๋งŒ ํ•ญ์ƒ UTC์ด๊ธฐ ๋•Œ๋ฌธ์— -9์‹œ๊ฐ„ํ•˜์—ฌ ๊ณ„์‚ฐ๋œ๋‹ค.
  • LocalDateTime๋„ ํƒ€์ž„์กด์ด ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค.
@Test
void ํ…Œ์ŠคํŠธ1() {
  // given
  final DateTimeFormatter dateTimeFormatterISO8601 = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssxxx");
  String dateTimeString = "2023-01-01T00:00:00+09:00";

  ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateTimeString, dateTimeFormatterISO8601);
  Instant instant = ZonedDateTime.parse(dateTimeString, dateTimeFormatterISO8601).toInstant();
  LocalDateTime localDateTime = LocalDateTime.parse(dateTimeString, dateTimeFormatterISO8601);

  // when
  System.out.println("================");
  System.out.println("zonedDateTime = " + zonedDateTime);
  System.out.println("instant = " + instant);
  System.out.println("localDateTime = " + localDateTime);
  System.out.println("================");
}

// zonedDateTime = 2023-01-01T00:00+09:00
// instant       = 2022-12-31T15:00:00Z
// localDateTime = 2023-01-01T00:00

LocalDateTime์€ ํƒ€์ž„์กด ์ ์šฉ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

LocalDateTime์€ ์ฒ˜์Œ ์ƒ์„ฑํ•  ๋•Œ๋Š” ํƒ€์ž„์กด์„ ๋ถ€์—ฌํ•˜์—ฌ ์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ณด๋Š” ๋ฐ”์™€ ๊ฐ™์ด UTC๋กœ ์ ์šฉ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ดํ›„ ํƒ€์ž„์กด์„ ๋ณ€๊ฒฝํ•˜๊ณ  ์‹ถ์–ด๋„ ZoneDateTime์œผ๋กœ ๋ณ€ํ™˜ํ•ด๋„ ํƒ€์ž„์กด์ด ์ ์šฉ๋œ ์‹œ๊ฐ„์ธ +9:00์ด ์ ์šฉ๋œ ์‹œ๊ฐ„์ด ์•„๋‹ˆ๋‹ค.

  • LocalDateTime์€ ๋‚˜๋ผ๋ณ„๋กœ ํƒ€์ž„์กด์„ ๋ณ€๊ฒฝํ•ด์•ผ ํ•œ๋‹ค๋ฉด ์‚ฌ์šฉํ•˜๊ธฐ ๋ถˆํŽธํ•˜๋‹ค.
// ํ˜„์žฌ ์‹œ๊ฐ„: 2023-05-28 17:59:00
@Test
void LocalDateTime_ํƒ€์ž„์กด_๋ณ€ํ™˜() {
  // given
  LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("UTC"));
  ZonedDateTime localDateTimeToZoneDateTime = localDateTime.atZone(ZoneId.of("Asia/Seoul"));
  LocalDateTime zoneDateTimeToLocalDateTime = localDateTimeToZoneDateTime.toLocalDateTime();

  // when
  System.out.println("============");
  System.out.println("localDateTime = " + localDateTime);
  System.out.println("localDateTimeToZoneDateTime = " + localDateTimeToZoneDateTime);
  System.out.println("zoneDateTimeToLocalDateTime = " + zoneDateTimeToLocalDateTime);
  System.out.println("============");
}

// localDateTime               = 2023-05-28T08:59:00.506969
// localDateTimeToZoneDateTime = 2023-05-28T08:59:00.506969+09:00[Asia/Seoul]
// zoneDateTimeToLocalDateTime = 2023-05-28T08:59:00.506969

ZoneDateTime์€ ํƒ€์ž„์กด ์ ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

๋ณด๋Š” ๋ฐ”์™€ ๊ฐ™์ด UTC โ†’ Asia/Seoul๋กœ ์ ์šฉ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

// ํ˜„์žฌ ์‹œ๊ฐ„: 2023-05-28 18:10:00

@Test
void ZoneDateTime_ํƒ€์ž„์กด_๋ณ€ํ™˜() {
  // given
  ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("UTC"));
  ZonedDateTime convertTimezone = zonedDateTime.withZoneSameInstant(ZoneId.of("Asia/Seoul"));

  // when
  System.out.println("===============");
  System.out.println("zonedDateTime = " + zonedDateTime);
  System.out.println("convertTimezone = " + convertTimezone);
  System.out.println("===============");
}

// zonedDateTime   = 2023-05-28T09:10:24.292591Z[UTC]
// convertTimezone = 2023-05-28T18:10:24.292591+09:00[Asia/Seoul]

Instant๋Š” ZoneDateTime์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋ฉด ํƒ€์ž„์กด ์ ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

// ํ˜„์žฌ ์‹œ๊ฐ„: 2023-05-28 18:34:00

@Test
void Instant_ํƒ€์ž„์กด_๋ณ€ํ™˜() {
  // given
  Instant instant = Instant.now();
  ZonedDateTime instantToZoneDateTime = instant.atZone(ZoneId.of("Asia/Seoul"));

  // when
  System.out.println("===============");
  System.out.println("instant = " + instant);
  System.out.println("instantToZoneDateTime = " + instantToZoneDateTime);
  System.out.println("===============");
}

// instant               = 2023-05-28T09:34:40.621189Z
// instantToZoneDateTime = 2023-05-28T18:34:40.621189+09:00[Asia/Seoul]

๐Ÿ™ ์‹ค๋ฌด ํ™œ์šฉ


Instant์™€ ZoneDateTime

์˜ˆ๋กœ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ UTC๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ๋‹ค๊ณ  ํ•˜์ž.

์ •์‚ฐ์„ ํ•˜๋Š”๋ฐ 2023-01-01 04:00์— ๋ฐฐ์น˜๊ฐ€ ๋Œ์•„์„œ 2022๋…„ 12์›”31์ผ์˜ ์ •์‚ฐ์„ ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๋ฉด,

2022-12-30 15:00 ~ 2022-12-31 14:59:59์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•ด์„œ ์ •์‚ฐํ•ด์•ผ ํ•œ๋‹ค.

์ฃผ์˜ํ•  ์ 

  • instant1์ด ์•„๋‹Œ instant ๋ฐฉ์‹์œผ๋กœ ํ™œ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
    instant1 ๋ฐฉ์‹์œผ๋กœ ํ•˜ ๊ฒฝ์šฐ ๋ฐฐ์น˜ ์‹คํ–‰ ์‹œ๊ฐ„์— ๋”ฐ๋ผ์„œ ์กฐํšŒ ๊ธฐ๊ฐ„์ด 15์‹œ๊ฐ€ ์•„๋‹Œ 19์‹œ๋กœ ๋ณ€๊ฒฝ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

  • Asia/Seoul์„ ๋ช…์‹œํ•œ ์ด์œ 

    defaultTimeString์ด +09:00๊ฐ€ ์•„๋‹Œ +10:00์œผ๋กœ ์ž˜ ๋ชป ๋„˜์–ด์˜ค๊ฒŒ ๋  ๊ฒฝ์šฐ์—๋„ ๋Œ€๋น„ํ•˜๊ธฐ ์œ„ํ•จ์ด๋‹ค.

    Asia/Seoul๋กœ ํ•˜๋ฉด +10:00์ด์–ด๋„ +9:00๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ, 15:00๋กœ ์žกํžˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋ฐ˜๋ฉด ์ง€์ •ํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ 14:00๋กœ ๋ณ€ํ™˜๋˜๊ธฐ ๋•Œ๋ฌธ์—

    ์กฐํšŒ ๊ธฐ๊ฐ„์ด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

@Test
void d123123dd() {
  // given
  final DateTimeFormatter dateTimeFormatterISO8601 = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssxxx");
  String defaultTimeString = "2023-01-01T04:00:00+09:00"; // ์ƒˆ๋ฒฝ 4์‹œ ์ •์‚ฐ

  Instant instant = ZonedDateTime.parse(defaultTimeString, dateTimeFormatterISO8601)
      .withZoneSameInstant(ZoneId.of("Asia/Seoul"))
      .truncatedTo(ChronoUnit.DAYS)
      .toInstant();
  long epochMilli = instant.toEpochMilli();

  Instant instant1 = ZonedDateTime.parse(defaultTimeString, dateTimeFormatterISO8601)
      .withZoneSameInstant(ZoneId.of("Asia/Seoul"))
      .toInstant();
  long epochMilli1 = instant1.toEpochMilli();

  // when
  System.out.println("==============");
  System.out.println("instant = " + instant);
  System.out.println("instant1 = " + instant1);
  System.out.println("epochMilli = " + epochMilli);
  System.out.println("epochMilli1 = " + epochMilli1);
  System.out.println("==============");
}

// instant     = 2022-12-31T15:00:00Z
// instant1    = 2022-12-31T19:00:00Z
// epochMilli  = 1672498800000
// epochMilli1 = 1672513200000

localDateTime๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

๊ทผ๋ฐ ๊ฒฐ๊ตญ ZoneDateTime๊ณผ Instant๋ฅผ ์“ด๋‹ค.

์‹ค์ œ๋กœ ํƒ€์ž„์กด์ด ์ ์šฉ๋œ ๊ฒƒ์€ ์•„๋‹ˆ์ง€๋งŒ ๋งˆ์ง€๋ง‰์— Instant๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ํ™œ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€๋Šฅํ•˜๋‹ค.

Instant instant3 = LocalDateTime.parse(defaultTimeString, dateTimeFormatterISO8601)
        .atZone(ZoneId.of("Asia/Seoul"))
        .truncatedTo(ChronoUnit.DAYS)
        .toInstant();

Epoch ํ™œ์šฉ

์œ„์™€ ๊ฐ™์ด ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

UTC๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ด€๋ฆฌ๋  ๊ฒฝ์šฐ ํƒ€์ž„์กด ์…‹ํŒ…์— ๋ฒˆ๊ฑฐ๋กœ์›€์„ ์ค„์ด๊ธฐ ์œ„ํ•ด Epoch์„ ํ™œ์šฉํ•˜๋ฉด

ํ•ญ์ƒ UTC second์ด๊ธฐ ๋•Œ๋ฌธ์— ํ›จ์”ฌ ํŽธ๋ฆฌํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿ™ Epoch์ด๋ž€?


https://ko.wikipedia.org/wiki/์œ ๋‹‰์Šค_์‹œ๊ฐ„

UTC ๊ธฐ์ค€์ธ 1970๋…„ 1์›” 1์ผ 00:00:00๋ถ€ํ„ฐ์˜ ๊ฒฝ๊ณผํ•œ ์‹œ๊ฐ„์„ ์ดˆ๋กœ ํ™˜์‚ฐํ•˜์—ฌ ์ •์ˆ˜๋กœ ํ‘œํ˜„ํ•œ ๊ฒƒ์„ ์˜๋ฏผํ•œ๋‹ค.

์ฆ‰ ํƒ€์ž„์กด์ด ์˜๋ฏธ๊ฐ€ ์—†์ด ๋ฌด์กฐ๊ฑด UTC ๊ฒฝ๊ณผ ์‹œ๊ฐ„์ด๋‹ค.

์ฃผ์˜ํ•  ๊ฒƒ์€ ๋งŒ์•ฝ DB์—์„œ epoch์„ FROM_UNIXTIME()์„ ์‚ฌ์šฉํ•ด์„œ ๋ณ€ํ™˜ํ•˜๋ฉด,

์ปค๋„ฅ์…˜๋œ ํƒ€์ž„์กด์— ๋”ฐ๋ผ์„œ ๊ฒฐ๊ณผ๊ฐ€ ๋‹ค๋ฅด๊ฒŒ ๋‚˜์˜ค๋‹ˆ ํ˜ผ๋™ํ•˜์ง€ ๋ง์ž.

์ด๋Š” FROM_UNIXTIME()๊ฐ€ DB์˜ ์„ธ์…˜ ํƒ€์ž„์กด์„ ๋”ฐ๋ผ๊ฐ€๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ๋ž˜์„œ ์•Œ์•„์„œ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

์ฝ”๋“œ๋กœ ํ…Œ์ŠคํŠธ ํ•ด๋ณด๋ฉด ํฌ๋งทํŒ… ๋œ ๋‚ ์งœ๋Š” ๋‹ค๋ฅด์ง€๋งŒ epoch์€ ๋™์ผํ•œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

@Test
void epoch() {
  // given
  ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("Asia/Seoul"));
  long zoneDateTimeEpoch = zonedDateTime.toEpochSecond();

  Instant instant = Instant.now();
  long instantEpoch = instant.toEpochMilli() / 1000;

  // when
  System.out.println("=============");
  System.out.println("zonedDateTime = " + zonedDateTime);
  System.out.println("instant = " + instant);
  System.out.println("zoneDateTimeEpoch = " + zoneDateTimeEpoch);
  System.out.println("instantEpoch = " + instantEpoch);
  System.out.println("=============");
}

// zonedDateTime     = 2023-05-28T18:54:34.260566+09:00[Asia/Seoul]
// instant           = 2023-05-28T09:54:34.260676Z
// zoneDateTimeEpoch = 1685267674
// instantEpoch      = 1685267674

๐Ÿ™ LocalDateTime, ZonedDateTime, Instant ๋ชฉ์ 


LocalDateTime

  • ํƒ€์ž„์กด์ด ํ•„์š”์—†๋Š” ๊ฒฝ์šฐ ๊ฐ„๋‹จํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๊ธฐ ์ข‹๋‹ค.
  • ์ฆ‰, ํŠน์ • ํƒ€์ž„์กด์— ์˜์กดํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๋งŒ ํ™œ์šฉํ•˜๊ธฐ ์ข‹๋‹ค.
  • ํƒ€์ž„์กด์— ์˜์กดํ•œ๋‹ค๋ฉด ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธธ. ์œ„์—์„œ ๋ดค๋˜ ๊ฒƒ ์ฒ˜๋Ÿผ ์•ˆ๋˜๋Š” ๊ฒƒ์ด ์žˆ๋‹ค.

ZonedDateTime

  • ํƒ€์ž„์กด์„ ํฌํ•จํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํƒ€์ž„์กด์— ๋”ฐ๋ฅธ ์‹œ๊ฐ„ ํ‘œํ˜„์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
  • ํƒ€์ž„์กด์ด ํ•„์š”ํ•œ ๊ธ€๋กœ๋ฒŒ ์„œ๋น„์Šค ๊ฐ™์€ ๊ฒฝ์šฐ ํ™œ์šฉํ•˜๊ธฐ ์ข‹๋‹ค.

Instant

  • ํƒ€์ž„์กด์„ ํฌํ•จํ•˜์ง€ ์•Š๊ณ , UTC ๊ธฐ์ค€์œผ๋กœ ํ™œ์šฉํ•  ๋•Œ ํŽธํ•˜๋‹ค.

ํ .. ์•„์ง ์ •ํ™•ํ•˜๊ฒŒ ์™€๋‹ฟ์ง€๊ฐ€ ์•Š๋Š”๋‹ค.

๋ฐ˜์‘ํ˜•
๋ณต์‚ฌํ–ˆ์Šต๋‹ˆ๋‹ค!