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). |
Veamos que podemos hacer con ella.
Métodos de formato
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.
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.
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"
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, vamos a echar un vistazo.
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
A continuación puedes ver un gráfico donde se indican los diferentes valores de estas opciones, para una fecha concreta:
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
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
.
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
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 startRange
si pertenece a la fecha de inicio, endRange
si pertenece a la fecha final y shared
si pertenece a ambas fechas.