La guía definitiva para la manipulación de fecha y hora

Publicado: 2022-03-11

Como desarrollador de software, no puede huir de la manipulación de fechas. Casi todas las aplicaciones que crea un desarrollador tendrán algún componente en el que la fecha y la hora deben obtenerse del usuario, almacenarse en una base de datos y mostrarse al usuario.

Pregúntele a cualquier programador sobre su experiencia en el manejo de fechas y zonas horarias y probablemente compartirá algunas historias de guerra. El manejo de los campos de fecha y hora ciertamente no es ciencia espacial, pero a menudo puede ser tedioso y propenso a errores.

Hay cientos de artículos sobre el tema, sin embargo, la mayoría son demasiado académicos y se centran en detalles esenciales, o son demasiado irregulares y proporcionan fragmentos cortos de código sin mucha explicación que los acompañe. Esta guía detallada para la manipulación de DateTime debería ayudarlo a comprender los conceptos de programación y las mejores prácticas relevantes para la hora y la fecha sin tener que navegar a través de un mar de información sobre este tema.

En este artículo, lo ayudaré a pensar claramente sobre los campos de fecha y hora y sugeriré algunas mejores prácticas que pueden ayudarlo a evitar el infierno de fecha/hora. Aquí exploraremos algunos de los conceptos clave que son necesarios para manipular valores de fecha y hora correctamente, formatos que son convenientes para almacenar valores de fecha y hora y transferirlos a través de API, y más.

Para empezar, la respuesta correcta para el código de producción casi siempre es usar una biblioteca adecuada en lugar de crear una propia. Las posibles dificultades con el cálculo de fecha y hora que se analizan en este artículo son solo la punta del iceberg, pero sigue siendo útil conocerlas, con o sin biblioteca.

Las bibliotecas DateTime ayudan si las entiende correctamente

Las bibliotecas de fechas ayudan de muchas maneras a hacer su vida más fácil. Simplifican en gran medida el análisis de fechas, las operaciones aritméticas y lógicas de fechas y el formateo de fechas. Puede encontrar una biblioteca de datos confiable tanto para el front-end como para el back-end para hacer la mayor parte del trabajo pesado por usted.

Sin embargo, a menudo usamos bibliotecas de fechas sin pensar en cómo funciona realmente la fecha/hora. Fecha/hora es un concepto complejo. Los errores que surgen debido a su comprensión incorrecta pueden ser extremadamente difíciles de entender y corregir, incluso con la ayuda de bibliotecas de datos. Como programador, debe comprender los conceptos básicos y ser capaz de apreciar los problemas que resuelven las bibliotecas de datos para aprovecharlos al máximo.

Además, las bibliotecas de fecha/hora solo pueden llevarlo hasta cierto punto. Todas las bibliotecas de fechas funcionan dándole acceso a estructuras de datos convenientes para representar un DateTime. Si está enviando y recibiendo datos a través de una API REST, eventualmente necesitará convertir la fecha en una cadena y viceversa porque JSON no tiene una estructura de datos nativa para representar DateTime. Los conceptos que describí aquí lo ayudarán a evitar algunos de los problemas comunes que pueden surgir al realizar estas transformaciones de fecha a cadena y de cadena a fecha.

Nota: aunque he usado JavaScript como el lenguaje de programación que se analiza en este artículo, estos son conceptos generales que se aplican, en gran medida, a prácticamente todos los lenguajes de programación y sus bibliotecas de datos. Entonces, incluso si nunca antes ha escrito una línea de JavaScript, siéntase libre de continuar leyendo, ya que no asumo ningún conocimiento previo de JavaScript en el artículo.

Estandarizando el tiempo

Un DateTime es un punto muy específico en el tiempo. Pensemos en esto. Mientras escribo este artículo, el reloj de mi computadora portátil marca el 21 de julio a la 1:29 p. m. Esto es lo que llamamos “hora local”, la hora que veo en los relojes de pared a mi alrededor y en mi reloj de pulsera.

Más o menos unos minutos, si le pido a mi amiga que se reúna conmigo en un café cercano a las 3:00 p. m., puedo esperar verla allí aproximadamente a esa hora. Del mismo modo, no habría ninguna confusión si en su lugar dijera, por ejemplo, “nos vemos en una hora y media”. Habitualmente hablamos sobre el tiempo de esta manera con personas que viven en la misma ciudad o zona horaria.

Pensemos en un escenario diferente: quiero decirle a un amigo que vive en Uppsala, Suecia, que quiero hablar con él a las 5 p. m. Le envío un mensaje: "Oye, Anton, hablemos a las 5 p. m.". Inmediatamente recibo la respuesta: "¿Tu tiempo o mi tiempo?"

Anton me dice que vive en la zona horaria de Europa Central, que es UTC+01:00. Vivo en UTC+05:45. Esto significa que cuando son las 5 p. m. donde vivo, son las 5 p. m. - 05:45 = 11:15 a. m. UTC, lo que se traduce como 11:15 a. m. UTC + 01:00 = 12:15 p. de nosotros.

Además, tenga en cuenta la diferencia entre la zona horaria (Hora de Europa Central) y la diferencia de zona horaria (UTC+05:45). Los países también pueden decidir cambiar sus compensaciones de zona horaria para el horario de verano por razones políticas. Casi todos los años hay un cambio en las reglas en al menos un país, lo que significa que cualquier código con estas reglas integradas debe mantenerse actualizado; vale la pena considerar de qué depende su base de código para esto para cada nivel de su aplicación.

Esa es otra buena razón por la que recomendaremos que solo la interfaz se ocupe de las zonas horarias en la mayoría de los casos. Cuando no es así, ¿qué sucede cuando las reglas que usa su motor de base de datos no coinciden con las de su front-end o back-end?

Este problema de manejar dos versiones diferentes de la hora, relativa al usuario y relativa a un estándar universalmente aceptado, es difícil, más aún en el mundo de la programación donde la precisión es clave e incluso un segundo puede marcar una gran diferencia. El primer paso para resolver estos problemas es almacenar DateTime en UTC.

Estandarización del formato

Estandarizar la hora es maravilloso porque solo necesito almacenar la hora UTC y siempre que sepa la zona horaria del usuario, siempre puedo convertirla a su hora. Por el contrario, si conozco la hora local de un usuario y su zona horaria, puedo convertirla a UTC.

Pero las fechas y horas se pueden especificar en muchos formatos diferentes. Para la fecha, puede escribir "30 de julio" o "30 de julio" o "30/7" (o 30/7, según el lugar donde viva). Para la hora, podrías escribir “9:30 PM” o “2130”.

Científicos de todo el mundo se unieron para abordar este problema y decidieron un formato para describir el tiempo que a los programadores les gusta mucho porque es corto y preciso. Nos gusta llamarlo "formato de fecha ISO", que es una versión simplificada del formato extendido ISO-8601 y se ve así:

Una imagen que muestra una versión simplificada del formato extendido ISO-8601 llamado formato de fecha ISO.

Para las 00:00 o UTC, usamos "Z", que significa hora zulú, otro nombre para UTC.

Manipulación de fechas y aritmética en JavaScript

Antes de comenzar con las mejores prácticas, aprenderemos sobre la manipulación de fechas usando JavaScript para comprender la sintaxis y los conceptos generales. Aunque usamos JavaScript, puede adaptar fácilmente esta información a su lenguaje de programación favorito.

Usaremos la aritmética de fechas para resolver problemas comunes relacionados con fechas con los que se encuentran la mayoría de los desarrolladores.

Mi objetivo es que se sienta cómodo creando un objeto de fecha a partir de una cadena y extrayendo componentes de uno. Esto es algo con lo que una biblioteca de fechas puede ayudarlo, pero siempre es mejor comprender cómo se hace detrás de escena.

Una vez que nos hemos ensuciado las manos con la fecha/hora, es más fácil pensar en los problemas que enfrentamos, extraer las mejores prácticas y seguir adelante. Si desea saltar a las mejores prácticas, siéntase libre de hacerlo, pero le recomiendo que al menos eche un vistazo a la sección de aritmética de fechas a continuación.

El objeto de fecha de JavaScript

Los lenguajes de programación contienen construcciones útiles para hacernos la vida más fácil. El objeto Date de JavaScript es una de esas cosas. Ofrece métodos convenientes para obtener la fecha y la hora actuales, almacenar una fecha en una variable, realizar aritmética de fechas y formatear la fecha en función de la configuración regional del usuario.

Debido a las diferencias entre las implementaciones de los navegadores y el manejo incorrecto del horario de verano (DST), no se recomienda depender del objeto Date para aplicaciones de misión crítica y probablemente debería usar una biblioteca DateTime como Luxon, date-fns o dayjs. (Independientemente de lo que use, evite el popular Moment.js, a menudo llamado simplemente moment , tal como aparece en el código, ya que ahora está obsoleto).

Pero con fines educativos, usaremos los métodos que proporciona el objeto Date() para aprender cómo JavaScript maneja DateTime.

Obtener la fecha actual

 const currentDate = new Date();

Si no pasa nada al constructor Fecha, el objeto de fecha devuelto contiene la fecha y la hora actuales.

Luego puede formatearlo para extraer solo la parte de la fecha de la siguiente manera:

 const currentDate = new Date(); const currentDayOfMonth = currentDate.getDate(); const currentMonth = currentDate.getMonth(); // Be careful! January is 0, not 1 const currentYear = currentDate.getFullYear(); const dateString = currentDayOfMonth + "-" + (currentMonth + 1) + "-" + currentYear; // "27-11-2020"

Nota: La trampa de "Enero es 0" es común pero no universal. Vale la pena verificar dos veces la documentación de cualquier idioma (o formato de configuración: por ejemplo, cron se basa principalmente en 1) antes de comenzar a usarlo.

Obtener la marca de tiempo actual

Si, en cambio, desea obtener la marca de tiempo actual, puede crear un nuevo objeto Date y usar el método getTime().

 const currentDate = new Date(); const timestamp = currentDate.getTime();

En JavaScript, una marca de tiempo es la cantidad de milisegundos que han pasado desde el 1 de enero de 1970.

Si no tiene la intención de admitir <IE8, puede usar Date.now() para obtener directamente la marca de tiempo sin tener que crear un nuevo objeto Date.

Analizando una fecha

La conversión de una cadena en un objeto de fecha de JavaScript se realiza de diferentes maneras.

El constructor del objeto Date acepta una amplia variedad de formatos de fecha:

 const date1 = new Date("Wed, 27 July 2016 13:30:00"); const date2 = new Date("Wed, 27 July 2016 07:45:00 UTC"); const date3 = new Date("27 July 2016 13:30:00 UTC+05:45");

Tenga en cuenta que no necesita incluir el día de la semana porque JS puede determinar el día de la semana para cualquier fecha.

También puede pasar el año, mes, día, horas, minutos y segundos como argumentos separados:

 const date = new Date(2016, 6, 27, 13, 30, 0);

Por supuesto, siempre puedes usar el formato de fecha ISO:

 const date = new Date("2016-07-27T07:45:00Z");

Sin embargo, puede tener problemas si no proporciona la zona horaria explícitamente.

 const date1 = new Date("25 July 2016"); const date2 = new Date("July 25, 2016");

Cualquiera de estos le dará el 25 de julio de 2016 00:00:00 hora local.

Si usa el formato ISO, incluso si proporciona solo la fecha y no la hora y la zona horaria, aceptará automáticamente la zona horaria como UTC.

Esto significa que:

 new Date("25 July 2016").getTime() !== new Date("2016-07-25").getTime() new Date("2016-07-25").getTime() === new Date("2016-07-25T00:00:00Z").getTime()

Dar formato a una fecha

Afortunadamente, el JavaScript moderno tiene algunas funciones de internacionalización convenientes integradas en el espacio de nombres Intl estándar que hacen que el formateo de fechas sea una operación sencilla.

Para esto necesitaremos dos objetos: una Date y un Intl.DateTimeFormat , inicializados con nuestras preferencias de salida. Suponiendo que nos gustaría usar el formato americano (M/D/YYYY), esto se vería así:

 const firstValentineOfTheDecade = new Date(2020, 1, 14); // 1 for February const enUSFormatter = new Intl.DateTimeFormat('en-US'); console.log(enUSFormatter.format(firstValentineOfTheDecade)); // 2/14/2020

Si, en cambio, quisiéramos el formato holandés (D/M/YYYY), simplemente pasaríamos un código cultural diferente al constructor DateTimeFormat :

 const nlBEFormatter = new Intl.DateTimeFormat('nl-BE'); console.log(nlBEFormatter.format(firstValentineOfTheDecade)); // 14/2/2020

O una forma más larga del formato americano, con el nombre del mes deletreado:

 const longEnUSFormatter = new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric', }); console.log(longEnUSFormatter.format(firstValentineOfTheDecade)); // February 14, 2020

Ahora, si quisiéramos un formato ordinal adecuado para el día del mes, es decir, "14" en lugar de solo "14", desafortunadamente esto necesita una pequeña solución, porque los únicos valores válidos del day a partir de este escrito son "numeric" o "2-digit" . Tomando prestada la versión de Flavio Copes del código de Mathias Bynens para aprovechar otra parte de Intl para esto, podemos personalizar la salida del día del mes a través de formatToParts() :

 const pluralRules = new Intl.PluralRules('en-US', { type: 'ordinal' }) const suffixes = { 'one': 'st', 'two': 'nd', 'few': 'rd', 'other': 'th' } const convertToOrdinal = (number) => `${number}${suffixes[pluralRules.select(number)]}` // At this point: // convertToOrdinal("1") === "1st" // convertToOrdinal("2") === "2nd" // etc. const extractValueAndCustomizeDayOfMonth = (part) => { if (part.type === "day") { return convertToOrdinal(part.value); } return part.value; }; console.log( longEnUSFormatter.formatToParts(firstValentineOfTheDecade) .map(extractValueAndCustomizeDayOfMonth) .join("") ); // February 14th, 2020

Desafortunadamente, formatToParts no es compatible con Internet Explorer (IE) a partir de este escrito, pero todas las demás tecnologías de escritorio, móviles y back-end (es decir, Node.js) sí son compatibles. Para aquellos que necesitan admitir IE y necesitan absolutamente ordinales, la nota al margen a continuación (o mejor, una biblioteca de fechas adecuada) proporciona una respuesta.

Si necesita admitir navegadores más antiguos como IE antes de la versión 11, el formato de fecha en JavaScript es más difícil porque no había funciones de formato de fecha estándar como strftime en Python o PHP.

En PHP, por ejemplo, la función strftime("Today is %b %d %Y %X", mktime(5,10,0,12,30,99)) te da Today is Dec 30 1999 05:10:00 .

Puede usar una combinación diferente de letras precedidas por % para obtener la fecha en diferentes formatos. (Cuidado, no todos los idiomas asignan el mismo significado a cada letra; en particular, 'M' y 'm' pueden intercambiarse por minutos y meses).

Si está seguro del formato que desea usar, es mejor extraer bits individuales usando las funciones de JavaScript que cubrimos anteriormente y crear una cadena usted mismo.

 var currentDate = new Date (); var date = currentDate.getDate(); var month = currentDate.getMonth(); var year = currentDate.getFullYear();

Podemos obtener la fecha en formato MM/DD/YYYY como

 var monthDateYear = (month+ 1 ) + "/" + date + "/" + year;

El problema con esta solución es que puede dar una longitud inconsistente a las fechas porque algunos meses y días del mes son de un solo dígito y otros de dos dígitos. Esto puede ser problemático, por ejemplo, si muestra la fecha en una columna de la tabla, porque las fechas no se alinean.

Podemos abordar esto usando una función de "almohadilla" que agrega un 0 inicial.

 function pad ( n ) { return n< 10 ? '0' +n : n; }

Ahora, obtenemos la fecha correcta en formato MM/DD/YYYY usando:

 var mmddyyyy = pad(month + 1 ) + "/" + pad(date) + "/" + year;

Si en su lugar queremos DD-MM-YYYY, el proceso es similar:

 var ddmmyyyy = pad(date) + "-" + pad(month + 1 ) + "-" + year;

Subamos la apuesta e intentemos imprimir la fecha en el formato "Mes Fecha, Año". Necesitaremos una asignación de índices de mes a nombres:

 var monthNames = [ "January" , "February" , "March" , "April" , "May" , "June" , "July" , "August" , "September" , "October" , "November" , "December" ]; var dateWithFullMonthName = monthNames[month] + " " + pad(date) + ", " + year;

A algunas personas les gusta mostrar la fecha como 1 de enero de 2013. No hay problema, todo lo que necesitamos es un ordinal de función auxiliar que devuelva 1 para 1, 12 para 12 y 103 para 103, etc., y el resto es simple:

 var ordinalDate = ordinal(date) + " " + monthNames[month] + ", " + year;

Es fácil determinar el día de la semana a partir del objeto de fecha, así que agreguemos eso:

 var daysOfWeek = [ "Sun" , "Mon" , "Tue" , "Wed" , "Thu" , "Fri" , "Sat" ]; ordinalDateWithDayOfWeek = daysOfWeek[currentDate.getDay()] + ", " + ordinalDate;

El punto más importante aquí es que, una vez que haya extraído los números de la fecha, el formato se relaciona principalmente con las cadenas.

Cambiar el formato de fecha

Una vez que sepa cómo analizar una fecha y formatearla, cambiar una fecha de un formato a otro es solo cuestión de combinar los dos.

Por ejemplo, si tiene una fecha con el formato 21 de julio de 2013 y desea cambiar el formato a 21-07-2013, se puede lograr así:

 const myDate = new Date("Jul 21, 2013"); const dayOfMonth = myDate.getDate(); const month = myDate.getMonth(); const year = myDate.getFullYear(); function pad(n) { return n<10 ? '0'+n : n } const ddmmyyyy = pad(dayOfMonth) + "-" + pad(month + 1) + "-" + year; // "21-07-2013"

Uso de las funciones de localización del objeto de fecha de JavaScript

Los métodos de formato de fecha que discutimos anteriormente deberían funcionar en la mayoría de las aplicaciones, pero si realmente desea localizar el formato de la fecha, le sugiero que use el método toLocaleDateString() del objeto Date :

 const today = new Date().toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric', });

…nos da algo como 26 Jul 2016 .

Cambiar la configuración regional a 'en-US' da "26 de julio de 2016" en su lugar. Observe cómo cambió el formato, pero las opciones de visualización se mantuvieron igual, una característica muy útil. Como se muestra en la sección anterior, la nueva técnica basada en Intl.DateTimeFormat funciona de manera muy similar a esta, pero le permite reutilizar un objeto formateador para que solo necesite configurar las opciones una vez.

Con toLocaleDateString() , es un buen hábito pasar siempre las opciones de formato, incluso si el resultado se ve bien en su computadora. Esto puede evitar que la interfaz de usuario se rompa en lugares inesperados con nombres de meses muy largos o que se vea incómodo debido a los cortos.

Si quisiera el mes completo "Julio", todo lo que hago es cambiar el parámetro del mes en las opciones a "largo". JavaScript maneja todo por mí. Para en-US, ahora obtengo el 26 de julio de 2016.

Nota: si desea que el navegador utilice automáticamente la configuración regional del usuario, puede pasar "indefinido" como primer parámetro.

Si desea mostrar la versión numérica de la fecha y no quiere preocuparse por DD/MM/AAAA frente a DD/MM/AAAA para diferentes configuraciones regionales, sugiero la siguiente solución simple:

 const today = new Date().toLocaleDateString(undefined, { day: 'numeric', month: 'numeric', year: 'numeric', });

En mi computadora, esto da como resultado el 7/26/2016 . Si desea asegurarse de que el mes y la fecha tengan dos dígitos, simplemente cambie las opciones:

 const today = new Date().toLocaleDateString(undefined, { day: '2-digit', month: '2-digit', year: 'numeric', });

Esto 07/26/2016 . ¡Justo lo que queríamos!

También puede usar algunas otras funciones relacionadas para localizar la forma en que se muestran la hora y la fecha:

Código Producción Descripción
 now.toLocaleTimeString()
"4:21:38 a.m." Mostrar versión localizada de solo tiempo
 now.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit', });
"04:21:38" Mostrar la hora localizada en función de las opciones proporcionadas
 now.toLocaleString()
"22/7/2016, 4:21:38" Mostrar fecha y hora para la configuración regional del usuario
 now.toLocaleString(undefined, { day: 'numeric', month: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit', });
"22/7/2016, 04:21" Muestra la fecha y la hora localizadas en función de las opciones proporcionadas

Cálculo de fechas y horas relativas

Aquí hay un ejemplo de agregar 20 días a una fecha de JavaScript (es decir, calcular la fecha 20 días después de una fecha conocida):

 const myDate = new Date("July 20, 2016 15:00:00"); const nextDayOfMonth = myDate.getDate() + 20; myDate.setDate(nextDayOfMonth); const newDate = myDate.toLocaleString();

El objeto de fecha original ahora representa una fecha 20 días después del 20 de julio y newDate contiene una cadena localizada que representa esa fecha. En mi navegador, newDate contiene "8/9/2016, 3:00:00 PM".

Para calcular las marcas de tiempo relativas con una diferencia más precisa que los días completos, puede usar Date.getTime() y Date.setTime() para trabajar con números enteros que representan el número de milisegundos desde una determinada época, es decir, el 1 de enero de 1970. Para ejemplo, si desea saber cuándo son 17 horas después de ahora:

 const msSinceEpoch = (new Date()).getTime(); const seventeenHoursLater = new Date(msSinceEpoch + 17 * 60 * 60 * 1000);

Comparando fechas

Al igual que con todo lo demás relacionado con la fecha, la comparación de fechas tiene sus propias trampas.

Primero, necesitamos crear objetos de fecha. Afortunadamente, <, >, <= y >= funcionan. Así que comparar el 19 de julio de 2014 y el 18 de julio de 2014 es tan fácil como:

 const date1 = new Date("July 19, 2014"); const date2 = new Date("July 28, 2014"); if(date1 > date2) { console.log("First date is more recent"); } else { console.log("Second date is more recent"); }

Comprobar la igualdad es más complicado, ya que dos objetos de fecha que representan la misma fecha siguen siendo dos objetos de fecha diferentes y no serán iguales. Comparar cadenas de fechas es una mala idea porque, por ejemplo, "20 de julio de 2014" y "20 de julio de 2014" representan la misma fecha pero tienen representaciones de cadenas diferentes. El siguiente fragmento ilustra el primer punto:

 const date1 = new Date("June 10, 2003"); const date2 = new Date(date1); const equalOrNot = date1 == date2 ? "equal" : "not equal"; console.log(equalOrNot);

Esto generará una salida not equal .

Este caso particular se puede solucionar comparando los equivalentes enteros de las fechas (sus marcas de tiempo) de la siguiente manera:

 date1.getTime() == date2.getTime()

He visto este ejemplo en muchos lugares, pero no me gusta porque normalmente no se crea un objeto de fecha a partir de otro objeto de fecha. Así que siento que el ejemplo es importante solo desde un punto de vista académico. Además, esto requiere que ambos objetos Date se refieran exactamente al mismo segundo, mientras que es posible que solo desee saber si se refieren al mismo día, hora o minuto.

Veamos un ejemplo más práctico. Está tratando de comparar si el cumpleaños que el usuario ha ingresado es el mismo que la fecha de la suerte que obtiene de una API.

 const userEnteredString = "12/20/1989"; // MM/DD/YYYY format const dateStringFromAPI = "1989-12-20T00:00:00Z"; const dateFromUserEnteredString = new Date(userEnteredString) const dateFromAPIString = new Date(dateStringFromAPI); if (dateFromUserEnteredString.getTime() == dateFromAPIString.getTime()) { transferOneMillionDollarsToUserAccount(); } else { doNothing(); }

Ambos representaban la misma fecha pero lamentablemente su usuario no obtendrá el millón de dólares.

Aquí está el problema: JavaScript siempre asume que la zona horaria es la que proporciona el navegador, a menos que se especifique explícitamente lo contrario.

Esto significa, para mí, new Date ("12/20/1989") creará una fecha 1989-12-20T00:00:00+5:45 o 1989-12-19T18:15:00Z que no es lo mismo que 1989-12-20T00:00:00Z en términos de marca de tiempo.

No es posible cambiar solo la zona horaria de un objeto de fecha existente, por lo que nuestro objetivo ahora es crear un nuevo objeto de fecha pero con UTC en lugar de la zona horaria local.

Ignoraremos la zona horaria del usuario y usaremos UTC al crear el objeto de fecha. Hay dos maneras de hacerlo:

  1. Cree una cadena de fecha con formato ISO a partir de la fecha de entrada del usuario y utilícela para crear un objeto de fecha. Usar un formato de fecha ISO válido para crear un objeto de fecha y dejar muy clara la intención de UTC frente a local.
 const userEnteredDate = "12/20/1989"; const parts = userEnteredDate.split("/"); const userEnteredDateISO = parts[2] + "-" + parts[0] + "-" + parts[1]; const userEnteredDateObj = new Date(userEnteredDateISO + "T00:00:00Z"); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDateObj.getTime() == dateFromAPI.getTime(); // true

Esto también funciona si no especifica la hora, ya que será la medianoche por defecto (es decir, 00:00:00Z):

 const userEnteredDate = new Date("1989-12-20"); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDate.getTime() == dateFromAPI.getTime(); // true

Recuerde: si al constructor de fecha se le pasa una cadena en el formato de fecha ISO correcto de AAAA-MM-DD, asumirá UTC automáticamente.

  1. JavaScript proporciona una función Date.UTC() ordenada que puede usar para obtener la marca de tiempo UTC de una fecha. Extraemos los componentes de la fecha y los pasamos a la función.
 const userEnteredDate = new Date("12/20/1989"); const userEnteredDateTimeStamp = Date.UTC(userEnteredDate.getFullYear(), userEnteredDate.getMonth(), userEnteredDate.getDate(), 0, 0, 0); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDateTimeStamp == dateFromAPI.getTime(); // true ...

Encontrar la diferencia entre dos fechas

Un escenario común con el que te encontrarás es encontrar la diferencia entre dos fechas.

Discutimos dos casos de uso:

Hallar el número de días entre dos fechas

Convierta ambas fechas a la marca de tiempo UTC, encuentre la diferencia en milisegundos y encuentre los días equivalentes.

 const dateFromAPI = "2016-02-10T00:00:00Z"; const now = new Date(); const datefromAPITimeStamp = (new Date(dateFromAPI)).getTime(); const nowTimeStamp = now.getTime(); const microSecondsDiff = Math.abs(datefromAPITimeStamp - nowTimeStamp); // Math.round is used instead of Math.floor to account for certain DST cases // Number of milliseconds per day = // 24 hrs/day * 60 minutes/hour * 60 seconds/minute * 1000 ms/second const daysDiff = Math.round(microSecondsDiff / (1000 * 60 * 60 * 24)); console.log(daysDiff);

Encontrar la edad del usuario a partir de su fecha de nacimiento

 const birthDateFromAPI = "12/10/1989";

Nota: Tenemos un formato no estándar. Lea el documento API para determinar si esto significa el 12 de octubre o el 10 de diciembre. Cambie al formato ISO según corresponda.

 const parts = birthDateFromAPI.split("/"); const birthDateISO = parts[2] + "-" + parts[0] + "-" + parts[1]; const birthDate = new Date(birthDateISO); const today = new Date(); let age = today.getFullYear() - birthDate.getFullYear(); if(today.getMonth() < birthDate.getMonth()) { age--; } if(today.getMonth() == birthDate.getMonth() && today.getDate() < birthDate.getDate()) { age--; }

Sé que hay formas más concisas de escribir este código, pero me gusta escribirlo de esta manera debido a la gran claridad de la lógica.

Sugerencias para evitar el infierno de las citas

Ahora que nos sentimos cómodos con la aritmética de fechas, estamos en condiciones de comprender las mejores prácticas a seguir y las razones para seguirlas.

Obtener fecha y hora del usuario

Si obtiene la fecha y la hora del usuario, lo más probable es que esté buscando su DateTime local. Vimos en la sección de aritmética de fechas que el constructor de fechas puede aceptar la Date de varias maneras diferentes.

Para eliminar cualquier confusión, siempre sugiero crear una fecha usando el new Date(year, month, day, hours, minutes, seconds, milliseconds) incluso si ya tiene la fecha en un formato analizable válido. Si todos los programadores de su equipo siguen esta regla simple, será extremadamente fácil mantener el código a largo plazo, ya que es tan explícito como puede serlo con el constructor Date .

Lo bueno es que puedes usar las variaciones que te permiten omitir cualquiera de los últimos cuatro parámetros si son cero; es decir, new Date(2012, 10, 12) es lo mismo que new Date(2012, 10, 12, 0, 0, 0, 0) porque los parámetros no especificados están predeterminados en cero.

Por ejemplo, si está utilizando un selector de fecha y hora que le da la fecha 2012-10-12 y la hora 12:30, puede extraer las partes y crear un nuevo objeto Fecha de la siguiente manera:

 const dateFromPicker = "2012-10-12"; const timeFromPicker = "12:30"; const dateParts = dateFromPicker.split("-"); const timeParts = timeFromPicker.split(":"); const localDate = new Date(dateParts[0], dateParts[1]-1, dateParts[2], timeParts[0], timeParts[1]);

Intente evitar crear una fecha a partir de una cadena a menos que esté en formato de fecha ISO. Utilice el método Fecha (año, mes, fecha, horas, minutos, segundos, microsegundos) en su lugar.

Obtener solo la fecha

Si solo obtiene la fecha, por ejemplo, la fecha de nacimiento de un usuario, es mejor convertir el formato a un formato de fecha ISO válido para eliminar cualquier información de zona horaria que pueda hacer que la fecha avance o retroceda cuando se convierte a UTC. Por ejemplo:

 const dateFromPicker = "12/20/2012"; const dateParts = dateFromPicker.split("/"); const ISODate = dateParts[2] + "-" + dateParts[0] + "-" + dateParts[1]; const birthDate = new Date(ISODate).toISOString();

En caso de que lo hayas olvidado, si creas un objeto Date con la entrada en un formato de fecha ISO válido (AAAA-MM-DD), se establecerá de forma predeterminada en UTC en lugar de la zona horaria predeterminada del navegador.

Almacenamiento de la fecha

Guarde siempre el DateTime en UTC. Envíe siempre una cadena de fecha ISO o una marca de tiempo al back-end.

Generaciones de programadores informáticos se han dado cuenta de esta simple verdad después de amargas experiencias tratando de mostrar la hora local correcta al usuario. Almacenar la hora local en el back-end es una mala idea, es mejor dejar que el navegador maneje la conversión a la hora local en el front-end.

Además, debe ser evidente que nunca debe enviar una cadena DateTime como "20 de julio de 1989 12:10 p. m." al back-end. Incluso si también envía la zona horaria, está aumentando el esfuerzo para que otros programadores comprendan sus intenciones y analicen y almacenen la fecha correctamente.

Utilice los toISOString() o toJSON() del objeto Date para convertir el DateTime local a UTC.

 const dateFromUI = "12-13-2012"; const timeFromUI = "10:20"; const dateParts = dateFromUI.split("-"); const timeParts = timeFromUI.split(":"); const date = new Date(dateParts[2], dateParts[0]-1, dateParts[1], timeParts[0], timeParts[1]); const dateISO = date.toISOString(); $.post("http://example.com/", {date: dateISO}, ...)

Visualización de la fecha y la hora

  1. Obtenga la marca de tiempo o la fecha con formato ISO de una API REST.
  2. Cree un objeto Date .
  3. Utilice los toLocaleString() o toLocaleDateString() y toLocaleTimeString() o una biblioteca de fechas para mostrar la hora local.
 const dateFromAPI = "2016-01-02T12:30:00Z"; const localDate = new Date(dateFromAPI); const localDateString = localDate.toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric', }); const localTimeString = localDate.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit', });

¿Cuándo debe almacenar también la hora local?

“A veces es importante saber la zona horaria en la que ocurrió un evento, y convertir a una única zona horaria borra irrevocablemente esa información.

“Si está realizando una promoción de marketing y desea saber qué clientes realizaron pedidos alrededor de la hora del almuerzo, un pedido que parece haberse realizado al mediodía GMT no es muy útil cuando en realidad se realizó durante el desayuno en Nueva York”.

Si te encuentras con este tipo de situación, sería más inteligente guardar la hora local también. Como de costumbre, nos gustaría crear la fecha en formato ISO, pero primero tenemos que encontrar el desplazamiento de la zona horaria.

La función getTimeZoneOffset() del objeto Date nos dice la cantidad de minutos que, cuando se agrega a una hora local dada, da la hora UTC equivalente. Sugiero convertirlo al formato (+-)hh:mm porque hace más obvio que es un desplazamiento de zona horaria.

 const now = new Date(); const tz = now.gettime zoneOffset();

Para mi zona horaria +05:45, obtengo -345, esto no solo es el signo opuesto, sino que un número como -345 podría ser completamente desconcertante para un desarrollador de back-end. Entonces convertimos esto a +05:45.

 const sign = tz > 0 ? "-" : "+"; const hours = pad(Math.floor(Math.abs(tz)/60)); const minutes = pad(Math.abs(tz)%60); const tzOffset = sign + hours + ":" + minutes;

Ahora obtenemos el resto de los valores y creamos una cadena ISO válida que representa el DateTime local.

 const localDateTime = now.getFullYear() + "-" + pad(now.getMonth()+1) + "-" + pad(now.getDate()) + "T" + pad(now.getHours()) + ":" + pad(now.getMinutes()) + ":" + pad(now.getSeconds());

Si lo desea, puede incluir las fechas UTC y locales en un objeto.

 const eventDate = { utc: now.toISOString(), local: localDateTime, tzOffset: tzOffset, }

Ahora, en el back-end, si desea averiguar si el evento ocurrió antes del mediodía, hora local, puede analizar la fecha y simplemente usar la función getHours() .

 const localDateString = eventDate.local; const localDate = new Date(localDateString); if(localDate.getHours() < 12) { console.log("Event happened before noon local time"); }

No usamos el tzOffset aquí, pero aún lo almacenamos porque podríamos necesitarlo en el futuro para fines de depuración. En realidad, solo podría enviar el desplazamiento de la zona horaria y la hora UTC solamente. Pero también me gusta almacenar la hora local porque eventualmente tendrá que almacenar la fecha en una base de datos y tener la hora local almacenada por separado le permite consultar directamente en función de un campo en lugar de tener que realizar cálculos para obtener la fecha local.

A veces, incluso con la zona horaria local almacenada, querrá mostrar las fechas en una zona horaria particular. Por ejemplo, las horas de los eventos pueden tener más sentido en la zona horaria del usuario actual si son virtuales, o en la zona horaria en la que tendrán lugar físicamente, si no lo son. En cualquier caso, vale la pena mirar de antemano las soluciones establecidas para formatear con nombres de zona horaria explícitos.

Configuración de servidor y base de datos

Siempre configure sus servidores y bases de datos para usar la zona horaria UTC. (Tenga en cuenta que UTC y GMT no son lo mismo; GMT, por ejemplo, podría implicar un cambio a BST durante el verano, mientras que UTC nunca lo hará).

Ya hemos visto lo dolorosas que pueden ser las conversiones de zona horaria, especialmente cuando no son intencionadas. Enviar siempre UTC DateTime y configurar sus servidores para que estén en la zona horaria UTC puede facilitarle la vida. Su código de back-end será mucho más simple y limpio ya que no tiene que hacer ninguna conversión de zona horaria. DateTime data coming in from servers across the world can be compared and sorted effortlessly.

Code in the back end should be able to assume the time zone of the server to be UTC (but should still have a check in place to be sure). A simple configuration check saves having to think about and code for conversions every time new DateTime code is written.

It's Time for Better Date Handling

Date manipulation is a hard problem. The concepts behind the practical examples in this article apply beyond JavaScript, and are just the beginning when it comes to properly handling DateTime data and calculations. Plus, every helper library will come with its own set of nuances—which is even true of the eventual official standard support{target=”_blank”} for these types of operations.

The bottom line is: Use ISO on the back end, and leave the front end to format things properly for the user. Professional programmers will be aware of some of the nuances, and will (all the more decidedly) use well-supported DateTime libraries on both the back end and the front end. Built-in functions on the database side are another story, but hopefully this article gives enough background to make wiser decisions in that context, too.

Related: Buggy JavaScript Code: The 10 Most Common Mistakes JavaScript Developers Make