2시간 30분처럼 시간 간격을 포매팅하는 방법
자동 로컬라이제이션으로 사용자의 언어로 기간 표시하기
소개
어떤 일이 얼마나 오래 걸리는지 보여줄 때, 사용자가 이해할 수 있는 방식으로 기간을 표시해야 합니다. 예를 들어, 동영상은 2시간 30분 동안 재생 시간을 보여주고, 운동 앱은 운동 시간을 추적하며, 프로젝트 관리 도구는 작업 완료 시간을 표시합니다. 로컬라이제이션이 없으면 아래와 같이 코드를 작성할 수 있습니다:
const hours = 2;
const minutes = 30;
const timeSpan = `${hours}h ${minutes}m`;
이렇게 하면 모든 사용자에게 "2h 30m"이 표시됩니다. 프랑스어 사용자는 "2 h 30 min"을 기대하는데도 "2h 30m"을 보며, 독일어 사용자는 "2 Std. 30 Min" 대신 영어 약어가 나오고, 스페인어 사용자는 단위 사이에 "y" 접속사가 보이지 않습니다.
JavaScript는 Intl.DurationFormat API를 제공해, 사용자의 언어와 문화적 기준에 맞게 시간 간격을 포매팅할 수 있습니다. 이 레슨에서는 기간 포매터를 만드는 방법, 기간 객체를 생성하는 방법, 그리고 모든 로케일에서 올바르게 시간 간격을 표시하는 방법을 설명합니다.
시간 간격이란
시간 간격은 특정 시점이 아니라 일정한 시간의 길이를 나타냅니다. 예를 들어, 150분은 하나의 기간이며, 2025년 3월 15일 오후 2시 30분은 특정 날짜와 시간입니다.
이 차이가 중요한 이유는 날짜에는 달력, 시간대, 과거 규칙 등이 포함되기 때문입니다. 시간 간격은 달력의 맥락과는 무관하게 경과한 시간을 측정합니다. 기간에는 특정 순간이 없기 때문에 타임존을 추가할 수 없습니다.
시간 간격에는 Intl.DurationFormat를 사용하세요. 날짜와 시간에는 Intl.DateTimeFormat를, "2시간 전"처럼 상대적 표현에는 Intl.RelativeTimeFormat를 사용하세요.
기간 포매터 생성하기
Intl.DurationFormat 생성자는 로케일과 옵션 객체를 인자로 받습니다. 로케일은 출력 언어를 결정하며, 옵션은 포매팅 스타일과 단위 표시 방식을 제어합니다.
const formatter = new Intl.DurationFormat('en', { style: 'long' });
format()에 duration 객체를 전달하면 포맷된 문자열을 반환합니다. duration 객체에는 시간 단위별로 숫자 속성이 들어 있습니다.
const duration = { hours: 2, minutes: 30 };
formatter.format(duration);
// "2 hours and 30 minutes"
이 API는 로케일에 따라 약어, 접속사, 단어 순서, 띄어쓰기를 자동으로 처리합니다.
duration 객체 생성하기
duration 객체는 시간 단위별 속성을 가진 일반 JavaScript 객체입니다. 표시하려는 단위만 추가하면 됩니다.
const duration1 = { hours: 2, minutes: 30 };
const duration2 = { minutes: 5, seconds: 45 };
const duration3 = { hours: 1, minutes: 15, seconds: 30 };
API는 다음 시간 단위를 지원합니다: years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds.
모든 단위를 포함할 필요는 없습니다. 표시하지 않을 단위는 생략하면 됩니다.
const formatter = new Intl.DurationFormat('en', { style: 'long' });
formatter.format({ hours: 2, minutes: 30 });
// "2 hours and 30 minutes"
formatter.format({ minutes: 30 });
// "30 minutes"
formatter.format({ hours: 2 });
// "2 hours"
포맷 스타일 선택하기
style 옵션은 출력의 밀도를 제어합니다. 사용할 수 있는 스타일은 네 가지입니다: long, short, narrow, digital.
Long 스타일은 전체 단어를 사용합니다. 문장이나 주요 내용 영역에서 사용하세요.
const duration = { hours: 2, minutes: 30 };
new Intl.DurationFormat('en', { style: 'long' }).format(duration);
// "2 hours and 30 minutes"
Short 스타일은 주로 약어를 사용합니다. 공간은 부족하지만 가독성이 필요한 상황에 적합합니다.
new Intl.DurationFormat('en', { style: 'short' }).format(duration);
// "2 hr and 30 min"
Narrow 스타일은 최소한의 문자만 사용합니다. 모바일 인터페이스나 데이터 테이블처럼 공간이 제한된 곳에 적합합니다.
new Intl.DurationFormat('en', { style: 'narrow' }).format(duration);
// "2h 30m"
Digital 스타일은 콜론으로 구분된 타이머 형태로 출력합니다. 미디어 플레이어나 카운트다운 디스플레이에 적합합니다.
new Intl.DurationFormat('en', { style: 'digital' }).format(duration);
// "2:30:00"
Digital 스타일을 사용할 때는 가장 큰 단위부터 가장 작은 단위까지 모두 포함해야 합니다. 시간과 분을 포맷할 경우 초도 반드시 포함해야 합니다.
다양한 언어로 시간 간격 포맷하기
같은 시간 간격이라도 각 언어마다 포맷 방식이 다릅니다. 이 API가 모든 로케일의 로컬라이징을 자동으로 처리합니다.
const duration = { hours: 2, minutes: 30 };
new Intl.DurationFormat('en', { style: 'long' }).format(duration);
// "2 hours and 30 minutes"
new Intl.DurationFormat('fr', { style: 'long' }).format(duration);
// "2 heures et 30 minutes"
new Intl.DurationFormat('de', { style: 'long' }).format(duration);
// "2 Stunden und 30 Minuten"
new Intl.DurationFormat('es', { style: 'long' }).format(duration);
// "2 horas y 30 minutos"
new Intl.DurationFormat('ja', { style: 'long' }).format(duration);
// "2時間30分"
각 로케일마다 단어와 접속사가 다르게 사용되는 것을 확인할 수 있습니다. 프랑스어는 "et"를, 독일어는 "und"를, 스페인어는 "y"를, 일본어는 접속사를 사용하지 않습니다. API는 이런 규칙을 모두 알고 있습니다.
짧고 간단한 스타일도 올바르게 현지화됩니다.
new Intl.DurationFormat('fr', { style: 'short' }).format(duration);
// "2 h et 30 min"
new Intl.DurationFormat('de', { style: 'narrow' }).format(duration);
// "2 Std. 30 Min."
사용자의 로케일에 맞춰 시간 범위 포맷하기
로케일을 하드코딩하지 말고, 브라우저에서 사용자의 선호 언어를 사용하세요. navigator.language 속성은 사용자의 주요 언어 선호도를 반환합니다.
const userLocale = navigator.language;
const formatter = new Intl.DurationFormat(userLocale, { style: 'short' });
const duration = { hours: 2, minutes: 30 };
formatter.format(duration);
// Output varies by user's locale
// For en-US: "2 hr and 30 min"
// For de-DE: "2 Std. und 30 Min."
// For fr-FR: "2 h et 30 min"
이렇게 하면 각 사용자의 기대에 맞는 시간 단위로 표시되어 로케일을 직접 선택하지 않아도 됩니다.
밀리초를 기간 객체로 변환하기
시간 계산 결과로 밀리초가 자주 생성됩니다. 밀리초를 적절한 값으로 나누어 기간 객체로 변환하세요.
const milliseconds = 9000000; // 2 hours 30 minutes
const hours = Math.floor(milliseconds / 3600000);
const minutes = Math.floor((milliseconds % 3600000) / 60000);
const seconds = Math.floor((milliseconds % 60000) / 1000);
const duration = { hours, minutes, seconds };
new Intl.DurationFormat('en', { style: 'long' }).format(duration);
// "2 hours, 30 minutes and 0 seconds"
특별히 보여주고 싶지 않다면 0인 값은 생략하세요.
const duration = {};
if (hours > 0) duration.hours = hours;
if (minutes > 0) duration.minutes = minutes;
if (seconds > 0) duration.seconds = seconds;
new Intl.DurationFormat('en', { style: 'long' }).format(duration);
// "2 hours and 30 minutes"
두 날짜로부터 시간 차이 계산하기
두 날짜의 타임스탬프를 빼서 기간을 계산한 후, 그 결과를 기간 객체로 변환하세요.
const startTime = new Date('2025-10-15T10:00:00');
const endTime = new Date('2025-10-15T12:30:00');
const diffMs = endTime - startTime;
const hours = Math.floor(diffMs / 3600000);
const minutes = Math.floor((diffMs % 3600000) / 60000);
const duration = { hours, minutes };
new Intl.DurationFormat('en', { style: 'short' }).format(duration);
// "2 hr and 30 min"
이 방법은 밀리초 단위로 계산되는 모든 시간 계산에 적용할 수 있습니다.
비디오 플레이어 재생시간 포맷하기
비디오 플레이어는 컨트롤에서 길이를 표시합니다. 컴팩트하게 보이도록 디지털 또는 좁은 스타일을 사용하세요.
function formatVideoDuration(totalSeconds) {
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = Math.floor(totalSeconds % 60);
const duration = hours > 0
? { hours, minutes, seconds }
: { minutes, seconds };
const locale = navigator.language;
return new Intl.DurationFormat(locale, { style: 'digital' }).format(duration);
}
formatVideoDuration(9000); // "2:30:00"
formatVideoDuration(330); // "5:30"
이 방식은 필요할 때만 시간을 포함해서, 짧은 영상에는 "5:30"을, 긴 영상에는 "2:30:00"을 표시합니다.
운동 시간 포맷하기
피트니스 앱은 운동 시간을 기록합니다. 세션 요약에는 긴 스타일을, 간결한 목록에는 좁은 스타일을 사용하세요.
function formatWorkoutDuration(startTime, endTime, locale) {
const diffMs = endTime - startTime;
const hours = Math.floor(diffMs / 3600000);
const minutes = Math.floor((diffMs % 3600000) / 60000);
const duration = hours > 0
? { hours, minutes }
: { minutes };
return new Intl.DurationFormat(locale, { style: 'long' }).format(duration);
}
const workoutStart = new Date('2025-10-15T07:00:00');
const workoutEnd = new Date('2025-10-15T09:30:00');
formatWorkoutDuration(workoutStart, workoutEnd, 'en');
// "2 hours and 30 minutes"
formatWorkoutDuration(workoutStart, workoutEnd, 'es');
// "2 horas y 30 minutos"
프로젝트 작업 시간 포맷하기
프로젝트 관리 도구는 작업 소요 시간을 보여줍니다. 대시보드 화면에는 짧은 스타일을 사용하세요.
function formatTaskDuration(minutes, locale) {
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
const duration = {};
if (hours > 0) duration.hours = hours;
if (mins > 0) duration.minutes = mins;
return new Intl.DurationFormat(locale, { style: 'short' }).format(duration);
}
formatTaskDuration(150, 'en');
// "2 hr and 30 min"
formatTaskDuration(45, 'en');
// "45 min"
formatTaskDuration(150, 'de');
// "2 Std. und 30 Min."
다양한 시간 단위 포맷팅
시간 간격은 시와 분에만 국한되지 않습니다. 지원되는 모든 단위를 조합하여 포맷할 수 있습니다.
const formatter = new Intl.DurationFormat('en', { style: 'long' });
formatter.format({ days: 3, hours: 2 });
// "3 days and 2 hours"
formatter.format({ minutes: 45, seconds: 30 });
// "45 minutes and 30 seconds"
formatter.format({ hours: 1, minutes: 30, seconds: 45 });
// "1 hour, 30 minutes and 45 seconds"
API는 어떤 단위 조합이든 적절한 접속사와 구분자를 자동으로 처리합니다.
초 단위만으로 시간 간격 포맷팅
기간이 1분 미만일 때는 초 단위만 포함하세요.
const formatter = new Intl.DurationFormat('en', { style: 'short' });
formatter.format({ seconds: 45 });
// "45 sec"
formatter.format({ seconds: 5 });
// "5 sec"
아주 짧은 시간은 밀리초까지 포함할 수 있습니다.
formatter.format({ seconds: 5, milliseconds: 500 });
// "5 sec and 500 ms"
성능을 위해 포매터 인스턴스 재사용하기
새 포매터를 생성하면 로케일 데이터가 로드되고 옵션이 처리됩니다. 같은 로케일과 스타일로 여러 시간 간격을 포맷할 경우, 포매터를 한 번만 생성해 반복해서 사용하는 것이 좋습니다.
const formatter = new Intl.DurationFormat('en', { style: 'short' });
const durations = [
{ hours: 1, minutes: 30 },
{ hours: 2, minutes: 15 },
{ minutes: 45 }
];
durations.map(d => formatter.format(d));
// ["1 hr and 30 min", "2 hr and 15 min", "45 min"]
이 패턴은 반복문이나 여러 번 렌더링할 때 많은 기간을 포맷할 경우 성능을 크게 개선합니다.
브라우저 지원
Intl.DurationFormat API는 2025년 3월부터 Baseline에 포함되었습니다. Chrome, Edge, Firefox, Safari 최신 버전에서 동작하며, 이전 브라우저에서는 지원되지 않습니다.
API를 사용하기 전에 지원 여부를 확인하세요.
if (typeof Intl.DurationFormat !== 'undefined') {
const formatter = new Intl.DurationFormat('en', { style: 'short' });
return formatter.format(duration);
} else {
return `${duration.hours}h ${duration.minutes}m`;
}
이를 통해 구형 브라우저에 대한 대체 기능을 제공하고, 사용 가능한 경우에는 네이티브 API를 사용할 수 있습니다.