1월 1일 - 1월 5일과 같은 날짜 범위를 포맷하는 방법

JavaScript를 사용하여 지역에 적합한 형식과 중복 제거 기능을 갖춘 날짜 범위 표시하기

소개

날짜 범위는 웹 애플리케이션 전반에 걸쳐 나타납니다. 예약 시스템은 1월 1일부터 1월 5일까지의 가용성을 보여주고, 이벤트 캘린더는 여러 날에 걸친 컨퍼런스를 표시하며, 분석 대시보드는 특정 기간의 데이터를 보여주고, 보고서는 회계 분기 또는 사용자 지정 날짜 범위를 다룹니다. 이러한 범위는 무언가가 두 끝점 사이의 모든 날짜에 적용된다는 것을 전달합니다.

두 개의 형식화된 날짜를 대시로 연결하여 날짜 범위를 수동으로 형식화할 때, 불필요하게 장황한 출력이 생성됩니다. 2024년 1월 1일부터 2024년 1월 5일까지의 범위는 두 번째 날짜에 월과 연도를 반복할 필요가 없습니다. "2024년 1월 1일 - 5일"과 같은 출력은 중복되는 구성 요소를 생략하여 동일한 정보를 더 간결하게 전달합니다.

자바스크립트는 Intl.DateTimeFormatformatRange() 메서드를 제공하여 날짜 범위 형식화를 자동으로 처리합니다. 이 메서드는 범위의 각 부분에 포함할 날짜 구성 요소를 결정하고, 구분자와 형식화를 위한 로케일별 규칙을 적용하면서 불필요한 반복을 제거합니다.

날짜 범위가 지능적인 형식화를 필요로 하는 이유

서로 다른 날짜 범위는 서로 다른 수준의 세부 정보를 필요로 합니다. 같은 월 내의 범위는 여러 해에 걸친 범위보다 적은 정보가 필요합니다. 최적의 형식은 시작 날짜와 종료 날짜 사이에 어떤 날짜 구성 요소가 다른지에 따라 달라집니다.

같은 날 내의 범위의 경우, 시간 차이만 표시하면 됩니다: "오전 10:00 - 오후 2:00". 두 시간 모두에 전체 날짜를 반복하는 것은 정보를 추가하지 않습니다.

같은 월 내의 범위의 경우, 월을 한 번 표시하고 두 날짜 번호를 나열합니다: "2024년 1월 1-5일". "1월"을 두 번 포함하면 명확성을 더하지 않고 출력을 읽기 어렵게 만듭니다.

같은 해의 다른 월에 걸친 범위의 경우, 두 월을 모두 표시하지만 첫 번째 날짜에서 연도를 생략할 수 있습니다: "2024년 12월 25일 - 2025년 1월 2일"은 전체 정보가 필요하지만, "2024년 1월 15일 - 2월 20일"은 일부 로케일에서 첫 번째 날짜의 연도를 생략할 수 있습니다.

여러 해에 걸친 범위의 경우, 두 날짜 모두에 연도를 포함해야 합니다: "2023년 12월 1일 - 2024년 3월 15일".

이러한 규칙을 수동으로 구현하려면 어떤 날짜 구성 요소가 다른지 확인하고 그에 따라 형식 문자열을 구성해야 합니다. 또한 다른 로케일은 이러한 규칙을 다르게 적용하여 다른 구분자와 순서 규칙을 사용합니다. formatRange() 메서드는 이러한 로직을 캡슐화합니다.

formatRange를 사용하여 날짜 범위 포맷팅하기

'formatRange()' 메서드는 두 개의 Date 객체를 받아 포맷팅된 문자열을 반환합니다. 원하는 로케일과 옵션으로 'Intl.DateTimeFormat' 인스턴스를 생성한 다음, 시작 및 종료 날짜와 함께 'formatRange()'를 호출하세요.

const formatter = new Intl.DateTimeFormat("en-US");

const start = new Date(2024, 0, 1);
const end = new Date(2024, 0, 5);

console.log(formatter.formatRange(start, end));
// 출력: "1/1/24 – 1/5/24"

포맷터는 기본 미국 영어 날짜 형식을 두 날짜에 적용하고 en 대시로 연결합니다. 같은 달 내의 날짜에 대해서도 기본 형식이 매우 짧기 때문에 출력에는 여전히 두 개의 완전한 날짜가 표시됩니다.

일반 날짜 포맷팅에 사용할 수 있는 것과 동일한 옵션을 사용하여 포함할 날짜 구성 요소를 지정할 수 있습니다.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const start = new Date(2024, 0, 1);
const end = new Date(2024, 0, 5);

console.log(formatter.formatRange(start, end));
// 출력: "January 1 – 5, 2024"

이제 포맷터는 두 번째 날짜에서 월과 연도가 첫 번째 날짜와 일치하기 때문에 이를 지능적으로 생략합니다. 출력에는 종료 날짜의 일 번호만 표시되어 범위를 더 읽기 쉽게 만듭니다.

formatRange가 날짜 범위 출력을 최적화하는 방법

'formatRange()' 메서드는 두 날짜를 검사하고 어떤 구성 요소가 다른지 결정합니다. 범위의 각 부분에 필요한 구성 요소만 포함합니다.

같은 월과 연도의 날짜의 경우, 두 번째 부분에는 종료일만 나타납니다.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(formatter.formatRange(
  new Date(2024, 0, 1),
  new Date(2024, 0, 5)
));
// 출력: "January 1 – 5, 2024"

console.log(formatter.formatRange(
  new Date(2024, 0, 15),
  new Date(2024, 0, 20)
));
// 출력: "January 15 – 20, 2024"

두 범위 모두 월과 연도는 한 번만 표시되며, 일 번호만 범위 구분자로 연결됩니다.

같은 연도의 다른 월에 있는 날짜의 경우, 두 월 모두 나타나지만 연도는 한 번만 나타납니다.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(formatter.formatRange(
  new Date(2024, 0, 15),
  new Date(2024, 1, 20)
));
// 출력: "January 15 – February 20, 2024"

포맷터는 두 월 이름을 모두 포함하지만 연도는 끝에 배치하여 전체 범위에 적용합니다.

다른 연도에 걸쳐 있는 날짜의 경우, 두 완전한 날짜가 모두 나타납니다.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(formatter.formatRange(
  new Date(2023, 11, 25),
  new Date(2024, 0, 5)
));
// 출력: "December 25, 2023 – January 5, 2024"

연도가 다르기 때문에 각 날짜에는 전체 연도가 포함됩니다. 포맷터는 모호함을 만들지 않고는 어느 연도도 생략할 수 없습니다.

날짜 및 시간 범위 포맷팅

포맷에 시간 구성 요소가 포함되면 formatRange() 메서드는 시간 필드에도 동일한 지능적 생략을 적용합니다.

같은 날의 시간에 대해서는 출력에서 시간 구성 요소만 다릅니다.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric",
  hour: "numeric",
  minute: "numeric"
});

const start = new Date(2024, 0, 1, 10, 0);
const end = new Date(2024, 0, 1, 14, 30);

console.log(formatter.formatRange(start, end));
// 출력: "January 1, 2024, 10:00 AM – 2:30 PM"

날짜는 한 번만 표시되고 그 뒤에 시간 범위가 표시됩니다. 포맷터는 종료 시간에 전체 날짜와 시간을 반복하는 것이 유용한 정보를 추가하지 않는다는 것을 인식합니다.

다른 날의 시간에 대해서는 완전한 날짜와 시간 값이 모두 표시됩니다.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric",
  hour: "numeric",
  minute: "numeric"
});

const start = new Date(2024, 0, 1, 10, 0);
const end = new Date(2024, 0, 2, 14, 30);

console.log(formatter.formatRange(start, end));
// 출력: "January 1, 2024, 10:00 AM – January 2, 2024, 2:30 PM"

날짜와 시간이 모두 표시되는 이유는 다른 날을 나타내기 때문입니다. 포맷터는 어떤 구성 요소도 안전하게 생략할 수 없습니다.

다양한 로케일에서 날짜 범위 포맷팅

범위 포맷팅은 각 로케일의 날짜 구성 요소 순서, 구분자 및 생략할 구성 요소에 대한 규칙에 맞게 조정됩니다.

const enFormatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(enFormatter.formatRange(
  new Date(2024, 0, 1),
  new Date(2024, 0, 5)
));
// 출력: "January 1 – 5, 2024"

const deFormatter = new Intl.DateTimeFormat("de-DE", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(deFormatter.formatRange(
  new Date(2024, 0, 1),
  new Date(2024, 0, 5)
));
// 출력: "1.–5. Januar 2024"

const jaFormatter = new Intl.DateTimeFormat("ja-JP", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(jaFormatter.formatRange(
  new Date(2024, 0, 1),
  new Date(2024, 0, 5)
));
// 출력: "2024年1月1日~5日"

영어는 월을 먼저 표시하고 연도를 마지막에 표시합니다. 독일어는 일 숫자를 마침표와 함께 먼저 표시한 다음 월 이름, 그리고 연도를 표시합니다. 일본어는 연-월-일 순서와 물결 대시(~)를 범위 구분자로 사용합니다. 각 로케일은 어떤 구성 요소를 한 번 또는 두 번 표시할지에 대한 자체 규칙을 적용합니다.

이러한 차이점은 월을 넘어가는 범위에도 적용됩니다.

const enFormatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(enFormatter.formatRange(
  new Date(2024, 0, 15),
  new Date(2024, 1, 20)
));
// 출력: "January 15 – February 20, 2024"

const deFormatter = new Intl.DateTimeFormat("de-DE", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(deFormatter.formatRange(
  new Date(2024, 0, 15),
  new Date(2024, 1, 20)
));
// 출력: "15. Januar – 20. Februar 2024"

const frFormatter = new Intl.DateTimeFormat("fr-FR", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(frFormatter.formatRange(
  new Date(2024, 0, 15),
  new Date(2024, 1, 20)
));
// 출력: "15 janvier – 20 février 2024"

세 로케일 모두 두 월 이름을 표시하지만 일 숫자에 대한 상대적 위치는 다릅니다. 포맷터는 이러한 변형을 자동으로 처리합니다.

다양한 스타일로 날짜 범위 포맷팅하기

단일 날짜 포맷팅과 마찬가지로 dateStyle 옵션을 사용하여 전체 포맷 길이를 제어할 수 있습니다.

const shortFormatter = new Intl.DateTimeFormat("en-US", {
  dateStyle: "short"
});

console.log(shortFormatter.formatRange(
  new Date(2024, 0, 1),
  new Date(2024, 0, 5)
));
// Output: "1/1/24 – 1/5/24"

const mediumFormatter = new Intl.DateTimeFormat("en-US", {
  dateStyle: "medium"
});

console.log(mediumFormatter.formatRange(
  new Date(2024, 0, 1),
  new Date(2024, 0, 5)
));
// Output: "Jan 1 – 5, 2024"

const longFormatter = new Intl.DateTimeFormat("en-US", {
  dateStyle: "long"
});

console.log(longFormatter.formatRange(
  new Date(2024, 0, 1),
  new Date(2024, 0, 5)
));
// Output: "January 1 – 5, 2024"

const fullFormatter = new Intl.DateTimeFormat("en-US", {
  dateStyle: "full"
});

console.log(fullFormatter.formatRange(
  new Date(2024, 0, 1),
  new Date(2024, 0, 5)
));
// Output: "Monday, January 1 – Friday, January 5, 2024"

short 스타일은 숫자 형식의 날짜를 생성하며 이미 간결한 형식이므로 지능적인 생략을 적용하지 않습니다. mediumlong 스타일은 월을 약어로 표시하거나 전체 이름으로 표기하고 중복되는 구성 요소를 생략합니다. full 스타일은 양쪽 날짜에 요일 이름을 포함합니다.

커스텀 스타일링을 위한 formatRangeToParts 사용하기

'formatRangeToParts()' 메서드는 포맷된 범위의 구성 요소를 나타내는 객체 배열을 반환합니다. 이를 통해 범위 출력의 개별 부분을 스타일링하거나 조작할 수 있습니다.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const parts = formatter.formatRangeToParts(
  new Date(2024, 0, 1),
  new Date(2024, 0, 5)
);

console.log(parts);

출력은 type, value, source 속성을 가진 객체 배열입니다.

[
  { type: "month", value: "January", source: "startRange" },
  { type: "literal", value: " ", source: "startRange" },
  { type: "day", value: "1", source: "startRange" },
  { type: "literal", value: " – ", source: "shared" },
  { type: "day", value: "5", source: "endRange" },
  { type: "literal", value: ", ", source: "shared" },
  { type: "year", value: "2024", source: "shared" }
]

type 속성은 월, 일, 년 또는 리터럴 텍스트와 같은 구성 요소를 식별합니다. value 속성은 포맷된 텍스트를 포함합니다. source 속성은 해당 구성 요소가 시작 날짜, 종료 날짜에 속하는지 또는 둘 사이에 공유되는지를 나타냅니다.

이러한 부분을 사용하여 다양한 구성 요소에 대한 스타일링이 적용된 커스텀 HTML을 만들 수 있습니다.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const parts = formatter.formatRangeToParts(
  new Date(2024, 0, 1),
  new Date(2024, 0, 5)
);

let html = "";

parts.forEach(part => {
  if (part.type === "month") {
    html += `<span class="month">${part.value}</span>`;
  } else if (part.type === "day") {
    html += `<span class="day">${part.value}</span>`;
  } else if (part.type === "year") {
    html += `<span class="year">${part.value}</span>`;
  } else if (part.type === "literal" && part.source === "shared" && part.value.includes("–")) {
    html += `<span class="separator">${part.value}</span>`;
  } else {
    html += part.value;
  }
});

console.log(html);
// Output: <span class="month">January</span> <span class="day">1</span><span class="separator"> – </span><span class="day">5</span>, <span class="year">2024</span>

이 기술은 로케일별 포맷팅을 유지하면서 커스텀 시각적 스타일링을 가능하게 합니다.

날짜가 동일할 때 발생하는 상황

시작과 종료 매개변수에 동일한 날짜를 전달하면, formatRange()는 범위가 아닌 단일 형식의 날짜를 출력합니다.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2024, 0, 1);

console.log(formatter.formatRange(date, date));
// 출력: "January 1, 2024"

포맷터는 동일한 끝점을 가진 범위가 실제로는 범위가 아니라는 것을 인식하고 단일 날짜로 형식화합니다. 이 동작은 Date 객체가 동일한 값을 가진 서로 다른 인스턴스인 경우에도 적용됩니다.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const start = new Date(2024, 0, 1, 10, 0);
const end = new Date(2024, 0, 1, 10, 0);

console.log(formatter.formatRange(start, end));
// 출력: "January 1, 2024"

이들이 별도의 Date 객체이지만, 동일한 날짜와 시간을 나타냅니다. 포맷터는 형식 옵션의 정밀도 수준에서 범위의 지속 시간이 0이기 때문에 단일 날짜를 출력합니다. 형식에 시간 구성 요소가 포함되지 않으므로 시간은 무관하며 날짜는 동일한 것으로 간주됩니다.

formatRange와 수동 형식화를 사용해야 하는 경우

사용자에게 날짜 범위를 표시할 때 formatRange()를 사용하세요. 이는 예약 날짜 범위, 이벤트 기간, 보고서 기간, 가용성 기간 또는 기타 시간 범위에 적용됩니다. 이 메서드는 올바른 로케일별 형식 지정과 최적의 구성 요소 생략을 보장합니다.

관련 없는 여러 날짜를 표시해야 할 때는 formatRange()를 사용하지 마세요. "1월 1일, 1월 15일, 2월 1일"과 같은 마감일 목록은 이를 범위로 처리하기보다 각 날짜에 대해 일반 format() 호출을 사용해야 합니다.

또한 날짜 간의 비교나 차이를 표시할 때도 formatRange()를 사용하지 마세요. 한 날짜가 다른 날짜보다 얼마나 빠르거나 늦은지 표시하는 경우, 이는 범위가 아닌 상대적 시간 계산을 나타냅니다.