← 개발 로그 목록

website: 블로그·관리자 화면 6곳에 남아있던 KST 표시 버그 수정

/ 4분 분량 / 개발 로그

PR#429에서 메일과 감사로그의 KST 표시 버그를 고쳤는데, 같은 문제가 다른 화면들에도 남아있었다. 그걸 찾아서 고친 PR이다.

PR#429를 병합하고 나서 안심하고 있었는데, 백엔드가 타임존 표기 없이 내려주는 값을 new Date()로 그대로 파싱하는 곳이 여전히 남아있었다. 공용 헬퍼인 formatDate.ts가 블로그 공개일, 멤버 마이페이지 글 목록, 지원서 접수일, 블로그 관리, 댓글 모더레이션 이렇게 5곳에서 공유되고 있어서, 헬퍼 하나만 봐도 실제로는 5개 화면이 영향을 받고 있었다. 거기에 AdminAccountManagement.tsx의 초대 만료일, CommentSection.tsx의 공개 댓글까지 더하면 총 7개 화면이 같은 증상을 갖고 있었다.

원인은 PR#429 때와 동일했다. 백엔드 JVM 기본 타임존이 UTC로 설정돼 있어서, TZ 설정이 따로 없으면 타임존 표기 없는 문자열을 KST가 아니라 UTC 벽시계 값으로 내려준다. 그런데 프론트에서 new Date(iso)로 그 문자열을 그대로 파싱하면 브라우저는 이걸 로컬(KST) 시간으로 해석해버린다. 결과적으로 UTC 15:0023:59에 생성된 데이터, 즉 실제로는 KST 00:0008:59에 생성된 데이터가 하루 이른 날짜로 표시되는 증상이 생긴다.

BlogAnalyticsPanel.tsx는 좀 더 헷갈리는 케이스였다. 여기는 이미 timeZone: 'Asia/Seoul' 옵션을 포맷터에 넣어놓은 상태였다. 그런데도 버그가 있었던 이유는, new Date(value)가 파싱 단계에서 이미 값을 브라우저 로컬 시간으로 잘못 해석해버리면, 그 뒤에 붙는 timeZone 옵션은 그 오류를 되돌릴 수 없다는 점이었다. 표시 시점의 타임존 옵션과 파싱 시점의 타임존 가정은 별개 문제라는 걸 다시 확인한 셈이다.

고친 방식은 PR#429에서 썼던 것과 동일한 패턴으로 통일했다. 정규식으로 문자열 끝에 Z나 +09:00 같은 타임존 표기가 있는지 확인하고, 없으면 Z를 붙여서 명시적으로 UTC로 파싱한 다음, 표시할 때 timeZone: 'Asia/Seoul'을 지정하는 흐름이다.

const hasZone = /(?:Z|[+-]\d{2}:\d{2})$/.test(iso);
const d = new Date(hasZone ? iso : `${iso}Z`);

formatDate.ts, AdminAccountManagement.tsx, CommentSection.tsx, BlogAnalyticsPanel.tsx 네 파일에 각각 같은 로직을 넣었다. 코드가 중복되긴 하지만 이번엔 통합 헬퍼로 묶는 작업까지는 하지 않았다. 급한 버그부터 동일한 검증된 패턴으로 막아두는 게 우선이었다.

테스트는 경계값 하나로 확인했다. formatDate("2026-08-04T23:30:00")은 실제로 KST 8/5 08:30인데, 수정 전에는 "8월 4일"로 표시되던 게 수정 후 "8월 5일"로 바뀌는 걸 확인했다. dev 서버를 stage 백엔드에 붙여서 /blog와 /blog/{slug} 렌더링도 크래시나 콘솔 에러 없이 확인했다. 다만 stage 배포 후 7개 화면을 실사용자 시나리오로 재확인하는 건 PR 시점에는 체크박스를 비워두고 수동 루틴으로 남겨뒀다.

PR#429 때 한 곳을 고치면서 같은 원인이 다른 곳에도 있을 거라는 걸 예상은 했지만, 실제로 찾아보니 헬퍼 공유 구조 때문에 영향 범위가 생각보다 넓었다. 다음에 비슷한 파싱 로직을 추가할 일이 있으면, 애초에 공용 유틸로 하나만 만들어서 쓰는 게 이런 산발적인 수정을 줄이는 방법이 될 것 같다.