lifecarelog
프론트엔드

경기 일정이 하루 밀렸다? date-only 문자열과 new Date()의 UTC 함정

달력엔 14일인데 화면엔 13일로 뜨는 버그, 원인은 new Date()의 UTC 파싱 규칙이에요. date-only 문자열을 다루는 프론트엔드 해법을 정리했어요.

4분 읽기

경기 일정이 하루 밀렸다? date-only 문자열과 new Date()의 UTC 함정

플랜비(plan-b)의 경기 일정 화면을 만들다가 이상한 걸 발견했어요. 분명 5월 14일로 등록한 경기가 목록에는 5월 13일로 떠 있었거든요. 혹시 이런 하루 밀림 버그, 겪어본 적 없나요?

범인은 new Date()의 파싱 규칙이에요

콘솔에 직접 찍어 보니 원인이 보였어요. new Date('2026-05-14')처럼 'YYYY-MM-DD' 10글자짜리 date-only 문자열을 넘기면, 자바스크립트는 이걸 UTC 자정으로 해석해요. 한국은 UTC보다 9시간 빠르니 실제로는 오전 9시가 되죠.

여기까지는 표시에 문제가 없어 보여요. 진짜 함정은 반대 방향이에요. 로컬 자정으로 만든 Date를 toISOString()으로 직렬화하면 9시간이 빠지면서 전날 오후 3시가 돼요. 여기서 날짜 부분만 잘라 쓰면 하루가 밀려요.

로컬 5월 14일 00:00toISOString()5월 13일 15:00Z날짜만 추출화면엔 13일

프론트엔드에서 자주 밟는 자리

테스트해 보면 밀림이 생기는 지점은 거의 정해져 있어요.

  • <input type="date">의 value는 원래 문자열인데, 서버로 보내려고 Date로 감쌌다가 다시 ISO 문자열로 바꾸는 순간
  • 캘린더 컴포넌트에서 셀마다 Date 객체를 만들어 날짜 숫자를 비교하는 순간
  • SSR 서버와 브라우저의 타임존이 달라 하이드레이션 결과가 어긋나는 순간

시간 정보가 없는 값에 시간을 입히는 순간 타임존이 따라붙는 구조예요.

제가 적용한 해법: 문자열로 끝까지 간다

플랜비 일정 화면에 적용해 봤더니 규칙 하나로 정리됐어요. 경기 날짜처럼 시간이 필요 없는 값은 'YYYY-MM-DD' 문자열 그대로 저장하고, 비교하고, 렌더링하는 거예요. 이 형식은 사전순 정렬이 곧 시간순 정렬이라 문자열 비교만으로 충분해요.

요일 계산처럼 Date 객체가 꼭 필요할 때만 이렇게 로컬 기준으로 만들어요.

const [y, m, d] = '2026-05-14'.split('-').map(Number);
const date = new Date(y, m - 1, d); // 월은 0부터 시작해요

이렇게 고쳐서 돌려 보니 14일 경기는 어느 기기에서도 14일에 떠요.

  • date-only는 문자열 유지
  • 직렬화에 toISOString() 금지
  • Date 필요 시 로컬 생성자 사용

마무리

날짜 버그는 코드가 틀려서가 아니라, 날짜와 시각이라는 다른 두 타입을 하나로 뭉쳐서 생기는 경우가 많아요. 시간이 없는 날짜는 문자열이 가장 안전한 타입이에요. 여러분 프로젝트의 일정 화면에서도 직렬화 경로 한 번만 점검해 보세요.

#JavaScript#Date#타임존#프론트엔드

라이프케어로그 서비스가 궁금하신가요?

AI 기반 건강·일정·재활 관리 앱을 직접 써보세요.

서비스 살펴보기

관련 글

댓글

아직 댓글이 없어요. 첫 댓글을 남겨주세요.