Herramientas de usuario

Herramientas del sitio


informatica:programacion:cursos:programacion_avanzada_javascript:trabajo_fechas_tiempo_temporizadores

Trabajo con fechas, tiempo y temporizadores

Módulo perteneciente al curso Programación avanzada con JavaScript y ECMAScript.

Introducción

Nada hay más importante que el tiempo. En el ajetreado y estresante mundo actual vale incluso más que el dinero para muchas personas.

Este módulo se centra en el manejo del tiempo con el lenguaje JavaScript. Nuevamente, al principio hay algunas cosas básicas que probablemente ya conozcas, pero que hemos decidido incluir por completitud y porque no viene mal repasarlas, aunque puedes saltártelas. Más adelante en el módulo verás algunas cuestiones más importantes sobre el manejo de zonas horarias o, por ejemplo, efectos que pueden retardar el uso de temporizadores.

Al final del mismo:

  • Conocerás los conceptos básicos sobre el manejo del tiempo en sistemas informáticos.
  • Aprenderás cómo se determinan la fecha y la hora actuales dependiendo del sistema en el que estemos.
  • Aprenderás los métodos básicos de la clase Date en JavaScript.
  • Sabrás dar formato arbitrario a las fechas.
  • Manejarás fechas y horas en formato universal coordinado.
  • Aprenderás a crear temporizadores y cronómetros en un navegador conociendo algunas cuestiones clave sobre ellos.

Conceptos básicos sobre el manejo del tiempo en informática

Un calendario es un sistema para organizar el tiempo con objetivos sociales, religiosos o administrativos. A cada periodo se le otorga un nombre (día, semana, mes, año…), y el calendario sitúa el momento actual frente a una referencia pasada elegida arbitrariamente (por ejemplo, en occidente, el momento en el que se considera que nació Jesucristo). La mayoría de los calendarios modernos se rigen por el ciclo que sigue la tierra al rodear al sol, por lo que se denominan calendarios solares. En la mayor parte del mundo el calendario que se utiliza es el calendario Gregoriano, solar. Éste debe su nombre al Papa Gregorio XIII y sustituyó en 1582 al calendario Juliano instaurado por Julio César en el año 46 a.C.

En cuanto a la medida más inmediata del tiempo, la unidad utilizada en el sistema internacional es el segundo, con sus unidades derivadas: el minuto (60 segundos) y la hora (60 minutos o 3.600 segundos). Un día del calendario consta de 24 horas.

La definición oficial de 1 segundo es la duración de 9.192.631.770 oscilaciones de la radiación emitida en la transición entre los dos niveles hiperfinos del estado fundamental del isótopo 133 del átomo de cesio (133Cs), a una temperatura de 0 K.

Por supuesto, estas unidades de medida del tiempo las tenemos todos muy interiorizadas. Las conocemos y las utilizamos con naturalidad. Sin embargo, un ordenador no lo tiene tan fácil. Una computadora no tiene conciencia del exterior y debe regirse por algún sistema interno que le marque el paso del tiempo.

Todos los ordenadores modernos incorporan un hardware especial que actúa de reloj interno y que está calibrado para medir la cantidad de tiempo que ha transcurrido desde una determinada fecha arbitraria denominada técnicamente “epoch”. Los sistemas operativos que corren por encima del hardware miden el tiempo en “ticks”, que son unidades arbitrarias trasladables a unidades de tiempo real en el sistema internacional. Cada sistema operativo define su propio valor para cada “tick”.

Por ejemplo, en Windows cada milisegundo de tiempo real contiene 10.000 "ticks" del procesador o lo que es lo mismo, cada tick representa un nanosegundo o una diezmillonésima de segundo. La precisión que ofrece, obviamente, es muy elevada. La fecha de referencia en este sistema operativo es el 1 de enero del año 1 a media noche, y el sistema informa permanentemente del número de “ticks” transcurridos desde entonces (sin contar los segundos intercalares).

Otros sistemas operativos usan otras referencias. Por ejemplo Mac OS X usa como “epoch” el 1 de enero de 1904, el primer año bisiesto del siglo XX, y Linux utiliza el 1 de Enero de 1970 que es el año elegido de manera arbitraria por los programadores del primer sistema UNIX.

Asimismo, cada lenguaje de programación ofrece su propia resolución temporal -la cantidad mínima de tiempo que puede medir- y su propia “epoch” o fecha de referencia. Por ejemplo la plataforma .NET utiliza el mismo año que Windows, pero el lenguaje COBOL usa el 1/1/1601, SQL Server el 1/1/1753, y Cocoa (la plataforma de desarrollo de Apple) usa el 1/1/2001. En la variedad está el gusto 😃

JavaScript utiliza como referencia temporal el 1 de Enero de 1970, como los sistemas UNIX, y la unidad más pequeña que puede medir es el milisegundo.

Por todo lo que acabamos de ver los ordenadores internamente no trabajan con fechas, como las personas, sino con números muy elevados que se relacionan con determinadas referencias arbitrarias. Y los lenguajes que utilizamos para construir programas usan otras referencias diferentes encima del sistema operativo, pero representan también las fechas con números larguísimos que carecen de sentido cuando los ve una persona.

Como hemos comprobado en los párrafos anteriores, la gestión del tiempo en los sistemas digitales dista mucho de ser un problema simple. Por fortuna, los sistemas operativos y lenguajes modernos nos abstraen de toda esta complejidad para facilitarnos el uso del tiempo en los programas que construimos.

Como veremos enseguida, JavaScript nos ofrece las herramientas que necesitamos para trabajar con el tiempo sin tener que preocuparnos de nada de lo anterior, que sin embargo es interesante conocer y debería formar parte de la cultura general de todo programador.

El tiempo en JavaScript

JavaScript proporciona las funciones necesarias para determinar las características temporales del sistema desde donde accede cada usuario.

Podemos averiguar la fecha del año y felicitarles así la Navidad, la hora del día para poder desearles las buenas noches, la franja horaria en la que se encuentran, etc…

Todas las funciones relacionadas con unidades temporales (fecha y hora) se ofrecen como métodos de la clase JavaScript llamada Date.

Como hemos visto en la introducción, una variable que contenga una fecha (o sea, un objeto de tipo Date) contiene en realidad un valor numérico que expresa el número de milisegundos transcurridos desde el 1/1/1970 a las 00:00:00 horas.

Las diferencias entre instantes temporales también se devuelven en milisegundos, de manera que si dos variables temporales se diferencian en 15.000 implica que están separadas por 15 segundos. Un día tiene 86.400.000 milisegundos.

De todos modos generalmente no tendremos que lidiar con estas medidas tan precisas y será suficiente con utilizar las unidades temporales más habituales para nosotros: días, meses, horas o minutos.

A continuación, vamos a ver lo básico de definir fechas en JavaScript mediante la clase Date.

Definiendo fechas por código

var d = new Date();
alert(d); // Thu Oct 03 2024 12:36:55 GMT+0200 (hora de verano de Europa central)

El formato por defecto que muestran los navegadores, como vemos arriba, puede no ser muy cómoda.

Sabiendo que JavaScript guarda el número de milisegundos que han transcurridos desde la fecha de referencia (epoch), 01/01/1970, podemos modificar el formato para la fecha, por ejemplo:

var d = new Date();
alert(d.toLocaleDateString()); // -> 3/10/2024

Con el método toLocaleDateString, JavaScript convertirá esos milisegundos en el formato de fecha que tengamos definido en el sistema.

Si queremos hacer lo mismo para la hora, usaremos el método toLocaleTimeString:

var d = new Date();
alert(d.toLocaleTimeString()); // -> 12:36:55

Podemos obtener la fecha de referencia pasándole un 0 a la clase Date:

var n = new Date(0);
alert(n); // -> Thu Jan 01 1970 01:00:00 GMT+0100 (hora estándar de Europa central)

Para definir una fecha concreta, podríamos añadirle más argumentos:

var n = new Date(2024, 4, 23, 11, 30, 00); // año, mes (empezando desde 0), día, hora, minutos, segundos
alert(n); // -> Thu May 23 2024 11:30:00 GMT+0200 (hora de verano de Europa central)

Si nos pasamos en el número al escribir el mes o el día, JavaScript mostrará el siguiente. Por ejemplo, si indicamos el mes 13, JavaScript entenderá que es febrero (recordemos que los meses empiezan en 0). Lo mismo pasaría con los días. Si en enero ponemos el día 32, realmente JavaScript considerará el 1 de febrero.

Otra forma de crear una fecha concreta es mediante una cadena de texto. La cadena debe ser en inglés y seguir este formato: mes abreviado dia, año hora:minuto:segundos.

var d = new Date("Feb 12, 2024 22:14:37");

Para saber si una variable contiene una fecha u otra cosa usaremos la función instanceof:

var f1 = new Date();
var f2 = 123;
var f3 = "cualquier cosa";
 
function esFecha(fecha) {
    return (fecha instanceof Date);
}
 
alert(esFecha(f1)); // -> true
alert(esFecha(f2)); // -> false
alert(esFecha(f3)); // -> false

Número de días de un mes cualquiera

A continuación vamos a ver un truco poco conocido de las fechas JavaScript.

Si en el constructor de una fecha especificamos como día del mes un 0 (un valor, a priori, no válido), JavaScript asigna a la fecha el último día del mes anterior.

Es un comportamiento extraño pero muy útil ya que podemos aprovecharlo para averiguar el número de días que tiene un mes cualquiera de un año dado, así:

function numDiasEnMes(mes, anio)
{
    var comodin = new Date(anio, mes+1, 0);
    return comodin.getDate();
}
 
alert( numDiasEnMes(1, 2012) );

Que en este caso devuelve 29 ya que el año 2012 fue bisiesto y por lo tanto el mes de febrero (mes 1: recuerda que comienzan a contarse en 0) tuvo 29 días.

Las partes del tiempo

Una vez que tenemos definida una variable que contiene un objeto de tipo Date, sería interesante poder acceder a las diferentes partes de la fecha y hora de manera directa, es decir, saber directamente el día de la semana en el que estamos, o sólo la hora, etc.

Para ello la clase Date cuenta con una serie de métodos que pasamos a enumerar a continuación:

  • getTime: obtiene solamente la parte correspondiente a la hora, rechazando la fecha. Devuelve el número de milisegundos transcurridos desde el 1/1/1970. Es útil para crear marcas de tiempo para ciertas aplicaciones especiales de comunicación con el servidor.
  • getDay: nos dice el día de la semana en el que nos encontramos, empezando a contar en domingo, que sería el día 0. De este modo el lunes sería el 1, el martes el 2 y así sucesivamente.
  • getDate: obtiene el día del mes dentro de la fecha dada.
  • getMonth: devuelve el número correspondiente al mes contenido en el objeto Date. Se comienza a contar en 0, que correspondería al mes de Enero.
  • getYear: devuelve el año contenido en la variable de tipo fecha. Esta función tiene un comportamiento algo extraño puesto que devuelve 2 cifras para años anteriores al 2000, y 3 cifras para años posteriores al 2000. Es decir, empieza a contar los años en el 1900. Así, para el año1980 devolverá 80, para 2016 devolverá 116 y para 1897 será el año -3 (negativo). Su uso no está recomendado y se prefiere el método siguiente que es consistente en lo que devuelve.
  • getFullYear: funciona igual que el anterior, pero en este caso el año se devuelve siempre con las cuatro cifras correspondientes. Debemos usarlo generalmente en sustitución de getYear, que se considera obsoleta.
  • getHours: permite obtener el valor de la hora en una variable Date, sin incluir la fecha actual, ni los minutos ni los segundos.
  • getMinutes: con este método conseguimos averiguar los minutos de la hora indicada.
  • getSeconds: permite obtener la parte correspondiente a los segundos en una variable de hora.
  • getMilliseconds: facilita el número de milisegundos después del segundo actual que está especificado dentro de una variable de tipo Date. Es decir, aunque la hora actual se muestre redondeada a 19:22:37, internamente hay unos cuantos milisegundos de precisión más que podemos obtener, por lo que sería por ejemplo 19:22:37:849 (siendo 849 los milisegundos a mayores que devolverá esta función).

Dado que JavaScript no nos proporciona método alguno para poder devolver formatos personalizados, y lo máximo que facilita de manera directa, como veremos enseguida, es que devolvamos fechas largas en la lengua local, usaremos las funciones anteriores para devolver el formato que deseemos.

Veámoslo con un ejemplo.

Formatos personalizados

Imaginemos que queremos conseguir el siguiente formato: 25 de Diciembre de 2024.

Empezaremos por algo sencillo:

// Para obtener fechas en formato dd/mm/YYYY
function fechaToStringES(fecha) {
    return fecha.getDate() + "/" + (fecha.getMonth()+1) + "/" + fecha.getFullYear();
}
 
var fecha = new Date(2024, 11, 25);
alert(fechaToStringES(fecha)); // -> 25/11/2024

Vamos ahora a por el formato deseado que comentábamos al principio:

// Para obtener el nombre del mes
function getNombreMes(fecha) {
    var meses = ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"];
 
    return meses[fecha.getMonth()];
}
 
// Para el formato 25 de Diciembre de 2024
function fechaToStringES(fecha) {
    return fecha.getDate() + " de " + (fecha.getNombreMes()+1) + "de " + fecha.getFullYear();
}
 
var fecha = new Date(2024, 11, 25);
alert(fechaToStringES(fecha)); // -> 25 de Diciembre de 2024

Zonas y diferencias horarias

De acuerdo con las especificaciones ECMA-Script que normalizan el lenguaje, JavaScript dispone de funciones para el manejo de unidades temporales estándar, lo que se conoce como Universal Coordinated Time, UTC u Hora Universal Coordinada. Este baremo para medir la hora es más conocido por todo el mundo como GMT (o Greenwich Meridian Time, hora en el meridiano de Greenwich) y todos estamos acostumbrados a utilizarlo.

Aunque en la práctica podemos considerar que UTC y GMT son lo mismo, pues las horas que se obtienen son las mismas en el uso normal, en realidad son muy diferentes conceptualmente. GMT está basado en el errático movimiento de los cuerpos celestes y es impreciso, mientras que UTC se basa en el uso de precisos relojes atómicos. UTC reemplazó a GMT el 1 de enero de 1972 y UTC 0, corresponde al antiguo GMT 0, por ello en la práctica los podemos usar indistintamente, pero siempre teniendo clara la verdadera diferencia entre ambos.

Prácticamente todas las funciones vistas en el apartado anterior para obtener las partes de un objeto Date se han implementado también para horas universales. Sólo hay que añadir la palabra “UTC” después de “get” en sus nombres. Así tenemos:

  • getUTCDay
  • getUTCDate
  • getUTCMonth
  • getUTCFullYear
  • getUTCHours
  • getUTCMinutes
  • getUTCSeconds
  • getUTCMilliseconds

Todos estos métodos tienen en cuenta la diferencia horaria del sistema en el que nos encontramos respecto al meridiano de Greenwich (o a UTC 0), de forma que es posible que un método “get” de hora local visto en el apartado anterior, y un método “get” de hora UTC ofrezcan resultados completamente diferentes. Por ejemplo, la fecha:

n = new Date(2013,0,1,0,15);

Correspondiente a los 15 primeros minutos del día de Año Nuevo del año 2013 en España (UTC+1, si ejecutamos el código en un ordenador en este país), pero es realmente una hora menos en UTC 0. Por ello,esa fecha que en hora local sería ya el 2013, en hora UTC es todavía del año anterior, 2012.

Las funciones toUTCString y toGMTString son equivalentes y devuelven las fechas expresadas respecto a UTC 0 o al meridiano de Greenwich. Así, el siguiente código:

var fecha = new Date(2013,0,1,0,15);
alert(fecha + "\n" + fecha.toUTCString());

Mostrará este resultado:

Obtener la zona horaria actual

El método getTimezoneOffset se ocupa de decirnos los minutos de diferencia existentes entre la hora local y la UTC/GMT, y puede resultar muy útil cuando la diferencia horaria entre donde está el servidor y los usuarios es importante.

Usando esta función podríamos escribir nosotros mismos todas las funciones UTC que acabamos de ver con suma facilidad. Para el caso de España esta función devuelve el número -60 o -120, ya que hay una hora de diferencia en invierno pero dos en verano con el horario de ahorro energético.

En realidad esta función puede devolver casi cualquier valor pues existen países con diferencias horarias con UTC que no son horas completas. Por ejemplo Venezuela está en UTC-04:30 (o sea, cuatro horas y media por detrás), y Nepal está en UTC+05:45 (5 horas y tres cuartos por delante).

JavaScript nos abstrae de todas estas dificultades y siempre devuelve la diferencia horaria correcta con código similar a este:

var fecha = new Date();
alert(fecha.getTimezoneOffset()); // -> -120 (en España en horario de verano)

Modificando fechas

Al igual que podemos guardar en una variable fechas y sus partes a través de los métodos “get…” y “getUTC..”, es posible igualmente fijar a posteriori estos valores. La clase Date posee unos métodos “set…” que están pensados para modificar el contenido de las variables de este tipo para reflejar fechas y horas diferentes.

Ninguno de los métodos que veremos a continuación afecta a la hora o fecha reales del sistema, sólo a los valores almacenados en las variables que tratemos con ellos.

Así, podemos almacenar fechas arbitrarias en una variable de tipo temporal previamente definida.

A continuación se detallan dichos métodos:

  • setDate: permite asignar un nuevo día del mes a la fecha contenida en una variable.
  • setMonth: Asigna un nuevo valor para el mes en la fecha contenida en la variable. Recuerda que los meses empiezan a contar desde 0 para enero.
  • setYear: permite cambiar el valor del año en una fecha. Si se usan sólo dos dígitos como argumento se considerará un año dentro del rango 1900-2000.
  • setFullYear: idéntico al método anterior pero se deben especificar siempre los cuatro dígitos del año. Es más recomendable que la anterior. Este método permite especificar opcionalmente el mes y el día del mes como segundo y tercer parámetro respectivamente.
  • setTime: permite asignar una nueva hora pasando como argumento el número de milisegundos transcurridos desde la medianoche del 1/1/1970. No se utiliza demasiado por razones obvias.
  • setMilliSeconds: fija el número de milisegundos que pasan del segundo en la hora actual de una variable.
  • setSeconds: permite fijar el número de segundos actuales en la variable. Opcionalmente se pueden especificar también los milisegundos como segundo parámetro.
  • setMinutes: fija los minutos actuales en la hora contenida en una variable. Tiene como parámetros opcionales el número de segundos y el de milisegundos transcurridos.
  • setHours: nos permite fijar la hora actual para una variable tipo Date. Tiene como tres parámetros opcionales los minutos, segundos y milisegundos.

Del mismo modo que antes había funciones “get…” para horas locales y “getUTC…” para horas GMT, en el caso de los métodos “set..” ocurre igual y existen las equivalentes referidas a UTC:

  • setUTCDate
  • setUTCMonth
  • setUTCFullYear
  • setUTCMilliSeconds
  • setUTCSeconds
  • setUTCMinutes
  • setUTCHours

Estas funcionan de la misma manera, pero refiriéndose sus parámetros a hora universal UTC y no a la hora local del sistema donde se ejecuten.

Gracias a estas funciones podremos modificar fechas preexistentes y operar con ellas.

Operaciones con fechas

var base = new Date(2023, 0, 1); // 1/1/2023
var hoy = new Date(); // 01/01/2024

Comparación de fechas:

alert(hoy > base); // -> true 

Diferencia entre fechas:

var diferencia = hoy - base;
alert(diferencia); // -> diferencia en milisegundos

Pero nos interesa ver la diferencia en días:

var diferencia = hoy - base;
var msDia = 86400000; // número de milisegundos que tiene un día
var difDias = diferencia / msDia;
alert(difDias ); // -> 641.5286664236111

En cuanto a la suma de fechas, no podemos utilizar el operador '+' porque concatena cadenas. Lo que podríamos hacer es obtener los milisegundos de las fechas y entonces sumarlos. Usaremos el método parse que procesa una fecha y devuelve el número de milisegundos desde el epoch para esa fecha:

alert(Date.parse(base); // -> 1672527600000
 
// Si lo que queremos es sumar 5 días a la fecha
// anterior, obtendríamos primero los milisegundos
// que hay en 5 días
var sumaDias = 86400000 * 5;
alert(Date.parse(base) + sumaDias);

Sin embargo, hay otras opciones mejores para sumar fechas.

// Sumamos 5 días a la fecha de 'base'
var base_nueva = new Date(base.setDate(base.getDate() + 5)); 

Otro ejemplo sumando segundos a una fecha:

// Sumamos 15 segundos a la fecha de 'base'
var base_nueva = new Date(base.setSeconds(base.getSeconds() + 15)); 

Temporizadores y cronómetros

Además de medir el tiempo, algo verdaderamente útil en cualquier plataforma de desarrollo es tener la capacidad de ejecutar código a intervalos regulares de tiempo.

Un programa en JavaScript, según todo lo que hemos visto hasta el momento, es una sucesión continua de líneas de código que se ejecutan una tras otra por orden para obtener un resultado. Sin embargo, en la realidad no solo llega con ejecutar el código en el momento de cargar una página, sino que es necesario que éste se ejecute en el momento apropiado: ante una acción concreta que ocurra en una página o al cabo de determinado tiempo. El primer caso se soluciona gracias a los eventos de HTML, que estudiaremos en un módulo posterior y nos permitirán reaccionar ante diferentes situaciones que se den en una página (como pulsar un botón o pasar el cursor sobre un texto). La segunda situación se resuelve con el uso de temporizadores, y es el objeto de esta lección.

Los temporizadores son objetos sin representación física que se encargan de ejecutar una determinada tarea al cabo de un cierto tiempo.

La orden para poner en marcha un temporizador es:

tempID = setTimeout(funcion, Tiempo);

siendo funcion el nombre de la función que el temporizador se encargará de ejecutar; y Tiempo es un valor expresado en milésimas de segundo que indica cuánto tiempo tardará éste en ejecutar la rutina especificada.

También se le puede pasar una función anónima que ejecute un determinado código:

setTimeout(function() { console.log('Haciendo algo'); }, 1000)

Pero solamente se utiliza cuando son funciones muy simples.

Por compatibilidad con versiones muy antiguas del lenguaje, se le puede pasar una cadena con código a evaluar, por ejemplo:

setTimeout("alert('Hola')", 1000);

Lo cual incluye llamadas a funciones:

setTimeout("funcion()", 1000);

Lo que hace es llamar a la función ''eval()'' para ejecutar dinámicamente el código que vaya en la cadena (un peligro y algo que deberíamos evitar).

Esto es considerado una mala práctica y solamente se soporta por compatibilidad con versiones antiguas del lenguaje. En la actualidad raramente lo verás en ningún sitio ya que, entre otras cosas, puede interferir con los procesos de minimización de código que se llevan a cabo para mejorar el rendimiento de descarga de las páginas.

Cancelar un temporizador

Al fijar un temporizador con setTimeout se puede anotar el valor devuelto en una variable. Este valor es un número que identifica de manera única al temporizador recién definido. El interés de este identificador reside únicamente en usarlo con el método complementario clearTimeout que lo toma como argumento para anular el temporizador relacionado en caso de que sea necesario cancelarlo antes de que se ejecute.

Según esto, si por ejemplo se dispone en el Script de una función llamada miFuncion y se quiere activar ésta al cabo de 2 segundos habría que escribir:

var miId = setTimeout("miFuncion()", 2000);

En caso de que, por cualquier motivo, sea necesario detener este temporizador antes de que llegue a actuar y ejecute la función, haremos:

clearTimeout(miId);

Temporizadores con repetición

Si necesitamos ejecutar la función repetidamente, a intervalos regulares de tiempo, es posible llamar repetidas veces al temporizador de nuevo con el método que acabamos de estudiar o, mucho mejor, se puede usar el método setInterval. Éste es exactamente igual al anterior en cuanto a parámetros y forma de uso, pero la llamada se repite continuamente cada intervalo de tiempo especificado, hasta que lo detengamos con una llamada a su función complementaria clearInterval. Por ejemplo:

id = setInterval("Mifuncion()", 2000);

O bien:

id = setInterval(Mifuncion, 2000);

Llamará continuamente a la función indicada cada dos segundos. Sólo dejará de invocarla cuando llamemos a clearInterval pasándole como parámetro el identificador devuelto al crear el temporizador (guardado en la variable “id” en la línea anterior):

clearInterval(id);

Tanto a setTimeout como a setInterval se les pueden pasar como parámetros opcionales los argumentos que necesite la función que vamos a ejecutar. Tantos como sea necesario:

setTimeout(Mifuncion, 2000, parametro1, parametro2…);

Esto funciona en todos los navegadores del mercado a excepción de en Internet Explorer 9 o anteriores versiones de éste, por lo que no es muy recomendable si queremos compatibilidad con navegadores antiguos. Si necesitas pasar parámetros a la llamada a la función es mejor que la envuelvas en otra función auxiliar sin parámetros y que llames a ésta.

Hay que tener en cuenta que estos cuatro métodos de temporizadores no forman parte en realidad del lenguaje JavaScript sino que son métodos proporcionados por los navegadores web (a través de su objeto global intrínseco window, que ya estudiaremos) y que pueden ser utilizados desde nuestro código JavaScript embebido en una página web. Si queremos usar el código descrito fuera de un navegador no funcionará. Sin embargo, otros lenguajes basados en JavaScript suelen incluir su propia implementación nativa para facilitar la compatibilidad. Por ejemplo Node.js ofrece las cuatro funciones, que funcionan del modo descrito aquí.

La precisión de los intervalos de tiempo

El tiempo del intervalo para un temporizador puede ser cualquier valor. Sin embargo, si nos pasamos poniéndolo demasiado pequeño, veremos que pueden ocurrir cosas malas. Aparte de poder llegar a bloquear la ejecución del navegador, a partir de determinado valor que varía de un navegador a otro (entre los 15 y los 25 milisegundos), el temporizador no tiene resolución suficiente y entonces da igual lo que bajemos el tiempo puesto que no habrá diferencia. En cualquier caso, debemos evitar los procesos periódicos lanzados con intervalos muy pequeños. Todo lo que sea inferior a 100 milisegundos no parece muy recomendable salvo en casos muy puntuales, así que deberíamos mantenernos por encima de esa zona de confort.

En la lección anterior afirmé que setInterval llamaba a una función cada “x” milisegundos, especificados como segundo parámetro. Sin embargo, esto no es exactamente así, y en ocasiones puede tener importancia esta distinción.

JavaScript solamente posee un hilo de ejecución, por lo que todas las funciones se ejecutan dentro de éste (dejando de lado la salvedad de la API de Web Workers de HTML, un tema avanzado). Esto significa que no se pueden ejecutar dos scripts al mismo tiempo en una página. Los métodos setTimeOut y setInterval ayudan a emular la ejecución en paralelo de código, ya que podemos lanzar procesos al cabo de cierto tiempo o periódicamente, que parecen ejecutarse en otro hilo. Pero es solo una ilusión.

En realidad, tanto los intervalos de estos temporizadores como cualquier otro evento asíncrono que se provoque (a través de la interfaz de usuario, por ejemplo), se encolan para su ejecución para cuando el motor de JavaScript (monohilo, recordémoslo) esté disponible.

Veamos qué pasa cuando ejecutamos una función cada cierto tiempo corto. Imaginemos una función relativamente larga de ejecutar (que tarde sobre 70 ms) ejecutada cada 100 ms:

Esto no deja de ser una simplificación ya que hay otros posibles eventos ejecutándose en el tiempo del proceso de JavaScript (eventos de la interfaz de usuario u otros temporizadores), pero nos sirve para explicar el fenómeno.

En este caso, como la función tarda menos tiempo que el intervalo en ejecutarse, dará tiempo a lanzarla con el periodo que hemos indicado en setInterval. Lo que JavaScript hace es introducir en una cola de ejecución los eventos asíncronos que debe ejecutar, y los ejecuta en el momento en el que puede hacerlo. En el ejemplo de la figura, la función se encola cada 100 ms y como tras cada ejecución aún sobra tiempo, al ir a ejecutarla nuevamente no hay nada pendiente en la cola y la ejecuta inmediatamente. Este sería el comportamiento normal deseable.

Imaginemos ahora un proceso que tarda más en ejecutarse que el intervalo de repetición que hemos especificado (se puede simular mostrando desde el evento periódico un diálogo bloqueante mediante alert –que bloquea la ejecución hasta que lo aceptamos- y tardando más tiempo del indicado en aceptarlo). Lo que ocurre lo vemos reflejado en la siguiente figura:

En este caso la primera vez que se ejecuta la función se tarda más tiempo del especificado en el intervalo en terminarla. Mientras tanto el código JavaScript está detenido. Al pasar los siguientes 100 ms (el intervalo de repetición) JavaScript no puede ejecutar la función puesto que está en un bloqueo, así que lo que hace es introducirla en la cola de ejecución de eventos asíncronos, a la espera de un momento libre para ejecutarla. Al acabo de otros 100 ms se debería introducir en la cola otra repetición de la tarea, pero en realidad se descarta pues sólo puede haber una de cada tipo. Finalmente al cabo de 335 milisegundos, se termina la primera ejecución y se lanza inmediatamente la que está en la cola (que es la segunda todavía). Con lo cual el intervalo, que era de 100 ms, se ha convertido en este caso en un intervalo mucho más largo.

Conclusión: en condiciones extremas los intervalos no son fiables, con posibles grandes variaciones, por lo que no podemos contar siempre con que nuestra función se ejecutará todas las veces que pensábamos. Esto puede tener importancia en ciertos tipos de aplicaciones y debemos tenerlo en cuenta.

Si realmente es importante para nosotros que una tarea se repita en un intervalo de tiempo preciso tras cada ejecución (es decir, que se ejecute “x” milisegundos después de haber terminado la primera ejecución, ojo) lo que tenemos que hacer es emplear un temporizador simple con setTimeOut y relanzarlo desde la propia función al final de su ejecución:

function periodica() {
    //Hacemos lo que sea que puede tardar
 
    setTimeOut(periodica, 100);
}

Es decir, iniciamos otro nuevo intervalo al terminar la ejecución del anterior. OJO: con esto no conseguimos repeticiones cada cierto tiempo, sino que la función se repita con un periodo de tiempo predecible entre cada ejecución, que no es lo mismo, como ilustra la siguiente figura:

Ejemplo - Cuenta regresiva

<!DOCTYPE html>
<html>
<head>
<title>Cuenta regresiva</title>
 
<link type="text/css" href="crono.css" rel="stylesheet" />
 
<script type="text/javascript">
    //tiempo objetivo;
    var dObjetivo = null;
    var divreloj = null;    //caché del div para mejorar rendimiento
    var idTimer = 0;    //Identificador del temporizador
 
    function comenzarParar()
    {
        if (idTimer != 0) { //parar
            //paramos el temporizador
            clearInterval(idTimer);
            idTimer = 0;
            //cambiar texto del botón a "Parar"
            document.getElementById("botonCrono").value = "Iniciar";
            //Poner el contador a cero
            pintarTiempo(0, 0);
        }
        else {  //arrancar
            //Se averigua el tiempo seleccionado (son minutos)
            var tDescuento = parseInt(document.getElementById("tiempo").value, 10);
            dObjetivo = new Date();  //hora actual como referencia
            dObjetivo.setMinutes(dObjetivo.getMinutes() + tDescuento);    //sumamos el número de minutos necesario: tiempo objetivo
            //cambiar texto del botón a "Parar"
            document.getElementById("botonCrono").value = "Parar";
            //poner el tiempo inicial
            pintarTiempo(tDescuento, 0);
            //lanzar temporizador
            idTimer = setInterval(tick, 900);
        }
    }
 
    function tick() {
        //Se calcula la diferencia entre la hora objetivo y la actual
        var dif = (dObjetivo - new Date()) / 1000; //dividimos entre 1000 para obtener segundos
        if (dif > 0) {
            var minutos = Math.floor(dif / 60);
            var segundos = Math.floor(dif % 60);
            pintarTiempo(minutos, segundos);
        }
        else {
            comenzarParar();    //Lo paramos si ya nos hemos pasado
        }
    }
 
    function pintarTiempo(minutos, segundos)
    {
        if (divreloj == null) divreloj = document.getElementById("reloj");
        if (minutos.toString().length == 1)
            minutos = "0" + minutos;
        if (segundos.toString().length == 1)
            segundos = "0" + segundos;
 
        divreloj.textContent = minutos + ":" + segundos;
    }
</script>
</head>
<body>
<div class="marco">
    <div id="reloj">00:00</div>
    <select id="tiempo">
        <option value="5">5 min</option>
        <option value="10">10 min</option>
        <option value="15">15 min</option>
        <option value="20">20 min</option>
        <option value="25">25 min</option>
        <option value="30">30 min</option>
    </select>
    <input type="button" id="botonCrono" value="Iniciar" onclick="comenzarParar();" />
</div>
</body>
</html>

Intl: la API de localización nativa de JavaScript

Como seguramente ya tengas claro, la traducción y la localización son conceptos relacionados pero muy diferentes.

La traducción hace referencia al proceso de mostrar las palabras y las frases visibles de la aplicación, en el idioma del usuario.

La localización es un concepto más sutil, ya que hace referencia a adaptar el desarrollo a las particularidades regionales de cada usuario. Un ejemplo sencillo, en el que se aprecia claramente la necesidad de localizar una aplicación, es el formato de las fechas en los distintos países. En España de manera general se usa el formato dd/mm/yyyy para representarla. Por ejemplo el 3/2/2020 representaría el 3 de febrero de 2020. Sin embargo, en Estados Unidos el formato que emplean para las fechas es mm/dd/yyyy, por lo que la fecha anterior se representaría como 2/3/2020. Es decir, la misma representación puede significar fechas completamente distintas según quien la lea.

Lo mismo ocurre con otros formatos, como las monedas o los números.

Para traducir tu aplicación basada en Web, mucho me temo que no te queda más remedio que usar archivos de lenguaje de algún tipo y alguna biblioteca especializada. Sin embargo, para la localización, es decir, la adaptación de la aplicación a las particularidades de cada idioma, todo lo que necesitas viene incluido con tu navegador, y es lo que vamos a ver en esta lección.

En esta lección se toca todo lo que tiene que ver con la localización, no solo de fechas.

El objeto Intl

JavaScript dispone de un objeto global específico para ayudarnos con la localización de aplicaciones a otros idiomas y culturas: Intl.

Podemos usar sus diferentes objetos asociados, mostrados en la figura anterior, para averiguar mucha información sobre localización en cualquier idioma.

Vamos a verlos…

Intl.Collator: para comparar cadenas de texto

El objeto Collator sirve para hacer comparaciones de cadenas teniendo en cuenta las particularidades locales.

Raramente se utiliza, ya que no suele ser necesario, gracias a que la clase String tiene un método específico para llevar a cabo este tipo de comparaciones: localeCompare().

Solo lo utilizaremos si tenemos que realizar muchísimas comparaciones en un bucle o algo así (algo muy poco habitual), ya que nos daría más rendimiento. En el resto de los casos puedes obviarlo. Por este motivo no entraremos a analizarlo en detalle, pues no merece la pena más allá de saber que existe.

Intl.DateTimeFormat: para dar formato a fechas y horas

Como su propio nombre sugiere, nos ayuda a dar formato a las fechas y las horas según las particularidades de cada país.

Como todos los objetos de Intl, se instancia pasándole como argumento una cadena de texto en formato IETF BCP 47. Esto suena muy complicado, pero en general no es más que el nombre abreviado internacional del idioma (es, en, it…) para idiomas genéricos, o lo anterior seguido de un guion y la abreviatura del país/cultura en mayúscula (es-ES, es-AR, en-US, en-UK…). Como ves, muy sencillo.

Así que, por ejemplo, para obtener una fecha bien formateada en varios idiomas sólo tenemos que hacer esto:

var fecha = new Date(2019, 6, 30, 16, 30, 0);
var dtfEs = new Intl.DateTimeFormat('es-ES');
var dtfEnUs = new Intl.DateTimeFormat('en-US');
var dtfArMa = new Intl.DateTimeFormat('ar-MA');
console.log(dtfEs.format(fecha));
console.log(dtfEnUs.format(fecha));
console.log(dtfArMa.format(fecha));

que nos devolverá por consola esa fecha en español (30 de julio de 2019: recuerda que los meses se numeran desde el 0), inglés americano y árabe de Marruecos (que tienen un formato inusual para lo que estamos acostumbrados):

Fíjate en que no nos devuelve la hora, ni tampoco hemos podido determinar el formato exacto de cada componente que queremos obtener. Eso lo controlaremos gracias a las opciones del constructor, que he omitido en el fragmento anterior.

Todos los objetos de Intl tienen un segundo argumento opcional para las opciones (valga la redundancia). En el caso de DateTimeFormat tiene un montón de propiedades posibles que no voy a detallar porque las tienes en la MDN. Pero vamos a ver un ejemplo de cómo usarlos porque son bastante sencillas:

var fecha = new Date(2019, 6, 30, 16, 30, 0);
var opciones = {
        weekday: 'long',
        month: 'long',
        year: 'numeric',
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit',
        timeZoneName: 'long'
    };
var dtfEs = new Intl.DateTimeFormat('es-ES', opciones);
var dtfEnUs = new Intl.DateTimeFormat('en-US', opciones);
var dtfArMa = new Intl.DateTimeFormat('ar-MA', opciones);
console.log(dtfEs.format(fecha));
console.log(dtfEnUs.format(fecha));
console.log(dtfArMa.format(fecha));

con el resultado siguiente:

Fíjate en que este código es exactamente igual al anterior solo que le hemos pasado como segundo argumento del constructor un objeto con las opciones pertinentes. Al haber especificado el formato en el que nos interesaba cada componente de la fecha, incluyendo las horas (para que las muestre), lo ha transformado adecuadamente y con las palabras apropiadas en cada idioma, e incluso escrito de derecha a izquierda en el caso del árabe de Marruecos.

Si queremos, podemos emplear el método formatToParts() para obtener cada una de las partes de la fecha, de modo que podamos utilizarlas en cualquier formato personalizado si lo necesitásemos (aunque no te lo recomiendo, pues para eso tienes las facilidades que da el objeto, sin recurrir a formatos propios):

Y podemos, en cada idioma, obtener exactamente cada parte de la cadena final, en función de las opciones que hayamos elegido.

Intl.RelativeTimeFormat: para facilitar la lectura de intervalos de tiempo relativos

Otra necesidad muy común en la mayor parte de las aplicaciones es la de expresar intervalos de tiempo relativos a la fecha actual. Por ejemplo, si tenemos un listado de tareas, en la columna de la fecha de vencimiento podemos poner tal cual la fecha, o bien ser mucho más amigables para el usuario y escribir cosas como “Vence en 3 días”, o “Caducada hace 5 horas”…

Esto es mucho más complicado de hacer de lo que parece de una manera consistente, y si además debemos hacerlo en varios idiomas no te quiero ni contar. Por suerte Intl nos ofrece también funcionalidad apropiada para lograrlo de manera sencilla.

Al igual que antes, lo único que tenemos que hacer es instanciar la clase pasándole el identificador del idioma a utilizar para la localización:

var rtf = new Intl.RelativeTimeFormat('es-ES');

Ahora podemos obtener los intervalos apropiados en ese idioma llamando al método format(), y pasándole el número de intervalos y el tipo de intervalo, que es una cadena en inglés. Por ejemplo:

rtf.format(1, 'day')    //dentro de 1 día
rtf.format(-3, 'day')   //hace 3 días
rtf.format(0, 'day')    //dentro de 0 días
rtf.format(5, 'hour')   //dentro de 5 horas

Esto es genial y ahorra muchas KB de bibliotecas JavaScript que ya no tendremos que descargarnos.

Además, en el constructor podemos establecer algunas opciones para especificar cómo queremos que generen esos intervalos. Por ejemplo, a mí no me gusta el estilo por defecto que tienen, usando siempre números, así que lo puedo cambiar estableciendo la propiedad numeric como 'auto':

var rtf = new Intl.RelativeTimeFormat('es-ES', { numeric: 'auto' });

y así conseguir que, por ejemplo, si es algo de hace un día ponga “ayer” y si es en un día obtenga “mañana”, haciéndolo aún más natural:

Como vemos, de gran utilidad.

Como antes, también existe el método formatToParts() para obtener una matriz con cada uno de los fragmentos del formato por separado.

Intl.NumberFormat: para dar formato a números y dinero

Seguimos con necesidades habituales de localización, en este caso con los números. Como sabes, cada idioma tiene formatos diferentes para muchas cosas con los números y las cantidades monetarias. Por ejemplo, en España los separadores de mil son puntos y el decimal es una coma, y la moneda se pone trás la cantidad. Sin embargo en EEUU es justo al revés: los miles se separan con comas, los decimales con puntos y la moneda va delante de la cantidad.

¿Cómo gestionamos esto de manera sencilla para cualquier idioma del planeta? Antes era complicadísimo. Ahora es muy sencillo gracias a Intl.NumberFormat.

Como todos los anteriores se instancia pasándole una cadena con el idioma (si no ponemos nada se usará el idioma del sistema operativo):

var nfEs = new Intl.NumberFormat('es-ES');
var nfEn = new Intl.NumberFormat('en-EU');
var nfFr = new Intl.NumberFormat('fr');
console.log(nfEs.format(123456.78));
console.log(nfEn.format(123456.78));
console.log(nfFr.format(123456.78));

y como podemos comprobar genera los separadores en el formato adecuado a cada caso:

Fíjate en cómo los franceses utilizan como separador de miles un espacio, por ejemplo.

En cuanto a las opciones podemos establecer incluso el sistema de numeración que no tiene por qué ser arábigo, el tipo de moneda si va a ser una cantidad de dinero, y también la forma de nombrar las monedas, entre otras muchas opciones. La más importante es style que nos permite seleccionar si queremos mostrar decimales ('decimal', valor por defecto), monedas ('currency') o porcentajes ('percent').

Por ejemplo, para mostrar una cantidad en euros o dólares escribiríamos:

var nfEs = new Intl.NumberFormat('es-ES', {style: 'currency', currency: 'EUR'});
var nfEn = new Intl.NumberFormat('en-EU', {style: 'currency', currency: 'USD'});
var nfFr = new Intl.NumberFormat('fr', {style: 'currency', currency: 'EUR', currencyDisplay: 'name'});
console.log(nfEs.format(123456.78));
console.log(nfEn.format(123456.78));
console.log(nfFr.format(123456.78));

Fíjate en cómo adapta perfectamente el formato a cada idioma y cómo además usa el símbolo o el nombre según las opciones indicadas:

Intl.ListFormat: par dar formato a listas

Otra necesidad clásica en las aplicaciones: partir de una lista o array de elementos y generar una lista legible para cada idioma.

Por ejemplo, si tenemos esta matriz, que generalmente en una aplicación la habremos obtenido de un servicio remoto:

var beatles = ['John', 'Paul', 'George', 'Ringo'];

y queremos meterlos en una lista amigable para el usuario para formar la frase: Los Beatles eran John, Paul, George y Ringo. Algo tan simple como esto requiere bastante trabajo si queremos adaptarlo a diversos idiomas. No todos usan las comas para separar y desde luego el último elemento no tiene que ser una “y” tampoco.

Con Intl.ListFormat la cosa es muy sencilla:

var beatles = ['John', 'Paul', 'George', 'Ringo'];
var lfEs = new Intl.ListFormat('es-ES');
var lfDe = new Intl.ListFormat('de-DE');
console.log(lfEs.format(beatles));
console.log(lfDe.format(beatles));

Como vemos nos devuelve la lista formateada para cada localización, incluyendo en este caso la palabra “y” en el idioma correspondiente:

Por supuesto no siempre querremos que la lista sea inclusiva, sino que a veces podemos necesitar que sea una lista de opciones y que esa “y” se convierta en una “o”, por ejemplo. Para cambiar este comportamiento en las opciones del constructor tenemos la propiedad type que puede tomar los valores:

  • 'conjunction', para listas de tipo “y”
  • 'disjunction' para listas de tipo “o”
  • 'unit' si la lista es de unidades de medida, que se suelen poner en forma de lista de modo diferente.

Así, con la lista anterior podemos poner esto:

var beatles = ['John', 'Paul', 'George', 'Ringo'];
var lfEs = new Intl.ListFormat('es-ES', {type:'disjunction'});
var lfDe = new Intl.ListFormat('de-DE', {type:'disjunction'});
console.log(lfEs.format(beatles));
console.log(lfDe.format(beatles));

para tenerla de tipo “o”:

Si fuesen unidades, por ejemplo la longitud de una viga en una aplicación de construcción pondríamos:

var medidas = ['3 metros', '12 centímetros'];
var lfEs = new Intl.ListFormat('es-ES', {type:'unit'});
var lfDe = new Intl.ListFormat('de-DE', {type:'unit'});
console.log(lfEs.format(medidas));
console.log(lfDe.format(medidas));

Fíjate en un detalle importante: aunque la localización ha funcionado perfectamente porque las listas tienen el formato adecuado para cada idioma, la traducción está mal ya que en alemán sigue poniendo las medidas en español. Obviamente esto no es responsabilidad de Intl ya que se trata de traducción y es responsabilidad de la aplicación. Antes de crear la lista de cadenas deberemos asegurarnos de que las medidas están en el idioma apropiado.

Hay algunos parámetros más para las opciones del constructor, pero lo importante es lo que hemos visto.

Intl.PluralRules: para pluralización

Esta ya es una característica avanzada. Al contrario que las otras clases que hemos visto, no está pensada para pasarle una cadena y que nos las devuelva en plural, sino que es a más bajo nivel. Lo que hace es facilitarnos la forma de plural que corresponde a cada número que se le pase a su método select().

Por ejemplo, en español, inglés u otros idiomas occidentales una viga mide 1 metro (singular), 3 metros (plural) o, curiosamente, 0 metros (plural aunque sea cero). Sin embargo, en árabe existen otras acepciones para ciertos números.

Si lo probamos con la clase PluralRules:

var prEs = new Intl.PluralRules('es-ES');
var prMa = new Intl.PluralRules('ar-MA');
console.log('ESPAÑOL:');
console.log(prEs.select(0));
console.log(prEs.select(1));
console.log(prEs.select(3));
console.log(prEs.select(0.5));
console.log('ÁRABE:');
console.log(prMa.select(0));
console.log(prMa.select(1));
console.log(prMa.select(3));
console.log(prMa.select(0.5));

Veremos lo siguiente:

Como puedes observar, para los idiomas occidentales generalmente hay dos posibilidades: 'one' (singular) o 'other' (plural), y con eso podemos decidir si se le pone una “s” al final o no.

Lo anterior es una simplificación enorme ya que en español es mucho más complicado que eso. A veces el plural lleva “s” (gato, gatos), otras veces lleva “es” (flor, flores), en ocasiones cambia la terminación de la palabra (pez, peces) y otras veces no lleva nada (¿cuál es el plural de “virus” en español? Pues “virus” también porque es invariable en plural, al contrario que en inglés, que es “viruses”). En ese sentido otros idiomas como el inglés son mucho más sencillos.

Pero en otros idiomas la cosa es mucho más compleja, como puedes comprobar con el árabe.

Por lo tanto, aunque está bien disponer de esta funcionalidad para algunas aplicaciones muy concretas, no te va a servir de gran ayuda a la hora de generar plurales “serios”, así que generalmente no lo vas a utilizar.

Soporte

El soporte actual de navegadores es ya universal desde hace años, por lo que no deberías tener problemas para usarla. La excepción, como casi siempre, es Internet Explorer, pero incluso éste tiene soporte para la mayor parte de las clases en su versión 11. En esta tabla de MDN tienes un buen resumen detallado del soporte específico por clase y navegador.

También tienes un polyfill que puedes utilizar si fuese necesario en estos navegadores antiguos, aunque no es tan potente.

En resumen

Para casi todas las tareas comunes relacionadas con la localización de aplicaciones, JavaScript nos proporciona ayuda integrada y no vamos a necesitar utilizar bibliotecas externas que añaden complejidad, peso y que además, con toda seguridad, no serán tan buenas como el sistema operativo para estos menesteres. Dado que la API de internacionalización de JavaScript, a través del objeto global Intl, utiliza por debajo los servicios del sistema operativo para conseguirlo, podemos garantizar resultados rápidos y correctos.

Deberíamos acostumbrarnos a usa esta API ya que nos ayudará a conectar mejor con nuestros usuarios y a hacer las aplicaciones más amigables.

Prácticas propuestas para el módulo

En este módulo hemos aprendido muchas cosas acerca del manejo del tiempo en los lenguajes de programación y más en concreto de JavaScript. Aunque manejar fechas y horas en este lenguaje no es muy complejo, no está exento de dificultades y conviene practicarlo para asegurarnos de que lo dominamos.

Para ello te propongo los siguientes ejercicios:

  • Crea una función de calendario perpetuo que al pasarle una fecha te diga en qué día de la semana cae, pudiéndole pasar cualquier fecha del pasado o del futuro. El día deberá devolverlo en formato textual, es decir, Lunes, Martes, etc… Una pregunta interesante que debes plantearte: ¿ocurrirá algo extraño si le pasas una fecha anterior al epoch, es decir, anterior a 1972? Prueba con la fecha del descubrimiento de América, a ver si te dice en qué día de la semana cayó 😉

Aunque en el descubrimiento de América se usaba todavía el calendario juliano y no el actual gregoriano, no pretendo que hagas la conversión de gregoriano a juliano o al revés en el ejercicio anterior. Solamente que manipules las funciones básicas de la clase Date para obtener el día de la semana. En concreto, el 12 de octubre de 1492 era una fecha del calendario juliano que sería equivalente al 21 de octubre de 1492 si lo llevamos a nuestro calendario gregoriano actual, pero no te preocupes por esos detalles. Puedes usar esta última fecha para ver cuál sería el día de la semana.

  • Crea una función capaz de convertir una cadena con la fecha y hora en formato corto como este “dd/mm/yyyy hh:mm:ss”, en una variable de tipo fecha en JavaScript. Esta función implica sobre todo el procesamiento de cadenas con una posterior conversión a fecha partiendo de sus partes. Ten en cuenta que puede faltar la hora o la propia fecha (ser solo la hora, pero sin fecha delante, con lo cual sería el día de hoy a esa hora), y que el año podría estar expresado con 2 o 4 números. Recomendación: utilizar expresiones regulares para analizar la cadena y validarla.
  • Intenta escribir al menos una de las funciones “get” de la clase Date para convertirlas en funciones UTC. Por ejemplo, el método getDate que devuelve el día del mes de una fecha, que tenga en cuenta la zona horaria actual para referir la fecha a UTC. Es decir, trata de clonar la funcionalidad del método getUTCDate. Comprueba que tu versión y la nativa son congruentes probándolo con fechas en los extremos de un día, teniendo en cuenta la zona horaria en la que tú te encuentras. Por ejemplo, si estás en España en horario de invierno (UTC+1), comprueba que cualquier fecha con una horaria dentro de la primera hora del día, con tu función devuelve realmente el día anterior (deberías restarle 1 a la fecha para referir a UTC).
  • Crea una función llamada dateAdd que permita sumar cualquier intervalo a una fecha. Los parámetros que tomará son:
    • intervalo: una cadena de texto con el tipo de intervalo a sumar a la fecha, siendo los valores válidos los siguientes:
      • “y”: años
      • “m”: meses
      • “w”: semanas
      • “d”: días
      • “h”: horas
      • “mm”: minutos
      • “s”: segundos
      • “ms”: milisegundos
    • numero: la cantidad a sumar
    • fecha: la fecha a la que se le suma el intervalo.

De esta manera, por ejemplo, para poder sumar 7 días a la fecha actual podríamos escribir lo siguiente:

    var hoy = new Date();
    var dentroDeUnaSemana = dateAdd("d", 7, hoy); //o también dateAdd("w", 1, hoy);

Facilitándonos mucho las operaciones con fechas.

Como requisitos adicionales para crear esta función se solicitan:

  • Que se compruebe la validez de todos los parámetros (que sea un intervalo válido, que el número a sumar sea un entero y la fecha una fecha válida). El intervalo puede especificarse en mayúsculas o minúsculas indistintamente.
  • Que soporte números negativos, de modo que en la práctica se puedan restar intervalos también.
  • Que la selección del intervalo no se realice con un switch, para hacer el código lo más compacto posible.
  • Crear una función dateDiff que nos devuelva la diferencia existente entre dos fechas expresada en el intervalo especificado. Tomará como parámetros:
    • intervalo: con los mismos valores posibles que en el caso anterior.
    • f1: la primera fecha con la que operar.
    • f2: la segunda fecha con la que operar.

De este modo, por ejemplo, para obtener la diferencia en años entre la fecha actual y el 1/1/2010 a las 00:00, se escribiría:

var base = new Date(2010, 0, 1); // 1/1/2010
var hoy = new Date();
alert(dateDiff("y", base, hoy)); //Diferencia en años
alert(dateDiff("w", base, hoy)); //Diferencia en semanas

Se aplican los mismos requisitos adicionales que en el caso anterior pero además se debe tener en cuenta el caso de que la segunda fecha no sea mayor que la primera y hacer que la función trabaje de manera correcta adaptándose a esta circunstancia.

Recursos

informatica/programacion/cursos/programacion_avanzada_javascript/trabajo_fechas_tiempo_temporizadores.txt · Última modificación: por tempwin