How do you display localized labels for date and time fields?

Use Intl.DisplayNames to get field labels and Intl.DateTimeFormat to get month and weekday names in any language.

Introduction

When you build date and time input forms, you need labels that describe each field. A date picker needs labels like "Month", "Year", and "Day". A time picker needs labels like "Hour" and "Minute". These labels must appear in the user's language.

Hardcoding these labels in English does not work for international applications. A French user expects to see "Mois" and "Année", while a Spanish user looks for "Mes" and "Año". You need a system that provides these labels automatically in any language.

JavaScript provides two complementary APIs for this. The Intl.DisplayNames API gives you field labels like "Month" and "Year". The Intl.DateTimeFormat API gives you the actual values for those fields, like month names and weekday names.

Understanding date and time field labels

Date and time interfaces require two types of labels. Field labels describe what type of data goes in each input. Field values are the actual data that appears in dropdowns and pickers.

Field labels include words like "Year", "Month", "Day", "Hour", "Minute", and "Second". These label the form fields themselves.

Field values include month names like "January" and "February", weekday names like "Monday" and "Tuesday", and period labels like "AM" and "PM". These populate dropdowns and selection lists.

A complete date form needs both types of labels.

<label>Month</label>
<select>
  <option>January</option>
  <option>February</option>
  <option>March</option>
  <!-- more months -->
</select>

<label>Year</label>
<input type="number" />

Both "Month" and "January" need localization, but they require different approaches.

Get field labels with Intl.DisplayNames

The Intl.DisplayNames constructor with type: "dateTimeField" returns localized labels for date and time components.

const labels = new Intl.DisplayNames('en-US', { type: 'dateTimeField' });

console.log(labels.of('year'));
// "year"

console.log(labels.of('month'));
// "month"

console.log(labels.of('day'));
// "day"

console.log(labels.of('hour'));
// "hour"

console.log(labels.of('minute'));
// "minute"

console.log(labels.of('second'));
// "second"

The of() method takes a field code and returns its localized label. The label matches the conventions of the specified locale.

You can get labels in any language by changing the locale.

// Spanish labels
const esLabels = new Intl.DisplayNames('es-ES', { type: 'dateTimeField' });
console.log(esLabels.of('year'));
// "año"
console.log(esLabels.of('month'));
// "mes"
console.log(esLabels.of('day'));
// "día"
console.log(esLabels.of('hour'));
// "hora"
console.log(esLabels.of('minute'));
// "minuto"

// French labels
const frLabels = new Intl.DisplayNames('fr-FR', { type: 'dateTimeField' });
console.log(frLabels.of('year'));
// "année"
console.log(frLabels.of('month'));
// "mois"
console.log(frLabels.of('day'));
// "jour"
console.log(frLabels.of('hour'));
// "heure"
console.log(frLabels.of('minute'));
// "minute"

// Japanese labels
const jaLabels = new Intl.DisplayNames('ja-JP', { type: 'dateTimeField' });
console.log(jaLabels.of('year'));
// "年"
console.log(jaLabels.of('month'));
// "月"
console.log(jaLabels.of('day'));
// "日"
console.log(jaLabels.of('hour'));
// "時"
console.log(jaLabels.of('minute'));
// "分"

Each locale provides labels in its own language and script.

Available date and time field codes

The Intl.DisplayNames API supports these field codes.

const labels = new Intl.DisplayNames('en-US', { type: 'dateTimeField' });

console.log(labels.of('era'));
// "era"

console.log(labels.of('year'));
// "year"

console.log(labels.of('quarter'));
// "quarter"

console.log(labels.of('month'));
// "month"

console.log(labels.of('weekOfYear'));
// "week"

console.log(labels.of('weekday'));
// "day of the week"

console.log(labels.of('day'));
// "day"

console.log(labels.of('dayPeriod'));
// "AM/PM"

console.log(labels.of('hour'));
// "hour"

console.log(labels.of('minute'));
// "minute"

console.log(labels.of('second'));
// "second"

console.log(labels.of('timeZoneName'));
// "time zone"

Use these codes to get labels for any date or time component your interface needs.

Get localized month names

The Intl.DateTimeFormat API provides month names for populating dropdowns and selection lists. Create a formatter with the month option set to "long", then format dates representing each month.

function getMonthNames(locale) {
  const formatter = new Intl.DateTimeFormat(locale, {
    month: 'long',
    timeZone: 'UTC'
  });

  const months = [];
  for (let month = 0; month < 12; month++) {
    const date = new Date(Date.UTC(2000, month, 1));
    months.push(formatter.format(date));
  }

  return months;
}

console.log(getMonthNames('en-US'));
// ["January", "February", "March", "April", "May", "June",
//  "July", "August", "September", "October", "November", "December"]

The function creates dates for each month of the year and formats them to extract the month name. Setting timeZone: 'UTC' ensures consistent results across time zones.

You can get month names in any language.

console.log(getMonthNames('es-ES'));
// ["enero", "febrero", "marzo", "abril", "mayo", "junio",
//  "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre"]

console.log(getMonthNames('fr-FR'));
// ["janvier", "février", "mars", "avril", "mai", "juin",
//  "juillet", "août", "septembre", "octobre", "novembre", "décembre"]

console.log(getMonthNames('ja-JP'));
// ["1月", "2月", "3月", "4月", "5月", "6月",
//  "7月", "8月", "9月", "10月", "11月", "12月"]

Each locale formats month names according to its own conventions.

Control month name length

The month option accepts different values that control the length of month names.

The "long" value returns full month names.

const longFormatter = new Intl.DateTimeFormat('en-US', {
  month: 'long',
  timeZone: 'UTC'
});

const date = new Date(Date.UTC(2000, 0, 1));
console.log(longFormatter.format(date));
// "January"

The "short" value returns abbreviated month names.

const shortFormatter = new Intl.DateTimeFormat('en-US', {
  month: 'short',
  timeZone: 'UTC'
});

console.log(shortFormatter.format(date));
// "Jan"

The "narrow" value returns the shortest possible month names, typically a single letter.

const narrowFormatter = new Intl.DateTimeFormat('en-US', {
  month: 'narrow',
  timeZone: 'UTC'
});

console.log(narrowFormatter.format(date));
// "J"

Use "narrow" carefully, as multiple months can share the same letter. In English, January, June, and July all produce "J".

Get localized weekday names

Use the same pattern to get weekday names. Set the weekday option to control the format.

function getWeekdayNames(locale, format = 'long') {
  const formatter = new Intl.DateTimeFormat(locale, {
    weekday: format,
    timeZone: 'UTC'
  });

  const weekdays = [];
  // Start from a Sunday (January 2, 2000 was a Sunday)
  for (let day = 0; day < 7; day++) {
    const date = new Date(Date.UTC(2000, 0, 2 + day));
    weekdays.push(formatter.format(date));
  }

  return weekdays;
}

console.log(getWeekdayNames('en-US'));
// ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]

console.log(getWeekdayNames('en-US', 'short'));
// ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]

console.log(getWeekdayNames('en-US', 'narrow'));
// ["S", "M", "T", "W", "T", "F", "S"]

You can get weekday names in any language.

console.log(getWeekdayNames('es-ES'));
// ["domingo", "lunes", "martes", "miércoles", "jueves", "viernes", "sábado"]

console.log(getWeekdayNames('fr-FR'));
// ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"]

console.log(getWeekdayNames('ja-JP'));
// ["日曜日", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日"]

Different locales may start the week on different days. This function always returns Sunday through Saturday in order.

Get localized period labels

Period labels are the AM and PM indicators used with 12-hour time. Use formatToParts() to extract these labels.

function getPeriodLabels(locale) {
  const formatter = new Intl.DateTimeFormat(locale, {
    hour: 'numeric',
    hour12: true,
    timeZone: 'UTC'
  });

  const amDate = new Date(Date.UTC(2000, 0, 1, 0, 0, 0));
  const pmDate = new Date(Date.UTC(2000, 0, 1, 12, 0, 0));

  const amParts = formatter.formatToParts(amDate);
  const pmParts = formatter.formatToParts(pmDate);

  const am = amParts.find(part => part.type === 'dayPeriod').value;
  const pm = pmParts.find(part => part.type === 'dayPeriod').value;

  return { am, pm };
}

console.log(getPeriodLabels('en-US'));
// { am: "AM", pm: "PM" }

console.log(getPeriodLabels('es-ES'));
// { am: "a. m.", pm: "p. m." }

console.log(getPeriodLabels('fr-FR'));
// { am: "AM", pm: "PM" }

console.log(getPeriodLabels('ja-JP'));
// { am: "午前", pm: "午後" }

The formatToParts() method returns an array of objects representing each part of the formatted time. The object with type: "dayPeriod" contains the AM or PM label.

Some locales use 24-hour time by default and do not include period labels. You force 12-hour time with the hour12: true option.

Build a complete localized date form

Combine all these techniques to create a fully localized date input form.

function createDateForm(locale) {
  const fieldLabels = new Intl.DisplayNames(locale, { type: 'dateTimeField' });
  const monthNames = getMonthNames(locale);
  const currentYear = new Date().getFullYear();

  return {
    monthLabel: fieldLabels.of('month'),
    months: monthNames.map((name, index) => ({
      value: index + 1,
      label: name
    })),
    dayLabel: fieldLabels.of('day'),
    yearLabel: fieldLabels.of('year'),
    yearPlaceholder: currentYear
  };
}

// Helper function from previous example
function getMonthNames(locale) {
  const formatter = new Intl.DateTimeFormat(locale, {
    month: 'long',
    timeZone: 'UTC'
  });

  const months = [];
  for (let month = 0; month < 12; month++) {
    const date = new Date(Date.UTC(2000, month, 1));
    months.push(formatter.format(date));
  }

  return months;
}

const enForm = createDateForm('en-US');
console.log(enForm.monthLabel);
// "month"
console.log(enForm.months[0]);
// { value: 1, label: "January" }
console.log(enForm.dayLabel);
// "day"
console.log(enForm.yearLabel);
// "year"

const esForm = createDateForm('es-ES');
console.log(esForm.monthLabel);
// "mes"
console.log(esForm.months[0]);
// { value: 1, label: "enero" }
console.log(esForm.dayLabel);
// "día"
console.log(esForm.yearLabel);
// "año"

This structure provides everything needed to render a localized date form in HTML.

function renderDateForm(locale) {
  const form = createDateForm(locale);

  return `
    <div class="date-form">
      <div class="form-field">
        <label>${form.monthLabel}</label>
        <select name="month">
          ${form.months.map(month =>
            `<option value="${month.value}">${month.label}</option>`
          ).join('')}
        </select>
      </div>

      <div class="form-field">
        <label>${form.dayLabel}</label>
        <input type="number" name="day" min="1" max="31" />
      </div>

      <div class="form-field">
        <label>${form.yearLabel}</label>
        <input type="number" name="year" placeholder="${form.yearPlaceholder}" />
      </div>
    </div>
  `;
}

console.log(renderDateForm('en-US'));
// Renders form with English labels and month names

console.log(renderDateForm('fr-FR'));
// Renders form with French labels and month names

The form automatically adapts to any locale you specify.

Build a localized time form

Apply the same approach to create a time input form with hour, minute, and period selectors.

function createTimeForm(locale) {
  const fieldLabels = new Intl.DisplayNames(locale, { type: 'dateTimeField' });
  const periods = getPeriodLabels(locale);

  const hours = [];
  for (let hour = 1; hour <= 12; hour++) {
    hours.push({ value: hour, label: hour.toString() });
  }

  const minutes = [];
  for (let minute = 0; minute < 60; minute += 5) {
    minutes.push({
      value: minute,
      label: minute.toString().padStart(2, '0')
    });
  }

  return {
    hourLabel: fieldLabels.of('hour'),
    hours: hours,
    minuteLabel: fieldLabels.of('minute'),
    minutes: minutes,
    periodLabel: fieldLabels.of('dayPeriod'),
    periods: [
      { value: 'am', label: periods.am },
      { value: 'pm', label: periods.pm }
    ]
  };
}

// Helper function from previous example
function getPeriodLabels(locale) {
  const formatter = new Intl.DateTimeFormat(locale, {
    hour: 'numeric',
    hour12: true,
    timeZone: 'UTC'
  });

  const amDate = new Date(Date.UTC(2000, 0, 1, 0, 0, 0));
  const pmDate = new Date(Date.UTC(2000, 0, 1, 12, 0, 0));

  const amParts = formatter.formatToParts(amDate);
  const pmParts = formatter.formatToParts(pmDate);

  const am = amParts.find(part => part.type === 'dayPeriod').value;
  const pm = pmParts.find(part => part.type === 'dayPeriod').value;

  return { am, pm };
}

const enTime = createTimeForm('en-US');
console.log(enTime.hourLabel);
// "hour"
console.log(enTime.minuteLabel);
// "minute"
console.log(enTime.periodLabel);
// "AM/PM"
console.log(enTime.periods);
// [{ value: "am", label: "AM" }, { value: "pm", label: "PM" }]

const jaTime = createTimeForm('ja-JP');
console.log(jaTime.hourLabel);
// "時"
console.log(jaTime.minuteLabel);
// "分"
console.log(jaTime.periodLabel);
// "午前/午後"
console.log(jaTime.periods);
// [{ value: "am", label: "午前" }, { value: "pm", label: "午後" }]

This provides all the data needed to render a localized time picker.

When to use date and time field labels

Date and time field labels appear in several types of interfaces.

Custom date and time pickers

Use localized labels when building date pickers, calendar widgets, or time selectors.

const locale = navigator.language;
const labels = new Intl.DisplayNames(locale, { type: 'dateTimeField' });

const datePicker = {
  yearLabel: labels.of('year'),
  monthLabel: labels.of('month'),
  dayLabel: labels.of('day')
};

Form field labels

Apply labels to standard form inputs for date and time data.

const labels = new Intl.DisplayNames('en-US', { type: 'dateTimeField' });

document.querySelector('#birthdate-month-label').textContent =
  labels.of('month');
document.querySelector('#birthdate-year-label').textContent =
  labels.of('year');

Accessibility labels

Provide localized ARIA labels for screen readers and assistive technology.

const locale = navigator.language;
const labels = new Intl.DisplayNames(locale, { type: 'dateTimeField' });

const input = document.querySelector('#date-input');
input.setAttribute('aria-label', labels.of('year'));

Data table headers

Label columns in tables that display date and time components.

const labels = new Intl.DisplayNames('en-US', { type: 'dateTimeField' });

const table = `
  <table>
    <thead>
      <tr>
        <th>${labels.of('year')}</th>
        <th>${labels.of('month')}</th>
        <th>${labels.of('day')}</th>
      </tr>
    </thead>
  </table>
`;

Browser support

The Intl.DisplayNames API with type: "dateTimeField" has been supported since March 2022 in major browsers.

Chrome and Edge support it from version 99. Firefox supports it from version 99. Safari supports it from version 15.4.

You can check if the feature is available before using it.

function supportsDateTimeFieldLabels() {
  try {
    const labels = new Intl.DisplayNames('en', { type: 'dateTimeField' });
    labels.of('year');
    return true;
  } catch (error) {
    return false;
  }
}

if (supportsDateTimeFieldLabels()) {
  const labels = new Intl.DisplayNames('en-US', { type: 'dateTimeField' });
  console.log(labels.of('month'));
} else {
  console.log('month'); // Fallback to English
}

For older browsers, you need to provide fallback labels. Create a simple mapping object for common fields.

const fallbackLabels = {
  en: {
    year: 'year',
    month: 'month',
    day: 'day',
    hour: 'hour',
    minute: 'minute',
    second: 'second'
  },
  es: {
    year: 'año',
    month: 'mes',
    day: 'día',
    hour: 'hora',
    minute: 'minuto',
    second: 'segundo'
  },
  fr: {
    year: 'année',
    month: 'mois',
    day: 'jour',
    hour: 'heure',
    minute: 'minute',
    second: 'seconde'
  }
};

function getFieldLabel(field, locale) {
  if (supportsDateTimeFieldLabels()) {
    const labels = new Intl.DisplayNames(locale, { type: 'dateTimeField' });
    return labels.of(field);
  }

  const language = locale.split('-')[0];
  return fallbackLabels[language]?.[field] || fallbackLabels.en[field];
}

console.log(getFieldLabel('month', 'es-ES'));
// "mes" (from API if supported, from fallback otherwise)

This ensures your forms work in all browsers while taking advantage of native localization when available.

The Intl.DateTimeFormat API for getting month and weekday names has broader support, dating back to Internet Explorer 11 and all modern browsers. You can use it without feature detection in most cases.