영상 재생 길이나 "예상 소요 시간" 같은 걸 화면에 뿌릴 때마다 비슷한 유틸 함수를 또 짜고 있었다. 초를 받아서 60으로 나누고, 나머지 구하고, 1시간 넘으면 "시간"도 붙이고... 매번 조금씩 다르게.
function formatDuration(totalSeconds: number) {
const h = Math.floor(totalSeconds / 3600);
const m = Math.floor((totalSeconds % 3600) / 60);
const s = totalSeconds % 60;
const parts = [];
if (h) parts.push(`${h}시간`);
if (m) parts.push(`${m}분`);
if (s) parts.push(`${s}초`);
return parts.join(' ');
}
동작은 한다. 근데 "시간/분/초" 단어가 코드에 박혀 있으니 다국어 대응이 안 되고, 0인 단위를 숨기는 규칙도 손으로 관리해야 한다. 이런 건 이미 브라우저가 해준다.
Intl.DurationFormat
Intl.DurationFormat은 { hours, minutes, seconds } 같은 객체를 받아서 로케일에 맞는 문자열로 만들어준다. 2025년에 Baseline에 들어왔고, 최신 크롬·사파리·파이어폭스에서 다 돈다.
const fmt = new Intl.DurationFormat('ko', { style: 'narrow' });
fmt.format({ hours: 1, minutes: 30 });
// "1시간 30분"
new Intl.DurationFormat('en', { style: 'narrow' }).format({ hours: 1, minutes: 30 });
// "1h 30m"
style은 long("1시간 30분 ..."은 "1 hour, 30 minutes"), short("1 hr, 30 min"), narrow("1h 30m") 중에 고르면 된다. 로케일 문자열만 바꾸면 단어가 알아서 따라오는 게 핵심이다.
초 하나만 들어올 때
보통 API나 <video>에서 넘어오는 건 초 하나다. 큰 단위로 쪼개는 건 여전히 직접 해야 하지만, 단어 붙이기와 로케일은 포매터에 넘기면 된다.
function format(totalSeconds: number, locale = 'ko') {
const duration = {
hours: Math.floor(totalSeconds / 3600),
minutes: Math.floor((totalSeconds % 3600) / 60),
seconds: totalSeconds % 60,
};
return new Intl.DurationFormat(locale, { style: 'narrow' }).format(duration);
}
format(5415); // "1시간 30분 15초"
0인 단위를 자동으로 빼고 싶으면 값이 있는 것만 객체에 담으면 된다. 단위별로 hoursDisplay: 'always' 같은 옵션으로 강제 표시도 조절할 수 있다.
마무리
재생 길이, 남은 시간, 소요 시간처럼 "기간"을 보여주는 자리라면 이제 유틸 함수를 새로 짤 필요가 없다. 나누는 로직만 남기고 포맷은 Intl에 맡기면, 다국어도 공짜로 따라온다. 상대 시간은 Intl.RelativeTimeFormat, 기간은 Intl.DurationFormat — 이렇게 역할을 나눠 쓰면 깔끔하다.