Trouble Shooting
[Android] UTC와 KST의 Timezone 보정 오류 해결
Marchbreeze
2025. 5. 16. 00:17
2024년 4월 6일에 작성되었던 글입니다.
문제 상황
- 방 생성 시 시간을 설정해도, 생성된 방에서는 9시간 이후의 시간으로 보여지는 이슈가 있었습니다.
- 서버로직을 점검해 보니, 생성 요청(request)에는 "2024-04-15T10:00:00.000Z"(UTC) 포맷을 보냈지만, 조회 응답(response)에서는 "2024-04-15 19:00:00"(KST) 형태로 받아와 9시간 차이가 발생했습니다.
1. TimeZone에 대한 이해
타임존(TimeZone)
서로 다른 지역의 현지 시간을 관리하기 위한 기준입니다.
전 세계를 UTC 기준으로 나눈 표준시로, 각 지역은 UTC에 더하거나 뺀 시차(offset)를 가집니다.
- UTC (Coordinated Universal Time) : 지구 자전과 원자시계를 결합해 만든 국제 표준시입니다
- KST (Korea Standard Time) : 대한민국의 표준시로, UTC에 9시간을 더한 시간대입니다. (ex. UTC 10:00 → KST 19:00)
ISO 8601 vs 로컬 표현
"2024-04-12T10:00:02.000Z"
- ISO 8601 국제 표준 포맷
- T는 날짜와 시간 구분, Z는 UTC 기준임을 나타냅니다.
"2024-04-12 19:00:02"
- 시간대 정보 없이 로컬 표기로만 제공된 경우
- 컨텍스트에 따라 KST(UTC+9)나 다른 시간대로 해석될 수 있습니다.
2. 시간 변환 로직 수정
(1) 시간 기준 설정
private const val UTC_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
private const val NO_TIME_FORMAT = "yyyy-MM-dd"
private const val WRONG_FORMAT = "날짜 형식이 잘못되었습니다."
private val UTC_TIME_ZONE: TimeZone = TimeZone.getTimeZone("UTC")
private val KOREA_TIME_ZONE: TimeZone = TimeZone.getTimeZone("Asia/Seoul")
private val utcFormat = SimpleDateFormat(UTC_DATE_FORMAT, Locale.KOREA).apply {
timeZone = UTC_TIME_ZONE
}
private val kstFormat = SimpleDateFormat(UTC_DATE_FORMAT, Locale.KOREA).apply {
timeZone = KOREA_TIME_ZONE
}
private val noTimeKstFormat = SimpleDateFormat(NO_TIME_FORMAT, Locale.KOREA).apply {
timeZone = KOREA_TIME_ZONE
}
- 클라이언트에서 사용하는 모든 시간 : KST(UTC +9) 기준
- 서버에서 사용하는 모든 시간 : UTC(UTC +0) 기준
(2) Date → Calendar 변환
// String 값으로 받은 expiration을 Date로 변환
private fun getDateInstanceFromLocalFormat(localFormatString: String): Date {
val inputFormat = SimpleDateFormat(LOCAL_DATE_FORMAT, Locale.KOREA)
return inputFormat.parse(localFormatString)
?: throw IllegalArgumentException("날짜 형식이 잘못되었습니다.")
}
// Date을 시간대를 적용한 후 Calendar 로 변환
fun getCalendarFromLocalFormat(localFormatString: String): Calendar {
// KST(TimeZone) 기준으로 Calendar 인스턴스 생성
return Calendar.getInstance(KOREA_TIME_ZONE).apply {
// 파싱된 Date(UTC 기준)를 Calendar.time에 설정
time = getDateInstanceFromLocalFormat(localFormatString)
// KST(UTC+9) → UTC 보정
add(Calendar.HOUR_OF_DAY, -9)
}
}
- Calendar.getInstance으로 KST 기준 Calendar를 얻은 뒤,
- time = Date 로 변환된 시간을 설정하고,
- add(Calendar.HOUR_OF_DAY, -9)로 9시간 뒤(UTC 기준)로 이동했습니다.
(3) 날짜 차이 계산
// 밀리초 단위 두 시각 차이로 일수 계산 (올림)
private fun getDayDiff(laterMs: Long, earlyMs: Long): Int {
val gap = laterMs - earlyMs
val days = gap / (24f * 60 * 60 * 1000)
return days.roundToInt()
}
// String 입력 두 개를 Calendar로 변환해 일수 차이 계산
fun getDayDiff(later: String, early: String): Int {
val laterCal = getCalendarFromLocalFormat(later).apply { initToMidnight() }
val earlyCal = getCalendarFromLocalFormat(early).apply { initToMidnight() }
return getDayDiff(laterCal.timeInMillis, earlyCal.timeInMillis)
}
// 현재 시각 대비 남은 일수 계산
fun getDayDiffFromNow(localFormatString: String): Int {
val targetCal = getCalendarFromLocalFormat(localFormatString).apply { initToMidnight() }
// 시스템 기본 타임존 기준 현재 시각
val nowCal = Calendar.getInstance().apply { initToMidnight() }
return getDayDiff(targetCal.timeInMillis, nowCal.timeInMillis)
}
// 시·분·초 이하를 0으로 초기화해 '날짜 단위'만 비교
private fun Calendar.initToMidnight() {
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
- getCalendarFromLocalFormat 으로 파싱한 시각을 UTC 기준으로 보정한 뒤,
- initToMidnight() 확장 함수로 시·분·초·밀리초를 모두 0으로 만들어 날짜 단위로 비교했습니다.
- Calendar.getInstance()로 얻은 현재 시각에도 같은 초기화를 적용해, 올바른 일수 차이를 구했습니다.
다음과 같이 UTC와 KST 간 9시간 차이를 명확히 이해하고, 타임존 보정을 적용한 문자열 변환 과정을 통해서 문제 상황을 해결했습니다.