Cheondi
개발 · 알고리즘·CS

날짜 범위와 시간대를 같이 생각해야 했던 이유

사용자의 하루와 서버의 조회 구간이 어긋나는 문제를 시간대 경계로 정리한 기록입니다.

  • #datetime
  • #timezone
  • #query

사용자가 오늘을 선택했는데 어제 마지막 내역이 섞이거나 오늘 첫 내역이 빠지는 문제가 있었다. 날짜 선택 UI는 정상이고 서버도 요청받은 범위를 정확히 조회했다. 서로 생각하는 하루의 시작이 달랐다. 화면은 사용자 시간대의 자정을 기준으로 했고 서버 요청은 UTC 날짜 문자열을 그대로 사용했다.

날짜는 달력의 칸이고 시각은 시간대가 포함된 순간이라는 차이를 다시 봐야 했다.

사용자 날짜를 먼저 구간으로 만들었다

선택한 시작일과 종료일을 사용자 시간대에서 시각 구간으로 만든 뒤 UTC로 변환했다.

DateTime localStart = selectedFrom.Date;
DateTime localEndExclusive = selectedTo.Date.AddDays(1);

DateTime utcStart = TimeZoneInfo.ConvertTimeToUtc(localStart, userZone);
DateTime utcEnd = TimeZoneInfo.ConvertTimeToUtc(localEndExclusive, userZone);

서버에는 utcStart <= timestamp < utcEnd 형태로 전달했다. 종료일의 23:59:59를 만드는 방식보다 정밀도 변화에 안전했다.

하루가 항상 24시간은 아니었다

일광절약시간이 적용되는 지역에서는 하루가 23시간이나 25시간일 수 있었다. AddHours(24)로 다음 날을 계산하지 않고 해당 시간대의 다음 날짜 자정을 다시 변환했다.

존재하지 않거나 두 번 나타나는 로컬 시각을 입력으로 받을 때의 정책도 확인했다. 날짜 단위 조회에서는 자정 경계가 유효한지 시간대 데이터에 따라 처리했다.

화면 표시와 서버 필터를 같은 기준에서 검증했다

테스트 데이터는 경계 바로 전과 직후에 만들었다.

시작 1밀리초 전   -> 제외
시작 시각         -> 포함
종료 직전         -> 포함
다음 날 시작 시각 -> 제외

서버 응답을 다시 사용자 시간대로 표시했을 때 선택한 날짜 안에 들어오는지도 확인했다. 요청만 맞거나 화면만 맞는 것으로는 충분하지 않았다.

시간대는 조회 계약의 일부였다

처음 시간 문제를 겪었을 때는 출력 포맷에 집중했다. 날짜 범위 조회에서는 어떤 시간대의 날짜를 선택했는지가 API 조건 자체를 바꿨다. UI와 서버가 각자 올바른 계산을 해도 경계를 합의하지 않으면 결과는 틀렸다.

이후 날짜 필터를 만들 때 날짜 문자열만 넘기지 않는다. 사용자 시간대, 포함·제외 규칙, 서버 저장 기준을 계약으로 적는다. 달력 UI 하나에도 데이터 경계가 숨어 있었다.