Obteniendo la fecha y la hora actuales

12 comentarios

En Firebird tenemos varias formas de obtener las fechas y las horas actuales, ellas son:

  • CURRENT_TIMESTAMP
  • CURRENT_DATE
  • CURRENT_TIME
  • ‘NOW’

Con CURRENT_TIMESTAMP se obtienen la fecha y la hora. Por ejemplo:

FECHAHORA01Captura 1. Si haces clic en la imagen la verás más grande

Con CURRENT_DATE obtenemos la fecha actual, por ejemplo:

FECHAHORA02

Captura 2. Si haces clic en la imagen la verás más grande

Con CURRENT_TIME obtenemos la hora actual, por ejemplo:

FECHAHORA03Captura 3. Si haces clic en la imagen la verás más grande

Fíjate que la precisión es de segundos, de la forma anterior podemos obtener la hora, los minutos, y los segundos, ¿pero y si necesitamos mayor precisión? Para eso a la variable de contexto CURRENT_TIME le podemos enviar entre paréntesis un número entre 0 y 3, significando la cantidad de decimales que queremos obtener. Por ejemplo:

FECHAHORA04

Captura 4. Si haces clic en la imagen la verás más grande

FECHAHORA05

Captura 5. Si haces clic en la imagen la verás más grande

FECHAHORA06

Captura 6. Si haces clic en la imagen la verás más grande

Si observas las capturas 4, 5, y 6 notarás que la cantidad de decimales significativos va aumentando, de acuerdo al parámetro que se le envió a la variable de contexto CURRENT_TIME.

1 = 1 decimal (o sea, una precisión de décimas de segundo)

2= 2 decimales (o sea, una precisión de centésimas de segundo)

3 = 3 decimales (o sea, una precisión de milésimas de segundo)

Con ‘NOW’ podemos obtener la fecha, la hora, o la fecha y hora, actuales. Por ejemplo:

FECHAHORA07

Captura 7. Si haces clic en la imagen la verás más grande

FECHAHORA08

Captura 8. Si haces clic en la imagen la verás más grande

FECHAHORA09

Captura 9. Si haces clic en la imagen la verás más grande

Como puedes ver, la precisión de la hora con ‘NOW’ siempre es de milisegundos.

Usando las fechas y horas dentro de un stored procedure, trigger o execute block

Si necesitamos utilizar los valores de las fechas u horas actuales dentro del código fuente, entonces podemos obtener sus valores de la misma manera a como lo haríamos con las columnas de las tablas, algo como:

FECHAHORA10

Captura 10. Si haces clic en la imagen la verás más grande

Desde luego que también podríamos obtener el valor de ‘NOW’ de la forma anterior.

Diferencias entre CURRENT_TIMESTAMP, CURRENT_DATE, CURRENT_TIME y ‘NOW’

Quizás te hayas preguntado ¿para qué existe la palabra ‘NOW’ siendo que las fechas y las horas podemos obtenerlas con las variables de contexto?

Bien, la diferencia es que dentro de un stored procedure, trigger o execute block las variables de contexto siempre devolverán el mismo valor. Por ejemplo, si en la primera línea de un stored procedure la variable de contexto CURRENT_TIME devolvía ’14:24:57′ y ese stored procedure demoró en finalizar 40 segundos, en la última línea de ese stored procedure la variable de contexto CURRENT_TIME seguirá devolviendo ’14:24:57′

No cambió su valor. Dentro de un stored procedure, trigger, o execute block, las variables de contexto siempre tienen exactamente el mismo valor.

Pero ‘NOW’ sí cambia su valor dentro de un stored procedure, trigger o execute block. Y con ‘NOW’ siempre tenemos una precisión de milisegundos.

Artículos relacionados:

El índice del blog Firebird21

El foro del blog Firebird21

 

Guardando y leyendo una columna de tipo TIMESTAMP en VFP

4 comentarios

Si programas usando el lenguaje Visual FoxPro entonces puedes tener dos dudas:

  1. ¿Cómo hago para guardar un dato en una columna de tipo TIMESTAMP?
  2. ¿Cómo hago para leer una columna de tipo TIMESTAMP?

1. Guardando una columna de tipo TIMESTAMP

Las columnas TIMESTAMP del Firebird se guardan como una fecha, un espacio en blanco, y una hora. Algo como:

03/08/2014 11:48:25

Que significa: «tres de agosto de dos mil catorce, once horas, cuarenta y ocho minutos, veinticinco segundos».

En Visual FoxPro para convertir un string en un timestamp se usa la función CTOT(). Veamos un ejemplo:

MiFecha = DATE()
MiHora ="11:48"
MiColumna = CTOT(DTOC(MiFecha) + " " + MiHora)

La función DTOC() significa «date to character» y convierte una fecha a caracter. La función CTOT() significa «character to timestamp» y convierte un caracter a timestamp.

Hay que usar la función DTOC() para poder concatenar la fecha, el espacio en blanco, y la hora.

Fíjate que no es obligatorio completar los segundos, si no pones los segundos el Firebird asumirá que es «00»

Otro ejemplo:

MiFecha = DATE()
MiHora ="11:48:25"
MiColumna = CTOT(DTOC(MiFecha) + " " + MiHora)

 Aquí sí se establecieron los segundos. Es opcional.

Otro ejemplo:

MiFecha = DATE(2014, 8, 3)
MiHora = TIME()
MiColumna = CTOT(DTOC(MiFecha) + " " + MiHora)

 Aquí se usó la función DATE() para establecer la fecha deseada y la función TIME() para establecer la hora deseada.

2. Leyendo una columna de tipo TIMESTAMP

Muy bien, ya has guardado una columna de tipo TIMESTAMP en tu tabla del Firebird, pero ahora quieres leer el contenido de esa columna. ¿Cómo lo haces?

Aquí tienes dos posibilidades:

  1. Dejar el resultado en una variable de tipo timestamp
  2. Separar la columna timestamp para tener en una variable la fecha y en otra variable la hora

Si la que te interesa es la primera posibilidad nada tienes que hacer, la columna que has leído de tu tabla Firebird ya está en el formato adecuado y puedes usarla sin problemas.

Si lo que te interesa es separar el contenido de la columna de tipo TIMESTAMP para tener en una variable la fecha y en otra variable la hora esto es lo que debes escribir:

MiFecha = TTOD(MiColumna)
MiHora = TTOC(MiColumna, 2)

La función TTOD() significa «timestamp to date» y retorna la parte de la fecha de un TIMESTAMP

La función TTOC() significa «timestamp to character» y cuando su segundo argumento es el número 2 retorna la parte de la hora de un TIMESTAMP.

Conclusión:

Usar columnas y variables de tipo TIMESTAMP entre Visual FoxPro y Firebird es sencillo, ninguna complicación, solamente hay que recordar cuales son las funciones adecuadas para usar, eso es todo.

Artículos relacionados:

El índice del blog Firebird21

El foro del blog Firebird21

Obteniendo el TIMESTAMP de la última actualización a una tabla

4 comentarios

Este artículo está basado en el FAQ1 escrito por Karol Bieniaszewski:

http://itstop.pl/en-en/Porady/Firebird/FAQ1/TABLE-MODIFICATION-TIMESTAMP

A veces puede ser útil para nosotros conocer cual fue la última vez que se realizó un INSERT, UPDATE, o DELETE a una tabla. Tenemos 3 formas de obtener esa información:

  1. Guardar el TIMESTAMP en cada fila de la tabla
  2. Crear otra tabla y guardar en ella el Nombre de la Tabla y el TIMESTAMP
  3. Usar generadores

Opción 1. Guardar el TIMESTAMP en cada fila de la tabla

La opción 1. es muy fácil de implementar (simplemente le agregamos una columna TIMESTAMP a la tabla) y también puede ser muy conveniente porque sabremos no solamente cuando se actualizó por última vez la tabla sino también cada fila de ella. Lo único que necesitamos hacer es un SELECT y en la cláusula ORDER BY elegimos la columna donde guardamos el TIMESTAMP y la ordenamos descendentemente con DESC. Por ejemplo:

SELECT
   PRD_TIMEST
FROM
   PRODUCTOS
ORDER BY
   PRD_TIMEST DESC
ROWS
   1

En este caso, a la columna donde guardamos el TIMESTAMP la hemos llamado PRD_TIMEST. El gran problema con esta solución es cuando las filas de nuestra tabla se cuentan por muchos millones. Si no tenemos un índice DESC sobre la columna PRD_TIMEST entonces obtener el resultado puede demorar una eternidad. Y si tenemos un índice DESC sobre esa columna entonces cada INSERT o UPDATE modificará el contenido de esa columna y también al índice asociado, lo cual causará una demora. En otras palabras, si no tenemos un índice DESC el SELECT será muy lento, y si tenemos un índice DESC entonces el INSERT y el UPDATE serán lentos. No es una buena situación cuando la tabla tiene millones de filas.

Opción 2. Crear otra tabla y guardar en ella el Nombre de la Tabla y el TIMESTAMP

Supongamos que creamos una tabla llamada TIEMPOS con las columnas TIE_NOMBRE y TIE_TIMEST, para guardar en esas columnas el nombre de la tabla y el TIMESTAMP de su última modificación.

Aquí podemos tener problemas de lentitud si se hacen muchos INSERT y UPDATE simultáneamente a la tabla original (a la tabla PRODUCTOS, en nuestro ejemplo). ¿Por qué? porque nuestra tabla TIEMPOS tendrá problemas de concurrencia cuando dos o más transacciones quieran actualizar la misma fila. Supongamos que 20 usuarios están actualizando a la tabla PRODUCTOS, entonces cada uno de esos 20 usuarios quiere guardar el TIMESTAMP de su modificación a la tabla PRODUCTOS en una fila de la tabla TIEMPOS. Como la fila es una sola (porque el TIMESTAMP de la tabla PRODUCTOS se guarda en una sola fila de la tabla TIEMPOS) entonces 1 solo usuario podrá guardar el TIMESTAMP, los restantes 19 tendrán que esperar. Luego, el usuario 2 modifica el TIMESTAMP y los restantes 18 tienen que seguir esperando. Y así sucesivamente. No es una buena situación tampoco.

Opción 3. Usando generadores

Esta solución es más complicada de implementar que las dos anteriores pero tiene la gran ventaja de que es rapidísima en todas las situaciones.

Paso 1. Crear un generador para la tabla original

CREATE GENERATOR ULTIMA_MODIFICACION_PRODUCTOS

En este generador se guardará el TIMESTAMP que nos interesa

Paso 2. Crear un trigger

CREATE TRIGGER AIU_PRODUCTOS FOR PRODUCTOS
   ACTIVE AFTER INSERT OR UPDATE
   POSITION 1
AS
   DECLARE VARIABLE ltTimeStamp TIMESTAMP;
   DECLARE VARIABLE lnNumero    BIGINT;
BEGIN

   ltTimeStamp = (SELECT CURRENT_TIMESTAMP FROM RDB$DATABASE);

   lnNumero = CAST(EXTRACT(YEAR        FROM ltTimeStamp) ||
                   LPAD(EXTRACT(MONTH  FROM ltTimeStamp), 2, '0') ||
                   LPAD(EXTRACT(DAY    FROM ltTimeStamp), 2, '0') ||
                   LPAD(EXTRACT(HOUR   FROM ltTimeStamp), 2, '0') ||
                   LPAD(EXTRACT(MINUTE FROM ltTimeStamp), 2, '0') ||
                   LPAD(CAST(EXTRACT(SECOND FROM ltTimeStamp) * 10000 AS INTEGER), 6, '0')
              AS BIGINT);

   lnNumero = GEN_ID(ULTIMA_MODIFICACION_PRODUCTOS, -GEN_ID(ULTIMA_MODIFICACION_PRODUCTOS, 0) + lnNumero);

END;

Lo que hace este trigger es:

  1. Obtener la fecha y hora actuales
  2. Convertir la fecha y hora en un número BIGINT
  3. Actualizar el generador para guardar en él el número BIGINT obtenido en el punto 2.

Cuando finaliza el trigger tendremos en el generador ULTIMA_MODIFICACION_PRODUCTOS la fecha y la hora en que se realizó el último INSERT o el último UPDATE a la tabla PRODUCTOS.

Por supuesto que si lo deseamos podemos tener tres generadores: uno para guardar los INSERT, otro para guardar los UPDATE y otro para guardar los DELETE, eso ya dependerá de nosotros.

Paso 3. Convertir el valor BIGINT del generador en un TIMESTAMP

El generador cuyo valor establecimos con el trigger será algo así: 201403112149285420

O sea, año 2014, mes 03, día 11, hora 21, minutos 49, segundos 28, milisegundos 5420

Pero en general no nos interesará ver un número entero sino un TIMESTAMP y para ello necesitamos escribir un stored procedure seleccionable que se encargue de convertir al número BIGINT en un TIMESTAMP.

CREATE PROCEDURE OBTENER_TIMESTAMP(
   tnGenerador BIGINT)
RETURNS(
   fttTimeStamp TIMESTAMP)
AS
BEGIN

   IF (tnGenerador = 0) THEN
      fttTimeStamp = NULL;
   ELSE
      fttTimeStamp = CAST(SUBSTRING(tnGenerador FROM  1 FOR 4) || '-' ||
                          SUBSTRING(tnGenerador FROM  5 FOR 2) || '-' ||
                          SUBSTRING(tnGenerador FROM  7 FOR 2) || '-' ||
                          SUBSTRING(tnGenerador FROM  9 FOR 2) || '-' ||
                          SUBSTRING(tnGenerador FROM 11 FOR 2) || '-' ||
                          SUBSTRING(tnGenerador FROM 13 FOR 2) || '-' ||
                          SUBSTRING(tnGenerador FROM 15 FOR 4)
                     AS TIMESTAMP);

   SUSPEND;

END;

Paso 4. Obtener el valor del generador

Ahora, queremos conocer el TIMESTAMP de la última modificación a la tabla PRODUCTOS, lo conseguimos con:

SELECT
   fttTimeStamp
FROM
   OBTENER_TIMESTAMP(GEN_ID(ULTIMA_MODIFICACION_PRODUCTOS, 0))

Y obtendremos algo como:

TIMESTAMP1

Captura 1. Si haces clic en la imagen la verás más grande

que es justamente lo que estábamos necesitando.

Conclusión:

Usar el método de los generadores para obtener el TIMESTAMP de la última modificación a una tabla es lo más rápido que podemos tener. Si la tabla no tiene muchas filas o si se la actualiza raramente entonces se podría emplear cualquiera de los otros dos métodos también, pero en tablas grandes y que son actualizadas muy frecuentemente el mejor método es el de los generadores.

Artículo relacionado:

El índice del blog Firebird21

Bases de datos shareware (2)

Deja un comentario

En este artículo ya habíamos visto las ventajas de tener una Base de Datos shareware, las dos formas usuales de hacerla, y los trucos que los usuarios tramposos podrían intentar:

Bases de datos shareware

Ahora explicaremos mejor el funcionamiento de esas dos formas.

Protección por fecha

Se le permite al usuario usar la aplicación hasta que llegue una cierta fecha, después ya no podrá usarla. Por ejemplo si instaló la aplicación el 24 de enero de 2014 podrá usarla hasta el 24 de febrero de 2014. Si quiere usarla después, entonces tendrá que pagar.

Aquí lo que debemos impedir es que si le cambia la fecha a la computadora la aplicación siga funcionando. Por ejemplo, el día 25 de febrero el usuario no puede usar la aplicación, le cambia la fecha a su computadora y le coloca 1 de febrero de 2014. A pesar de haber cambiado la fecha no debería poder usar la aplicación. Debemos detectar ese cambio y actuar en consecuencia.

¿Cómo protegemos por fecha?

Guardamos en una tabla de nuestra Base de Datos o en un archivo externo a ella, tres valores: la fecha inicial, la fecha final, y la fecha última.

La fecha inicial es el primer día que puede usar la aplicación. Por ejemplo el 24 de enero de 2014

La fecha final es el último día que puede usar la aplicación. Por ejemplo el 24 de febrero de 2014

La fecha última, cuando se instala la aplicación es igual a la fecha inicial. Después, cada vez que se ejecuta la aplicación se verifica si la fecha del Servidor es igual o posterior a la fecha última. Si es así, se actualiza la fecha última.

Entonces, si el 25 de febrero el usuario ejecuta la aplicación el valor de fecha última será 25 de febrero. Si le cambia la fecha a su computadora y le pone 1 de febrero, fecha última seguirá siendo 25 de febrero, porque el valor de fecha última jamás puede retroceder. El nuevo valor de fecha última solamente puede ser igual o posterior al valor anterior de fecha última.

En nuestra rutina de validación verificamos:

  1. Que la fecha de la computadora sea mayor o igual que fecha inicial y que también sea menor o igual a fecha final
  2. Que la fecha última sea mayor o igual que fecha inicial y que también sea menor o igual a fecha final

Podemos mejorar el algoritmo si en vez de guardar solamente la fecha guardamos la fecha y la hora. Inclusive lo mejoraríamos aún más si nos aseguramos que cada vez que se ejecuta la aplicación el tiempo aumente por lo menos en 5 minutos.

Primera ejecución del sistema: 24 de enero de 2014, 16:12

Segunda ejecución del sistema, como mínimo debería ser el 24 de enero de 2014 a las 16:17, ó sea 5 minutos después que la ejecución anterior. Fíjate que no importa cuando el usuario realmente ejecutó la aplicación, lo que nos aseguramos es que siempre haya un incremento de por lo menos 5 minutos. Si la segunda ejecución fue el 24 de enero a las 16:50, en fecha última tendremos guardado 24 de enero y 16:50, pero si la segunda ejecución fue el 24 de enero a las 16:13 en fecha última tendremos guardado 24 de enero y 16:17 porque siempre incrementamos como mínimo 5 minutos.

Por supuesto que no es obligatorio sumarle 5 minutos, podrías sumarle 10 minutos, 30 minutos, 60 minutos, los que te parezcan. La idea es que siempre que se ejecuta la aplicación la fecha y hora se incrementen, que jamás puedan mantenerse igual o retroceder.

De esta manera, tarde o temprano, haga el usuario lo que haga, se sobrepasará la fecha última y la aplicación dejará de funcionar.

Protección por cantidad de ejecuciones

Esto es mucho más fácil de programar que la protección por fechas. Simplemente guardamos en una tabla de nuestra Base de Datos o en un archivo externo la cantidad de veces que el usuario ejecutó nuestra aplicación. Cada vez que la ejecuta incrementamos esa cantidad en 1.

Cuando instala la aplicación, el contador es 0. La primera vez que la ejecuta, el contador es 1. La segunda vez el contador es 2. Y así sucesivamente. Cuando se llegue a la cantidad predeterminada (por ejemplo: 25), la aplicación dejará de funcionar.

Usando ambas protecciones

Para que nuestra aplicación esté más protegida lo mejor es protegerla con ambos métodos: fecha y cantidad.

Siguiendo con nuestro ejemplo, la aplicación dejará de funcionar el 25 de febrero de 2014 ó cuando haya sido ejecutada 25 veces, lo que ocurra primero.

Encriptando los valores

Evidentemente ninguna protección será efectiva si el usuario puede ver y cambiar las fechas o las cantidades de ejecución. Esos valores deben encontrarse encriptados (es decir, ilegibles para quienes no conozcan la clave) y cuanto más ocultos, mejor.

Un lugar donde tradicionalmente se guardan esos valores es en el Registro del Windows. Muy pocos usuarios conocen el Registro del Windows, menos aún han entrado a curiosearlo y muchos menos aún se atreven a cambiar algo por su propia cuenta.

Otro lugar es en la propia Base de Datos. Si tienes una tabla de un solo registro donde guardas la configuración o algo similar entonces puedes aprovechar y agregarle una columna a esa tabla. En esa nueva columna guardas (convenientemente encriptadas, por supuesto) la fecha inicial, la fecha final, la fecha última y la cantidad de ejecuciones.

Recuperando los valores

Así como has guardado las fechas y la cantidad de ejecuciones encriptadas, debes poder desencriptarlas para realizar las verificaciones y determinar si la aplicación puede seguir usándose o no.

Esto puedes realizarlo en dos lugares:

  1. En tu aplicación. Es decir en el programa que escribiste con Visual FoxPro, Visual Basic, C, C++, Delphi, Java, etc.
  2. En un trigger de tu Base de Datos. El usuario debe tener solamente la versión compilada de ese trigger, no debe tener el código fuente o de nada te servirán las protecciones.

Lo ideal y lo correcto por lo tanto es que verifiques que tu aplicación puede ejecutarse, en ambos lugares: en tu aplicación y en un trigger. De esa manera aunque el usuario llegara a descubrir y evitar la protección que se encuentra en un lugar aún le faltaría descubrir y evitar la protección que se encuentra en el otro. Y si debe trabajar más para conseguirlo es más probable que desista de su intención. Y entonces la protección habrá cumplido su misión.

En el siguiente artículo ya veremos como implementar lo expuesto hasta acá.

Artículos relacionados:

Bases de datos shareware

El índice del blog Firebird21

DATEADD()

1 comentario

Descripción: Agrega el especificado número de años, meses, semanas, días, horas, minutos, segundos o milisegundos a una fecha/hora.

Tipo de resultado: Date, Time, o Timestamp

Sintaxis:

DATEADD(argumento)

Ejemplos:


SELECT
   DATEADD(20 DAY TO CURRENT_DATE)
FROM
   RDB$DATABASE

SELECT
   DATEADD(-5 HOUR TO CURRENT_TIME)
FROM
   RDB$DATABASE

SELECT
   DATEADD(MINUTE, 42, TIME 'NOW')
FROM
   RDB$DATABASE

SELECT
   DATEADD(-40 WEEK TO FECHA_NACIMIENTO)
FROM
   RDB$DATABASE

.