Intl.DurationFormat API

자동 현지화를 통해 JavaScript에서 시간 지속 기간 형식 지정

소개

무언가가 얼마나 오래 걸리는지 표시할 때, 사용자를 위해 지속 시간 값을 형식화해야 합니다. 비디오 플레이어는 재생 시간을 보여주고, 항공편 예약은 이동 시간을 보여주며, 피트니스 앱은 운동 지속 시간을 보여줍니다. 현지화 없이는 다음과 같은 코드를 작성할 수 있습니다:

const hours = 1;
const minutes = 46;
const seconds = 40;
const duration = `${hours}h ${minutes}m ${seconds}s`;

이는 언어에 관계없이 모든 사용자에게 "1h 46m 40s"를 생성합니다. 프랑스 사용자는 "1 h 46 min 40 s"를 기대할 때 "1h 46m 40s"를 보게 됩니다. 독일 사용자도 동일한 영어 약어를 보게 됩니다. 스페인어 사용자는 단위 사이에 "y" 접속사가 없습니다.

Intl.DurationFormat API는 이 문제를 해결합니다. 외부 라이브러리나 수동 문자열 구성 없이 사용자의 언어와 문화적 관습에 따라 시간 지속을 형식화합니다.

const duration = { hours: 1, minutes: 46, seconds: 40 };
new Intl.DurationFormat('fr-FR', { style: 'short' }).format(duration);
// "1 h, 46 min et 40 s"

포맷터는 모든 로케일에 대해 약어, 접속사, 단어 순서 및 간격을 자동으로 처리합니다.

지속 시간이란 무엇인가

지속 시간은 특정 시점이 아닌 시간의 범위를 나타냅니다. 날짜와 시간은 무언가가 발생하는 시점을 표시합니다. 지속 시간은 무언가가 얼마나 오래 걸리는지 측정합니다.

이 구분은 형식화에 중요합니다. 날짜는 달력, 시간대 및 역사적 규칙을 포함합니다. 지속 시간은 더 단순합니다: 달력 컨텍스트 없이 표준 단위로 경과 시간을 측정합니다.

Intl.DurationFormat API는 지속 시간을 처리합니다. Intl.DateTimeFormat API는 날짜와 시간을 처리합니다. 각 작업에 맞는 도구를 사용하세요.

지속 시간 포맷터 생성하기

생성자는 로케일과 옵션 객체를 받습니다. 로케일은 출력 언어를 결정합니다. 옵션은 형식 스타일과 단위 표시를 제어합니다.

const formatter = new Intl.DurationFormat('en', { style: 'long' });

지속 시간 객체와 함께 format()을 호출합니다. 객체는 시간 단위에 대한 숫자 속성을 포함합니다. 표시하려는 단위만 포함하세요.

const duration = { hours: 2, minutes: 30 };
formatter.format(duration);
// "2 hours and 30 minutes"

API는 다음 시간 단위를 지원합니다: years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds. 데이터에 맞는 단위를 사용하세요.

서식 스타일 선택하기

'style' 옵션은 출력 밀도를 제어합니다. 네 가지 스타일을 사용할 수 있습니다: 'long', 'short', 'narrow', 그리고 'digital'.

긴(long) 스타일은 전체 단어를 사용합니다. 이것은 산문과 접근성을 위해 사용하세요.

const duration = { hours: 1, minutes: 46, seconds: 40 };
new Intl.DurationFormat('en', { style: 'long' }).format(duration);
// "1 hour, 46 minutes and 40 seconds"

짧은(short) 스타일은 일반적인 약어를 사용합니다. 공간이 제한되어 있지만 가독성이 중요할 때 사용하세요.

new Intl.DurationFormat('en', { style: 'short' }).format(duration);
// "1 hr, 46 min and 40 sec"

좁은(narrow) 스타일은 최소한의 문자를 사용합니다. 모바일 인터페이스와 같은 컴팩트한 디스플레이에 사용하세요.

new Intl.DurationFormat('en', { style: 'narrow' }).format(duration);
// "1h 46m 40s"

디지털(digital) 스타일은 콜론이 있는 타이머와 같은 출력을 생성합니다. 미디어 플레이어와 카운트다운 디스플레이에 사용하세요.

new Intl.DurationFormat('en', { style: 'digital' }).format(duration);
// "1:46:40"

언어 간 지역화

동일한 기간이 각 언어마다 다르게 형식화됩니다. API는 모든 지역화를 자동으로 처리합니다.

const duration = { hours: 1, minutes: 46, seconds: 40 };

new Intl.DurationFormat('en', { style: 'long' }).format(duration);
// "1 hour, 46 minutes and 40 seconds"

new Intl.DurationFormat('fr', { style: 'long' }).format(duration);
// "1 heure, 46 minutes et 40 secondes"

new Intl.DurationFormat('de', { style: 'long' }).format(duration);
// "1 Stunde, 46 Minuten und 40 Sekunden"

new Intl.DurationFormat('es', { style: 'long' }).format(duration);
// "1 hora, 46 minutos y 40 segundos"

new Intl.DurationFormat('ja', { style: 'long' }).format(duration);
// "1時間46分40秒"

각 로케일이 다른 단어, 약어 및 접속사를 사용하는 것에 주목하세요. 프랑스어는 "et", 독일어는 "und", 스페인어는 "y", 일본어는 접속사를 사용하지 않습니다. API는 이러한 규칙을 알고 있습니다.

짧은(short)과 좁은(narrow) 스타일도 올바르게 지역화됩니다.

new Intl.DurationFormat('fr', { style: 'short' }).format(duration);
// "1 h, 46 min et 40 s"

new Intl.DurationFormat('de', { style: 'narrow' }).format(duration);
// "1 Std. 46 Min. 40 Sek."

시간 계산에서 기간 객체 생성하기

기간 객체는 시간 단위 속성을 가진 일반 JavaScript 객체입니다. 모든 시간 계산에서 이를 생성할 수 있습니다.

밀리초를 적절한 요소로 나누어 기간 단위로 변환합니다.

const milliseconds = 6400000; // 1시간, 46분, 40초
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: 'short' }).format(duration);
// "1 hr, 46 min and 40 sec"

두 날짜 사이의 기간을 계산하려면 타임스탬프를 빼면 됩니다.

const start = new Date('2025-01-01T10:00:00');
const end = new Date('2025-01-01T11:46:40');
const diffMs = end - start;

const hours = Math.floor(diffMs / 3600000);
const minutes = Math.floor((diffMs % 3600000) / 60000);
const seconds = Math.floor((diffMs % 60000) / 1000);

const duration = { hours, minutes, seconds };

필요한 단위만 포함하세요. 표시하고 싶지 않다면 0값은 생략하세요.

const duration = { minutes: 5, seconds: 30 };
new Intl.DurationFormat('en', { style: 'long' }).format(duration);
// "5 minutes and 30 seconds"

표시할 단위 제어하기

기본적으로 포맷터는 기간 객체에 제공한 모든 단위를 표시합니다. 옵션 객체를 사용하여 단위 표시를 제어할 수 있습니다.

각 단위별로 표시 스타일을 지정하세요. 옵션은 long, short, narrow, numeric, 2-digit입니다.

const duration = { hours: 1, minutes: 5, seconds: 3 };

new Intl.DurationFormat('en', {
  hours: 'long',
  minutes: 'numeric',
  seconds: '2-digit'
}).format(duration);
// "1 hour, 5:03"

numeric 스타일은 레이블 없이 숫자만 표시합니다. 2-digit 스타일은 0으로 패딩을 추가합니다. 텍스트와 숫자를 혼합한 간결한 표시에 이를 사용하세요.

기간 객체와 옵션 모두에서 단위를 생략하여 해당 단위를 숨길 수 있습니다.

const duration = { hours: 2, minutes: 30 };
new Intl.DurationFormat('en', { style: 'short' }).format(duration);
// "2 hr and 30 min"

디지털 스타일은 가장 큰 단위부터 가장 작은 단위까지 모든 단위가 존재하거나 명시적으로 구성되어야 합니다.

const duration = { minutes: 5, seconds: 30 };
new Intl.DurationFormat('en', { style: 'digital' }).format(duration);
// "5:30"

비디오 플레이어 재생 시간 형식 지정

비디오 플레이어는 컨트롤에 재생 시간을 표시합니다. 간결한 표시를 위해 좁은 형식 또는 디지털 스타일을 사용하세요.

function formatVideoDuration(seconds) {
  const hours = Math.floor(seconds / 3600);
  const minutes = Math.floor((seconds % 3600) / 60);
  const secs = Math.floor(seconds % 60);

  const duration = hours > 0
    ? { hours, minutes, seconds: secs }
    : { minutes, seconds: secs };

  const locale = navigator.language;
  return new Intl.DurationFormat(locale, { style: 'digital' }).format(duration);
}

formatVideoDuration(6400); // "1:46:40"
formatVideoDuration(330);  // "5:30"

이 방식은 시간을 조건부로 포함시켜 짧은 비디오와 긴 비디오 모두를 처리합니다.

항공 및 여행 시간 형식 지정

여행 예약 인터페이스는 여정 시간을 표시합니다. 제한된 공간에서 가독성을 위해 짧은 스타일을 사용하세요.

function formatFlightDuration(departureDate, arrivalDate, locale) {
  const diffMs = arrivalDate - departureDate;
  const hours = Math.floor(diffMs / 3600000);
  const minutes = Math.floor((diffMs % 3600000) / 60000);

  const duration = { hours, minutes };
  return new Intl.DurationFormat(locale, { style: 'short' }).format(duration);
}

const departure = new Date('2025-06-15T10:30:00');
const arrival = new Date('2025-06-15T18:45:00');

formatFlightDuration(departure, arrival, 'en');
// "8 hr and 15 min"

formatFlightDuration(departure, arrival, 'fr');
// "8 h et 15 min"

운동 및 활동 시간 형식 지정

피트니스 앱은 운동 시간을 추적합니다. 세션 요약에는 긴 스타일을, 목록 보기에는 좁은 스타일을 사용하세요.

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-06-15T07:00:00');
const workoutEnd = new Date('2025-06-15T08:15:00');

formatWorkoutDuration(workoutStart, workoutEnd, 'en');
// "1 hour and 15 minutes"

커스텀 표시를 위한 형식화된 부분 가져오기

formatToParts() 메서드는 형식화된 출력의 각 부분을 나타내는 객체 배열을 반환합니다. 이를 사용하여 개별 부분에 스타일을 적용하거나 커스텀 레이아웃을 구축할 수 있습니다.

const duration = { hours: 1, minutes: 46, seconds: 40 };
const parts = new Intl.DurationFormat('en', { style: 'long' }).formatToParts(duration);

각 부분에는 typevalue가 있습니다. 유형에는 hour, minute, second, literalhourUnit, minuteUnit과 같은 단위 레이블이 포함됩니다.

[
  { type: "integer", value: "1" },
  { type: "literal", value: " " },
  { type: "unit", value: "hour" },
  { type: "literal", value: ", " },
  { type: "integer", value: "46" },
  { type: "literal", value: " " },
  { type: "unit", value: "minutes" },
  { type: "literal", value: " and " },
  { type: "integer", value: "40" },
  { type: "literal", value: " " },
  { type: "unit", value: "seconds" }
]

부분을 매핑하여 커스텀 스타일을 적용합니다.

function StyledDuration({ duration }) {
  const parts = new Intl.DurationFormat('en', { style: 'long' }).formatToParts(duration);

  return parts.map((part, i) => {
    if (part.type === 'integer') {
      return <strong key={i}>{part.value}</strong>;
    }
    return <span key={i}>{part.value}</span>;
  });
}

성능을 위한 포매터 인스턴스 재사용

각 지속 시간마다 새 포매터를 생성하면 오버헤드가 발생합니다. 포매터를 한 번 생성하고 재사용하세요.

const formatter = new Intl.DurationFormat('en', { style: 'short' });

const durations = [
  { hours: 1, minutes: 30 },
  { hours: 2, minutes: 15 },
  { hours: 0, minutes: 45 }
];

durations.map(d => formatter.format(d));
// ["1 hr and 30 min", "2 hr and 15 min", "45 min"]

이 패턴은 루프나 렌더링에서 많은 지속 시간을 형식화할 때 성능을 향상시킵니다.

수동 문자열 구성에서 마이그레이션

수동 문자열 연결을 API로 대체하세요. 이를 통해 코드를 줄이고 현지화를 추가할 수 있습니다.

이전:

function formatDuration(hours, minutes) {
  if (hours > 0 && minutes > 0) {
    return `${hours}h ${minutes}m`;
  } else if (hours > 0) {
    return `${hours}h`;
  } else {
    return `${minutes}m`;
  }
}

이후:

function formatDuration(hours, minutes, locale) {
  const duration = {};
  if (hours > 0) duration.hours = hours;
  if (minutes > 0) duration.minutes = minutes;

  return new Intl.DurationFormat(locale, { style: 'narrow' }).format(duration);
}

API 버전은 모든 조건부 로직을 자동으로 처리하고 여러 언어를 지원합니다.

라이브러리에서 마이그레이션

Moment.js 및 date-fns와 같은 라이브러리는 기간 형식을 제공하지만 번들 크기가 증가합니다. 네이티브 API는 이러한 종속성을 제거합니다.

Moment.js 기간 형식 대체:

// 이전: Moment.js
const duration = moment.duration(6400, 'seconds');
const formatted = duration.hours() + 'h ' + duration.minutes() + 'm';

// 이후: Intl.DurationFormat
const duration = {
  hours: Math.floor(6400 / 3600),
  minutes: Math.floor((6400 % 3600) / 60)
};
new Intl.DurationFormat('en', { style: 'narrow' }).format(duration);

date-fns 기간 형식 대체:

// 이전: date-fns
import { formatDuration, intervalToDuration } from 'date-fns';
const duration = intervalToDuration({ start: 0, end: 6400000 });
const formatted = formatDuration(duration);

// 이후: Intl.DurationFormat
const ms = 6400000;
const duration = {
  hours: Math.floor(ms / 3600000),
  minutes: Math.floor((ms % 3600000) / 60000),
  seconds: Math.floor((ms % 60000) / 1000)
};
new Intl.DurationFormat('en', { style: 'long' }).format(duration);

네이티브 API는 종속성 없이 동일한 기능을 제공합니다.

브라우저 지원 및 폴리필

Intl.DurationFormat API는 2025년 3월에 기준선이 되었습니다. Chrome, Edge, Firefox 및 Safari의 최신 버전에서 작동합니다.

사용하기 전에 지원 여부를 확인하세요:

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`;
}

더 넓은 지원을 위해 폴리필을 사용하세요. FormatJS 프로젝트는 @formatjs/intl-durationformat을 제공합니다.

npm install @formatjs/intl-durationformat

필요한 경우 가져오기 및 폴리필:

if (!Intl.DurationFormat) {
  await import('@formatjs/intl-durationformat/polyfill');
}

폴리필은 gzip 압축 시 약 15KB를 추가합니다. 최신 브라우저의 오버헤드를 방지하기 위해 조건부로 로드하세요.

DurationFormat을 사용해야 하는 경우

Intl.DurationFormat은 경과 시간, 남은 시간 또는 지속 시간을 표시할 때 사용하세요. 여기에는 비디오 플레이어, 타이머, 카운트다운, 추적 앱, 여행 예약 및 세션 지속 시간 표시가 포함됩니다.

날짜, 시간 또는 타임스탬프에는 사용하지 마세요. 이러한 경우에는 Intl.DateTimeFormat을 사용하세요. "2시간 전"과 같은 상대적 시간 표현에도 사용하지 마세요. 이러한 경우에는 Intl.RelativeTimeFormat을 사용하세요.

이 API는 시간 계산이 아닌 지속 시간을 형식화합니다. 날짜, 타임스탬프 또는 다른 소스에서 지속 시간 값을 직접 계산해야 합니다. 포맷터는 표시만 처리합니다.