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.

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

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('en', { style: 'short' }).format(duration);
// "1 hr, 46 min and 40 sec"

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

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

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

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는 이러한 규칙을 알고 있습니다.

짧은 스타일과 좁은 스타일도 올바르게 현지화됩니다.

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 hour, 46 minutes, 40 seconds
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"

표시할 단위 제어

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

각 단위에 대한 표시 스타일을 개별적으로 지정합니다. 옵션은 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 패딩을 추가합니다. 텍스트와 숫자를 혼합하는 간결한 표시에 사용하세요.

duration 객체와 options 모두에서 단위를 생략하여 숨길 수 있습니다.

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

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

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

비디오 플레이어 재생 시간 포맷

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

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"

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

항공편 및 여행 시간 포맷

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

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"

운동 및 활동 시간 포맷

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

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 기간 포맷 교체:

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

// After: 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 기간 포맷 교체:

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

// After: 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월에 Baseline이 되었습니다. Chrome, Edge, Firefox, Safari의 최신 버전에서 작동합니다.

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

if (typeof Intl.DurationFormat !== 'undefined') {
  const formatter = new Intl.DurationFormat('en', { style: 'short' });
  return formatter.format(duration);
} else {
  // Fallback for older browsers
  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는 기간을 형식화하며 시간 계산을 수행하지 않습니다. 날짜, 타임스탬프 또는 기타 소스에서 기간 값을 직접 계산해야 합니다. 포매터는 표시만 처리합니다.