Formatear fechas con Intl

Internacionalización y conversión de fechas


Una de las partes más complejas de trabajar con fechas es formatear una fecha para mostrarla de forma adecuada (o de la forma que nos interesa). Una fecha tiene múltiples representaciones posibles: numéricas, alfabéticas, abreviaciones, con diferentes idiomas, estilo, orden, combinaciones entre sí, etc...

  • Numéricas: 25/1/2021, 25/01/2021, 1/25/2021, 01/25/2021, 2021/01/25, 25-1-2021...
  • Idiomáticas: 25 de Enero de 2021, 25 de Ene. de 2021, 25 January, 2021, 25 Jan 2021...
  • Orden/combinaciones: Jan/25/2021, 25/Febrero/2021, 25-Febrero-2021...

Es muy habitual, que queramos formatear esa fecha con la representación de la región a la que pertenecemos (o a la que pertenece el usuario), por lo que la cosa se puede complicar bastante.

Por suerte, el objeto Intl posee DateTimeFormat, un sistema que unido a lo que ya sabemos sobre fechas del objeto Date nativo, pueden hacernos la vida más fácil para dar formato a fechas:

Objeto Descripción
Intl.DateTimeFormat Crea un objeto de formato con las preferencias de tu región (o la región indicada).

Dicho objeto, tiene una serie de métodos interesantes, como por ejemplo .format(), en el cuál nos centraremos para saber como formatear una fecha. Los métodos de los que dispone son los siguientes:

Método Descripción
.format(date) Formatea la fecha date con la configuración de región iniciada.
.formatToParts(date) Idem, dividiendo sus partes en un array de objetos.
.formatRange(a, b) Crea un rango con las fechas a-b usando la configuración de región.
.formatRangeToParts(a, b) Idem, pero divide sus partes en un array de objetos.
.resolvedOptions() Devuelve las opciones de región definidas en la instancia.

Observa que tanto .format() como .formatRange() tienen una versión *ToParts() que hace exactamente lo mismo, sólo que en lugar de devolver un , devuelven un de con cada parte diferenciada.

Crear una fecha

Cuando trabajamos con el objeto Date nativo de Javascript, tenemos opciones (aunque limitadas) para personalizar las fechas con las que trabajamos. Métodos como .toString(), .toDateString(), .toGMTString() o .toISOString() se pueden utilizar para personalizar el modo de representación de una fecha. Incluso podemos utilizar .toLocaleDateString() para formatearla dependiendo de la configuración regional del sistema del usuario:

// 30 de Enero de 2021
const date = new Date(2021, 0, 30);

date.toString();           // "Sat Jan 30 2021 00:00:00 GMT+0000 (hora estándar...)"
date.toDateString();       // "Sat Jan 30 2021"
date.toGMTString();        // "Sat, 30 Jan 2021 00:00:00 GMT"
date.toISOString();        // "2021-01-30T00:00:00.000Z"
date.toLocaleDateString(); // "30/1/2021"

En caso de querer un tipo de representación diferente a las anteriores, tendríamos que optar por usar librerías externas o por crear una función personalizada que devuelva el tipo de representación buscada utilizando getters nativos, lo que puede llegar a ser una tarea tediosa.

Formatear una fecha

En su lugar, podemos utilizar el objeto Intl, creando una nueva instancia de DateTimeFormat(). Se trata de un objeto que nos permitirá formatear fechas, indicando la configuración regional a seguir, e indepedientemente de la que tenga el usuario en su sistema. Observa el siguiente ejemplo, donde se muestra la fecha del ejemplo anterior, formateada en localización de España (es), Estados Unidos (en-US), Alemania (de), Azerbaiyán (az) o Mauritania (mr):

const esDate = new Intl.DateTimeFormat("es").format(date);
"30/1/2021"

const enDate = new Intl.DateTimeFormat("en-US").format(date);
"1/30/2021"

const deDate = new Intl.DateTimeFormat("de").format(date);
"30.1.2021"

const azDate = new Intl.DateTimeFormat("az").format(date);
"2021-1-30"

const mrDate = new Intl.DateTimeFormat("mr").format(date);
"३०/१/२०२१"

Si al instanciar new Intl.DateTimeFormat() no indicamos ningún parámetro, se indicará por defecto el código del país del sistema, por lo que si tenemos un navegador con el sistema en Español, sería como si se hiciera un new Intl.DateTimeFormat("es-ES").

Realmente, new Intl.DateTimeFormat(country, options) tiene dos parámetros opcionales:

Opciones Descripción
country Código del país que usaremos para formatear el fecha. Por ejemplo, es-ES.
options Objeto de opciones para personalizar la fecha.

Podemos observar los parámetros por defecto si ejecutamos el método .resolvedOptions(), que en mi caso me muestra la siguiente salida (pueden variar dependiendo de tu región):

new Intl.DateTimeFormat().resolvedOptions()
{
  calendar: "gregory",        // Calendario gregoriano
  locale: "es-ES",            // Español de España
  numberingSystem: "latn",    // Latín
  day: "numeric",             // Formato numérico: 1, 2, 3...
  month: "numeric",           // Idem
  year: "numeric",            // Idem
  timeZone: "Atlantic/Canary" // Zona horaria
}

Ten en cuenta que el campo indicado en locale es el primer parámetro, el código de país para indicar la localización. El resto de parámetros son los que se pasan en el options de Intl.DateTimeFormat() y que nosotros podemos personalizar a nuestro gusto, dependiendo de la salida que busquemos. Por ejemplo:

const dtf = new Intl.DateTimeFormat("es-ES", {
  calendar: "gregory",
  numberingSystem: "latn",
  day: "numeric",
  month: "numeric",
  year: "numeric",
  timeZone: "Atlantic/Canary"
});
const date = dtf.format(new Date());

Este objeto de opciones puede ser muy variado y con diferentes parámetros. Vamos a unirlos en diferentes grupos para ver como funcionan.

Estilos predefinidos de fecha

En primer lugar tenemos un primer gran bloque de opciones, donde tenemos los parámetros dateStyle y timeStyle. Se trata de un sistema rápido para indicar perfiles genéricos con un estilo concreto. No se pueden utilizar junto a parámetros que veremos más tarde como weekday, day, month o similares:

Opción Descripción
dateStyle Establece un perfil de estilo de fecha: full, long, medium o short.
timeStyle Establece un perfil de estilo de hora: full, long, medium o short.

Observa que en cada ejemplo vamos cambiando los valores de dateStyle y timeStyle, y como consecuencia se muestra de diferente forma. Estos parámetros pueden mezclarse, no necesariamente tienen que ser los mismos siempre. Uno afecta a la fecha y otro a la hora:

new Intl.DateTimeFormat("es-ES", {
  dateStyle: "full",
  timeStyle: "full"
}).format(new Date());
"viernes, 26 de febrero de 2021, 10:46:26 (hora estándar de Europa occidental)"

new Intl.DateTimeFormat("es-ES", {
  dateStyle: "long",
  timeStyle: "long"
}).format(new Date());
"26 de febrero de 2021, 10:47:10 WET"

new Intl.DateTimeFormat("es-ES", {
  dateStyle: "medium",
  timeStyle: "medium"
}).format(new Date());
"26 feb 2021 10:47:29"

new Intl.DateTimeFormat("es-ES", {
  dateStyle: "short",
  timeStyle: "short"
}).format(new Date());
"26/2/21 10:48"

Opciones de configuración

En dicho objeto de opciones también es posible añadir ciertas personalizaciones, como por ejemplo, el tipo de calendario (el calendario gregoriano es el que solemos utilizar), el sistema de numeración (latín, por defecto), la zona horaria con timeZone o los algoritmos de formateo o localización a utilizar:

Opción Descripción
calendar Calendario: buddhist, chinese, gregory (el nuestro), hebrew, japanese, etc...
numberingSystem Sistema de numeración: arab, fullwide, bali, latn (por defecto), entre otros.
formatMatcher Indica el algoritmo de formateo a utilizar: basic o best fit (por defecto).
localeMatcher Indica el algoritmo de localización a utilizar: lookup o best fit (por defecto).
timeZone Zona horaria a usar: UTC, Europe/London, Atlantic/Canary, Europe/Madrid...

Hay que tener en cuenta que estas opciones tendrán un valor por defecto dependiendo del sistema donde estemos trabajando.

Formato personalizado

A diferencia de los estilos predefinidos que podemos seleccionar rápidamente con las opciones dateStyle y timeStyle, tenemos un segundo modo de personalización. Antes de nada, tener en cuenta que debemos seleccionar uno de los dos, es decir, si utilizamos dateStyle o timeStyle, no podremos utilizar ninguna de las siguientes opciones.

Las opciones son las siguientes:

Opción Descripción
weekday Día de la semana: long, short o narrow.
era Era actual: long, short o narrow.
year Año: numeric o 2-digit.
month Mes: numeric, 2-digit, long, short o narrow.
day Día: numeric o 2-digit.
dayPeriod Periodo del día: narrow, short o long. Solo en inglés de momento.
hour Hora: numeric o 2-digit.
hour12 Activa el formato de 12 horas (01:00 p.m.) o lo desactiva (13:00).
hourCycle Formato de 12 horas (h11 o h12) o de 24 horas (h23 o h24).
minute Minutos: numeric o 2-digit.
second Segundos: numeric o 2-digit.
fractionalSecondDigits Dígitos de las fracciones de segundos: 1, 2 o 3.
timeZoneName Nombre de la zona horaria: long o short.

El funcionamiento de estas opciones es muy sencillo. Simplemente se trata de añadir la característica que queremos mostrar en la representación de la hora, con el valor que más nos interese. Si no queremos mostrar alguno, simplemente lo omitimos:

// Localización: España
// Día de la semana, día numérico e inicial del mes
new Intl.DateTimeFormat("es", {
  weekday: "long",
  day: "2-digit",
  month: "narrow"
}).format(new Date());
"viernes, 26 F"

// Localización : Inglesa
// Día en 2 dígitos, periodo del día, mes, hora y minutos
new Intl.DateTimeFormat("en", {
  day: "2-digit",
  dayPeriod: "long",
  month: "short",
  hour: "2-digit",
  minute: "2-digit"
}).format(new Date());
"Feb 26, 03:07 at night"

Es importante destacar que el orden de los parámetros en el de opciones no importa, puesto que se colocarán en el lugar apropiado en la representación final generada. También es importante observar que cada parámetro tiene un valor que hace que la fecha se representa de una forma particular. Dependiendo de los demás valores presentes, la representación puede variar ligeramente (por ejemplo, la opción era en formato largo necesita mostrar año y otros datos aunque no se indiquen).

A continuación puedes ver un gráfico donde se indican los diferentes valores de estas opciones, para una fecha concreta:

Intl.DateTimeFormat

Formatear partes de una fecha

Hemos visto como trabajar con el método .format(date), sin embargo, existe una variante de este método, denominada .formatToParts(date). Funciona exactamente igual, sólo que la primera devuelve un mientras que esta última devuelve un de .

Por ejemplo, el último fragmento de código con este método devolvería lo siguiente:

new Intl.DateTimeFormat("en", {
  day: "2-digit",
  dayPeriod: "long",
  month: "short",
  hour: "2-digit",
  minute: "2-digit"
}).formatToParts(new Date());

// Devuelve un array de objetos
[
  { type: "month", value: "Feb" },
  { type: "literal", value: " " }
  { type: "day", value: "26" }
  { type: "literal", value: ", " }
  { type: "hour", value: "03" }
  { type: "literal", value: ":" }
  { type: "minute", value: "56" }
  { type: "literal", value: " " }
  { type: "dayPeriod", value: "in the afternoon" }
]

Observa que este método nos devuelve varios objetos, con las claves type y value. Podemos utilizar la clave type para acceder al valor concreto que nos interesa. Además, los separadores de texto, se identifican como tipo literal.

Formatear rangos de fechas

Al igual que tenemos el método .format(date), también disponemos de un método llamado .formatRange(date1, date2). Este método nos acepta dos fechas por parámetro para crear una fecha donde se representa un rango fecha1 - fecha2. La razón de esto es respetar la configuración regional con la que nos encontramos trabajando para crear dicho rango.

const date1 = new Date(2021, 0, 1);
const date2 = new Date(2021, 0, 20);
const range = new Intl.DateTimeFormat("es-ES", {
  dateStyle: "medium"
}).formatRange(date1, date2);

// Devuelve un string
"1–20 ene 2020"

Al tratarse del mismo mes, observa que no se repite de nuevo las partes que ya se definen en la segunda fecha.

De la misma manera que la anterior, también tiene su versión .formatRangeToParts(date1, date2), que en lugar de devolvernos un , nos devuelve un de con las partes de las fechas divididas e identificadas:

const date1 = new Date(2021, 0, 1);
const date2 = new Date(2021, 0, 20);
const range = new Intl.DateTimeFormat("es-ES", {
  dateStyle: "medium"
}).formatRangeToParts(date1, date2);

// Devuelve un array de objetos:
[
  { type: "day", value: "1", source: "startRange" },
  { type: "literal", value: "-", source: "shared" },
  { type: "day", value: "20", source: "endRange" },
  { type: "literal", value: " ", source: "shared" },
  { type: "month", value: "ene", source: "shared" },
  { type: "literal", value: " ", source: "shared" },
  { type: "year", value: "2021", source: "shared" },
]

Observa que en la clave source de cada se nos indica startRange si pertenece a la fecha de inicio, endRange si pertenece a la fecha final y shared si pertenece a ambas fechas.

Soporte de Intl

Aunque en principio el soporte de internacionalización en navegadores es bastante bueno actualmente, puedes ver más detalladamente el soporte por características más especificas en la siguiente búsqueda de Intl en CanIUse:

¿Quién soy yo?

Soy Manz, vivo en Tenerife (España) y soy streamer partner en Twitch y profesor. Me apasiona el universo de la programación web, el diseño y desarrollo web y la tecnología en general. Aunque soy full-stack, mi pasión es el front-end, la terminal y crear cosas divertidas y locas.

Puedes encontrar más sobre mi en Manz.dev