시간 단위 변환과 코드에서 시간을 다룰 때의 원칙
밀리초, 초, 분, 시간, 일 사이의 변환은 산수지만, 실무에서 이 계산이 틀리는 이유는 계산이 어려워서가 아니라 단위가 코드에 드러나 있지 않아서입니다. setTimeout은 밀리초를 받고 JWT의 exp는 초를 쓰며 캐시 TTL은 라이브러리마다 다릅니다. 이 도구는 변환을 대신해 주고, 아래 내용은 그 실수를 애초에 줄이는 방법입니다.
기본 환산표
자주 쓰는 값 몇 개는 외워 두면 코드를 읽을 때 도움이 됩니다. 86400은 하루의 초, 3600은 한 시간의 초, 604800은 일주일의 초입니다. 캐시 헤더나 토큰 만료 설정에서 이 숫자들을 자주 만나게 됩니다.
| 단위 | 밀리초 | 초 | 분 | 시간 |
|---|---|---|---|---|
| 1초 | 1,000 | 1 | 0.0167 | 0.000278 |
| 1분 | 60,000 | 60 | 1 | 0.0167 |
| 1시간 | 3,600,000 | 3,600 | 60 | 1 |
| 1일 | 86,400,000 | 86,400 | 1,440 | 24 |
| 1주 | 604,800,000 | 604,800 | 10,080 | 168 |
| 30일 | 2,592,000,000 | 2,592,000 | 43,200 | 720 |
| 1년(365일) | 31,536,000,000 | 31,536,000 | 525,600 | 8,760 |
사용 방법
- 변환할 값과 원래 단위를 입력합니다.
- 다른 단위로 환산된 결과를 확인합니다.
- 설정 파일에 넣을 값이라면 계산 결과를 그대로 쓰지 말고 아래의 가독성 항목을 참고하세요.
어느 API가 어떤 단위를 쓰는가
JavaScript가 밀리초를, Unix 시각 표준이 초를 쓰는 이 어긋남이 실무에서 가장 흔한 버그의 원천입니다. JWT의 exp에 Date.now()를 그대로 넣으면 값이 1000배가 되어 만료가 수만 년 뒤로 설정되고, 반대로 초 단위 타임스탬프를 new Date()에 넣으면 1970년 근처의 날짜가 나옵니다. 후자는 화면에 이상한 날짜가 보여 금방 발견되지만, 전자는 아무 증상 없이 통과해 보안 문제로 남습니다.
| 대상 | 단위 | 예 |
|---|---|---|
| JavaScript setTimeout / setInterval | 밀리초 | setTimeout(fn, 5000) → 5초 |
| Date.now() | 밀리초 | 1735689600000 |
| Unix timestamp (표준) | 초 | 1735689600 |
| JWT exp / iat / nbf | 초 | Math.floor(Date.now()/1000) |
| HTTP Cache-Control max-age | 초 | max-age=3600 → 1시간 |
| 쿠키 Max-Age | 초 | Max-Age=86400 → 1일 |
| Redis EXPIRE / PEXPIRE | 초 / 밀리초 | 명령이 다름 — 혼동 주의 |
| Python time.time() | 초 (소수점 포함) | 1735689600.123 |
| Go time.Duration | 나노초 기반 | 5 * time.Second |
| Java System.currentTimeMillis() | 밀리초 | 1735689600000 |
코드에서 단위를 드러내기
setTimeout(fn, 900000) 을 보고 15분이라는 것을 즉시 알아채는 사람은 없습니다. 계산해서 상수로 박아 넣는 대신 곱셈식을 그대로 남겨 두는 것이 훨씬 낫습니다. 컴파일러나 런타임이 상수 접기를 하므로 성능 차이는 없고, 읽는 사람이 의도를 바로 파악할 수 있습니다.
변수 이름에 단위를 넣는 것도 효과적입니다. timeout 이 아니라 timeoutMs, expiresIn 이 아니라 expiresInSeconds 로 쓰면 잘못된 값을 넘기는 실수가 코드 리뷰에서 잡힙니다. TypeScript라면 브랜드 타입으로 단위를 타입 수준에서 구분할 수도 있습니다.
// 나쁨: 이 숫자가 뭔지 알 수 없다 setTimeout(refresh, 900000); const TTL = 604800; // 좋음: 단위와 의도가 드러난다 const SECOND = 1000; const MINUTE = 60 * SECOND; const HOUR = 60 * MINUTE; const DAY = 24 * HOUR; setTimeout(refresh, 15 * MINUTE); const SESSION_TTL_SECONDS = 7 * 24 * 60 * 60; // 7일
하루가 항상 86,400초는 아니다
산술적으로 하루는 86,400초지만 달력상의 하루는 그렇지 않을 수 있습니다. 서머타임을 시행하는 지역에서는 시계를 앞으로 돌리는 날이 23시간, 뒤로 돌리는 날이 25시간입니다. 그래서 '내일 같은 시각'을 계산하려고 현재 시각에 86,400,000밀리초를 더하면 그 두 날에는 한 시간 어긋난 결과가 나옵니다.
정확하게 하려면 밀리초 산술이 아니라 달력 연산을 지원하는 API를 써야 합니다. JavaScript에서는 Temporal API가 이런 구분을 명시적으로 다루고, 그 전까지는 date-fns나 Luxon 같은 라이브러리가 타임존을 고려한 덧셈을 제공합니다. 윤초도 존재하지만 Unix 시각 자체가 윤초를 무시하도록 정의되어 있어 애플리케이션 코드에서 신경 쓸 일은 거의 없습니다.
또 '한 달'과 '1년'은 고정된 길이가 없습니다. 30일을 한 달로 계산하는 것은 근사치일 뿐이므로, 구독 갱신일처럼 정확해야 하는 계산에는 달력 기준 덧셈을 써야 합니다.
이런 곳에 씁니다
- 캐시 TTL을 사람이 이해하는 단위에서 초로 환산 (Cache-Control, Redis)
- 토큰 만료 시간 설정값 계산
- 성능 측정 결과를 밀리초에서 읽기 쉬운 단위로 변환
- 로그의 소요 시간 필드 해석
- 배치 작업 실행 주기 설계
- 타임아웃 값을 라이브러리가 기대하는 단위로 맞추기
자주 묻는 질문
- Unix 타임스탬프는 초인가요 밀리초인가요?
- 표준은 초입니다. 다만 JavaScript의 Date.now()와 Java의 currentTimeMillis()가 밀리초를 반환하므로 두 형태가 함께 쓰입니다. 자리 수로 구분할 수 있습니다. 현재 시각의 초 단위 값은 10자리, 밀리초 단위 값은 13자리입니다. 13자리 값을 초로 착각하면 서기 5만 년경의 날짜가 됩니다.
- 1년을 365일로 계산해도 되나요?
- 대략적인 환산에는 무해하지만 정확한 날짜 계산에는 쓰면 안 됩니다. 윤년은 4년마다 있고(100의 배수는 제외, 400의 배수는 포함) 평균은 365.2425일입니다. 구독 만료일이나 나이 계산처럼 정확성이 중요한 곳에서는 달력 연산 API를 쓰세요.
- 왜 setTimeout이 정확한 시간에 실행되지 않나요?
- setTimeout은 최소 지연을 보장할 뿐 정확한 실행 시점을 보장하지 않습니다. 지정한 시간이 지난 뒤 이벤트 루프가 한가해질 때 실행되므로, 메인 스레드가 바쁘면 늦어집니다. 또 브라우저는 중첩된 타이머에 최소 4ms를 강제하고, 비활성 탭에서는 타이머 실행 빈도를 크게 낮춥니다. 정확한 타이밍이 필요하면 requestAnimationFrame이나 Web Worker를 검토하세요.
- 밀리초보다 정밀한 시간이 필요하면?
- 브라우저에서는 performance.now()가 마이크로초 수준의 정밀도를 제공하며, Date.now()와 달리 시스템 시계 변경에 영향받지 않는 단조 증가 값입니다. 경과 시간 측정에는 항상 performance.now()가 적합합니다. 다만 스펙터 계열 공격 대응으로 브라우저가 해상도를 의도적으로 낮춰 둔 경우가 있습니다.
- 타임존 변환은 이 도구로 할 수 있나요?
- 이 도구는 시간 길이(duration) 변환을 다룹니다. 특정 시각을 다른 지역 시간으로 바꾸는 것은 Timezone Converter 도구를 쓰세요. 둘은 다른 문제입니다. 전자는 단순 곱셈이고, 후자는 오프셋과 서머타임 규칙이 관여합니다.
- '초'와 '초 단위 타임스탬프'를 혼동하지 않는 방법이 있나요?
- 이름을 다르게 쓰는 것이 가장 실용적입니다. 시간 길이는 durationSeconds나 ttlSeconds로, 특정 시점은 expiresAt이나 createdAt처럼 At 접미사를 붙이면 둘의 성격이 구분됩니다. 코드에서 '길이'와 '시점'을 더하거나 비교하는 실수도 이름만으로 눈에 띄게 됩니다.
💡 참고: 타임스탬프 값이 이상하면 자리 수부터 세어 보세요. 10자리면 초, 13자리면 밀리초입니다. 이 확인만으로 상당수 문제가 즉시 설명됩니다.