// $Id: DateType.java,v 1.4 2004/10/15 19:16:07 caron Exp $
/*
 * Copyright 2002-2004 Unidata Program Center/University Corporation for
 * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
 * support@unidata.ucar.edu.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

package thredds.datatype;

import java.util.*;
import java.text.SimpleDateFormat;

/**
 * Implements the thredds "dateType" and "dateTypeFormatted" XML element types.
 * This is mostly a general way to specify dates in a string.
 * It also allows a date to mean "present".
 * It also allows an optional attribute called "type" which is an enumeration like "created", "modified", etc
 *  taken from Dublin Core vocabulary.
 *
 * A DateType can be specified in the following ways:
 * <ol>
   <li> an xsd:date, with form "CCYY-MM-DD"
   <li> an xsd:dateTime with form "CCYY-MM-DDThh:mm:ss"
   <li> a valid udunits date string
   <li> the string "present"
   </ol>
 *
 * @see "http://www.unidata.ucar.edu/projects/THREDDS/tech/catalog/InvCatalogSpec.html#dateType"
 * @see "http://www.unidata.ucar.edu/projects/THREDDS/tech/catalog/InvCatalogSpec.html#dateTypeFormatted"
 * @author john caron
 * @version $Revision: 1.4 $ $Date: 2004/10/15 19:16:07 $
 */

public class DateType {
  // for bean editing
  static public String hiddenProperties() { return "text blank present"; }
  // static public String editableProperties() { return "date format type"; }

  static private java.text.SimpleDateFormat xsdDateFormat, xsdDateTimeFormat, stdDateTimeFormat;
  static {
    xsdDateFormat = new java.text.SimpleDateFormat("yyyy-MM-dd");
    xsdDateFormat.setTimeZone(java.util.TimeZone.getTimeZone("GMT"));

    xsdDateTimeFormat = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    xsdDateTimeFormat.setTimeZone(java.util.TimeZone.getTimeZone("GMT"));

    stdDateTimeFormat = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    stdDateTimeFormat.setTimeZone(java.util.TimeZone.getTimeZone("GMT"));
  }

  private String text, format, type;
  private boolean isPresent, isBlank;
  private Date date;

  /**
   * Constructor using a java.util.Date
   * @param isPresent
   * @param date
   */
  public DateType(boolean isPresent, java.util.Date date) {
    this.isPresent = isPresent;
    this.date = date;
    if (isPresent) text = "present";
  }

  /** no argument constructor for beans */
  public DateType() {
    isBlank = true;
  }

  /** copy constructor */
  public DateType( DateType src) {
    text = src.getText();
    format = src.getFormat();
    type = src.getType();
    isPresent = src.isPresent();
    isBlank = src.isBlank();
    date = src.getDate();
    if (isPresent) text = "present";
  }

  /**
   * Constructor.
   * @param text string representation
   * @param format using java.text.SimpleDateFormat, or null
   * @param type type of date, or null
   * @throws java.text.ParseException
   */
  public DateType(String text, String format, String type) throws java.text.ParseException {
    java.text.ParseException p = null;

    text = (text == null) ? "" : text.trim();
    this.text = text;
    this.format = format;
    this.type = type;

    // see if its blank
    if (text.length() == 0) {
      isBlank = true;
      return;
    }

    // see if its got a format
    if (format != null) {
      SimpleDateFormat dateFormat = new java.text.SimpleDateFormat(format);
      date = dateFormat.parse(text);
      return;
    }

    // see if its the string "present"
    isPresent = text.equalsIgnoreCase("present");
    if (isPresent) {
      date = new Date();
      return;
    }

    // see if its a udunits string
    if (text.indexOf("since") > 0) {
      date = ucar.nc2.units.DateUnit.getStandardDate(text);
      if (date == null)
        throw new java.text.ParseException("invalid udunit date unit", 0);
      return;
    }

    // now try  "CCYY-MM-DDThh:mm:ss"
      try {
        date = xsdDateTimeFormat.parse(text);
        return;
      } catch (java.text.ParseException e) { p = e; }

    // now try "yyyy-MM-dd"
      try {
        date = xsdDateFormat.parse(text);
        return;
      } catch (java.text.ParseException e) { p = e; }

    // error
    throw p;
  }

  public Date getDate() { return date; }
  public void setDate( Date date) {
    this.date = date;
    this.text = toDateTimeString();
  }

  public boolean isPresent() { return isPresent; }
  public boolean isBlank() { return isBlank; }
  public String getText() {
    if (text == null) text = toDateTimeString();
    return text;
  }
  public String getFormat() { return format; }
  public String getType() { return type; }

  public boolean before( Date d) {
    if (isPresent()) return false;
    return date.before( d);
  }

  public boolean after( Date d) {
    if (isPresent()) return true;
    return date.after( d);
  }

  public String toDateString() { return xsdDateFormat.format( date); }
  public String toDateTimeString() { return stdDateTimeFormat.format( date); }
  public String toDateTimeStringISO() { return xsdDateTimeFormat.format( date); }
  public String toString() { return getText(); }

  public int hashCode() {
    if (isBlank()) return 0;
    if (isPresent()) return 1;
    else if (date != null) return date.hashCode();
    else return 0;
  }
   public boolean equals(Object o) {
     if (this == o) return true;
     if (!(o instanceof DateType)) return false;
     return o.hashCode() == this.hashCode();
  }

  private Calendar cal = null;
  public DateType add( TimeDuration d) {
    Date useDate = (isPresent) ? new Date() : date;
    if (cal == null) cal = Calendar.getInstance();
    cal.setTime( useDate);
    cal.add( Calendar.SECOND, (int) d.getSeconds());
    //System.out.println(" add start "+date+" add "+d.getSeconds()+" = "+cal.getTime());
    return new DateType(false, (Date) cal.getTime().clone()); // prob dont need clone LOOK
  }

  public DateType subtract( TimeDuration d) {
    Date useDate = (isPresent) ? new Date() : date;
    if (cal == null) cal = Calendar.getInstance();
    cal.setTime( useDate);
    cal.add( Calendar.SECOND, (int) -d.getSeconds());
    //System.out.println(" subtract start "+date+" add "+d.getSeconds()+" = "+cal.getTime());
    return new DateType(false, (Date) cal.getTime().clone());
  }

  ////////////////////////////////////////////
  // test
  private static void doOne( String s) {
    try {
      System.out.println("\nStart = (" + s + ")");
      DateType d = new DateType(s, null, null);
      System.out.println("Date = (" + d.toString() + ")");
    } catch (java.text.ParseException e) {
      e.printStackTrace();
    }
  }

  public static void main(String[] args) {
    doOne("1991-11-16");
    doOne("1991-11-16T12:00:00");
    doOne("1991-11-16T16:03:09");
    doOne("5 hours since 1991-11-16");
    doOne("3600 secs since 1991-11-16");
    doOne("36000 secs since 1991-11-16 01:00:00");
    doOne("5 days since 1991-11-16");
    doOne("5 days since 1991-11-16T12:00:00");
    doOne("5 days since 1991-BADDOG!:00");
  }

}


/**
 * $Log: DateType.java,v $
 * Revision 1.4  2004/10/15 19:16:07  caron
 * enum now keyword in 1.5
 * SelectDateRange send ISO date string
 *
 * Revision 1.3  2004/09/24 03:26:32  caron
 * merge nj22
 *
 * Revision 1.2  2004/06/12 02:12:37  caron
 * update viewer
 *
 * Revision 1.1  2004/05/11 23:30:34  caron
 * release 2.0a
 */
