/*
 * Fichero:     FechaISO8601.java
 * Fecha:       20/10/06
 * Autor:       Alvaro Sarmiento Losada
 * Asignatura:  Proyecto Fin de Carrera
 * Descripción: Contiene la clase FechaISO8601, que proporciona métodos
 *              de generación de fechas ISO8601.
 *
 * Este fichero pertenece al proyecto fin de carrera del autor,
 * consistente en el estudio de JXTA y su aplicación al comercio 
 * electrónico b2b.
 */

package com.pfc.lib.utiles;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.StringTokenizer;
import java.util.TimeZone;

/**
 * Colección de métodos útiles para trabajar con fechas en formato ISO-8601.
 * <br>
 * El formato ISO-8601 especifica la representación de fecha y hora para 
 * estandarizar la notación a la hora de las comunicaciones internacionales.
 * Además, su estructura es idónea para su empleo en computadoras. <br>
 * <br>El estandar ISO8601 admite una amplia variedad de formatos según 
 * situaciones concretas. En esta clase se implementa un caso particular
 * respecto a la hora UTC (Universal Time). El formato consta de:
 *       YYYY-MM-DDThh:mm:ss.000+DD:DD
 *     ó YYYY-MM-DDThh:mm:ss.000-DD:DD
 * donde :
 *       YYYY  : año con 4 cifras
 *       MM    : mes con 2 cifras
 *       DD    : dia con 2 cifras
 *       T     : indica que lo siguiente es la hora
 *       hh    : hora con 2 cifras
 *       mm    : minutos con 2 cifras
 *       ss    : segundos con 2 cifras
 *       000   : milisegundos con 3 cifras
 *       DD:DD : desfase en horas respecto a la hora UTC
 *
 * <b>Nota:</b>
 * La hora UTC se considera respecto al meridiano de Greenwich. También es 
 * conocida por GMT (Greenwich Mean Time).
 *
 * @author  Josef Ignacio Hötz Ordoño
 */
public abstract class FechaISO8601 {
    
    /** 
     * Constante que define el caracter de desfase horario respecto a UTC.
     */
    protected static final String MAS_TOKEN = "+";
    
    /** 
     * Constante que define el caracter separador entre los digitos de la fecha
     * y de desfase horario respecto a UTC.
     */
    protected static final String MENOS_TOKEN = "-";
    
    /** 
     * Constante que define el caracter separador entre los digitos de la fecha
     * y los digitos de la hora.
     */
    protected static final String T_TOKEN = "T";
    
    /** 
     * Constante que define el caracter separador entre los digitos de la hora.
     */
    protected static final String DOSPUNTOS_TOKEN = ":";
    
    /** 
     * Constante que define el caracter separador entre los digitos de la hora
     * y los digitos de los milisegundos.
     */
    protected static final String PUNTO_TOKEN = ".";
    
    /** Constante que define el caracter indicador de hora UTC */
    protected static final String Z_TOKEN = "Z";

    /**
     * Convierte un entero en una cadena de 2 caracteres representando su valor.
     * Si el entero es menor que 10, se le añade un 0. Si es mayor que 10 pero
     * menor que 100, se deja tal cual. Si es mayor de 100, el método usará las
     * 2 primeras cifras significativas del entero.
     *
     * @param  i el entero que se quiere transformar en 2 dígitos. 
     * @return   un <code>String</code> de 2 caracteres representando el 
     *           valor del entero.
     */
    protected static String convertirEnDosDigitos(int i) {
        //Se halla el valor absoluto del entero
        int j = (i > 0) ? i 
                        : ( i * (-1) );
        //Se obtiene la representación del valor absoluto del entero como cadena
        String aux = String.valueOf(j);
        /*
         * Si el entero es mayor que 99, se cogen las dos primeras cifras
         * significativas.
         */
        if (j > 99) {
            aux = aux.substring(0, 2);
        }
        /* 
         * Si el entero es menor que 10, se le añade un cero delante. Si no, 
         * se deja igual.
         */
        return ( (j >=0 && j < 10) ? ( "0" + aux ) : aux );		        
    }
    
    /**
     * Convierte un entero en una cadena de 3 caracteres representando su valor.
     * Si el entero es menor que 100, se le añade un 0 a la izquierda. Si es 
     * mayor que 99, se deja tal cual. 
     *
     * @param  i el entero que se quiere transformar en 3 dígitos. 
     * @return   un <code>String</code> de 3 caracteres representando el 
     *           valor del entero.
     */
    protected static String convertirEnTresDigitos(int i) {
        //Se halla el valor absoluto del entero
        int j = (i > 0) ? i 
                        : ( i * (-1) );
        //Se obtiene la representación del valor absoluto del entero como cadena
        String aux = String.valueOf(j);
        /* 
         * Si el entero es menor que 99, se le añade un cero delante. Si no, 
         * se deja igual.
         */
        return (  (j < 100) ? ( "0" + aux ) : aux );		        
    }
    
    /**
     * Obtiene la representación de la fecha y hora actuales en formato 
     * ISO8601.
     *
     * @return  un <code>String</code> en formato ISO8601 con el tiempo actual.
     */
    public static String fechaISO() {
        return fechaISO( new GregorianCalendar() );        
    }
    
    /**
     * Obtiene la representación en formato ISO8601 de la fecha especificada.
     *
     * @param  fecha  la fecha y hora que se desea tranformar
     * @return        un <code>String</code> con la representación ISO8601 
     *                de la fecha especificada.
     */    
    public static String fechaISO(Calendar fecha) {
        StringBuffer buffer = new StringBuffer();
        
        /* Obtenemos un objeto Calendar con la hora local */
        Calendar calendario = new GregorianCalendar();
        calendario.setTime( fecha.getTime() );
        
        // Obtenemos el año y lo añadimos a la cadena buffer
        buffer.append(calendario.get(Calendar.YEAR));
        buffer.append(MENOS_TOKEN);
        //Obtenemos el mes y lo añadimos al buffer
        buffer.append(convertirEnDosDigitos(calendario.get(Calendar.MONTH) + 1));
        buffer.append(MENOS_TOKEN);
        //Obtenemos el dia y lo añadimos al buffer
        buffer.append(convertirEnDosDigitos(calendario.get(Calendar.DAY_OF_MONTH)));
        buffer.append(T_TOKEN);
        //Obtenemos la hora y lo añadimos al buffer
        buffer.append(convertirEnDosDigitos(calendario.get(Calendar.HOUR_OF_DAY)));
        buffer.append(DOSPUNTOS_TOKEN);
        //Obtenemos los minutos y los añadimos al buffer
        buffer.append(convertirEnDosDigitos(calendario.get(Calendar.MINUTE)));
        buffer.append(DOSPUNTOS_TOKEN);
        //Obtenemos los segundos y los añadimos al buffer
        buffer.append(convertirEnDosDigitos(calendario.get(Calendar.SECOND)));
        buffer.append(PUNTO_TOKEN);
        //Obtenemos los milisegundos y los añadimos al buffer
        buffer.append(convertirEnTresDigitos(calendario.get(Calendar.MILLISECOND)));
        
        //Se obtiene el offset respecto a la hora UTC en horas.
        int offset = calendario.get(Calendar.ZONE_OFFSET)/(60*60*1000);
        if(offset < 0) {
            buffer.append(MENOS_TOKEN);
        } else {
            buffer.append(MAS_TOKEN);
        }
        buffer.append(convertirEnDosDigitos(offset));
        buffer.append(DOSPUNTOS_TOKEN + "00");
        
        return buffer.toString();        
    }
    
    /**
     * Obtiene un objeto <code>Calendar</code> a partir de una cadena
     * <code>String</code> que contiene una fecha en formato ISO8601.
     *
     * @param  fechaISO  un cadena con una fecha ISO8601
     * @return           un objeto <code>Calendar</code> con la misma 
     *                   fecha que la cadena ISO8601.
     */
    public static Calendar fecha(String fechaISO) {
        //Se crea un objeto Calendar apropiado para UTC
        Calendar calendario = new GregorianCalendar();
        //Se usan tokens de los caracteres separadores de ISO8601
        StringTokenizer st = new StringTokenizer(fechaISO, 
              MAS_TOKEN + MENOS_TOKEN + T_TOKEN + Z_TOKEN + DOSPUNTOS_TOKEN + 
              PUNTO_TOKEN, false);

        try {
            /*
             * Se obtiene la cadena que representa al año buscando hasta
             * el siguiente token. Esto se repite para los meses, dias, etc.
             */
            int agno = Integer.parseInt(st.nextToken());
            calendario.set(Calendar.YEAR, agno);

            //El mes
            int mes = Integer.parseInt(st.nextToken()) -1;
            calendario.set(Calendar.MONTH, mes);

            //El dia
            int dia = Integer.parseInt(st.nextToken());
            calendario.set(Calendar.DAY_OF_MONTH, dia);

            //La hora
            int hora = Integer.parseInt(st.nextToken());
            calendario.set(Calendar.HOUR_OF_DAY, hora);

            //Los minutos
            int minutos = Integer.parseInt(st.nextToken());
            calendario.set(Calendar.MINUTE, minutos);

            //Los segundos
            int segundos = Integer.parseInt(st.nextToken());
            calendario.set(Calendar.SECOND, segundos);

            /* No se procesan los milisegundos */
        } catch (Exception e) {
            //No se procesa nada, sólo se devuelve null
            calendario = null;
        }

        return calendario;
    }
    
}