Posts con la categoría ‘Tutoriales’

Pasar el W3C Validator

Escrito en Tutoriales, Uncategorized

El servicio w3c validator tiene la utilidad de testear una web para encontrar las partes en las que esta puede ser incompatible con los estándares, las partes con errores y los posibles conflictos que se pueden dar.

El W3C validator sirve para validar el código HTML

Este servicio puede validar HTML indicando la URL de la web, subiendo un archivo .html o escribiendo el HTML directamente.

Pasar correctamente estas validaciones y por consiguiente conseguir el logo de «W3C validator» es bastante complicado, sobre todo en el caso de intentar pasar este test una vez finalizada la parte de desarrollo en la página web; a nosotros nos sucedió ese problema: tratamos de solucionar la mayor parte de problemas de compatibilidad posibles, pero no fue posible en todos los casos.

A priori puede parecer que, mientras la página funcione y se vea más o menos bien en la mayoría de los navegadores, es suficiente; pero no deberíamos conformarnos con esto porque el principal motivo para esforzarse en este tema es que Google indexa mejor páginas que sean W3C compatible, esto es debido a que para el robot de Google le es más fácil leer nuestra web, por lo que obtendríamos mejores resultados en las búsquedas de Google.

A continuación voy a detallarle las cosas que suelen dar errores y que hay que tratar de evitar porque dan problemas con el validador de W3C:

Dejarse etiquetas sin cerrar:
Esto puede parecer obvio, pero no lo es tanto. A veces se nos puede olvidar cerrar un div, es normal, y este validador nos lo indicará.
Pero hay casos en los que hay que cerrar las etiquetas aunque no haga falta, por ejemplo, en el caso de utilizar XHTML, las etiquetas img deben terminar con />, las etiquetas meta , link, input, etc. necesitan ser cerradas de manera explícita.
Otro caso es el de las etiquetas que aparentemente no es necesario cerrar, sino que el navegador sobreentiende que ya han finalizado. Este es el caso, por ejemplo de las etiquetas <option> y <li> no son imprescindibles de cerrar, porque cuando empieza la siguiente etiqueta de este tipo ya se sobreentiende que se han cerrado. Esta práctica no cumple con los estándares, por lo que deberíamos evitarla.

Uso excesivo de tablas
Este es un problema dificil de diagnosticar, puesto que el validador nos mostrará un montón de errores cuando una página tiene muchas tablas. Para que las tablas sean compatibles, tienen que estar compuestas de la siguiente manera: <table><tbody><tr><td>, en caso de que el tbody se nos olvide o algo, ya nos dará error.
El consejo sería evitar el uso de las tablas, aunque, como a veces es inevitable, se pueden o bien ignorar los errores, o tratar de usar todas las etiquetas necesarias.

Elementos que están donde no deben
Este problema es típico cuando se define unos estilos dentro del body o cuando se tratan de usar etiquetas meta dentro del body. También da error en caso de introducir una etiqueta que dibuje algo en la pantalla dentro del head.
Estos errores son habitualmente fáciles de solucionar, aunque hay veces que hay problemas inevitables. Se trata de situar cada elemento en su sitio y ya está; si nos es imprescindible que un elemento esté fuera del sitio, tampoco nos tenemos que obsesionar.

Atributos que una etiqueta no tiene
Este tipo de errores se suele dar en páginas en las que se aplica Javascript, como nos es necesario acceder a los elementos de la página a través de atributos id, name, class, etc., a veces podemos tener el caso de que nos hagan falta más atributos o algo así.
Un caso puede ser tener un elemento <td> en el que hemos aplicado un atributo id para ponerle estilo CSS, si este id se repite (más adelante veremos que esto también da problemas), entonces tenemos que tener otro atributo que se refiera a este elemento td. Una posible solución es usar un atributo name, esta práctica no es muy recomendable, puesto que da errores de compatibilidad; siempre hay que tratar de usar un atributo id y que sea único.
El atributo name se debería utilizar solo para formularios y para anclas.

Problemas con IDs
Un ID es un identificador único, por lo tanto, se debe utilizar tan solo para una etiqueta hml. En caso de usarlo para aplicar un estilo CSS, se debe tener en cuenta que si ese estilo solo se va a usar en una etiqueta de la misma página, entonces si se puede usar un ID; en caso de tener que reutilizar el mismo estilo varias veces en el mismo documento, entonces hay que usar una class.
Los IDs no pueden empezar por un número sino que deben empezar por una letra o un guión bajo o similar. Esto puede suponer un problema cuando, para aplicar Javascript nos es necesario aplicar un ID a un elemento que sea su número identificador; por ejemplo, en la pantalla de lista de sorteos cada sorteo tiene en el botón de borrar sorteo asignado un id que es el número de sorteo. En nuestro caso nos es imprescindible, puesto que así se facilitan mucho las operaciones internas; podríamos haber evitado esta práctica, aunque no lo hemos visto necesario.
En resumen, que se evite empezar por un número, aunque tampoco tiene que ser algo que complique mucho la programación interna en Javascript o PHP.

Resumen
Concluyendo, hay que tratar de pasar el W3C validator aunque tampoco debe ser una obsesión que haga que pongamos todos nuestros recursos en pasar con éxito. Es más importante saber como evitar los errores que saber cómo arreglarlos; esto es debido a que a veces es tan avanzada la programación interna, que es muy complicado modificarla a posteriori para que se tomen en cuenta los cambios.

En sortea2 hemos tratado de pasar el W3C validator, aunque en la mayoría de las páginas no nos ha sido posible. Lo importante es saber qué técnicas evitar a la hora de programar y es por eso por lo que os escribo este artículo.

¿crees que faltan errores de compatibilidad comunes? comunícanoslo a través de los comentarios.


Etiquetas: , ,
Escrito por .

Evitar problemas cuando Javascript no está habilitado en el navegador

Escrito en Tutoriales

Cuando una página de nuestra web necesita obligatoriamente tener javascript habilitado en el navegador para poder mostrarse correctamente, pero no queremos que los usuarios que no lo tengan habilitado accedan automáticamente a otra página para que pueda llevar a cabo la tarea que quería utilizar, tenemos que idear un método que detecte si javascript está habilitado y, en caso negativo, redireccionar a otra página.

Este problema lo tuvimos en la pantalla de sorteos simples, queríamos que los usuarios sin javascript pudieran explotar todas las posibilidades de nuestro sistema para sortear. Para ello, creamos una página alternativa a esta, la de sorteos simples sin javascript. Como vemos, visualmente es practicamente idéntica, pero a la hora de darle al botón de ¡sortear! el resultado necesita que la página se actualice.

Bien, ya teníamos las dos páginas, pero ¿cómo hacer que la gente que no tenga javascript sepa facilmente que hay otra página que hace exactamente lo mismo y que sí le funciona?. La solución que pensamos que era la más fácil era usando la clásica etiqueta de <noscript></noscript>. Esta etiqueta lo que hace es ejecutar lo que haya dentro en el caso de no tener javascript. Entonces lo que había que hacer era que lo que hubiera dentro fuera una instrucción que redireccionara la página a otra.

La instrucción es una etiqueta meta, la siguiente:

<NOSCRIPT>
<META HTTP-EQUIV=’Refresh’ CONTENT=’0;URL=$parametro’>
</NOSCRIPT>

Más tarde veremos por qué en el atributo URL ponemos una variable de javascript, pero si unicamente necesitas redireccionar una página, puedes poner directamente la página a la que quieres que se redireccione.

Con este código ya podría funcionar todo correctamente, aunque nosotros tuvimos que ir más allá, porque hicimos una función que, entre otras cosas, miraba que página quería ser redireccionada y la redireccionaba donde debía.

Un fragmento de la función sería este:

$paginanojs = array(1 => “sorteos-simples”, 2 => “sorteos”, 3 => “registro”, 4 => “login”);

for ($i = 1 ; $i <= count($paginanojs) ; $i++)
{
if ($noscript == $paginanojs[$i])
{
if ($noscript == "sorteos-simples")
$parametro = "../sorteosnoscript";
elseif ($noscript == "sorteos")
$parametro = "../nojs?pag=sorteos_av";
else
$parametro = "../nojs?pag=".$paginanojs[$i];

echo"<NOSCRIPT>".
"<META HTTP-EQUIV=’Refresh’ CONTENT=’0;URL=$parametro’>".
"</NOSCRIPT>;

$noscript = 0;
break;
}
}

A esta función se la llama con el parámetro $noscript, y ya hace lo que predeterminadamente hemos programado.

En nuestro caso, necesitábamos que las páginas de registro, de login y de sorteos avanzados fueran a una página auxiliar, porque no habíamos creado ninguna página que hiciera su misma función pero sin javascript, en estas páginas lo que se hace es informar al usuario de que no puede utilizar esas funciones. Puede que en el futuro habilitemos estas páginas para este tipo de usuarios.


Etiquetas: , ,
Escrito por .

Problemas CSS en Internet Explorer

Escrito en Tutoriales

Problemas de CSS en Internet Explorer

Internet Explorer tiene una manera de entender CSS muy “peculiar” que  hace que todos los diseñadores web tengan que dedicar gran parte de su tiempo en hacer que su proyecto funcione particularmente en Internet Explorer. Esto es una gran pérdida de tiempo y puede que de dinero, pero que no hay más remedio que afrontar, por esto:

estadisticas_navegadores.jpg

Hay que preocuparse por el volumen de visitas que van a llegar al website por parte de Internet Explorer, porque es tal que no se puede despreciar.

Caso particular

Particularmente, en Sortea2, nuestra página de sorteos, también tuvimos que lidiar con estos problemas, tuvimos que tomar medidas porque la página, que se visualizaba correctamente en firefox, opera, etc., era impresentable en Explorer, las cosas normalmente se montaban unas encima de otras y tal.

Qué cosas dan problemas

Tras haber terminado el diseño de Sortea2, he llegado a la conclusión de que estas cosas son las que dan problemas:

  1. Listas.
  2. Márgenes negativos para posicionar.
  3. Posicionamiento normal.
  4. Desbordamiento de divs.
  5. Imágenes de fondo.

1.  Las listas es lo que habitualmente más problemas da, cuando quieres formatearlas con padding y margin suelen tratar las cosas de diferente manera que los navegadores normales. Por ejemplo, no puedes variar el valor de margin-left en las listas para que estén alineadas a la izquierda con tan solo poner: “margin-left:0px;”.
También suelen dar problemas las listas que se colocan a lo ancho (float:left;), hay que ir probando con qué valores funcionan bien. Por ejemplo, en nuestro menú horizontal, el valor float:left; había que ponerlo en #menu_horizontal li a {} para que funcionase, mientras que en otros lugares se podía colocar directamente en #menu_horizontal li {}.

2. Los márgenes negativos no sé exactamente por qué dan tantos problemas. A veces funcionan, otras veces no, y otras veces hay que poner valores distintos a lo habitual para que se coloque en el lugar que se desea. El mejor ejemplo es en la parte de arriba de sortea2, en la que se ve una diferencia de colocación entre cualquier navegador y Internet Explorer, esto es debido a que a veces los márgenes negativos no funcionaban y tuvimos que intentar colocar las cosas con otros valores.

3. El posicionamiento normal, con “position:relative y absolute” no suele dar muchos quebraderos de cabeza, simplemente algunas veces hay que cambiar los valores un poco para que las cosas cuadren.

4. El desbordamiento de divs tiene su “particularidades” en Internet Explorer cuando se quiere hacer “overflow”. Normalmente al desbordarlo suele tener después un tamaño extraño que cuesta colocar. Esto nos pasó con el div #resultado, que se despliega en la pantalla de sorteos simples al hacer un sorteo, en firefox funciona perfectamente, pero en internet explorer se ve como se contrae un poco cuando la lista de ganadores es demasiado grande y hace que salga el scroll en el div.

5. Las imágenes de fondo se suelen colocar correctamente y tal, el problema es el orden en que se cargan. Por ejemplo, al principio, en las pestañas que se utilizan cuando se utiliza el buscador o en la pantalla de Mis Sorteos, utilizábamos un hover que consistía en otra imagen, Bien, pues firefox y opera cargaban esa imagen antes de que terminara de cargar la página, por lo que el efecto se hacía correctamente; el problema lo daba Internet Explorer, porque se cargaba la imagen en el momento en que se hacía el hover, con lo que había que esperar un tiempo para ver el efecto. Fue tan desastroso el resultado que optamos por subrayar esos links al pasar por encima y listo, de camino lo dejamos igual para ambos navegadores.

Soluciones

La solución por la que optamos nosotros fue:

  • Una hoja de estilos común, en la que las propiedades que funcionasen en ambas estuvieran recogidas.
  • Una hoja de estilos para Internet Explorer, con todas las correcciones posibles para que se mostrase bien la página.
  • Una hoja de estilos para los demás navegadores. Para evitar que unas propiedades se pisaran a otras, optamos por poner en una hoja de estilos independiente todas las propiedades que daban problemas en Explorer, pero con los valores que serían normales.

Ahora bien, ¿cómo sabemos qué navegador está mostrando la página y por lo tanto qué hoja de estilo abrir?
Bien, nuestra página está hecha con PHP, por lo que os voy a mostrar un método para hacerlo con este lenguaje (también se podría hacer con javascript).

Abrir una hoja u otra dependiendo del navegador

Bien, PHP recibe por parte del navegador una serie de variables de servidor (idioma, navegador, versión de este último, etc.), pues tenemos que recoger lo que diga la variable que dice qué navegador está mostrando la página:

$idnavegador = $_SERVER[‘HTTP_USER_AGENT’];

Esto es lo que obtendríamos de firefox:
Mozilla/5.0 (Windows; U; Windows NT 5.1; es-ES; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5

Y esto es lo que nos devuelve Internet Explorer:
Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; InfoPath.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30)

Debe haber alguna manera universal que distinga ambos códigos, porque Internet Explorer da problemas igualmente en su versión 6 como en su versión 7.
Bueno, pues vamos a buscar las diferencias entre ambas cadenas para ver qué podemos sacar que nos diga que estamos en Explorer.

En Mozilla/X.0 no se puede sacar mucho, la verdad, porque es casi idéntico y con PHP no se podría hacer un cálculo. Pero si nos metemos dentro del paréntesis  empezamos a ver cosas interesantes, y ya si nos metemos en ver que hay después del punto y coma, ya lo tenemos.

Uno nos devuelve una U y otro nos dice MSIE X.XX, ahí está la diferencia; pero queremos detectar cualquier explorer, no una versión determinada; bien, pues recoges la palabra MSIE y listo, con un parseo veremos que se puede hacer fácilmente:

$puntocoma = ";";
      
$total_length = strlen($idnavegador);
$puntocoma1 = strpos($idnavegador, $puntocoma);
$navegador = substr($idnavegador, ($puntocoma1 + 2), ($total_length – $puntocoma1));
$navegador = substr($navegador, 0, 5);

Es cierto que puede ser un poco lioso, pero es simple: se busca el punto y coma, se coge lo que hay después de él, y luego se cogen 5 caracteres (con 4 recuerdo que dio problemas una vez) de lo que se haya sacado. Con esto ya tenemos esto: “MSIE “, ¡era lo que queríamos!, bueno, pues ahora con un if abrimos una hoja u otra y ya está todo listo.

if ($navegador == "MSIE ")
echo "\t<link href=’../css/ie-hacks.css’ rel=’stylesheet’ type=’text/css’>\n";
else
echo "\t<link href=\"../css/normal.css\" rel=\"stylesheet\" type=\"text/css\" />\n";

Ya está, ahora ponemos las propiedades que dan problemas en ie-hacks y ya solo se verán en ese navegador.

Conclusión

Internet Explorer es un navegador problemático y tenemos que poner nuestros medios para hacer que esos problemas desaparezcan porque tiene una cuota de mercado espectacular y merece la pena dedicarle tiempo.
Puede que nuestro método de obtención del navegador no sea precisamente la mejor y la más simple, pero lo cierto es que funciona y que jamás ha dado ningún problema.

Consejo final

Como consejo final te diré que cada vez que hagas algún cambio relativamente importante en tu web, si no eres un asiduo a Internet Explorer, que la pruebes ahí antes de seguir con otra cosa, porque luego volver atrás para resolver problemas antiguos es muy molesto.


Escrito por .

Jquery. Plugin útiles y otros no tanto.

Escrito en Jquery, Tutoriales

La página de Sortea2 abunda en el uso de Javascript mediante librerías de Jquery. Quizás a veces los efectos sean hasta molestos (lo sentimos) pero no se puede decir que la página es estática.

Cuando se utiliza Jquery una de las mayores ventajas que se esgrimen es la existencia de innumerables plugins, a cual más útil. Y es verdad. Pero también es complicado adaptarse a cada uno de ellos. Cada programador tiene sus manías, sus ideas propias, su forma de hacer las cosas diferente. El uso de plugins tomados cada uno de un sitio, creados por programadores diferentes, da una absoluta falta de homogeneidad en el código.

Si uno se acostumbra a copiar trozos de código de aquí y de allí, es un script kiddie, un niñato que no sabe programar, sólo mal reutilizar.

Pero ante la ingente cantidad de herramientas tecnológicas, cada una con sus peculiaridades, se debe elegir entre eso o luchar vanamente por tratar de conocer un lenguaje para que pocos días después surja uno nuevo y también imprescindible. Por eso creo que es justificable el copypasteo de código ajeno, aunque a veces obtengamos resultados poco coherentes. Pero operativos.

Siempre luchando por entender las peculiaridades de cada uno de los plugins, a veces uno se frustra al ver que no hay forma de hacer funcionar uno en particular. Esto no se suele comentar mucho, porque se mira la página del desarrollador, con la demo operativa, se da plamas, se guarda el link y luego cae en el más oscuro de los olvidos, sin pensar la utilidad de aquello.

Por eso si vas a desarrollar un plugin para Jquery, te recomendaría en que emplearas el máximo tiempo posible en tener una demo que funcione, con diseño excelente. Que luego el plugin sea operativo o no, es lo de menos porque no se va a usar tanto como parece.

En fin, que paso a detallar los plugins que hemos usado en Sortea2.com, y cuales no y por qué. No trata de ser una lista de «mejores plugins», sino simplemente de plugins que han funcionado.

1. Calendario.

Hemos empleado el plugin datePicker.
Su funcionamiento no es muy complejo de configurar y el resultado es sencillo, admitiendo días y fechas en castellano.
Lo tenemos en la ventana de sorteos programados, para ver la fecha en que se programa un sorteo.

Por contra, aunque existen varios plugins para seleccionar la hora que nos podían haber servido en esa misma página, tras probar con varios siempre obtuvimos resultados poco satisfactorios. No dudo que el plugin funcione, pero para nuestro caso no servía. Y no pedíamos nada especial, simplemente un selector de fechas.

Tanto este TimePicker, com este otro timepicker llegaron a funcionar, pero la hora no había forma de ajustarla al control indicado, siempre aparecía en la parte de abajo de la página. También existe un tercer plugin para seleccionar horas, pero ya es suficiente por el momento.

Seguramente la causa no era otra que algún tag de html mal cerrado por nuestra parte, o una mala configuración de las propiedades. El caso es que tras mucho tiempo perdido, decidimos descartarlo definitivamente.

Preparar un selector de horas y minutos es trivial, ni siquiera hace falta javascript, por lo que al final tomamos ese camino, con el resultado que podéis ver en la página de sorteos avanzados.

Quizás falte coherencia en el diseño entre la selección de fecha y hora. Es algo que podemos mejorar con el tiempo.

2. Cookie.

El que hayamos usado un plugin para manejar las cookies lo veo más como un error y un síntoma de la paranoia que hay en torno al uso de los plugins. Hasta para la tarea más sencilla se buscan utilidades hechas por otros. Creo que si hubiéramos usado javascript puro y duro, habríamos necesitado menos código que con este plugin. Pero al menos permite el uso sencillo de cookies.

En la aplicación tenemos dos cookies, una para controlar el idioma con el que se accede a la página y otra para gestionar las identidades de los usuarios.

El plugin al que me refiero es este.

3. Subida de ficheros mediante Ajax.

Una de las peores ideas que tuvimos al desarrollar la página fue permitir el subir un listado de nombres cargándolo desde un fichero. Es una de estas funcionalidades que consumen mucho tiempo de desarrollo y que luego nadie usa. O peor aún, nadie usa para bien porque son una puerta abierta a las vulnerabilidades.

Al final la hicimos. También en la ventana de sorteos avanzados, se puede cargar una lista de nombres de un fichero, idealmente un .txt en que cada nombre ocupa una línea.

De nuevo aquí tiramos de un plugin que permite hacer una subida de fichero en forma totalmente asíncrona, sin tener que recargar la página y sin causar problemas.

Este es el plugin para subir ficheros con Jquery. Causó numerosos problemas conseguir adaptarlo al funcionamiento de la página, en gran parte porque fue una de las primeras tareas desempeñadas, cuando aún estábamos iniciándonos con el Jquery.

La idea inicial y algo que llegamos a hacer era mostrar la lista de nombres en un espacio dentro de la ventana, para que el usuario pueda ver quiénes participarán en el sorteo. Pero con el tiempo se nos fue acabando el sitio en la ventana y ya era todo muy complicado. Costó quitar una funcionalidad que ya estaba hecha, pero era necesaria una mayor simplicidad.

4. Livequery.

Livequery es posiblemente el plugin más útil de todos los que hemos empleado.

Nuestro problema es que cuando creábamos controles dinámicamente (como las listas de nombres y la de premios, que se amplian conforme el usuario va añadiendo más) era complicado hacer que estos controles heredaran los eventos de los controles iniciales en la página.

La tarea de añadir un listener a los eventos para nuevos controles era un motivo de quebraderos de cabeza. Y sin embargo con usar livequery esto se solucionó automáticamente.

Además fue el plugin más sencillo de implementar, pues es casi trivial. ¡Estamos enamorados de Livequery!

5. Dynacloud.

También tenemos una nube de etiquetas, como toda aplicación que quiera parecer 2.0. Y para ello lo primero era buscar el plugin correspondiente. En este caso, Dynacloud, para hacer una nube de tags.

Pues bien, no conseguimos hacerlo funcionar en la forma en que nos hubiera gustado. De nuevo mil historias, y cuando lográbamos que apareciera algo, estaba fuera de formato o al final de la página. Hicimos la funcionalidad desde cero, no es nada difícil.

6. Sugerir tag.

Finalmente, un plugin para sugerir un tag cuando estás escribiendo un nuevo sorteo. Como se hace en delicious, cuando introduces las primeras letras realiza una consulta y obtiene tags similares ya empleados para crear mayor uniformidad en la información.

No sin algo de sufrimiento, también se pudo poner en marcha con un resultado que consideramos muy bueno.

Como se hacen consultas continuas al servidor, para cada uno de los caracteres, en orden a aliviar la carga sobre la base de datos, optamos por cargar todos los tags en un fichero. De esta forma, no se realiza ninguna consulta a base de datos, simplemente se abre el fichero, se vuelca directamente sobre un array y se buscan las coincidencias.

Este fichero de tags se va actualizando cada cierto tiempo mediante una tarea y santas pascuas. Nos libramos de tener que estar machacando la base de datos realizando consultas poco eficientes (el «like» en SQL no es lo más rápido y directo que digamos).

Como resumen, espero que esta entrada le sirva a alguien de ayuda para ver cómo los plugins te solucionan problemas y ahorran tiempo, pero a veces pierdes mucho tiempo sin poder hacerlos funcionar y otras es más eficaz codificar uno mismo la tarea que necesita.
También puedes ver plugins que a lo mejor no son muy espectaculares, pero son operativos y necesarios para el funcionamiento de una página.


Escrito por .

Zonas horarias y PHP

Escrito en PHP, Tutoriales

Introducción. Métodos sencillos que no funcionan

Cuando escribimos nuestra página de sorteos consideramos que sería útil que los usuarios pudieran realizar un sorteo a una fecha y hora determinadas. Por si desean darle un poco de emoción o dar claridad a un sorteo. No es lo mismo que tú publiques unos resultados sino que digas «en esta página ofrecerán los resultados». Es una forma de mostrar que no influyes en el resultado.

Al querer realizar los sorteos a una hora concreta detectamos que era problemática la situación de cara a los usuarios que no usasen la misma zona horaria que nosotros (que es la de España). Una solución sería dar todas las fechas con nuestro horario. Es exigir a los clientes que se adapten a nuestras condiciones. Aunque hay muchos que optan por ella, me parece inadmisible.

Nuestra primera idea fue la de mostrar un desplegable con zonas horarias, mediante una función sencilla:
Se muestra una zona horaria e internamente se guarda un valor numérico: el número de horas de diferencia respecto de la hora de Madrid.

Lisboa(-1)
Madrid(0)
Canarias(-1)
Nueva York(-6)
México DF (-7)

Esta aproximación es una de las más frecuentes. Es sencilla y fácil de tratar. Cuando un usuario de México solicita que su sorteo sea a las 13:00 horas, convertimos el horario al de España (sumamos 7 horas) y lo guardamos (20:00 horas). Luego cuando ese usuario decida consultar el sorteo, volvemos a realizar la conversión inversa, restando siete horas.

Sin embargo la vida no es tan fácil. Quizás el mayor de los problemas es la maligna DST (Daylight Saving Time, Horario de verano en castellano).

Porque cuando nos acercamos a estos días en que se suma o resta una hora, los resultados pueden ser impredecibles.

a) Hay países que no cambian nunca de horario (sabia decisión). Entonces respecto a ellos habrá veces que España tenga X horas de diferencia y el resto del año X +1 horas.

b) Hay países que cambian de horario en fechas diferentes, como sucede con Estados Unidos. No se puede ignorar a Estados Unidos, que tiene más usuarios de castellano que la propia España.
Estados Unidos es aún más complejo por cuanto las elecciones presidenciales pueden cambiar la fecha en que se realice el cambio de hora. Además, tiene potestad para realizar cambios en el día seleccionado para la modificación horaria, sabemos cuando cambiarán el año que viene, pero dentro de tres años puede cambiar de sistema (ya lo han hecho varias veces). Vamos, que es todo un caso a tratar por separado.

c) Hay países que incluso cambian la regla. Muchos países decidieron dejar de cambiar de hora hace algún tiempo. De cara al futuro no es problemático pero si queremos hacer una correcta conversión de fechas, su situación debe ser tenida en cuenta.

d) Hay países que no tienen zonas horarias exactas. Ya sea porque tengan diferencias de cuarto de hora respecto de una zona (como Nepal, +5:45) o Venezuela (-4:30). Además, el caso de Venezuela, en que dicho cambio de zona horaria es muy actual, es problemático para fechas del pasado reciente.

Por todas estas razones, el método de sumar o restar horas, sencillamente no sirve.


Zonas horarias y PHP

Para nuestro caso, al estar desarrollada la página con PHP, tras buscar soluciones posibles por Internet, sin mucho éxito y tratando de hacer alguna cosa bien, optamos por mirar algunas funciones propias de PHP.

Las funciones propias de PHP para zonas horarias en la versión actual (4.4) son las siguientes:

    # timezone_abbreviations_list
    # timezone_identifiers_list
    # timezone_name_from_abbr
    # timezone_name_get
    # timezone_offset_get
    # timezone_open
    # timezone_transitions_get

No creemos que sea necesario dar una enumeración de las mismas, máxime cuando se puede consultar la completa documentación online. Lo importante es saber que la forma en que PHP guarda las zonas horarias. Aunque hay posibles conversiones de texto (siempre más fáciles si el texto está en inglés), lo suyo es usar los pares Continente/País que presenta PHP en su lista de zonas horarias soportadas.

Por ejemplo:

    Europe/Madrid
    America/Mexico_City
    America/Buenos_Aires
    Atlantic/Canary

La lista de zonas horarias admitidas es extensísima. En muchos casos estas zonas horarias son idénticas, como sucede con Madrid, Berlín o París. De ahí que no tenga mucho sentido mostrar desplegables casi infinitos en sus posibilidades (sólo para la Antártida PHP soporta ¡Ocho zonas horarias!).

Se realiza una selección de países y ciudades representativas. Lo ideal es mostrar junto a dicha ciudad la zona horaria correspondiente para que así un usuario de un lugar recóndito pueda localizar la zona que mejor se adapte a su ubicación:

    Buenos Aires (GMT -3)

    La Paz (GMT -4)
    Caracas (GMT -4.5)
    Montreal (GMT -5)

Desplegable zonas horarias

Desplegable zonas horarias

Por lo tanto, trabajamos con tres valores:

a) El código de zona horaria de PHP: America/Buenos_Aires
b) La descripción para seres humanos de dicha zona: Buenos Aires
c) Un indicativo para usuarios que no encajen exactamente con dicho país: GMT -3

Esto ya sugiere que crear una tabla donde se guarde la información de las zonas horarias puede ser de gran ayuda.

Si te interesa la que usamos en nuestra página, puedes descargártela de aquí, en formato de creación para MySQL (fácilmente adaptable a otras bases de datos).


Zonas horarias. ¿Qué convertimos?

Antes de continuar una buena tarea sobre la que meditar es cómo vamos a guardar las fechas y horas en nuestra base de datos. Si nos centramos en la ciudad en que vivimos, nos podemos encontrar con que el servidor que alberga nuestra página esté ubicado en otra zona horaria. Cierto es que a veces podemos manipular esta hora, pero en hostings compartidos, a veces esto no es posible.

Así, uno puede encontrase con que:

a) Su zona horaria es de Madrid, España.
b) Quiere guardar una fecha y hora de Canarias, España.
c) El servidor está situado en Los Ángeles, Estados Unidos.

Con lo cual los problemas están casi garantizados.

Tampoco hay que volverse loco ante las diferencias de fechas. Salvo que tengas una situación tan especial como la nuestra, en que necesitamos programar tareas futuras, quizás puedas sobrevivir sin problemas y sin saber nada de todo esto. Pero el conocimiento siempre es útil.

En las tablas de datos es común guardar un registro con la fecha y hora de la última modificación. ¿Debemos convertir esa fecha y hora?

Pues salvo que sean tareas críticas, probablemente no. Las fechas de creación de registro bien se pueden quedar en el horario que tenga el servidor. Sólo aquellas que requieran de ser mostradas en pantalla pueden y deben ser tratadas con mayor consideración. En el caso de sortea2.com, las horas a las que se programan los sorteos. Para ellas no sólo se guardará la fecha y hora, sino también la zona horaria en que se realizaron.

Por lo tanto, por sentido común, no es muy lógico guardarlas en una horario tan poco estándar como el de Madrid. Hay dos opciones principales:

    Guardar la hora y decir «son las 14:30 con el horario de Buenos Aires».
    Convertirla al estandar GMT (Hora de Greenwich, Inglaterra) e indicar la zona horaria deseada. «son las 16:30, zona horaria preferida Buenos Aires».

Esta segunda opción nos parece mejor porque permite en un momento dado comparar fechas de inmediato. Del otro modo casi siempre habrá que hacer una conversión antes de poder continuar.

Como resumen: se guardan las horas en estandar GMT, sólo en los casos en que no sean necesarias manipulaciones posteriores.


Zonas horarias y PHP. Las transiciones.

Tras planear el desarrollo de la página, sólo queda un problema: cómo convertir entre dos zonas horarias dadas. No sé cómo lo habrán desarrollado en otros lenguajes de programación, pero en PHP el método es realmente ingenioso y complicado.

Vaya por delante que en internet hay numerosas clases realizadas por otros que explican cómo realizar estos cambios. Hay incluso clases hechas en PEAR que se supone son totalmente estándar. Tras la nefasta experiencia de usar una de ellas y descubrir que estaba mal (no distinguía los cambios de hora), decidí seguir el camino difícil y propio.

Siempre es más fácil usar algo ya hecho y que funcione. Y uno puede vivir dignamente sin conocer las transiciones de PHP.

Pero si quieres continuar, las transiciones son un array de fechas correspondiente a cada una de las zonas horarias. Es un array monstruoso en que se muestran los días del año en que se produce un cambio de hora para dicha zona horaria. Por ejemplo, para la zona horaria Europe/Madrid dice:

( [0] => Array ( [ts] => -1661734800 [time] => 1917-05-05T23:00:00+0000 [offset] => 3600 [isdst] => 1 [abbr] => WEST )

Que traducido al cristiano es algo así como:

  • A la fecha y hora UNIX (segundos desde el 1 de enero de 1970) (correspondiente a GMT, hora estandar de Greenwich) es la de -1661734800).
  • Que se corresponde con la fecha y hora «en inglés» de 1917-05-05 23:00:00+0000.
  • Se produjo un cambio de hora que deja a la hora de Madrid respecto de la de GMT en 3600 segundos (una hora más).
  • Dejando la zona horaria en GMT+1.
  • Quedando en la zona horaria la WEST.

El listado de transiciones de la zona horaria de Madrid nos avisa de cada cambio de hora que se ha producido en la historia, desde el verano de 1917 hasta el año 2037. Si se necesita tratar fechas anteriores o posteriores, aparte de considerar la opción del suicidio, habría que tirar de enciclopedias y realizar un tedioso trabajo manual.

Este extensísimo array es la pieza fundamental para realizar conversiones entre zonas horarias en PHP.

Veamos por ahora cómo obtener el fichero de transiciones para una zona horaria:

$timezone = «Europe/Madrid»;
$timezoneinfo = new DateTimeZone($timezone);
$arraytime = $timezoneinfo->getTransitions();

En la primera línea definimos la zona horaria a tratar, según las zonas horarias que PHP entiende.

En la segunda creamos un objeto de tipo zona horaria, relativo a dicha zona inicial.

En la tercera obtenemos las transiciones: cuándo se produjeron los cambios de hora respecto de GMT para esa zona horaria.

Es curioso ver el fichero de transiciones. Ahora solemos cambiar de hora los sábados de 2:00 am a 3:00 am, pero en 1917 decidieron hacerlo a las 0:00 horas. Se nota que se trasnochaba menos por aquel entonces. Durante la II Guerra Mundial hubo cambios de hora a las 11:00 de la noche. Luego se volvió a las 12:00 y en los ochenta se pasó a hacerlo a la 1:00 am.

Como podemos ver, y evitemos dispersarnos, los cambios de hora son una verdadera pesadilla en lo que al horario de verano se refiere.

Con el array de transiciones ya es más o menos fácil. Si tenemos una fecha en formato GMT, junto con una zona horaria deseada y deseamos convertirla a la hora propiamente dicha, basta con:

Tomamos la hora en GMT y recorremos el array de transiciones, hasta que encontramos una hora superior a la nuestra.
Por ejemplo, si tenemos las 20:00 del 15 de Enero de 2012, y el horario deseado es el de Madrid, vemos que el último cambio de horas se produce el 30 de octubre del 2011. Y que entonces España tendrá una hora más que la hora GMT. Esto sucederá así hasta el 25 de Marzo del 2012, por lo que efectivamente, si queremos convertir esa hora en GMT a hora de Madrid, debemos sumarle una hora (3600 segundos) más.

[110] => Array ( [ts] => 1319936400 [time] => 2011-10-30T01:00:00+0000 [offset] => 3600 [isdst] => [abbr] => CET )
[111] => Array ( [ts] => 1332637200 [time] => 2012-03-25T01:00:00+0000 [offset] => 7200 [isdst] => 1 [abbr] => CEST )

Este proceso es mucho más delicado si la hora está en el mismo borde del cambio de hora. Por ejemplo, si queremos convertir las 01:00 horas del 25 de marzo de 2012, de horario GMT al horario de Madrid.

Para afinar completamente la regla es la siguiente:

  • Partimos de una hora.
  • Recorremos las transiciones hasta pasarnos de dicha hora.
  • Miramos el registro anterior. Obtenemos la diferencia de segundos.
  • Se la sumamos a la hora que teníamos. Si nos hemos pasado de la siguiente transición, tomamos esa siguiente, sino (lo habitual) nos quedamos con la que teníamos.

La verdad es que esto se entiende mejor con el código que escrito con palabras:

$timezoneinfo = new DateTimeZone($timezone);
$arraytime = $timezoneinfo->getTransitions();

$i = 1;

foreach ($arraytime as $transicion)
{
$i ++;
if ($transicion[ts] + $arraytime[$i][offset] >= $gmttime)
{
$indice = $i –1;
break;
}
}
$newtime = $gmttime + $arraytime[$indice][offset];

Nos recorremos el array de transiciones. Cuando la transición + las horas de diferencia sean mayores que nuestra fecha de partida, quiere decir que hemos llegado y es el momento de tomar ese valor, sumarlo a la fecha que teníamos y voilá, ya hemos convertido la fecha.

Zonas horarias y PHP. Funciones de conversión.

Básicamente ya hemos expuesto todo el proceso. Lo único necesario son dos funciones: una que parta de una zona horaria y una hora expresada en dicha zona horaria y la transforme en GMT y otra que partiendo de una hora en GMT y una zona horaria, realice la transformación inversa.

Pueden servir las dos siguientes funciones:

function of_gmttolocaltime ($gmttime, $timezone)
{
$timezoneinfo = new DateTimeZone($timezone);
$arraytime = $timezoneinfo->getTransitions();

$i = 1;

foreach ($arraytime as $transicion)
{
$i ++;
if ($transicion[ts] + $arraytime[$i][offset] >= $gmttime)
{
$indice = $i –1;
break;
}
}
$newtime = $gmttime + $arraytime[$indice][offset];
return $newtime;
}

/**
* Entrada. Una hora en formato UNIX de una zona horaria, y el nombre de la zona horaria.
* Salida. La hora convertida a GMT.
*/

function of_localtimetogmt ($localtime, $timezone)
{
$timezoneinfo = new DateTimeZone($timezone);
$arraytime = $timezoneinfo->getTransitions();

$i = –1;

foreach ($arraytime as $transicion)
{
$i ++;
if ($transicion[ts] – $arraytime[$i][offset]>= $localtime)
{
$indice = $i –1;
break;
}
}
$newtime = $localtime – $arraytime[$indice][offset];
return $newtime;
}

Las horas siempre se expresan en horario UNIX. ¡Bastante quebradero de cabeza dan las zonas horarias para sufrir aún más con los formatos locales de fecha y hora!

Creo que ha quedado todo bien expresado pero si tienes alguna duda o aclaración déjala en los comentarios. Las correcciones también son bienvenidas.

Actualización. Tras dos años usando este sistema hemos visto bastantes claroscuros que exponemos en esta otra entrada: problemas con las zonas horarias y PHP.


Escrito por .