1월 1일 - 1월 5일과 같은 날짜 범위 형식을 지정하는 방법

JavaScript를 사용하여 로케일에 적합한 형식과 지능적인 중복 제거로 날짜 범위를 표시합니다

소개

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

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

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

날짜 범위에 지능적인 형식화가 필요한 이유

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

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

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

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

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

이러한 규칙을 수동으로 구현하려면 어떤 날짜 구성 요소가 다른지 확인하고 그에 따라 형식 문자열을 구성해야 합니다. 또한 로케일마다 이러한 규칙을 다르게 적용하며, 서로 다른 구분 기호와 순서 규칙을 사용합니다. 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));
// Output: "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));
// Output: "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)
));
// Output: "January 1 – 5, 2024"

console.log(formatter.formatRange(
  new Date(2024, 0, 15),
  new Date(2024, 0, 20)
));
// Output: "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)
));
// Output: "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)
));
// Output: "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));
// Output: "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));
// Output: "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)
));
// Output: "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)
));
// Output: "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)
));
// Output: "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)
));
// Output: "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)
));
// Output: "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)
));
// Output: "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));
// Output: "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));
// Output: "January 1, 2024"

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

formatRange와 수동 형식 지정 중 언제 사용해야 하는가

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

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

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