lunes, 9 de mayo de 2011

Fechas: Juliana, Gregoriana, DB2...

Para aquellos que habéis estado en más de un proyecto, seguro que os ha pasado que en cada sitio almacenan las fechas de una manera distinta. En este artículo vamos a ver las formas más comunes de almacenar fechas y como pasarlas de uno a otro formato.

Fecha DB2
Es aquella que tiene formato "DATE" en la correspondiente tabla.
Su definición sería: AAAA-MM-DD

Ejemplos
COBOL: 01  WX-FECHA-DB2 PIC X(10) VALUE '2011-01-01'.
PL/I : DCL WX_FECHA_DB2 CHAR(10)  INIT ('2011-01-01');


Fecha Juliana
En tabla se tendría formato DECIMAL (7), pues se trata de un número de 7 dígitos. La información que almacena es el año de 4 dígitos, y el día del año entre 1 y 365. Su definición sería: AAAADDD

Ejemplos
COBOL: 01  WX-FECHA-JUL PIC 9(7)   VALUE 2011001.
PL/I : DCL WX_FECHA_JUL PIC '(7)9' INIT (2011001);


Fecha gregoriana
En tabla tendría formato DECIMAL (8), pues se trata de un número de 8 dígitos. La información que se almacena es el año de 4 dígitos, el mes y el día del mes.
Su definición sería: AAAAMMDD

Ejemplos
COBOL: 01  WX-FECHA-GREG PIC 9(8)   VALUE 20110101.
PL/I : DCL WX_FECHA_GREG PIC '(8)9' INIT (20110101);


Pero a la hora de mostrar una fecha por pantalla, no se usa ninguno de esos formatos, sino el más entendible por el usuario: el mítico DD-MM-AAAA (01-01-2011).
Ya sea con guiones o con barras, la transformación a este formato suele ser común en los programas online.
Vamos a ver como transformaríamos cada uno de los formatos anteriores al formato de pantalla.

De AAAA-MM-DD a DD-MM-AAAA
(...)
WORKING-STORAGE SECTION.
01 WX-FECHA-DB2.
   05 WX-AAAA-DB2    PIC 9(4).
   05 FILLER         PIC X.
   05 WX-MM-DB2      PIC 9(2).
   05 FILLER         PIC X.
   05 WX-DD-DB2      PIC 9(2).

01 WX-FECHA-ONLINE.
   05 WX-DD-ONLINE   PIC 9(2).
   05 FILLER         PIC X VALUE '-'.
   05 WX-MM-ONLINE   PIC 9(2).
   05 FILLER         PIC X VALUE '-'.
   05 WX-AAAA-ONLINE PIC 9(4).

PROCEDURE DIVISION.
 

     MOVE '1999-31-12' TO WX-FECHA-DB2

     MOVE WX-AAAA-DB2  TO WX-AAAA-ONLINE
     MOVE WX-MM-DB2    TO WX-MM-ONLINE
     MOVE WX-DD-DB2    TO WX-DD-ONLINE

     DISPLAY 'FECHA ONLINE:' WX-FECHA-ONLINE

     STOP RUN
     .



Como veis este cambio no tiene mucha ciencia^^

De AAAAMMDD a DD-MM-AAAA
(...)
WORKING-STORAGE SECTION.
01 WX-FECHA-GREG.
   05 WX-AAAA-8      PIC 9(4).
   05 WX-MM-8        PIC 9(2).
   05 WX-DD-8        PIC 9(2).

01 WX-FECHA-ONLINE.
   05 WX-DD-ONLINE   PIC 9(2).
   05 FILLER         PIC X VALUE '-'.
   05 WX-MM-ONLINE   PIC 9(2).
   05 FILLER         PIC X VALUE '-'.
   05 WX-AAAA-ONLINE PIC 9(4).

PROCEDURE DIVISION.

     MOVE 19991231  TO WX-FECHA-8
 
     MOVE WX-AAAA-8 TO WX-AAAA-ONLINE
     MOVE WX-MM-8   TO WX-MM-ONLINE
     MOVE WX-DD-8   TO WX-DD-ONLINE

     DISPLAY 'FECHA ONLINE:' WX-FECHA-ONLINE

     STOP RUN
     .


Veis que es prácticamente lo mismo que en el caso anterior.

También puede ser necesario transformar las fechas entre el formato juliano y el gregoriano:

De AAAAMMDD a AAAADDD
(...)
WORKING-STORAGE SECTION.
01 WX-FECHA-JUL.
   05 WX-AAAA-JUL PIC 9(4).
   05 WX-DDD-JUL  PIC 9(3).

01 WX-FECHA-GREG.
   05 WX-AAAA-8   PIC 9(4).
   05 WX-MM-8     PIC 9(2).
   05 WX-DD-8     PIC 9(2).

PROCEDURE DIVISION.

     MOVE 20111231  TO WX-FECHA-GREG

     MOVE WX-AAAA-8 TO WX-AAAAA-JUL
     MOVE WX-DD-8   TO WX-DD-JUL

     IF WX-MM-8 EQUAL 12
        ADD 30      TO WX-DD-JUL
        SUBSTRACT 1 FROM WX-MM-8
     END-IF

     IF WX-MM-8 EQUAL 11
        ADD 31      TO WX-DD-JUL
        SUBSTRACT 1 FROM WX-MM-8
     END-IF

     IF WX-MM-8 EQUAL 10
        ADD 30      TO WX-DD-JUL
        SUBSTRACT 1 FROM WX-MM-8
     END-IF

     IF WX-MM-8 EQUAL 9
        ADD 31      TO WX-DD-JUL
        SUBSTRACT 1 FROM WX-MM-8
     END-IF

     IF WX-MM-8 EQUAL 8
        ADD 31      TO WX-DD-JUL
        SUBSTRACT 1 FROM WX-MM-8
     END-IF

     IF WX-MM-8 EQUAL 7
        ADD 30      TO WX-DD-JUL
        SUBSTRACT 1 FROM WX-MM-8
     END-IF

     IF WX-MM-8 EQUAL 6
        ADD 31      TO WX-DD-JUL
        SUBSTRACT 1 FROM WX-MM-8
     END-IF

     IF WX-MM-8 EQUAL 5
        ADD 30      TO WX-DD-JUL
        SUBSTRACT 1 FROM WX-MM-8
     END-IF

     IF WX-MM-8 EQUAL 4
        ADD 31      TO WX-DD-JUL
        SUBSTRACT 1 FROM WX-MM-8
     END-IF

     IF WX-MM-8 EQUAL 3
        ADD 28      TO WX-DD-JUL
        SUBSTRACT 1 FROM WX-MM-8
     END-IF

     DIVIDE WX-AAAA-8 BY 4 GIVING WX-AAAA-8 REMAINDER WX-RESTO

     IF WX-RESTO EQUAL ZEROES
        ADD 1       TO WX-DDD-JUL
     END-IF

     IF WX-MM-8 EQUAL 2
        ADD 31      TO WX-DD-JUL
     END-IF

     DISPLAY 'FECHA JULIANA (YYYYDDD):' WX-FECHA-JUL

     STOP RUN
     .



Aquí ya se complica un poco la cosa. Normalmente suelen existir rutinas generales para hacer este formateo. Por ejemplo, una rutina que recibe la fecha en formato AAAAMMDD por linkage y la devuelve en formato AAAADDD.

De AAAADDD a AAAAMMDD
(...)
WORKING-STORAGE SECTION.
01 TABLA-MM-DD.
   05 MM-01       PIC 9(3) COMP-3 VALUE 0.
   05 MM-02       PIC 9(3) COMP-3 VALUE 31.
   05 MM-03       PIC 9(3) COMP-3 VALUE 59.
   05 MM-04       PIC 9(3) COMP-3 VALUE 90.
   05 MM-05       PIC 9(3) COMP-3 VALUE 120.
   05 MM-06       PIC 9(3) COMP-3 VALUE 151.
   05 MM-07       PIC 9(3) COMP-3 VALUE 181.
   05 MM-08       PIC 9(3) COMP-3 VALUE 212.
   05 MM-09       PIC 9(3) COMP-3 VALUE 243.
   05 MM-10       PIC 9(3) COMP-3 VALUE 273.
   05 MM-11       PIC 9(3) COMP-3 VALUE 304.
   05 MM-12       PIC 9(3) COMP-3 VALUE 334.

01 TABLA-MM-DD-R REDEFINES TABLA-MM-DD.
   05 MM-DIAS OCCURS 12 TIMES
                  PIC 9(3) COMP-3.
01 WX-ANO-BIS     PIC S9(3) COMP-3.
   88 ANO-BIS-SI                  VALUE 0.
01 WX-FIL9        PIC S9(3) COMP-3.
01 WX-FECHA-JUL.
   05 WX-AAAA-JUL PIC 9(4).
   05 WX-DDD-JUL  PIC 9(3). 

01 WX-FECHA-GREG.
   05 WX-AAAA-8   PIC 9(4).
   05 WX-MM-8     PIC 9(2).
   05 WX-DD-8     PIC 9(2).

PROCEDURE DIVISION.
     DISPLAY 'FECHA JULIANA: 2011059'

     MOVE 2011059     TO WX-FECHA-JUL
     MOVE WX-AAAA-JUL TO WX-AAAA-8 


*--- COMPROBAR AÑO BISIESTO: RESTO = 0 = AÑO BISIESTO
     DIVIDE WX-AAAA-JUL BY 4 GIVING WX-FIL9 REMAINDER WX-ANO-BIS

*--- CORREGIR VALORES MM-DD PARA AÑO BISIESTO
     IF ANO-BIS-SI
        ADD 1 TO MM-03
                 MM-04
                 MM-05
                 MM-06
                 MM-07
                 MM-08
                 MM-09
                 MM-10
                 MM-11
                 MM-12
     END-IF

*--- DETERMINA EN QUE MES ESTAMOS
     PERFORM VARYING WX-MM-8 FROM 12 BY -1
        UNTIL MM-DIAS(WX-MM-8) < WX-DDD-JUL 

     END-PERFORM 

*--- EL RESTO DE DDD SON LOS DIAS DENTRO DEL MES 
     SUBTRACT MM-DIAS(WX-MM-8) FROM WX-DDD-JUL GIVING WX-DD-8 

     DISPLAY 'FECHA AAAAMMDD:' WX-FECHA-GREG

     STOP RUN 
     .



Por supuesto puede haber otras maneras de hacer estos cambios. Existen también funciones intrínsecas de COBOL que hacen la transformación, pero normalmente no dejan usarlas porque consumen muchos recursos.

10 comentarios:

José Antonio Romero dijo...

Bueno, yo creo que más bien quienes dicen eso en dichas instalaciones no tienen mucha idea, por varios motivos:

1) Las funciones intrínsecas una vez que las conocen son tan simples de usar como cualquier otra instrucción de COBOL.
2) Son más rápidas ya que el manejo de los datos de la fecha se hace a nivel de máquina usando directamente sus registros, no a alto nivel usando variables COBOL.
3) Y la más importante creo, seguro que siempre funciona de igual forma sin posibilidad de error a la hora de codificarla.

Añado un ejemplo:

...
...
01 FECHA1 PIC 9(08).
01 FECHA2 PIC 9(07).
...
...
MOVE FUNCTION CURRENT-DATE(1:8) TO FECHA1
COMPUTE FECHA2 =
FUNCTION DAY-OF-INTEGER(FUNCTION INTEGER-OF-DATE(FECHA1))

Aquí primero obtenemos la fecha del día en la variable FECHA1 y luego en FECHA2 obtenemos su equivalente en formato JULIANO y todo en una única línea, bueno dos si contamos la carga de FECHA1.

Tallian dijo...

Pues sí Jose Antonio... pero quien manda manda!! jeje
Gracias por el ejemplo : )

Anónimo dijo...

Quería hacer una aportación a los algoritmos "De AAAAMMDD a AAAADDD", y "De AAAADDD a AAAAMMDD".
Falta un detalle en los bisiestos para que esté incompleto, ya que el cálculo de año bisiesto es:
Si es divisible por 4
Si es divisible por 100
Si es divisible por 400
SI es bisiesto
Si no
NO es bisiesto
fin SI
Si no
SI es bisiesto
fin Si
Si no
NO es bisiesto
fin Si

Anónimo dijo...

Perdón, se ha comido la indentación y creo que no se entiende nada, así que lo repito:

Quería hacer una aportación a los algoritmos "De AAAAMMDD a AAAADDD", y "De AAAADDD a AAAAMMDD".
Falta un detalle en los bisiestos para que esté incompleto, ya que el cálculo de año bisiesto es:
.Si es divisible por 4
. Si es divisible por 100
. Si es divisible por 400
. SI es bisiesto
. Si no
. NO es bisiesto
. fin SI
. Si no
. SI es bisiesto
. fin Si
.Si no
. NO es bisiesto
.fin Si

Anónimo dijo...

Bueno, a ver si a la tercera va la vencida!!!

Perdón, se ha comido la indentación y creo que no se entiende nada, así que lo repito:

Quería hacer una aportación a los algoritmos "De AAAAMMDD a AAAADDD", y "De AAAADDD a AAAAMMDD".
Falta un detalle en los bisiestos para que esté incompleto, ya que el cálculo de año bisiesto es:
.si es divisible por 4
....si es divisible por 100
.......si es divisible por 400
..........SI es bisiesto
.......si no
..........NO es bisiesto
.......fin si
....si no
.......SI es bisiesto
....fin si
.Si no
....NO es bisiesto
.fin si

Tallian dijo...

Muchas gracias por la aportación! Lo revisaré cuando tenga un momento : )

Rodrigo dijo...

Ese algoritmo no funciona para todos los años... mas bien tendriamos que hacer el algoritmo domsday, oye deberias de hacer un articulo para sumar fechas por que eso siempre nos da lata jaja al menos a mi siempre me da lata, luego tenemos las rutinas existentes para sumar fechas pero bueno me tope en una instalacion donde solo sumaba dias laborables y necesitaba dias naturales... bueno igual y si me lo permites yo hago el articulo

Tallian dijo...

Todo tuyo Rodrigo! jaja
Anímate a hacerlo y lo sacamos : )

Ariel Medrano dijo...

*--- COMPROBAR AÑO BISIESTO: RESTO = 0 = AÑO BISIESTO
DIVIDE WX-AAAA-JUL BY 100 GIVING WX-FIL9 REMAINDER WX-ANO-BIS
IF ANO-BIS-SI
DIVIDE WX-AAAA-JUL BY 400 GIVING WX-FIL9 REMAINDER WX-ANO-BIS
ELSE
DIVIDE WX-AAAA-JUL BY 4 GIVING WX-FIL9 REMAINDER WX-ANO-BIS
END-IF.

esté artículo me ayudó con un tp de la facu en assembler, dandome una idea para continuar, así que les dejo mi colaboración para el año bisiesto.

Saludos.

Tallian dijo...

Muchas gracias Ariel!