/* $Id$ */

/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.manifoldcf.core.common;

import java.util.*;

/** Class to parse and format common dates.
*/
public class DateParser
{
  public static final String _rcsid = "@(#)$Id$";

  /** Parse ISO 8601 dates, and their common variants.
  */
  public static Date parseISO8601Date(String isoDateValue)
  {
    if (isoDateValue == null)
      return null;
    
    boolean isMicrosoft = (isoDateValue.indexOf("T") == -1);
    
    String formatString;
    if (isMicrosoft)
    {
      formatString = "yyyy-MM-dd' 'HH:mm:ss";
    }
    else
    {
      // There are a number of variations on the basic format.
      // We'll look for key characters to help is determine which is which.
      StringBuilder isoFormatString = new StringBuilder("yy");
      if (isoDateValue.length() > 2 && isoDateValue.charAt(2) != '-')
        isoFormatString.append("yy");
      isoFormatString.append("-MM-dd'T'HH:mm:ss");
      if (isoDateValue.indexOf(".") != -1)
        isoFormatString.append(".SSS");
      if (isoDateValue.endsWith("Z"))
      {
        isoDateValue = isoDateValue.substring(0,isoDateValue.length()-1) + "-0000";
        isoFormatString.append("Z");
      }
      else
      {
        // We need to be able to parse either "-08:00" or "-0800".  The 'Z' specifier only handles
        // -0800, unfortunately - see CONNECTORS-700.  So we have to do some hackery to remove the colon.
        int colonIndex = isoDateValue.lastIndexOf(":");
        int dashIndex = isoDateValue.lastIndexOf("-");
        int plusIndex = isoDateValue.lastIndexOf("+");
        if (colonIndex != -1 &&
          ((dashIndex != -1 && colonIndex == dashIndex+3 && isNumeral(isoDateValue,dashIndex-1)) || (plusIndex != -1 && colonIndex == plusIndex+3 && isNumeral(isoDateValue,plusIndex-1))))
          isoDateValue = isoDateValue.substring(0,colonIndex) + isoDateValue.substring(colonIndex+1);
        isoFormatString.append("Z");      // RFC 822 time, including general time zones
      }
      formatString = isoFormatString.toString();
    }
    java.text.DateFormat iso8601Format = new java.text.SimpleDateFormat(formatString, Locale.ROOT);
    try
    {
      return iso8601Format.parse(isoDateValue);
    }
    catch (java.text.ParseException e)
    {
      return null;
    }
  }
  
  protected static boolean isNumeral(String value, int index)
  {
    return index >= 0 && value.charAt(index) >= '0' && value.charAt(index) <= '9';
  }
  
  /** Format ISO8601 date.
  */
  public static String formatISO8601Date(Date dateValue)
  {
    java.text.DateFormat df = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ROOT);
    df.setTimeZone(TimeZone.getTimeZone("GMT"));
    return df.format(dateValue);
  }
  
  /** Timezone mapping from RFC822 timezones to ones understood by Java */
  
  // Month map
  protected static HashMap monthMap = new HashMap();
  static
  {
    monthMap.put("jan",Integer.valueOf(1));
    monthMap.put("feb",Integer.valueOf(2));
    monthMap.put("mar",Integer.valueOf(3));
    monthMap.put("apr",Integer.valueOf(4));
    monthMap.put("may",Integer.valueOf(5));
    monthMap.put("jun",Integer.valueOf(6));
    monthMap.put("jul",Integer.valueOf(7));
    monthMap.put("aug",Integer.valueOf(8));
    monthMap.put("sep",Integer.valueOf(9));
    monthMap.put("oct",Integer.valueOf(10));
    monthMap.put("nov",Integer.valueOf(11));
    monthMap.put("dec",Integer.valueOf(12));
  }

  protected static final HashMap milTzMap;
  static
  {
    milTzMap = new HashMap();
    milTzMap.put("Z","GMT");
    milTzMap.put("UT","GMT");
    milTzMap.put("A","GMT-01:00");
    milTzMap.put("M","GMT-12:00");
    milTzMap.put("N","GMT+01:00");
    milTzMap.put("Y","GMT+12:00");
  }

  /** Parse RFC822 date */
  public static Date parseRFC822Date(String dateValue)
  {
    if (dateValue == null)
      return null;
    dateValue = dateValue.trim();
    // See http://www.faqs.org/rfcs/rfc822.html for legal formats
    // Format: [day of week,] day mo year hh24:mm:ss tz
    int commaIndex = dateValue.indexOf(",");
    String usable;
    if (commaIndex == -1)
      usable = dateValue;
    else
      usable = dateValue.substring(commaIndex+1).trim();
    int index;

    index = usable.indexOf(" ");
    if (index == -1)
      return null;
    String day = usable.substring(0,index);
    usable = usable.substring(index+1).trim();

    index = usable.indexOf(" ");
    if (index == -1)
      return null;
    String month = usable.substring(0,index).toLowerCase(Locale.ROOT);
    usable = usable.substring(index+1).trim();

    String year;
    String hour = null;
    String minute = null;
    String second = null;
    String timezone = null;

    index = usable.indexOf(" ");
    if (index != -1)
    {
      year = usable.substring(0,index);
      usable = usable.substring(index+1).trim();

      index = usable.indexOf(":");
      if (index == -1)
        return null;
      hour = usable.substring(0,index);
      usable = usable.substring(index+1).trim();

      index = usable.indexOf(":");
      if (index != -1)
      {
        minute = usable.substring(0,index);
        usable = usable.substring(index+1).trim();

        index = usable.indexOf(" ");
        if (index == -1)
          second = usable;
        else
        {
          second = usable.substring(0,index);
          timezone = usable.substring(index+1).trim();
        }
      }
      else
      {
        index = usable.indexOf(" ");
        if (index == -1)
          minute = usable;
        else
        {
          minute = usable.substring(0,index);
          timezone = usable.substring(index+1).trim();
        }
      }
    }
    else
      year = usable;

    // Now construct a calendar object from this
    TimeZone tz;
    if (timezone != null && timezone.length() > 0)
    {
      if (timezone.startsWith("+") || timezone.startsWith("-"))
      {
        if (timezone.indexOf(":") == -1 && timezone.length() > 3)
          timezone = timezone.substring(0,timezone.length()-2) + ":" + timezone.substring(timezone.length()-2);
        timezone = "GMT"+timezone;
      }
      else
      {
        // Map special timezones to java timezones
        if (milTzMap.get(timezone) != null)
          timezone = (String)milTzMap.get(timezone);
      }

    }
    else
      timezone = "GMT";


    tz = TimeZone.getTimeZone(timezone);

    Calendar c = new GregorianCalendar(tz, Locale.ROOT);
    try
    {
      int value = Integer.parseInt(year);
      if (value < 1900)
        value += 1900;
      c.set(Calendar.YEAR,value);

      Integer x = (Integer)monthMap.get(month);
      if (x == null)
        return null;
      c.set(Calendar.MONTH,x.intValue()-1);

      value = Integer.parseInt(day);
      c.set(Calendar.DAY_OF_MONTH,value);

      if (hour != null)
        value = Integer.parseInt(hour);
      else
        value = 0;
      c.set(Calendar.HOUR_OF_DAY,value);

      if (minute != null)
        value = Integer.parseInt(minute);
      else
        value = 0;
      c.set(Calendar.MINUTE,value);

      if (second != null)
        value = Integer.parseInt(second);
      else
        value = 0;
      c.set(Calendar.SECOND,value);

      c.set(Calendar.MILLISECOND,0);
      return new Date(c.getTimeInMillis());
    }
    catch (NumberFormatException e)
    {
      return null;
    }

  }

  /** Parse a China Daily News date */
  public static Date parseChinaDate(String dateValue)
  {
    if (dateValue == null)
      return null;
    dateValue = dateValue.trim();
    // Format: 2007/12/30 11:01
    int index;
    index = dateValue.indexOf("/");
    if (index == -1)
      return null;
    String year = dateValue.substring(0,index);
    dateValue = dateValue.substring(index+1);
    index = dateValue.indexOf("/");
    if (index == -1)
      return null;
    String month = dateValue.substring(0,index);
    dateValue = dateValue.substring(index+1);
    index = dateValue.indexOf(" ");
    String day;
    String hour = null;
    String minute = null;
    String second = null;
    if (index == -1)
      day = dateValue;
    else
    {
      day = dateValue.substring(0,index);
      dateValue = dateValue.substring(index+1);
      index = dateValue.indexOf(":");
      if (index == -1)
        return null;
      hour = dateValue.substring(0,index);
      dateValue = dateValue.substring(index+1);
      index = dateValue.indexOf(":");
      if (index != -1)
      {
        minute = dateValue.substring(0,index);
        dateValue = dateValue.substring(index+1);
        second = dateValue;
      }
      else
        minute = dateValue;
    }
    TimeZone tz = TimeZone.getTimeZone("GMT");
    Calendar c = new GregorianCalendar(tz, Locale.ROOT);
    try
    {
      int value = Integer.parseInt(year);
      if (value < 1900)
        value += 1900;
      c.set(Calendar.YEAR,value);

      value = Integer.parseInt(month);
      c.set(Calendar.MONTH,value-1);

      value = Integer.parseInt(day);
      c.set(Calendar.DAY_OF_MONTH,value);

      if (hour != null)
        value = Integer.parseInt(hour);
      else
        value = 0;
      c.set(Calendar.HOUR_OF_DAY,value);

      if (minute != null)
        value = Integer.parseInt(minute);
      else
        value = 0;
      c.set(Calendar.MINUTE,value);

      if (second != null)
        value = Integer.parseInt(second);
      else
        value = 0;
      c.set(Calendar.SECOND,value);

      c.set(Calendar.MILLISECOND,0);
      return new Date(c.getTimeInMillis());
    }
    catch (NumberFormatException e)
    {
      return null;
    }

  }


}
