Librería Fecha en C++

En el desarrollo de nuestros programas, independientemente del lenguaje utilizado, es habitual que necesitemos tratar con datos de tipo fecha/hora. Normalmente este tipo de datos estén soportados por la API del lenguaje, o bien por cualquier librería o framework externo. Para el caso particular de la programación en C++ algunas alternativas son:

Sin embargo, siempre cabe la posibilidad, aun a riesgo de reinventar la rueda, de realizar nuestra propia implementación, que para este caso realizaremos en el lenguaje C++ haciendo uso de su soporte al paradigma de orientación a objetos, así como de sus mecanismos de excepciones y operadores sobrecargados, por lo que si bien puede que realizar una implementación propia no aporte nada nuevo respecto a las librerías antes mencionadas, si que puede representar un interesante ejercicio de cara a profundizar en ciertas características del lenguaje C++, así como a la practica de realizar librerías propias.

A continuación mostramos el fichero fecha.h con la declaración de la clase Fecha, la cual implementa un TAD para el trabajo con fechas y horas sobre el calendario gregoriano:

#ifndef FECHA_H
#define FECHA_H

#include <iostream>
#include <string>

using namespace std;

namespace Tools {

   typedef enum { DOMINGO, LUNES, MARTES, MIERCOLES,
                  JUEVES, VIERNES, SABADO } DiaSemana;

Con las lineas 1 y 2 evitamos que se produzcan redefiniciones de la cabecera, en la linea 9 declaramos el espacio de nombres Tools al que pertenecerán todos los elementos declarados en la cabecera. En la linea 11 definimos el tipo DiaSemana como una enumeración que describe el día de la semana.

   class FechaNoValida {
      private:
         string msg;
      public:
         FechaNoValida(string msg="Error :: Fecha :: No valida.") { this->msg=msg; }
         string message() { return msg; }
   };

En la linea 14 declaramos la clase FechaNoValida que representa a una excepción de fecha no valida.

   class Fecha {
      friend ostream& operator<<(ostream& os,const Fecha& f);
      private:
         static Fecha fechaDefecto;
         short dia;
         short mes;
         short anio;
         short hora;
         short minuto;
         short segundo;
         void setDefecto();
         bool esCorrecta() const;

En la linea 23 declaramos la relación de amistad con el operador de inserción sobrecargado(será explicado mas adelante), ya que este necesitará poder acceder a los miembros de la clase Fecha.

En la linea 25 declaramos el atributo privado fechaDefecto como estático(o miembro “de clase” en vez de “de instancia”) ya que representa a la fecha que será asignada por defecto por el método setDefecto en caso de que la fecha asignada por el usuario no sea válida.

En la linea 33 declaramos el método privado esCorrecta que comprueba si la fecha asignada por el usuario es correcta según el calendario gregoriano.

      public:
         Fecha(short dia=1,short mes=1,short anio=2000,
               short hora=0,short minuto=0,short segundo=0);

En la linea 35 declaramos el constructor de la clase cuyos parámetros por omisión definen la fecha por defecto.

         void setDia(short dia) throw(FechaNoValida);
         short getDia() const;
         void setMes(short mes) throw(FechaNoValida);
         short getMes() const;
         void setAnio(short anio) throw(FechaNoValida);
         short getAnio() const;
         void setHora(short hora) throw(FechaNoValida);
         short getHora() const;
         void setMinuto(short minuto) throw(FechaNoValida);
         short getMinuto() const;
         void setSegundo(short segundo) throw(FechaNoValida);
         short getSegundo() const;

De las lineas 37 a 48 declaramos los métodos de acceso y modificación de los atributos de la clase. Los métodos de modificación pueden lanzar una excepción FechaNoValida al establecer una fecha inválida.

         void setActual();
         bool anioBisiesto() const;
         DiaSemana diaSemana() const;

En la linea 49 declaramos el método setActual que establece la fecha actual del sistema. En la linea 50 declaramos el método anioBisiesto que determina si el anio es bisiesto en el calendario gregoriano.

En la linea 51 declaramos el método diaSemana que devuelve el día de la semana (empezando por domingo) correspondiente a una fecha del calendario gregoriano, para lo cual empleará el algoritmo Doomsday.

         bool operator==(const Fecha& f) const;
         bool operator!=(const Fecha& f) const;
         bool operator<(const Fecha& f) const;
         bool operator<=(const Fecha& f) const;
         bool operator>(const Fecha& f) const;
         bool operator>=(const Fecha& f) const;
   };

De las lineas 52 a 58 declaramos los operadores sobrecargados de comparación entre dos fechas.

   ostream& operator<<(ostream& os,const Fecha& f);
}

#endif

En la linea 59 declaramos el operador de inserción sobrecargado, al que pasamos una referencia a un objeto de la clase ostream(normalmente el objeto de flujo de salida estandar cout) y una referencia a un objeto de la clase Fecha. El operador de inserción devuelve una referencia a un objeto de la clase ostream para poder realizar invocaciones encadenadas.

A continuación mostramos el fichero fecha.cpp con la definición de los métodos de la clase Fecha:

#include <ctime>
#include "fecha.h"

using namespace std;

Tools::Fecha Tools::Fecha::fechaDefecto = Tools::Fecha();

Tools::Fecha::Fecha(short dia,short mes,short anio,
                    short hora,short minuto,short segundo) :
                       dia(dia),mes(mes),anio(anio),
                       hora(hora),minuto(minuto),segundo(segundo) {
   if(!esCorrecta()) {
      setDefecto();
      throw FechaNoValida();
   }
}

En la linea 6 inicializamos el atributo estático fechaDefecto con una instancia de la clase Fecha cuyos atributos toman el valor correspondiente a los parámetros por omisión del constructor.

En la linea 8 definimos el constructor de la clase Fecha, en el que una vez iniciados los atributos de la clase, comprobamos si la fecha es inválida, en cuyo caso asignamos la fecha por defecto y lanzamos una excepción FechaNoValida.

void Tools::Fecha::setDefecto() {
   *this=fechaDefecto;
}

El método setDefecto establece la fecha por defecto, para ello aplica el operador de asignación por defecto sobre la referencia implícita this y el atributo estático fechaDefecto.

bool Tools::Fecha::esCorrecta() const {
   bool ok=anio>=1 && 
           mes>=1 && mes<=12 &&
           hora>=0 && hora<24 &&
           minuto>=0 && minuto<60 &&
           segundo>=0 && segundo<60;
   int dias;
   if(ok) {
      switch(mes) {
         case 2: dias=(anioBisiesto()?29:28); break;
         case 4: case 6: case 9: case 11: dias=30; break;
         default: dias=31; break;
      }
      ok=dia<=dias;
   }
   return ok;
}

El método esCorrecta determina si la fecha es correcta conforme el calendario gregoriano, para ello se comprueba si el valor de los atributos se encuentra en el intervalo correcto, que en el caso del día, dicho intervalo dependerá de que el año sea normal o bisiesto.

void Tools::Fecha::setDia(short dia) throw(FechaNoValida) {
   short aux=this->dia;
   this->dia=dia;
   if(!esCorrecta()) {
      this->dia=aux;
      throw FechaNoValida("Error :: Fecha :: Dia no valido.");
   }
}

short Tools::Fecha::getDia() const { return dia; }

void Tools::Fecha::setMes(short mes) throw(FechaNoValida) {
   short aux=this->mes;
   this->mes=mes;
   if(!esCorrecta()) {
      this->mes=aux;
      throw FechaNoValida("Error :: Fecha :: Mes no valido.");
   }
}

short Tools::Fecha::getMes() const { return mes; }

void Tools::Fecha::setAnio(short anio) throw(FechaNoValida) {
   short aux=this->anio;
   this->anio=anio;
   if(!esCorrecta()) {
      this->anio=aux;
      throw FechaNoValida("Error :: Fecha :: Anio no valido.");
   }
}

short Tools::Fecha::getAnio() const { return anio; }

void Tools::Fecha::setHora(short hora) throw(FechaNoValida) {
   short aux=this->hora;
   this->hora=hora;
   if(!esCorrecta()) {
      this->hora=aux;
      throw FechaNoValida("Error :: Fecha :: Hora no valida.");
   }
}

short Tools::Fecha::getHora() const { return hora; }

void Tools::Fecha::setMinuto(short minuto) throw(FechaNoValida) {
   short aux=this->minuto;
   this->minuto=minuto;
   if(!esCorrecta()) {
      this->minuto=aux;
      throw FechaNoValida("Error :: Fecha :: Minuto no valido.");
   }
}

short Tools::Fecha::getMinuto() const { return minuto; }

void Tools::Fecha::setSegundo(short segundo) throw(FechaNoValida) {
   short aux=this->segundo;
   this->segundo=segundo;
   if(!esCorrecta()) {
      this->segundo=aux;
      throw FechaNoValida("Error :: Fecha :: Segundo no valido.");
   }
}

short Tools::Fecha::getSegundo() const { return segundo; }

De las lineas 40 a 104 definimos los métodos de acceso y modificación de la clase Fecha. Los métodos de modificación, lanzan una excepción FechaNoValida en caso de que la fecha modificada sea inválida, restaurando el valor anterior del atributo modificado.

void Tools::Fecha::setActual() {
   struct tm *fa;
   time_t segs;
   time(&segs);
   fa=localtime(&segs);
   dia=fa->tm_mday;
   mes=fa->tm_mon+1;
   anio=fa->tm_year+1900;
   hora=fa->tm_hour;
   minuto=fa->tm_min;
   segundo=fa->tm_sec;
}

bool Tools::Fecha::anioBisiesto() const {
   return (anio%4==0 && anio%100!=0) || anio%400==0;
}

Tools::DiaSemana Tools::Fecha::diaSemana() const {
   static short mDiasAcu[2][12]={{0,3,3,6,1,4,6,2,5,0,3,5},
                               {0,3,4,0,2,5,0,3,6,1,4,6}};
   return static_cast<DiaSemana>(((anio-1)%7 +
                      (((anio-1)/4)-3*((((anio-1)/100)+1)/4))%7 +
                      mDiasAcu[(anioBisiesto()?1:0)][mes-1] +
                      dia%7)%7);
}

En la linea 106 definimos el método setActual que establece la fecha actual del sistema, para ello se hace uso de las funciones de la cabecera ctime de la biblioteca estandar.

En la linea 119 definimos el método anioBisiesto que determina si el año de la fecha es o no bisiesto según el calendario gregoriano.

En la linea 123 definimos el método diaSemana que determina el dia de la semana correspondiente a la fecha aplicando el algoritmo Doomsday. En la implementación de dicho algoritmo utilizamos una matriz estática de 2×12 enteros cortos, en que el entero e[i][j] se refiere al módulo del número de dias acumulados del año en el mes j+1, siendo la componente i=0 para un año normal e i=1 para un año bisiesto.

bool Tools::Fecha::operator==(const Tools::Fecha& f) const {
   return dia==f.dia && mes==f.mes && anio==f.anio &&
          hora==f.hora && minuto==f.minuto && segundo==f.segundo;
}

bool Tools::Fecha::operator!=(const Tools::Fecha& f) const {
   return !(*this==f);
}

bool Tools::Fecha::operator<(const Tools::Fecha& f) const {
   return anio<f.anio ||
          (anio==f.anio && mes<f.mes) ||
          (anio==f.anio && mes==f.mes && dia<f.dia) ||
          (anio==f.anio && mes==f.mes && dia==f.dia && hora<f.hora) ||
          (anio==f.anio && mes==f.mes && dia==f.dia && hora==f.hora && minuto<f.minuto) ||
          (anio==f.anio && mes==f.mes && dia==f.dia && hora==f.hora && minuto==f.minuto && segundo<f.segundo);
}

bool Tools::Fecha::operator<=(const Tools::Fecha& f) const {
   return *this<f || *this==f;
}

bool Tools::Fecha::operator>(const Tools::Fecha& f) const {
   return !(*this<f) && *this!=f;
}

bool Tools::Fecha::operator>=(const Tools::Fecha& f) const {
   return *this>f || *this==f;
}

De las lineas 132 a 160 definimos los operadores de comparación sobrecargados.

ostream& Tools::operator<<(ostream& os,const Fecha& f) {
   return os << f.anio << "-" 
             << ((f.mes<10)?"0":"") << f.mes << "-" 
             << ((f.dia<10)?"0":"") << f.dia << "T" 
             << ((f.hora<10)?"0":"") << f.hora << ":" 
             << ((f.minuto<10)?"0":"") << f.minuto << ":" 
             << ((f.segundo<10)?"0":"") << f.segundo;
}

El operador de inserción sobrecargado permite insertar en un objeto de la clase ostream(normalmente el objeto cout) el contenido de los atributos de la clase según el formato para la fecha y hora especificado en la norma ISO 8601, por ejemplo, la fecha y hora del 28 de Febrero del año 2011 a las 11 horas, 25 minutos y 48 segundos correspondería a la cadena 2011-02-28T11:25:48.

A continuación mostramos el fichero main.cpp que implementa el método main para probar la clase Fecha:

#include <iostream>
#include "fecha.h"

using namespace std;

int main(int argc,char *argv[]) {
   cout << "Testing TAD Fecha ..." << endl;
   // Objeto f1 de la clase Fecha (15/10/2008).
   Tools::Fecha f1(15,10,2008);
   // Puntero a instancia de objeto de la clase Fecha (Por defecto).
   Tools::Fecha *f2=new Tools::Fecha();
   // Invocamos operadores de insercion sobrecargados.
   cout << "Fecha1 : " << f1 << endl;
   cout << "Fecha2 : " << *f2 << endl;
   // Establecemos la fecha actual sobre f2.
   f2->setActual();
   cout << "Fecha2 : " << *f2 << endl;
   // Asignamos una fecha invalida sobre f1.
   try {
      f1.setDia(32);
   } catch(Tools::FechaNoValida &fnv) {
      cout << fnv.message() << endl;
   }
   // Comprobamos si f1 y f2 tienen anios bisiestos.
   cout << "El anio " << f1.getAnio() << (f1.anioBisiesto()?" ":" no ") << "es bisiesto" << endl;
   cout << "El anio " << f2->getAnio() << (f2->anioBisiesto()?" ":" no ") << "es bisiesto" << endl;
   // Comprobamos los dias de la semana de f1 y f2.
   const char *diaSemana[]={"Domingo","Lunes","Martes","Miercoles",
                            "Jueves","Viernes","Sabado"};
   cout << "La fecha " << f1 << " cae en " << diaSemana[f1.diaSemana()] << endl;	
   cout << "La fecha " << *f2 << " cae en " << diaSemana[f2->diaSemana()] << endl;	
   // Comprobamos los operadores sobrecargados de comparacion.
   cout << f1 << (f1==*f2?" es igual a ":" es distinta a ") << *f2 << endl;
   cout << f1 << (f1!=*f2?" es distinta a ":" es igual a ") << *f2 << endl;
   cout << f1 << (f1<*f2?" es anterior a ":" es posterior a ") << *f2 << endl;
   cout << f1 << (f1>=*f2?" es posteior o igual  a ":" es anterior a ") << *f2 << endl;
   delete f2;
   return 0;
}

Con esto hemos concluido la implementación, por lo que llegados a este puntos ya podríamos utilizar la clase Fecha en nuestros programas, sin embargo, para facilitar su utilización y no tener varias copias de los ficheros fuente y cabecera dispersas en distintos proyectos, vamos ha crear una librería estática que instalaremos en el sistema.

Para automatizar el proceso de compilación, instalación, pruebas, etc, vamos ha crear un fichero makefile cuyo contenido es el siguiente:

CC = g++
LNKFLAGS  = -Wall -ansi -ggdb
CFLAGS = $(LNKFLAGS) -c
DIRSRC = src/
DIRHEA = include/
DIROBJ = obj/
DIRLIB = lib/
DIRBIN = bin/
NAMEEX = testfecha

all : $(NAMEEX)

run-test :
	./$(DIRBIN)$(NAMEEX)

install :
	cp $(DIRHEA)* /usr/include
	cp $(DIRLIB)* /usr/lib

uninstall :
	rm -f /usr/include/fecha.h
	rm -f /usr/lib/libfecha.a

clean :
	rm -f $(DIRSRC)*~ $(DIRHEA)*~ $(DIRBIN)* $(DIROBJ)* $(DIRLIB)*

$(NAMEEX) : main.o libfecha.a
	$(CC) $(LNKFLAGS) -o $(DIRBIN)$(NAMEEX) $(DIROBJ)main.o -L$(DIRLIB) -lfecha

main.o : $(DIRSRC)main.cpp
	$(CC) $(CFLAGS) -o $(DIROBJ)main.o -I $(DIRHEA) $(DIRSRC)main.cpp

libfecha.a : libfecha.o
	ar rcs $(DIRLIB)libfecha.a $(DIROBJ)libfecha.o

libfecha.o : $(DIRSRC)fecha.cpp $(DIRHEA)fecha.h
	$(CC) $(CFLAGS) -o $(DIROBJ)libfecha.o -I $(DIRHEA) $(DIRSRC)fecha.cpp

Tal como podemos intuir en el makefile la estructura de directorios del proyecto será la siguiente:

.
├── bin
├── include
│   └── fecha.h
├── lib
├── LICENSE
├── makefile
├── obj
├── README
└── src
    ├── fecha.cpp
    └── main.cpp

A continuación compilaremos las fuentes, generando la librería estática libfecha.a y el binario ejecutable del programa de pruebas testfecha:

j2sg@raptor:~/trabajo/proyectos/fechacpp$ make
g++ -Wall -ansi -ggdb -c -o obj/main.o -I include/ src/main.cpp
g++ -Wall -ansi -ggdb -c -o obj/libfecha.o -I include/ src/fecha.cpp
ar rcs lib/libfecha.a obj/libfecha.o
g++ -Wall -ansi -ggdb -o bin/testfecha obj/main.o -Llib/ -lfecha

Ejecutamos el programa de pruebas:

j2sg@raptor:~/trabajo/proyectos/fechacpp$ make run-test
./bin/testfecha
Testing TAD Fecha ...
Fecha1 : 2008-10-15T00:00:00
Fecha2 : 2000-01-01T00:00:00
Fecha2 : 2011-02-28T15:09:30
Error :: Fecha :: Dia no valido.
El anio 2008 es bisiesto
El anio 2011 no es bisiesto
La fecha 2008-10-15T00:00:00 cae en Miercoles
La fecha 2011-02-28T15:09:30 cae en Lunes
2008-10-15T00:00:00 es distinta a 2011-02-28T15:09:30
2008-10-15T00:00:00 es distinta a 2011-02-28T15:09:30
2008-10-15T00:00:00 es anterior a 2011-02-28T15:09:30
2008-10-15T00:00:00 es anterior a 2011-02-28T15:09:30

Instalamos en el sistema la librería creada:

j2sg@raptor:~/trabajo/proyectos/fechacpp$ su
Contraseña: 
root@raptor:/home/j2sg/trabajo/proyectos/fechacpp# make install
cp include/* /usr/include
cp lib/* /usr/lib

En este momento ya disponemos de la librería en nuestro sistema para poder utilizarla en nuestro proyectos, por ejemplo en el fichero fuente miproyecto.cpp:

#include <iostream>
#include <fecha.h>

using namespace std;

int main() {
   Tools::Fecha fecha;
   fecha.setActual();
   cout << fecha << endl;
   return 0;
}

Compilamos y enlazamos con la librería estática libfecha.a:

j2sg@raptor:~/prueba$ g++ -o miproyecto miproyecto.cpp -lfecha

Ejecutamos el programa de prueba miproyecto:

j2sg@raptor:~/prueba$ ./miproyecto 
2011-02-28T15:34:59

Para desinstalar la librería de nuestro sistema bastará invocar:

j2sg@raptor:~/trabajo/proyectos/fechacpp$ su
Contraseña: 
root@raptor:/home/j2sg/trabajo/proyectos/fechacpp# make uninstall
rm -f /usr/include/fecha.h
rm -f /usr/lib/libfecha.a

Eso es todo, como hemos visto es posible crear nuestras propias librerías de trabajo sin demasiado esfuerzo. Aquí podéis descargar todo el código, el cual se encuentra bajo licencia GPLv3.

Fuentes:

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s