ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Android] ZonedDateTime, LocalDateTime, Instant의 차이
    Android 2025. 10. 29. 09:23
    728x90
    반응형

    workmanager를 사용해서 안 읽은 알림이 있을 시 오전 9시에 정기적으로 알림을 주고자 했다

     

    val now = LocalDateTime.now()
    val targetTime = LocalDateTime.of(
                LocalDate.now().plusDays(1),
                LocalTime.of(9, 0))
    
    val delay = Duration.between(now, targetTime).toMillis()
    
    notifyWorkRequest = PeriodicWorkRequestBuilder<NotifyUnReadNotiWorkManager>(
                1, TimeUnit.DAYS).setInitialDelay(delay, TimeUnit.MILLISECONDS)
                .build()

     

    그런데 자꾸만 오후 10시가 넘어서야 알림이 왔다;

    뭔가 이상해서 확인해봤더니 지금 현재 한국시간과의 시차가 9시간 정도 차이가 났다

    내가 쓰는 맥북의 언어 설정 등을 살펴봐도 한국으로 잘 설정되어있는데 왜 이렇게 되어있는지는 알 수가 없었다...

     

    일단 workManager의 duration을 설정해주기 위해 지금 현재 한국 시간을 가져오는 걸로 변경해주어야했다

     

    찾아보니 Java SE8부터 나온 java.time 패키지 중 LocalDateTime, ZonedDateTime, Instant라는 것이 있었다

    (Android는 API Level 26이상부터 이 패키지가 포함되어있다고 한다)

     

    이전에 날짜 및 시간 관련 패키지가 있었을 텐데 왜 java.time이라는 패키지가 새로 생겼을까?

    - 기존에 Java에서 사용하던 Date(지금은 Deprecated됨)는 mutable해서 수정이 가능했음

    -> 멀티스레드 환경에서 mutable한 객체에 여러 스레드들이 접근해서 값을 변경시킬 수 있는데 이로 인해 mutable한 객체에 담긴 데이터는 정확하지 않을 확률이 큼 -> 멀티스레드 환경에서 안전하지 못함

    - 1월부터 12월까지의 월(month)이 0부터 시작해서 개발할 때 불편함을 느끼게 됨

    - UTC의 시간보정을 위한 leap second 같은 특수한 상황을 고려하지 않았음

     

     이러한 점들을 보완하기 위해 java.time이라는 패키지가 만들어졌다고 한다

     

    근데 또 보다보니 GMT, UTC이라는 개념들도 있어서 찾아보았다

    더보기
    • GMT(Greenwitch Mean Time)

    경도 0도에 위치한 영국 천문대 그리니치 천문대를 기준으로 한 시간

     

    • UTC(Coordinated Universial Time)

    지구 자전의 영향을 받아 시간이 흐를 수록 GMT의 시간이 조금씩 늦어질 수 있음 -> 이를 해결하기 위해 UTC가 나옴

    UTC는 원자시를 사용하기 때문에 자전 주기와 무관함

     

    일단 java.time에서 주로 쓰는 LocalDateTime, ZonedDateTime, Instant 중에 하나를 써보고자 했다

    근데 차이점이 뭔지 몰라서 검색해보고 내가 이해한 차이점은 다음과 같다

     

    1. LocalDateTime, LocalTime, LocalDate

    특정 타임존(지역정보)이나 오프셋(UTC와의 시간 차이) 정보가 없고 그저 날짜와 시간을 표현함

     

    예를 들면 크리스마스가 12월 25일 자정이라고 치면 

    한국의 12월 25일의 자정과 뉴욕의 12월 25일 자정은 시차가 있기 때문에 다름

    하지만 LocalDateTime은 타임존이나 오프셋을 신경쓰지 않고 그저 12월 25일 자정을 나타내는 것임

    (어디의 12월 25일 자정인지 모름)

     

    LocalDateTime.now()의 경우 출력해보면 타임존 정보는 없고 그저 각자의 시스템의 로컬 시간이 표현된다

    ZoneId를 통해 타임존 정보를 넣을 수도 있다

        val example0 = LocalDateTime.of(2025, 12, 25, 0, 0)
        println(example0) // 2025-12-25T00:00
    
        val example00 = LocalDateTime.now()
        println(example00) // 2025-10-28T13:49:26.273845602
        
        val example1 = LocalDateTime.now(ZoneId.of("Asia/Seoul"))
        println(example1) // 2025-10-28T22:49:26.294620650
    
        val example2 = LocalDate.now()
        println(example2) // 2025-10-28
    
        val example22 = LocalTime.now()
        println(example22) // 13:50:21.470527987

     

    2. ZonedDateTime

    LocalDateTime + 타임존 + 오프셋 이라고 보면 됨

    즉, 특정 타임존에 오프셋이 적용된 명확한 순간의 날짜와 시간을 뜻함 

    val zoneId = ZoneId.of("Asia/Seoul")
        
    val example3 = ZonedDateTime.now(zoneId)
    println(example3.toString()) // 2025-10-28T21:05:56.214161473+09:00[Asia/Seoul]

    zoneId를 seoul로 지정하고 

    ZonedDateTime.now()를 했을 때 Asia/Seoul(타임존)에 offSet(+09:00(UTC 기준 +9시간을 해야 한국 시간)이 적용된 지금 현재 날짜와 시간을 표현함

     

    3. Instant

    UTC를 기준으로 한 명확한 순간의 시간과 날짜를 표현함(타임존, 오프셋 X)

    UTC를 기준으로 하기 때문에 다른 타임존에서 실행해도 같은 결과를 리턴함

    주로 서버와 통신하거나 로그를 남길 때 사용함

    toEpochMilli()를 사용해서 1970년 1월 1일 00:00(UTC)부터 지금 현재 순간까지 얼마나 흘렀는지 밀리세컨드로 표현함

    val example4 = Instant.now()
    println(example4.toString()) // 2025-10-28T12:08:00.315288699Z
        
    val example5 = Instant.now().toEpochMilli()
    println(example5.toString()) // 1761653280318

     


     

    나의 경우 일단 한국에서만 운영한다고 가정했다

    그래서 ZoneId.of()를 통해 Asia/Seoul로만 타임존을 설정하고 다음과 같이 지금 현재 서울의 날짜와 시간을 가져오게 했다

    const val ZONE_ID = "Asia/Seoul"
    val now = ZonedDateTime.now(ZoneId.of(ZONE_ID))

     

    그리고 기존에는 9시쯤에 알림을 보냈다면 오전 8시~9시 사이에 랜덤하게 보내주는 걸로 변경했다(출근시간대니까 이때 알림 보라고)

    Kotlin의 Random.nextInt(from, until)를 사용해서 랜덤한 시간과 분을 설정해주었다

    참고로 nextInt() 두 번째 인자는 제외하고 그 앞의 값까지만 포함한다

     

    val randomHour = Random.nextInt(8, 10) // 8, 9 중에 랜덤
    val randomMinute = Random.nextInt(0, 60) // 0~59 중에 랜덤

     

    그리고 ZonedDateTime에는 여러가지 메소드들이 있는데 그 중에서 plusDays(1)를 통해서 다음날로 타겟 날짜를 설정하고 withHour, WithMinute를 사용해서 알림이 보여질 시간대와 분을 타겟시간으로 설정했다

    val targetTime = now.plusDays(1)
        .withHour(randomHour)
        .withMinute(randomMinute)

     

    Duration을 사용해 현재 시간에서 타겟시간과의 차이를 구해 밀리세컨드로 변환시켰다

    val delay = Duration.between(now, targetTime).toMillis()
    

     

    아래와 같이 함수로 따로 분리해주었다

    const val ZONE_ID = "Asia/Seoul"
    fun workDuration(
        hourFrom: Int = 8,
        hourUntil: Int = 10,
        minuteFrom: Int = 0,
        minuteUntil: Int = 60,
        days: Long = 1L
    ): Long {
        val now = ZonedDateTime.now(ZoneId.of(ZONE_ID))
        val randomHour = Random.nextInt(hourFrom, hourUntil)
        val randomMinute = Random.nextInt(minuteFrom, minuteUntil)
        val targetTime = now.plusDays(days)
            .withHour(randomHour)
            .withMinute(randomMinute)
    
        val delay = Duration.between(now, targetTime).toMillis()
    
        return delay
    }

     

    그리고 workmanager에서 다음과 같이 작성해주었다

    덕분에 코드를 좀 줄였다 ㅎ...

    fun startWork(context: Context) {
        notifyWorkRequest = PeriodicWorkRequestBuilder<NotifyUnReadNotiWorkManager>(
            1, TimeUnit.DAYS
        ).setInitialDelay(workDuration(), TimeUnit.MILLISECONDS)
            .build()

     

     


     

     

    자바스크립트에서 Date 쓰던 것처럼 LocalDateTime만 썼었는데

    더 좋은 것들이 많고 세세하게 있어서 좋았다

    이번에는 간단하게 사용했지만 다음번에 시간을 처리해야할 일이 있으면 더 재밌게 쓸 수 있을 거 같다

     

     

     

    - 참고
    http://www.tcpschool.com/java/java_time_javaTime

    http://www.tcpschool.com/java/java_intro_java8

    https://stackoverflow.com/questions/32437550/whats-the-difference-between-instant-and-localdatetime

    https://medium.com/elca-it/how-to-get-time-zones-right-with-java-8dea13aabe5c
    https://velog.io/@hazzang/LocalDateTime-vs-Instant-vs-ZonedDateTime

    https://en.wikipedia.org/wiki/Greenwich_Mean_Time

    https://24timezones.com/gmt-vs-utc

    반응형
Designed by Tistory.