导航

时间类

Posted on 2017-03-09 14:33  _eve  阅读(358)  评论(0编辑  收藏  举报
/*
 * 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 com.accu.common.util.time;

import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

/**
 * 格式化日期到字符串工具,线程安全
 * <p>
 * Date and time formatting utilities and constants.
 * </p>
 * 
 * <p>
 * Formatting is performed using the
 * {@link org.apache.commons.lang.time.FastDateFormat} class.
 * </p>
 * 
 * @author Apache Software Foundation
 * @author Apache Ant - DateUtils
 * @author <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a>
 * @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
 * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
 * @since 2.0
 * @version $Id: DateFormatUtils.java 905636 2010-02-02 14:03:32Z niallp $
 */
public class DateFormatUtils {

	/**
	 * ISO8601 formatter for date-time without time zone. The format used is
	 * <tt>yyyy-MM-dd'T'HH:mm:ss</tt>.
	 */
	public static final FastDateFormat ISO_DATETIME_FORMAT = FastDateFormat
			.getInstance("yyyy-MM-dd'T'HH:mm:ss");

	/**
	 * ISO8601 formatter for date-time with time zone. The format used is
	 * <tt>yyyy-MM-dd'T'HH:mm:ssZZ</tt>.
	 */
	public static final FastDateFormat ISO_DATETIME_TIME_ZONE_FORMAT = FastDateFormat
			.getInstance("yyyy-MM-dd'T'HH:mm:ssZZ");

	/**
	 * ISO8601 formatter for date without time zone. The format used is
	 * <tt>yyyy-MM-dd</tt>.
	 */
	public static final FastDateFormat ISO_DATE_FORMAT = FastDateFormat
			.getInstance("yyyy-MM-dd");

	/**
	 * ISO8601-like formatter for date with time zone. The format used is
	 * <tt>yyyy-MM-ddZZ</tt>. This pattern does not comply with the formal
	 * ISO8601 specification as the standard does not allow a time zone without
	 * a time.
	 */
	public static final FastDateFormat ISO_DATE_TIME_ZONE_FORMAT = FastDateFormat
			.getInstance("yyyy-MM-ddZZ");

	/**
	 * ISO8601 formatter for time without time zone. The format used is
	 * <tt>'T'HH:mm:ss</tt>.
	 */
	public static final FastDateFormat ISO_TIME_FORMAT = FastDateFormat
			.getInstance("'T'HH:mm:ss");

	/**
	 * ISO8601 formatter for time with time zone. The format used is
	 * <tt>'T'HH:mm:ssZZ</tt>.
	 */
	public static final FastDateFormat ISO_TIME_TIME_ZONE_FORMAT = FastDateFormat
			.getInstance("'T'HH:mm:ssZZ");

	/**
	 * ISO8601-like formatter for time without time zone. The format used is
	 * <tt>HH:mm:ss</tt>. This pattern does not comply with the formal
	 * ISO8601 specification as the standard requires the 'T' prefix for times.
	 */
	public static final FastDateFormat ISO_TIME_NO_T_FORMAT = FastDateFormat
			.getInstance("HH:mm:ss");

	/**
	 * ISO8601-like formatter for time with time zone. The format used is
	 * <tt>HH:mm:ssZZ</tt>. This pattern does not comply with the formal
	 * ISO8601 specification as the standard requires the 'T' prefix for times.
	 */
	public static final FastDateFormat ISO_TIME_NO_T_TIME_ZONE_FORMAT = FastDateFormat
			.getInstance("HH:mm:ssZZ");

	/**
	 * SMTP (and probably other) date headers. The format used is
	 * <tt>EEE, dd MMM yyyy HH:mm:ss Z</tt> in US locale.
	 */
	public static final FastDateFormat SMTP_DATETIME_FORMAT = FastDateFormat
			.getInstance("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);

	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * DateFormatUtils instances should NOT be constructed in standard
	 * programming.
	 * </p>
	 * 
	 * <p>
	 * This constructor is public to permit tools that require a JavaBean
	 * instance to operate.
	 * </p>
	 */
	public DateFormatUtils() {
		super();
	}

	/**
	 * <p>
	 * Formats a date/time into a specific pattern using the UTC time zone.
	 * </p>
	 * 
	 * @param millis
	 *            the date to format expressed in milliseconds
	 * @param pattern
	 *            the pattern to use to format the date
	 * @return the formatted date
	 */
	public static String formatUTC(long millis, String pattern) {
		return format(new Date(millis), pattern, DateUtils.UTC_TIME_ZONE, null);
	}

	/**
	 * <p>
	 * Formats a date/time into a specific pattern using the UTC time zone.
	 * </p>
	 * 
	 * @param date
	 *            the date to format
	 * @param pattern
	 *            the pattern to use to format the date
	 * @return the formatted date
	 */
	public static String formatUTC(Date date, String pattern) {
		return format(date, pattern, DateUtils.UTC_TIME_ZONE, null);
	}

	/**
	 * <p>
	 * Formats a date/time into a specific pattern using the UTC time zone.
	 * </p>
	 * 
	 * @param millis
	 *            the date to format expressed in milliseconds
	 * @param pattern
	 *            the pattern to use to format the date
	 * @param locale
	 *            the locale to use, may be <code>null</code>
	 * @return the formatted date
	 */
	public static String formatUTC(long millis, String pattern, Locale locale) {
		return format(new Date(millis), pattern, DateUtils.UTC_TIME_ZONE,
				locale);
	}

	/**
	 * <p>
	 * Formats a date/time into a specific pattern using the UTC time zone.
	 * </p>
	 * 
	 * @param date
	 *            the date to format
	 * @param pattern
	 *            the pattern to use to format the date
	 * @param locale
	 *            the locale to use, may be <code>null</code>
	 * @return the formatted date
	 */
	public static String formatUTC(Date date, String pattern, Locale locale) {
		return format(date, pattern, DateUtils.UTC_TIME_ZONE, locale);
	}

	/**
	 * <p>
	 * Formats a date/time into a specific pattern.
	 * </p>
	 * 
	 * @param millis
	 *            the date to format expressed in milliseconds
	 * @param pattern
	 *            the pattern to use to format the date
	 * @return the formatted date
	 */
	public static String format(long millis, String pattern) {
		return format(new Date(millis), pattern, null, null);
	}

	/**
	 * <p>
	 * Formats a date/time into a specific pattern.
	 * </p>
	 * 
	 * @param date
	 *            the date to format
	 * @param pattern
	 *            the pattern to use to format the date
	 * @return the formatted date
	 */
	public static String format(Date date, String pattern) {
		return format(date, pattern, null, null);
	}

	/**
	 * <p>
	 * Formats a calendar into a specific pattern.
	 * </p>
	 * 
	 * @param calendar
	 *            the calendar to format
	 * @param pattern
	 *            the pattern to use to format the calendar
	 * @return the formatted calendar
	 * @see FastDateFormat#format(Calendar)
	 * @since 2.4
	 */
	public static String format(Calendar calendar, String pattern) {
		return format(calendar, pattern, null, null);
	}

	/**
	 * <p>
	 * Formats a date/time into a specific pattern in a time zone.
	 * </p>
	 * 
	 * @param millis
	 *            the time expressed in milliseconds
	 * @param pattern
	 *            the pattern to use to format the date
	 * @param timeZone
	 *            the time zone to use, may be <code>null</code>
	 * @return the formatted date
	 */
	public static String format(long millis, String pattern, TimeZone timeZone) {
		return format(new Date(millis), pattern, timeZone, null);
	}

	/**
	 * <p>
	 * Formats a date/time into a specific pattern in a time zone.
	 * </p>
	 * 
	 * @param date
	 *            the date to format
	 * @param pattern
	 *            the pattern to use to format the date
	 * @param timeZone
	 *            the time zone to use, may be <code>null</code>
	 * @return the formatted date
	 */
	public static String format(Date date, String pattern, TimeZone timeZone) {
		return format(date, pattern, timeZone, null);
	}

	/**
	 * <p>
	 * Formats a calendar into a specific pattern in a time zone.
	 * </p>
	 * 
	 * @param calendar
	 *            the calendar to format
	 * @param pattern
	 *            the pattern to use to format the calendar
	 * @param timeZone
	 *            the time zone to use, may be <code>null</code>
	 * @return the formatted calendar
	 * @see FastDateFormat#format(Calendar)
	 * @since 2.4
	 */
	public static String format(Calendar calendar, String pattern,
			TimeZone timeZone) {
		return format(calendar, pattern, timeZone, null);
	}

	/**
	 * <p>
	 * Formats a date/time into a specific pattern in a locale.
	 * </p>
	 * 
	 * @param millis
	 *            the date to format expressed in milliseconds
	 * @param pattern
	 *            the pattern to use to format the date
	 * @param locale
	 *            the locale to use, may be <code>null</code>
	 * @return the formatted date
	 */
	public static String format(long millis, String pattern, Locale locale) {
		return format(new Date(millis), pattern, null, locale);
	}

	/**
	 * <p>
	 * Formats a date/time into a specific pattern in a locale.
	 * </p>
	 * 
	 * @param date
	 *            the date to format
	 * @param pattern
	 *            the pattern to use to format the date
	 * @param locale
	 *            the locale to use, may be <code>null</code>
	 * @return the formatted date
	 */
	public static String format(Date date, String pattern, Locale locale) {
		return format(date, pattern, null, locale);
	}

	/**
	 * <p>
	 * Formats a calendar into a specific pattern in a locale.
	 * </p>
	 * 
	 * @param calendar
	 *            the calendar to format
	 * @param pattern
	 *            the pattern to use to format the calendar
	 * @param locale
	 *            the locale to use, may be <code>null</code>
	 * @return the formatted calendar
	 * @see FastDateFormat#format(Calendar)
	 * @since 2.4
	 */
	public static String format(Calendar calendar, String pattern, Locale locale) {
		return format(calendar, pattern, null, locale);
	}

	/**
	 * <p>
	 * Formats a date/time into a specific pattern in a time zone and locale.
	 * </p>
	 * 
	 * @param millis
	 *            the date to format expressed in milliseconds
	 * @param pattern
	 *            the pattern to use to format the date
	 * @param timeZone
	 *            the time zone to use, may be <code>null</code>
	 * @param locale
	 *            the locale to use, may be <code>null</code>
	 * @return the formatted date
	 */
	public static String format(long millis, String pattern, TimeZone timeZone,
			Locale locale) {
		return format(new Date(millis), pattern, timeZone, locale);
	}

	/**
	 * <p>
	 * Formats a date/time into a specific pattern in a time zone and locale.
	 * </p>
	 * 
	 * @param date
	 *            the date to format
	 * @param pattern
	 *            the pattern to use to format the date
	 * @param timeZone
	 *            the time zone to use, may be <code>null</code>
	 * @param locale
	 *            the locale to use, may be <code>null</code>
	 * @return the formatted date
	 */
	public static String format(Date date, String pattern, TimeZone timeZone,
			Locale locale) {
		FastDateFormat df = FastDateFormat.getInstance(pattern, timeZone,
				locale);
		return df.format(date);
	}

	/**
	 * <p>
	 * Formats a calendar into a specific pattern in a time zone and locale.
	 * </p>
	 * 
	 * @param calendar
	 *            the calendar to format
	 * @param pattern
	 *            the pattern to use to format the calendar
	 * @param timeZone
	 *            the time zone to use, may be <code>null</code>
	 * @param locale
	 *            the locale to use, may be <code>null</code>
	 * @return the formatted calendar
	 * @see FastDateFormat#format(Calendar)
	 * @since 2.4
	 */
	public static String format(Calendar calendar, String pattern,
			TimeZone timeZone, Locale locale) {
		FastDateFormat df = FastDateFormat.getInstance(pattern, timeZone,
				locale);
		return df.format(calendar);
	}

}
/*----------------------------------------------------------------------------*/
package com.accu.common.util.time;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DateIntervalTools {
	/**
	 * 字串 转 日期
	 * @param dateString
	 * @param format
	 * @return
	 * @throws ParseException
	 */
	public static Date parserStringToDate(String dateString,String format) throws ParseException {
		DateFormat dateFormat=new SimpleDateFormat(format);
		return dateFormat.parse(dateString);
	}
	/**
	 * 日期 转 字串
	 * @param date
	 * @param format
	 * @return
	 * @throws ParseException
	 */
	public static String parserDateToString(Date date,String format) throws ParseException {
		DateFormat dateFormat=new SimpleDateFormat(format);
		return dateFormat.format(date);
	}
	/**
	 * 取得前年当前日期
	 * @param specifyDate
	 * @param specifyDateFormat
	 * @param returnDateFormat
	 * @return String   
	 * @throws ParseException 
	 */
	public static String getYearBeforeDate(String specifyDate,String specifyDateFormat,String returnDateFormat) throws ParseException{
		Calendar c =Calendar.getInstance();
		Date date=new SimpleDateFormat(specifyDateFormat).parse(specifyDate);
		c.setTime(date);
		int year=c.get(Calendar.YEAR);
		c.set(Calendar.YEAR, year-1);
		String dayLater=new SimpleDateFormat(returnDateFormat).format(c.getTime());
		return dayLater;
	}
	/**
	 * 取得当前日期所在的周
	 * @param specifyDate
	 * @param specifyDateFormat
	 * @return
	 * @throws ParseException
	 */
	public static String getWeekOfDate(String specifyDate,String specifyDateFormat) throws ParseException{
		Calendar c =new GregorianCalendar();
		Date date=new SimpleDateFormat(specifyDateFormat).parse(specifyDate);
		c.setTime(date);
		int weeks=c.getActualMaximum(Calendar.WEEK_OF_YEAR);
		int year=c.get(Calendar.YEAR);
		int week=c.get(Calendar.WEEK_OF_YEAR);
		int month=Integer.parseInt(specifyDate.substring(5, 7).toString());
		if(month==12 && week<10){
			week=weeks;
		}
		return year+"-"+week;
	}
	/**
	 * 取得当前日期所在的周
	 * @param specifyDate
	 * @param specifyDateFormat
	 * @return
	 * @throws ParseException
	 */
	public static String getWeekOfDate() throws ParseException{
		Calendar c =new GregorianCalendar();
		Date date=new Date();
				//new SimpleDateFormat(specifyDateFormat).parse(specifyDate);
		c.setTime(date);
		int weeks=c.getActualMaximum(Calendar.WEEK_OF_YEAR);
		int year=c.get(Calendar.YEAR);
		int week=c.get(Calendar.WEEK_OF_YEAR);
		int month=c.get(Calendar.MONTH)+1;
		if(month==12 && week<10){
			week=weeks;
		}
		return year+"-"+week;
	}
	/**
	 * 取得当前日期所在的周的上一周
	 * @param specifyDate
	 * @param specifyDateFormat
	 * @return
	 * @throws ParseException
	 */
	public static String getBeforeWeekOfDate() throws ParseException{
		Calendar c =new GregorianCalendar();
		Date date=new Date();
				//new SimpleDateFormat(specifyDateFormat).parse(specifyDate);
		c.setTime(date);
		int weeks=c.getActualMaximum(Calendar.WEEK_OF_YEAR);
		int year=c.get(Calendar.YEAR);
		int week=c.get(Calendar.WEEK_OF_YEAR)-1;
		int month=c.get(Calendar.MONTH)+1;
		if(month==12 && week<10){
			week=weeks;
		}
		return year+"年"+week+"周";
	}
	/**
	 * 取得指定日期所在周的上一周
	 * @param specifyDate
	 * @param specifyDateFormat
	 * @return
	 * @throws ParseException
	 */
	public static String getBeforeWeekOfDates(Date d) throws ParseException{
		Calendar c =new GregorianCalendar();
				//new SimpleDateFormat(specifyDateFormat).parse(specifyDate);
		if(d==null)d=new Date();
		c.setTime(d);
		int weeks=c.getActualMaximum(Calendar.WEEK_OF_YEAR);
		int year=c.get(Calendar.YEAR);
		int week=c.get(Calendar.WEEK_OF_YEAR)-1;
		int month=c.get(Calendar.MONTH)+1;
		if(month==12 && week<10){
			week=weeks;
		}
		return year+"年"+week+"周";
	}
	/**
	 *  获得当前日期与本周一相差的天数
	 * @return
	 */
    private static int getMondayPlus() {
         Calendar cd = Calendar.getInstance();
         // 获得今天是一周的第几天,星期日是第一天,星期二是第二天......
         int dayOfWeek = cd.get(Calendar.DAY_OF_WEEK);
         if (dayOfWeek == 1) {
             return -6;
         } else {
             return 2 - dayOfWeek;
         }
     } 
    /**
	 *  获得指定日期与本周一相差的天数
	 * @return
	 */
    private static int getMondayPluss(Date e) {
         Calendar cd = Calendar.getInstance();
         cd.setTime(e);
         // 获得今天是一周的第几天,星期日是第一天,星期二是第二天......
         int dayOfWeek = cd.get(Calendar.DAY_OF_WEEK);
         if (dayOfWeek == 1) {
             return -6;
         } else {
             return 2 - dayOfWeek;
         }
     } 
    /**
     *  获得当前周- 周一的日期
     * @return
     */
    public static String getCurrentMonday(){
         int mondayPlus = getMondayPlus();
         GregorianCalendar currentDate = new GregorianCalendar();
         currentDate.add(GregorianCalendar.DATE, mondayPlus);
         Date monday = currentDate.getTime();
         DateFormat df = DateFormat.getDateInstance();
         String preMonday = df.format(monday);
         return preMonday;
     }
    /**
     *  获得指定日期所在周- 周一的日期
     * @return
     */
    public static String getMonday(Date d){
         int mondayPlus = getMondayPluss(d);
         Calendar calendar=Calendar.getInstance();
 		 calendar.setTime(d);
 		 calendar.add(GregorianCalendar.DATE, mondayPlus);
         Date monday = calendar.getTime();
         SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
         String preMonday = df.format(monday);
         return preMonday;
     }
 /**
  *  获得当前周- 周日  的日期
  * @return
  */
    public static String getPreviousSunday() {
         int mondayPlus = getMondayPlus();
         GregorianCalendar currentDate = new GregorianCalendar();
         currentDate.add(GregorianCalendar.DATE, mondayPlus +6);
         Date monday = currentDate.getTime();
         DateFormat df = DateFormat.getDateInstance();
         String preMonday = df.format(monday);
         return preMonday;
 
    }
    /**
     *  获得指定日期所在周- 周日  的日期
     * @return
     */
       public static String getSunday(Date d) {
            int mondayPlus = getMondayPluss(d);
            Calendar calendar=Calendar.getInstance();
    		calendar.setTime(d);
    		calendar.add(GregorianCalendar.DATE, mondayPlus +6);
            Date monday = calendar.getTime();
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
            String preMonday = df.format(monday);
            return preMonday;
    
       }
	/**
	 * 取得当前日期所在的季度
	 * @param specifyDate
	 * @param specifyDateFormat
	 * @return
	 * @throws ParseException
	 */
	public static String getQuarterOfDate(String specifyDate,String specifyDateFormat) throws ParseException{
		Calendar c =Calendar.getInstance();
		Date date=new SimpleDateFormat(specifyDateFormat).parse(specifyDate);
		c.setTime(date);
		int year=c.get(Calendar.YEAR);
		int month=c.get(Calendar.MONTH)+1;
		int quarter;
		if(month%3==0){
			quarter=month/3;
		}else{
			quarter=month/3+1;
		}
		return year+"-"+quarter;
	}
	//--------------------------------------------------------------------------------------------
	/**
	 * 取得日期时间段中的所有时间年
	 * @param minDateStr
	 * @param maxDateStr
	 * @return
	 */
	public static List<String> getYearBetween(String minDateStr,String maxDateStr ){
		List<String> result=new ArrayList<String>();
		int startTime=Integer.valueOf(minDateStr).intValue();
		int endTime=Integer.valueOf(maxDateStr).intValue();
	    for(int a=startTime;a<endTime+1;a++){
	    	result.add(String.valueOf(a));
	    }	
	    return result;
	}
	/**
	 * 取得日期段中的所有时间月
	 * @param minDateStr
	 * @param maxDateStr
	 * @return
	 */
	public static List<String> getMonthBetween(String minDateStr,String maxDateStr,String paramDateFormat,String returnDateFormat){
		ArrayList<String> result = new ArrayList<String>();
		try {
			SimpleDateFormat paramFormat = new SimpleDateFormat(paramDateFormat);
			SimpleDateFormat returnFormat = new SimpleDateFormat(returnDateFormat);
			Date minDate = paramFormat.parse(minDateStr);
			
	        Date maxDate=paramFormat.parse(maxDateStr);
			Calendar min = Calendar.getInstance();
			Calendar max = Calendar.getInstance();
	
			min.setTime(minDate);
			min.set(min.get(Calendar.YEAR), min.get(Calendar.MONTH), 1);
	
			max.setTime(maxDate);
			max.set(max.get(Calendar.YEAR), max.get(Calendar.MONTH), 2);
	
			Calendar curr = min;
			while (curr.before(max)) {
			 result.add(returnFormat.format(curr.getTime()));
			 curr.add(Calendar.MONTH, 1);
			}
			} catch (ParseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		  return result;
		}
	/**
	 * 取得日期段中的所有时间月? 需求调周设定接口
	 * @param minDateStr
	 * @param maxDateStr
	 * @return
	 * @throws ParseException 
	 */
	public static List<String> getWeekBetweenDate(String minDateStr,String maxDateStr,String paramDateFormat) throws ParseException{
		ArrayList<String> result = new ArrayList<String>();
		SimpleDateFormat paramFormat = new SimpleDateFormat(paramDateFormat);
		Date minDate = paramFormat.parse(minDateStr);
        Date maxDate=paramFormat.parse(maxDateStr);
		Calendar min = Calendar.getInstance();
		Calendar max = Calendar.getInstance();
		min.setTime(minDate);
		max.setTime(maxDate);
		int count=1;
		Calendar curr = min;
		Calendar curr1 = max;
		while(curr.before(max)){
			curr.add(Calendar.DAY_OF_YEAR, 1);
			if(curr.get(Calendar.DAY_OF_WEEK)==Calendar.SATURDAY){
				count++;
				result.add(curr.get(Calendar.YEAR)+"/"+curr.get(Calendar.WEEK_OF_YEAR));
			}
		}
		if (!result.get(result.size()-1).equals(curr1.get(Calendar.YEAR)+"/"+curr1.get(Calendar.WEEK_OF_YEAR))) {
			result.add(curr1.get(Calendar.YEAR)+"/"+curr1.get(Calendar.WEEK_OF_YEAR));
		}
		
		return  result;
		}
	public static List<String> getWeekBetweenDate1(String minDateStr,String maxDateStr,String paramDateFormat){
		SimpleDateFormat paramFormat = new SimpleDateFormat(paramDateFormat);
		List<String> dateGap=new ArrayList<String>();
		
		try {
			Date minDate = paramFormat.parse(minDateStr);
			Date maxDate=paramFormat.parse(maxDateStr);
			Calendar min = Calendar.getInstance();
			Calendar max = Calendar.getInstance();
			min.setTime(minDate);
			max.setTime(maxDate);
			Calendar curr = min;
			Calendar curr1 = max;
			int minTime = curr.get(Calendar.DAY_OF_YEAR);
			int maxTime = curr1.get(Calendar.DAY_OF_YEAR);
			if ((maxTime-minTime)>6) {
				try {
					dateGap=DateIntervalTools.getWeekBetweenDate( minDateStr, maxDateStr, paramDateFormat);
				} catch (ParseException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}else{
				if (curr.get(Calendar.WEEK_OF_YEAR)==curr1.get(Calendar.WEEK_OF_YEAR)) {
					String week=curr.get(Calendar.YEAR)+"/"+curr.get(Calendar.WEEK_OF_YEAR);
					dateGap.add(week);
				}else{
					String week5=curr.get(Calendar.YEAR)+"/"+curr.get(Calendar.WEEK_OF_YEAR);
					String week1=curr1.get(Calendar.YEAR)+"/"+curr1.get(Calendar.WEEK_OF_YEAR);
					dateGap.add(week5);
					dateGap.add(week1);
				}
			}
		} catch (ParseException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
       
		return dateGap;
	}
	/**
	 * 取得日期段中的所有时间月
	 * @param minDateStr
	 * @param maxDateStr
	 * @return
	 */
	public static List<String> getMonthBetween(String minDateStr, String maxDateStr) {
		ArrayList<String> result = new ArrayList<String>();
		try {
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM");
			Date minDate = sdf.parse(minDateStr);
			
	        Date maxDate=sdf.parse(maxDateStr);
			Calendar min = Calendar.getInstance();
			Calendar max = Calendar.getInstance();
	
			min.setTime(minDate);
			min.set(min.get(Calendar.YEAR), min.get(Calendar.MONTH), 1);
	
			max.setTime(maxDate);
			max.set(max.get(Calendar.YEAR), max.get(Calendar.MONTH), 2);
	
			Calendar curr = min;
			while (curr.before(max)) {
			 result.add(sdf.format(curr.getTime()));
			 curr.add(Calendar.MONTH, 1);
			}
			} catch (ParseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		  return result;
		}
	/**
	 * 取得日期段中的所有时间天
	 * @param minDateStr
	 * @param maxDateStr
	 * @return
	 */
	public static List<String> getDayBetween(String minDateStr, String maxDateStr,String paramDateFormat,String returnDateFormat) {
		ArrayList<String> result = new ArrayList<String>();
		try {
			SimpleDateFormat sdfResult = new SimpleDateFormat(returnDateFormat);
			SimpleDateFormat sdf = new SimpleDateFormat(paramDateFormat);
			Date minDate = sdf.parse(minDateStr);
			
	        Date maxDate=sdf.parse(maxDateStr);
			Calendar min = Calendar.getInstance();
			Calendar max = Calendar.getInstance();
			min.setTime(minDate);
			max.setTime(maxDate);
			max.add(Calendar.DAY_OF_MONTH, 1);
			Calendar curr = min;
			while (curr.before(max)) {
				 result.add(sdfResult.format(curr.getTime()));
				 curr.add(Calendar.DAY_OF_MONTH, 1);
			}
			} catch (ParseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		return result;
		}
	/**
	 * 取得日期段中的所有时间天
	 * @param minDateStr
	 * @param maxDateStr
	 * @return
	 */
	public static List<String> getHourBetweenDate(String minDateStr, String maxDateStr,String paramDateFormat,String returnDateFormat) {
		ArrayList<String> result = new ArrayList<String>();
		try {
			SimpleDateFormat sdfResult = new SimpleDateFormat(returnDateFormat);
			SimpleDateFormat sdf = new SimpleDateFormat(paramDateFormat);
			Date minDate = sdf.parse(minDateStr);
			
			Date maxDate=sdf.parse(maxDateStr);
			Calendar min = Calendar.getInstance();
			Calendar max = Calendar.getInstance();
			min.setTime(minDate);
			max.setTime(maxDate);
			max.add(Calendar.DAY_OF_MONTH, 1);
			Calendar curr = min;
			while (curr.before(max)) {
				 result.add(sdfResult.format(curr.getTime()));
				 curr.add(Calendar.HOUR_OF_DAY, 1);
			}
			} catch (ParseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		return result;
		}
	/**
	 * 取得日期段中的所有时间天
	 * @param minDateStr
	 * @param maxDateStr
	 * @return
	 */
	public static List<String> getDayBetween(String minDateStr, String maxDateStr) {
		ArrayList<String> result = new ArrayList<String>();
		try {
			if(minDateStr.indexOf("-") == -1){
				minDateStr = minDateStr.replaceAll("/", "-");
			}
			if(maxDateStr.indexOf("-") == -1){
				maxDateStr = maxDateStr.replaceAll("/", "-");
			}
			SimpleDateFormat sdfResult = new SimpleDateFormat("yyyy/MM/dd");
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
			Date minDate = sdf.parse(minDateStr);
			
	        Date maxDate=sdf.parse(maxDateStr);
			Calendar min = Calendar.getInstance();
			Calendar max = Calendar.getInstance();
			min.setTime(minDate);
			max.setTime(maxDate);
			max.add(Calendar.DAY_OF_MONTH, 1);
			Calendar curr = min;
			while (curr.before(max)) {
				 result.add(sdfResult.format(curr.getTime()));
				 curr.add(Calendar.DAY_OF_MONTH, 1);
			}
			} catch (ParseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		return result;
	}
	
	/**
	 * 取得日期段中的所有时间天
	 * @param minDateStr
	 * @param maxDateStr
	 * @return
	 */
	public static List<String> getDayBetween(Date minDateStr, Date maxDateStr) {
		ArrayList<String> result = new ArrayList<String>();
		if(minDateStr == null){
			return null;
		}
		if(maxDateStr == null){
			return null;
		}
		if(minDateStr.compareTo(maxDateStr) >= 0){
			return null;
		}
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		Calendar min = Calendar.getInstance();
		Calendar max = Calendar.getInstance();
		min.setTime(minDateStr);
		max.setTime(maxDateStr);
		max.add(Calendar.DAY_OF_MONTH, 1);
		Calendar curr = min;
		while (curr.before(max)) {
			 result.add(sdf.format(curr.getTime()));
			 curr.add(Calendar.DAY_OF_MONTH, 1);
		}
		return result;
	}
	/**
	 * 取得日期段中的所有时间周
	 * @param minDateStr
	 * @param maxDateStr
	 * @return
	 */
	public static List<String>  getWeekBetween(String minDateStr,String maxDateStr,String paramDateFormat){
		ArrayList<String> result = new ArrayList<String>();
		String[] startDate=minDateStr.split("/");
		String[] endDate=maxDateStr.split("/");
		int startYear=0;
		int endYear=0;
		
		int startWeek=0;
		int endWeek=0;
		if(paramDateFormat.equals("yyyy/ww")){
			startYear=Integer.valueOf(startDate[0]).intValue();
			endYear=Integer.valueOf(endDate[0]).intValue();
			startWeek=Integer.valueOf(startDate[1]).intValue();
			endWeek=Integer.valueOf(endDate[1]).intValue();
		}else if(paramDateFormat.equals("ww/yyyy")){
			startYear=Integer.valueOf(startDate[1]).intValue();
			endYear=Integer.valueOf(endDate[1]).intValue();
			startWeek=Integer.valueOf(startDate[0]).intValue();
			endWeek=Integer.valueOf(endDate[0]).intValue();
		}
		//同年
		if(startYear==endYear){
			for(int i=startWeek;i<endWeek+1;i++){
				String date=startYear+"/"+i;
				result.add(date);
			}
		}else if(startYear==endYear-1){//连续年
			for(int i=startWeek;i<54;i++){
				String date=startYear+"/"+i;
				result.add(date);
			}
			for(int j=1;j<endWeek+1;j++){
				String date=endYear+"/"+j;
				result.add(date);
			}
		}else{//不连续年
			for(int i=startWeek;i<54;i++){
				String date=startYear+"/"+i;
				result.add(date);
			}
			for(int k=startYear+1;k<endYear;k++){
				for(int s=1;s<54;s++){
					String date=k+"/"+s;
					result.add(date);
				}
			}
			for(int j=1;j<endWeek+1;j++){
				String date=endYear+"/"+j;
				result.add(date);
			}
		}
		return  result;
	}
	/**
	 * 取得日期段中的所有时间周
	 * @param minDateStr
	 * @param maxDateStr
	 * @return
	 */
	public static List<String>  getWeekBetween(String minDateStr,String maxDateStr){
		ArrayList<String> result = new ArrayList<String>();
		String[] startDate=minDateStr.split("/");
		String[] endDate=maxDateStr.split("/");
		
		int startYear=Integer.valueOf(startDate[0]).intValue();
		int endYear=Integer.valueOf(endDate[0]).intValue();
		
		int startWeek=Integer.valueOf(startDate[1]).intValue();
		int endWeek=Integer.valueOf(endDate[1]).intValue();
		
		//同年
		if(startYear==endYear){
			for(int i=startWeek;i<endWeek+1;i++){
				String date=startYear+"/"+i;
				result.add(date);
			}
		}else if(startYear==endYear-1){//连续年
			for(int i=startWeek;i<54;i++){
				String date=startYear+"/"+i;
				result.add(date);
			}
			for(int j=1;j<endWeek+1;j++){
				String date=endYear+"/"+j;
				result.add(date);
			}
		}else{//不连续年
			for(int i=startWeek;i<54;i++){
				String date=startYear+"/"+i;
				result.add(date);
			}
			for(int k=startYear+1;k<endYear;k++){
				for(int s=1;s<54;s++){
					String date=k+"/"+s;
					result.add(date);
				}
			}
			for(int j=1;j<endWeek+1;j++){
				String date=endYear+"/"+j;
				result.add(date);
			}
		}
		return  result;
	}
	/**
	 * 取得时间段中的所有季度数
	 * @param minDateStr
	 * @param maxDateStr
	 * @return
	 */
	public static List<String> getQuarterBetwEeen(String minDateStr,String maxDateStr,String paramDateFormat){
		ArrayList<String> result =new ArrayList<String>();
		String[] startDate=minDateStr.split("/");
		String[] endDate=maxDateStr.split("/");
		int startYear=0;
		int endYear=0;
		int startQuarter=0;
		int endQuarter=0;
		if(paramDateFormat.equals("yyyy/qq")){
			 startYear=Integer.valueOf(startDate[0]).intValue();
			 endYear=Integer.valueOf(endDate[0]).intValue();
			
			 startQuarter=Integer.valueOf(startDate[1]).intValue();
			 endQuarter=Integer.valueOf(endDate[1]).intValue();
		}else if(paramDateFormat.equals("qq/yyyy")){
			 startYear=Integer.valueOf(startDate[1]).intValue();
			 endYear=Integer.valueOf(endDate[1]).intValue();
			
			 startQuarter=Integer.valueOf(startDate[0]).intValue();
			 endQuarter=Integer.valueOf(endDate[0]).intValue();
		}
		//同年
		if(startYear==endYear){
			for(int i=startQuarter;i<endQuarter+1;i++){
				String date=startYear+"/"+i;
				result.add(date);
			}
		}else if(startYear==endYear-1){//连续年
			for(int i=startQuarter;i<5;i++){
				String date=startYear+"/"+i;
				result.add(date);
			}
			for(int j=1;j<endQuarter+1;j++){
				String date=endYear+"/"+j;
				result.add(date);
			}
		}else{//不连续年
			for(int i=startQuarter;i<5;i++){
				String date=startYear+"/"+i;
				result.add(date);
			}
			for(int k=startYear+1;k<endYear;k++){
				for(int s=1;s<5;s++){
					String date=k+"/"+s;
					result.add(date);
				}
			}
			for(int j=1;j<endQuarter+1;j++){
				String date=endYear+"/"+j;
				result.add(date);
			}
		}
		return result;
	}
	/**
	 * 取得时间段中的所有季度数
	 * @param minDateStr
	 * @param maxDateStr
	 * @return
	 */
	public static List<String> getQuarterBetwEeen(String minDateStr,String maxDateStr){
		ArrayList<String> result =new ArrayList<String>();
		String[] startDate=minDateStr.split("/");
		String[] endDate=maxDateStr.split("/");
		
		int startYear=Integer.valueOf(startDate[0]).intValue();
		int endYear=Integer.valueOf(endDate[0]).intValue();
		
		int startQuarter=Integer.valueOf(startDate[1]).intValue();
		int endQuarter=Integer.valueOf(endDate[1]).intValue();
		//同年
		if(startYear==endYear){
			for(int i=startQuarter;i<endQuarter+1;i++){
				String date=startYear+"/"+i;
				result.add(date);
			}
		}else if(startYear==endYear-1){//连续年
			for(int i=startQuarter;i<5;i++){
				String date=startYear+"/"+i;
				result.add(date);
			}
			for(int j=1;j<endQuarter+1;j++){
				String date=endYear+"/"+j;
				result.add(date);
			}
		}else{//不连续年
			for(int i=startQuarter;i<5;i++){
				String date=startYear+"/"+i;
				result.add(date);
			}
			for(int k=startYear+1;k<endYear;k++){
				for(int s=1;s<5;s++){
					String date=k+"/"+s;
					result.add(date);
				}
			}
			for(int j=1;j<endQuarter+1;j++){
				String date=endYear+"/"+j;
				result.add(date);
			}
		}
		return result;
	}
	/**
	 * 取得日期段中的所有时间小时2011/03/01 01
	 * @param minDateStr
	 * @param maxDateStr
	 * @return
	 */
	public static List<String> getHourBetween(String minDateStr, String maxDateStr) {
		ArrayList<String> result = new ArrayList<String>();
		try {
			SimpleDateFormat resultSdf = new SimpleDateFormat("yyyy/MM/dd HH");
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH");
			Date minDate = sdf.parse(minDateStr);
			
	        Date maxDate=sdf.parse(maxDateStr);
			Calendar min = Calendar.getInstance();
			Calendar max = Calendar.getInstance();
			min.setTime(minDate);
			max.setTime(maxDate);
			max.add(Calendar.HOUR_OF_DAY, 1);
			Calendar curr = min;
			while (curr.before(max)) {
				 result.add(resultSdf.format(curr.getTime()));
				 curr.add(Calendar.HOUR_OF_DAY, 1);
			}
			} catch (ParseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		return result;
		}
	/**
	 * 取得日期段中的所有分钟2011/03/01 01
	 * @param minDateStr
	 * @param maxDateStr
	 * @return
	 */
	public static List<String> getMinuteBetween(String minDateStr, String maxDateStr) {
		ArrayList<String> result = new ArrayList<String>();
		try {
			SimpleDateFormat resultSdf = new SimpleDateFormat("yyyy/MM/dd HH:mm");
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
			Date minDate = sdf.parse(minDateStr);
			
	        Date maxDate=sdf.parse(maxDateStr);
			Calendar min = Calendar.getInstance();
			Calendar max = Calendar.getInstance();
			min.setTime(minDate);
			max.setTime(maxDate);
			max.add(Calendar.MINUTE, 5);
			Calendar curr = min;
			while (curr.before(max)) {
				 result.add(resultSdf.format(curr.getTime()));
				 curr.add(Calendar.MINUTE, 5);
			}
			} catch (ParseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		return result;
		}
	/**
	 * 取得日期段中的所有分钟2011/03/01 01
	 * @param minDateStr
	 * @param maxDateStr
	 * @return
	 */
	public static List<String> getMinuteBetweenYearOnYear(String minDateStr, String maxDateStr) {
		ArrayList<String> result = new ArrayList<String>();
		try {
			SimpleDateFormat resultSdf = new SimpleDateFormat("/MM/dd HH:mm");
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
			Date minDate = sdf.parse(minDateStr);
			
	        Date maxDate=sdf.parse(maxDateStr);
			Calendar min = Calendar.getInstance();
			Calendar max = Calendar.getInstance();
			min.setTime(minDate);
			max.setTime(maxDate);
			max.add(Calendar.MINUTE, 5);
			Calendar curr = min;
			while (curr.before(max)) {
				 result.add(resultSdf.format(curr.getTime()));
				 curr.add(Calendar.MINUTE, 5);
			}
			} catch (ParseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		return result;
		}
	
	/**
	 * 取得日期的前一月
	 * yyyy-MM
	 * @return String    返回类型
	 * @throws
	 */
	public static String getMonthBefore(String specifyDate){
        Calendar c =Calendar.getInstance();
        Date date=null; 
        String dayBefore="";
        try {
			date =new SimpleDateFormat("yyyy-MM").parse(specifyDate);
		    c.setTime(date);
		    int month=c.get(Calendar.MONTH);
		    c.set(Calendar.MONTH, month-1);
		    dayBefore=new SimpleDateFormat("yyyy/MM").format(c.getTime());
        } catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        return dayBefore;
	}
	
	/**
	 * 取得日期的前一天 
	 * yyyy-MM-dd
	 * @return String    返回类型
	 * @throws
	 */
	public static String getDayBefore(String specifyDate){
        Calendar c =Calendar.getInstance();
        Date date=null; 
        String dayBefore="";
        try {
			date =new SimpleDateFormat("yyyy-MM-dd").parse(specifyDate);
		    c.setTime(date);
		    int day=c.get(Calendar.DATE);
		    c.set(Calendar.DATE, day-1);
		    dayBefore=new SimpleDateFormat("yyyy/MM/dd").format(c.getTime());
        } catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        return dayBefore;
	}
	
	
	public static String getDayAfter(String specifyDate){
        Calendar c =Calendar.getInstance();
        Date date=null; 
        String dayBefore="";
        try {
			date =new SimpleDateFormat("yyyy-MM-dd").parse(specifyDate);
		    c.setTime(date);
		    int day=c.get(Calendar.DATE);
		    c.set(Calendar.DATE, day+1);
		    dayBefore=new SimpleDateFormat("yyyy/MM/dd").format(c.getTime());
        } catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        return dayBefore;
	}
	
	
	public static String getYearAfter(String specifyDate){
        Calendar c =Calendar.getInstance();
        Date date=null; 
        String dayBefore="";
        try {
			date =new SimpleDateFormat("yyyy-MM-dd").parse(specifyDate);
		    c.setTime(date);
		    int year=c.get(Calendar.YEAR);
		    c.set(Calendar.YEAR, year+1);
		    dayBefore=new SimpleDateFormat("yyyy/MM/dd").format(c.getTime());
        } catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        return dayBefore;
	}
	/**
	 * 取得日期的后一天 
	 * @param specifyDate
	 * @param specifyDateFormat
	 * @param returnDateFormat
	 * @return String   
	 */
	public static String getDayLater(String specifyDate,String specifyDateFormat,String returnDateFormat){
        Calendar c =Calendar.getInstance();
        Date date=null; 
        String dayLater="";
        try {
			date =new SimpleDateFormat(specifyDateFormat).parse(specifyDate);
		    c.setTime(date);
		    int day=c.get(Calendar.DATE);
		    c.set(Calendar.DATE, day+1);
		    dayLater=new SimpleDateFormat(returnDateFormat).format(c.getTime());
        } catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        return dayLater;
	}
	
	/**
	 * 取得日期的前一天 
	 * @param specifyDate
	 * @param specifyDateFormat
	 * @param returnDateFormat
	 * @return String   
	 */
	public static String getDayBefore(String specifyDate,String specifyDateFormat,String returnDateFormat){
        Calendar c =Calendar.getInstance();
        Date date=null; 
        String dayLater="";
        try {
			date =new SimpleDateFormat(specifyDateFormat).parse(specifyDate);
		    c.setTime(date);
		    int day=c.get(Calendar.DATE);
		    c.set(Calendar.DATE, day-1);
		    dayLater=new SimpleDateFormat(returnDateFormat).format(c.getTime());
        } catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        return dayLater;
	}
	
	
	/**
	 * 取得日期的前一年 
	 * @param specifyDate
	 * @param specifyDateFormat
	 * @param returnDateFormat
	 * @return String   
	 */
	public static String getYearBefore(String specifyDate,String specifyDateFormat,String returnDateFormat){
        Calendar c =Calendar.getInstance();
        Date date=null; 
        String dayLater="";
        try {
			date =new SimpleDateFormat(specifyDateFormat).parse(specifyDate);
		    c.setTime(date);
		    int year=c.get(Calendar.YEAR);
		    c.set(Calendar.YEAR, year-1);
		    dayLater=new SimpleDateFormat(returnDateFormat).format(c.getTime());
        } catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        return dayLater;
	}
	/**
	 * 取得日期的前一周
	 * 2012/1
	 * @return String    返回类型
	 * @throws
	 */
	public static String getWeekBefore(String yearAndWeek){
		String[] date=yearAndWeek.split("-");
		int year=Integer.valueOf(date[0]).intValue();
		int week=Integer.valueOf(date[1]).intValue();
		Calendar c =Calendar.getInstance();
		c.set(Calendar.YEAR, year);
		c.set(Calendar.WEEK_OF_YEAR, week);
		
	    int cweek=c.get(Calendar.WEEK_OF_YEAR);
	    c.set(Calendar.WEEK_OF_YEAR, cweek-1);
	    
	    String weekBefore=c.get(Calendar.YEAR)+"/"+c.get(Calendar.WEEK_OF_YEAR);
        return weekBefore;
	}
	/**
	 * 取得日期的前一季度
	 * 2012/1
	 * @return String    返回类型
	 * @throws
	 */
	public static String getQuarterBefore(String yearAndQuarter){
		String[] date=yearAndQuarter.split("-");
		int year=Integer.valueOf(date[0]).intValue();
		int quarter=Integer.valueOf(date[1]).intValue();
		if(quarter==1){
			year=year-1;
			quarter=4;
		}else{
			quarter=quarter-1;
		}
	    
	    String quarterBefore=year+"/"+quarter;
        return quarterBefore;
	}
	/**
	 * 获得某年某月的天数
	 * @return int    返回类型
	 */
	public static int getDayOfYearMonth(String ym){
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM");
        Date date;
        int dayOfMonth=0;
        try {
	          date = format.parse(ym);
	          Calendar calendar = new GregorianCalendar();
	          calendar.setTime(date);
	          dayOfMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
       } catch (ParseException e) {
           e.printStackTrace();
       }
		return dayOfMonth;
	}
	/**
	 * 获得日期所在年的周数
	 * @return int    返回类型
	 * @throws ParseException 
	 */
	public static int getWeekNumberOfDate(String specifyDate,String specifyDateFormat) throws ParseException{
		SimpleDateFormat format = new SimpleDateFormat(specifyDateFormat);
		Date date;
		int weekNumber=0;
		date = format.parse(specifyDate);
		Calendar calendar = new GregorianCalendar();
		calendar.setTime(date);
		weekNumber = calendar.getActualMaximum(Calendar.WEEK_OF_YEAR);
		return weekNumber;
	}
}
/*------------------------------------------------------------------------------*/
/*
 * 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 com.accu.common.util.time;

import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.TimeZone;

import com.accu.common.util.StringUtils;


public class DateUtils {
    
    /**
     * The UTC time zone  (often referred to as GMT).
     */
    public static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("GMT");
    /**
     * Number of milliseconds in a standard second.
     * @since 2.1
     */
    public static final long MILLIS_PER_SECOND = 1000;
    /**
     * Number of milliseconds in a standard minute.
     * @since 2.1
     */
    public static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
    /**
     * Number of milliseconds in a standard hour.
     * @since 2.1
     */
    public static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
    /**
     * Number of milliseconds in a standard day.
     * @since 2.1
     */
    public static final long MILLIS_PER_DAY = 24 * MILLIS_PER_HOUR;

    /**
     * This is half a month, so this represents whether a date is in the top
     * or bottom half of the month.
     */
    public final static int SEMI_MONTH = 1001;

    private static final int[][] fields = {
            {Calendar.MILLISECOND},
            {Calendar.SECOND},
            {Calendar.MINUTE},
            {Calendar.HOUR_OF_DAY, Calendar.HOUR},
            {Calendar.DATE, Calendar.DAY_OF_MONTH, Calendar.AM_PM 
                /* Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK, Calendar.DAY_OF_WEEK_IN_MONTH */
            },
            {Calendar.MONTH, DateUtils.SEMI_MONTH},
            {Calendar.YEAR},
            {Calendar.ERA}};

    /**
     * A week range, starting on Sunday.
     */
    public final static int RANGE_WEEK_SUNDAY = 1;

    /**
     * A week range, starting on Monday.
     */
    public final static int RANGE_WEEK_MONDAY = 2;

    /**
     * A week range, starting on the day focused.
     */
    public final static int RANGE_WEEK_RELATIVE = 3;

    /**
     * A week range, centered around the day focused.
     */
    public final static int RANGE_WEEK_CENTER = 4;

    /**
     * A month range, the week starting on Sunday.
     */
    public final static int RANGE_MONTH_SUNDAY = 5;

    /**
     * A month range, the week starting on Monday.
     */
    public final static int RANGE_MONTH_MONDAY = 6;

    /**
     * Constant marker for truncating 
     */
    private final static int MODIFY_TRUNCATE = 0;

    /**
     * Constant marker for rounding
     */
    private final static int MODIFY_ROUND = 1;

    /**
     * Constant marker for ceiling
     */
    private final static int MODIFY_CEILING= 2;


    public DateUtils() {
        super();
    }

    public static boolean isSameDay(Date date1, Date date2) {
        if (date1 == null || date2 == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        Calendar cal1 = Calendar.getInstance();
        cal1.setTime(date1);
        Calendar cal2 = Calendar.getInstance();
        cal2.setTime(date2);
        return isSameDay(cal1, cal2);
    }

 
    public static boolean isSameDay(Calendar cal1, Calendar cal2) {
        if (cal1 == null || cal2 == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        return (cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) &&
                cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
                cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR));
    }

 
    public static boolean isSameInstant(Date date1, Date date2) {
        if (date1 == null || date2 == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        return date1.getTime() == date2.getTime();
    }

    public static boolean isSameInstant(Calendar cal1, Calendar cal2) {
        if (cal1 == null || cal2 == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        return cal1.getTime().getTime() == cal2.getTime().getTime();
    }

  
    public static boolean isSameLocalTime(Calendar cal1, Calendar cal2) {
        if (cal1 == null || cal2 == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        return (cal1.get(Calendar.MILLISECOND) == cal2.get(Calendar.MILLISECOND) &&
                cal1.get(Calendar.SECOND) == cal2.get(Calendar.SECOND) &&
                cal1.get(Calendar.MINUTE) == cal2.get(Calendar.MINUTE) &&
                cal1.get(Calendar.HOUR) == cal2.get(Calendar.HOUR) &&
                cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR) &&
                cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
                cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) &&
                cal1.getClass() == cal2.getClass());
    }

  
    public static Date parseDate(String str, String[] parsePatterns) throws ParseException {
        return parseDateWithLeniency(str, parsePatterns, true);
    }
    

    public static Date parseDateStrictly(String str, String[] parsePatterns) throws ParseException {
        return parseDateWithLeniency(str, parsePatterns, false);
    }

  
    private static Date parseDateWithLeniency(String str, String[] parsePatterns,
            boolean lenient) throws ParseException {
        if (str == null || parsePatterns == null) {
            throw new IllegalArgumentException("Date and Patterns must not be null");
        }
        
        SimpleDateFormat parser = new SimpleDateFormat();
        parser.setLenient(lenient);
        ParsePosition pos = new ParsePosition(0);
        for (int i = 0; i < parsePatterns.length; i++) {

            String pattern = parsePatterns[i];

            // LANG-530 - need to make sure 'ZZ' output doesn't get passed to SimpleDateFormat
            if (parsePatterns[i].endsWith("ZZ")) {
                pattern = pattern.substring(0, pattern.length() - 1);
            }
            
            parser.applyPattern(pattern);
            pos.setIndex(0);

            String str2 = str;
            // LANG-530 - need to make sure 'ZZ' output doesn't hit SimpleDateFormat as it will ParseException
            if (parsePatterns[i].endsWith("ZZ")) {
                int signIdx  = indexOfSignChars(str2, 0);
                while (signIdx >=0) {
                    str2 = reformatTimezone(str2, signIdx);
                    signIdx = indexOfSignChars(str2, ++signIdx);
                }
            }

            Date date = parser.parse(str2, pos);
            if (date != null && pos.getIndex() == str2.length()) {
                return date;
            }
        }
        throw new ParseException("Unable to parse the date: " + str, -1);
    }

    /**
     * Index of sign charaters (i.e. '+' or '-').
     * 
     * @param str The string to search
     * @param startPos The start position
     * @return the index of the first sign character or -1 if not found
     */
    private static int indexOfSignChars(String str, int startPos) {
        int idx = StringUtils.indexOf(str, '+', startPos);
        if (idx < 0) {
            idx = StringUtils.indexOf(str, '-', startPos);
        }
        return idx;
    }

    /**
     * Reformat the timezone in a date string.
     *
     * @param str The input string
     * @param signIdx The index position of the sign characters
     * @return The reformatted string
     */
    private static String reformatTimezone(String str, int signIdx) {
        String str2 = str;
        if (signIdx >= 0 &&
            signIdx + 5 < str.length() &&
            Character.isDigit(str.charAt(signIdx + 1)) &&
            Character.isDigit(str.charAt(signIdx + 2)) &&
            str.charAt(signIdx + 3) == ':' &&
            Character.isDigit(str.charAt(signIdx + 4)) &&
            Character.isDigit(str.charAt(signIdx + 5))) {
            str2 = str.substring(0, signIdx + 3) + str.substring(signIdx + 4);
        }
        return str2;
    }

    //-----------------------------------------------------------------------
    /**
     * Adds a number of years to a date returning a new object.
     * The original date object is unchanged.
     *
     * @param date  the date, not null
     * @param amount  the amount to add, may be negative
     * @return the new date object with the amount added
     * @throws IllegalArgumentException if the date is null
     */
    public static Date addYears(Date date, int amount) {
        return add(date, Calendar.YEAR, amount);
    }


    public static Date addMonths(Date date, int amount) {
        return add(date, Calendar.MONTH, amount);
    }


    public static Date addWeeks(Date date, int amount) {
        return add(date, Calendar.WEEK_OF_YEAR, amount);
    }


    public static Date addDays(Date date, int amount) {
        return add(date, Calendar.DAY_OF_MONTH, amount);
    }


    public static Date addHours(Date date, int amount) {
        return add(date, Calendar.HOUR_OF_DAY, amount);
    }


    public static Date addMinutes(Date date, int amount) {
        return add(date, Calendar.MINUTE, amount);
    }


    public static Date addSeconds(Date date, int amount) {
        return add(date, Calendar.SECOND, amount);
    }


    public static Date addMilliseconds(Date date, int amount) {
        return add(date, Calendar.MILLISECOND, amount);
    }


    @Deprecated
	public static Date add(Date date, int calendarField, int amount) {
        if (date == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        Calendar c = Calendar.getInstance();
        c.setTime(date);
        c.add(calendarField, amount);
        return c.getTime();
    }
    

    public static Date setYears(Date date, int amount) {
        return set(date, Calendar.YEAR, amount);
    }


    public static Date setMonths(Date date, int amount) {
        return set(date, Calendar.MONTH, amount);
    }


    public static Date setDays(Date date, int amount) {
        return set(date, Calendar.DAY_OF_MONTH, amount);
    }


    public static Date setHours(Date date, int amount) {
        return set(date, Calendar.HOUR_OF_DAY, amount);
    }


    public static Date setMinutes(Date date, int amount) {
        return set(date, Calendar.MINUTE, amount);
    }
    

    public static Date setSeconds(Date date, int amount) {
        return set(date, Calendar.SECOND, amount);
    }

    public static Date setMilliseconds(Date date, int amount) {
        return set(date, Calendar.MILLISECOND, amount);
    } 
    

    private static Date set(Date date, int calendarField, int amount) {
        if (date == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        // getInstance() returns a new object, so this method is thread safe.
        Calendar c = Calendar.getInstance();
        c.setLenient(false);
        c.setTime(date);
        c.set(calendarField, amount);
        return c.getTime();
    }   
    
    public static Date round(Date date, int field) {
        if (date == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        Calendar gval = Calendar.getInstance();
        gval.setTime(date);
        modify(gval, field, MODIFY_ROUND);
        return gval.getTime();
    }


    public static Calendar round(Calendar date, int field) {
        if (date == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        Calendar rounded = (Calendar) date.clone();
        modify(rounded, field, MODIFY_ROUND);
        return rounded;
    }


    public static Date round(Object date, int field) {
        if (date == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        if (date instanceof Date) {
            return round((Date) date, field);
        } else if (date instanceof Calendar) {
            return round((Calendar) date, field).getTime();
        } else {
            throw new ClassCastException("Could not round " + date);
        }
    }

	/* @author ray
	 * @date 2014-05-24 ############################################################################
	 * @comment add code
	 */
	
	/**
	 * 返回两个日期之前的差值
	 * @see 返回值支持毫秒到天,根据field参数来判断,标准以Calendar类为准
	 * @param sDate
	 * @param eDate
	 * @param field [Calendar.SECOND, Calendar.MINUTE, Calendar.HOUR, Calendar.DATE]
	 * @return
	 */
	public static int getGapFromTwoDate(Date sDate, Date eDate, int field){
		long gap = (eDate.getTime() - sDate.getTime());
		if(Calendar.SECOND == field){
			gap = gap/1000;
		}else if(Calendar.MINUTE == field){
			gap =  gap/(60*1000);
		}else if(Calendar.HOUR == field){
			gap =  gap/(60*60*1000);
		}else if(Calendar.DATE == field){
			gap =  gap/(24*60*60*1000);
		}
		return (int) gap;
	}
	/**
	 * 推算一个日期加上或减去多长时间后的日期
	 * @param date
	 * @param field
	 * @param amount
	 * @return
	 */
	public static Date update(Date date, int field, int amount){
		long time = date.getTime();
		if(Calendar.SECOND == field){
			time += amount*1000L;
		}else if(Calendar.MINUTE == field){
			time += amount*60000L;
		}else if(Calendar.HOUR == field){
			time += amount*3600000L;
		}else if(Calendar.DATE == field){
			time += (long)amount*(long)(24*60*60*1000);
		}
		return new Date(time);
	}
	
	public static String format(Date date, String pattern){
		if(date == null){
			throw new NullPointerException("日期参数为空!");
		}
		if("yyyy-QQ".equals(pattern)){
			SimpleDateFormat df = new SimpleDateFormat();
			df.applyPattern("yyyy-MM");
			String strDate = df.format(date);
			String year = strDate.split("-")[0];
			int month = Integer.parseInt(strDate.split("-")[1]);
			if(month % 3 == 0){
				return year + "-" + (month / 3);
			}else{
				return year + "-" + (month / 3 + 1);
			}
		}else{
			SimpleDateFormat df = new SimpleDateFormat();
			df.applyPattern(pattern);
			return df.format(date);
		}
	}
    //-----------------------------------------------------------------------
 
    public static Date truncate(Date date, int field) {
        if (date == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        Calendar gval = Calendar.getInstance();
        gval.setTime(date);
        modify(gval, field, MODIFY_TRUNCATE);
        return gval.getTime();
    }

    public static Calendar truncate(Calendar date, int field) {
        if (date == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        Calendar truncated = (Calendar) date.clone();
        modify(truncated, field, MODIFY_TRUNCATE);
        return truncated;
    }

 
    public static Date truncate(Object date, int field) {
        if (date == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        if (date instanceof Date) {
            return truncate((Date) date, field);
        } else if (date instanceof Calendar) {
            return truncate((Calendar) date, field).getTime();
        } else {
            throw new ClassCastException("Could not truncate " + date);
        }
    }
    

    public static Date ceiling(Date date, int field) {
        if (date == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        Calendar gval = Calendar.getInstance();
        gval.setTime(date);
        modify(gval, field, MODIFY_CEILING);
        return gval.getTime();
    }

    public static Calendar ceiling(Calendar date, int field) {
        if (date == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        Calendar ceiled = (Calendar) date.clone();
        modify(ceiled, field, MODIFY_CEILING);
        return ceiled;
    }

 
    public static Date ceiling(Object date, int field) {
        if (date == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        if (date instanceof Date) {
            return ceiling((Date) date, field);
        } else if (date instanceof Calendar) {
            return ceiling((Calendar) date, field).getTime();
        } else {
            throw new ClassCastException("Could not find ceiling of for type: " + date.getClass());
        }
    }

 
    private static void modify(Calendar val, int field, int modType) {
        if (val.get(Calendar.YEAR) > 280000000) {
            throw new ArithmeticException("Calendar value too large for accurate calculations");
        }
        
        if (field == Calendar.MILLISECOND) {
            return;
        }

        Date date = val.getTime();
        long time = date.getTime();
        boolean done = false;

        // truncate milliseconds
        int millisecs = val.get(Calendar.MILLISECOND);
        if (MODIFY_TRUNCATE == modType || millisecs < 500) {
            time = time - millisecs;
        }
        if (field == Calendar.SECOND) {
            done = true;
        }

        // truncate seconds
        int seconds = val.get(Calendar.SECOND);
        if (!done && (MODIFY_TRUNCATE == modType || seconds < 30)) {
            time = time - (seconds * 1000L);
        }
        if (field == Calendar.MINUTE) {
            done = true;
        }

        // truncate minutes
        int minutes = val.get(Calendar.MINUTE);
        if (!done && (MODIFY_TRUNCATE == modType || minutes < 30)) {
            time = time - (minutes * 60000L);
        }

        // reset time
        if (date.getTime() != time) {
            date.setTime(time);
            val.setTime(date);
        }
        // ----------------- Fix for LANG-59 ----------------------- END ----------------

        boolean roundUp = false;
        for (int i = 0; i < fields.length; i++) {
            for (int j = 0; j < fields[i].length; j++) {
                if (fields[i][j] == field) {
                    //This is our field... we stop looping
                    if (modType == MODIFY_CEILING || (modType == MODIFY_ROUND && roundUp)) {
                        if (field == DateUtils.SEMI_MONTH) {
                            //This is a special case that's hard to generalize
                            //If the date is 1, we round up to 16, otherwise
                            //  we subtract 15 days and add 1 month
                            if (val.get(Calendar.DATE) == 1) {
                                val.add(Calendar.DATE, 15);
                            } else {
                                val.add(Calendar.DATE, -15);
                                val.add(Calendar.MONTH, 1);
                            }
// ----------------- Fix for LANG-440 ---------------------- START ---------------
                        } else if (field == Calendar.AM_PM) {
                            // This is a special case
                            // If the time is 0, we round up to 12, otherwise
                            //  we subtract 12 hours and add 1 day
                            if (val.get(Calendar.HOUR_OF_DAY) == 0) {
                                val.add(Calendar.HOUR_OF_DAY, 12);
                            } else {
                                val.add(Calendar.HOUR_OF_DAY, -12);
                                val.add(Calendar.DATE, 1);
                            }
// ----------------- Fix for LANG-440 ---------------------- END ---------------
                        } else {
                            //We need at add one to this field since the
                            //  last number causes us to round up
                            val.add(fields[i][0], 1);
                        }
                    }
                    return;
                }
            }
            //We have various fields that are not easy roundings
            int offset = 0;
            boolean offsetSet = false;
            //These are special types of fields that require different rounding rules
            switch (field) {
                case DateUtils.SEMI_MONTH:
                    if (fields[i][0] == Calendar.DATE) {
                        offset = val.get(Calendar.DATE) - 1;
                        if (offset >= 15) {
                            offset -= 15;
                        }
                        roundUp = offset > 7;
                        offsetSet = true;
                    }
                    break;
                case Calendar.AM_PM:
                    if (fields[i][0] == Calendar.HOUR_OF_DAY) {
                        offset = val.get(Calendar.HOUR_OF_DAY);
                        if (offset >= 12) {
                            offset -= 12;
                        }
                        roundUp = offset >= 6;
                        offsetSet = true;
                    }
                    break;
            }
            if (!offsetSet) {
                int min = val.getActualMinimum(fields[i][0]);
                int max = val.getActualMaximum(fields[i][0]);
                offset = val.get(fields[i][0]) - min;
                roundUp = offset > ((max - min) / 2);
            }
            if (offset != 0) {
                val.set(fields[i][0], val.get(fields[i][0]) - offset);
            }
        }
        throw new IllegalArgumentException("The field " + field + " is not supported");

    }


    @SuppressWarnings("rawtypes")
	public static Iterator iterator(Date focus, int rangeStyle) {
        if (focus == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        Calendar gval = Calendar.getInstance();
        gval.setTime(focus);
        return iterator(gval, rangeStyle);
    }

    @SuppressWarnings("rawtypes")
	public static Iterator iterator(Calendar focus, int rangeStyle) {
        if (focus == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        Calendar start = null;
        Calendar end = null;
        int startCutoff = Calendar.SUNDAY;
        int endCutoff = Calendar.SATURDAY;
        switch (rangeStyle) {
            case RANGE_MONTH_SUNDAY:
            case RANGE_MONTH_MONDAY:
                //Set start to the first of the month
                start = truncate(focus, Calendar.MONTH);
                //Set end to the last of the month
                end = (Calendar) start.clone();
                end.add(Calendar.MONTH, 1);
                end.add(Calendar.DATE, -1);
                //Loop start back to the previous sunday or monday
                if (rangeStyle == RANGE_MONTH_MONDAY) {
                    startCutoff = Calendar.MONDAY;
                    endCutoff = Calendar.SUNDAY;
                }
                break;
            case RANGE_WEEK_SUNDAY:
            case RANGE_WEEK_MONDAY:
            case RANGE_WEEK_RELATIVE:
            case RANGE_WEEK_CENTER:
                //Set start and end to the current date
                start = truncate(focus, Calendar.DATE);
                end = truncate(focus, Calendar.DATE);
                switch (rangeStyle) {
                    case RANGE_WEEK_SUNDAY:
                        //already set by default
                        break;
                    case RANGE_WEEK_MONDAY:
                        startCutoff = Calendar.MONDAY;
                        endCutoff = Calendar.SUNDAY;
                        break;
                    case RANGE_WEEK_RELATIVE:
                        startCutoff = focus.get(Calendar.DAY_OF_WEEK);
                        endCutoff = startCutoff - 1;
                        break;
                    case RANGE_WEEK_CENTER:
                        startCutoff = focus.get(Calendar.DAY_OF_WEEK) - 3;
                        endCutoff = focus.get(Calendar.DAY_OF_WEEK) + 3;
                        break;
                }
                break;
            default:
                throw new IllegalArgumentException("The range style " + rangeStyle + " is not valid.");
        }
        if (startCutoff < Calendar.SUNDAY) {
            startCutoff += 7;
        }
        if (startCutoff > Calendar.SATURDAY) {
            startCutoff -= 7;
        }
        if (endCutoff < Calendar.SUNDAY) {
            endCutoff += 7;
        }
        if (endCutoff > Calendar.SATURDAY) {
            endCutoff -= 7;
        }
        while (start.get(Calendar.DAY_OF_WEEK) != startCutoff) {
            start.add(Calendar.DATE, -1);
        }
        while (end.get(Calendar.DAY_OF_WEEK) != endCutoff) {
            end.add(Calendar.DATE, 1);
        }
        return new DateIterator(start, end);
    }

 
    @SuppressWarnings("rawtypes")
	public static Iterator iterator(Object focus, int rangeStyle) {
        if (focus == null) {
            throw new IllegalArgumentException("The date must not be null");
        }
        if (focus instanceof Date) {
            return iterator((Date) focus, rangeStyle);
        } else if (focus instanceof Calendar) {
            return iterator((Calendar) focus, rangeStyle);
        } else {
            throw new ClassCastException("Could not iterate based on " + focus);
        }
    }
    
 
    public static long getFragmentInMilliseconds(Date date, int fragment) {
        return getFragment(date, fragment, Calendar.MILLISECOND);    
    }
    
 
    public static long getFragmentInSeconds(Date date, int fragment) {
        return getFragment(date, fragment, Calendar.SECOND);
    }
    
 
    public static long getFragmentInMinutes(Date date, int fragment) {
        return getFragment(date, fragment, Calendar.MINUTE);
    }
    
 
    public static long getFragmentInHours(Date date, int fragment) {
        return getFragment(date, fragment, Calendar.HOUR_OF_DAY);
    }
    
 
    public static long getFragmentInDays(Date date, int fragment) {
        return getFragment(date, fragment, Calendar.DAY_OF_YEAR);
    }

  public static long getFragmentInMilliseconds(Calendar calendar, int fragment) {
    return getFragment(calendar, fragment, Calendar.MILLISECOND);
  }
 
    public static long getFragmentInSeconds(Calendar calendar, int fragment) {
        return getFragment(calendar, fragment, Calendar.SECOND);
    }
    
 
    public static long getFragmentInMinutes(Calendar calendar, int fragment) {
        return getFragment(calendar, fragment, Calendar.MINUTE);
    }
    
 
    public static long getFragmentInHours(Calendar calendar, int fragment) {
        return getFragment(calendar, fragment, Calendar.HOUR_OF_DAY);
    }
    

    public static long getFragmentInDays(Calendar calendar, int fragment) {
        return getFragment(calendar, fragment, Calendar.DAY_OF_YEAR);
    }
    
 
    private static long getFragment(Date date, int fragment, int unit) {
        if(date == null) {
            throw  new IllegalArgumentException("The date must not be null");
        }
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return getFragment(calendar, fragment, unit);
    }

 
    private static long getFragment(Calendar calendar, int fragment, int unit) {
        if(calendar == null) {
            throw  new IllegalArgumentException("The date must not be null"); 
        }
        long millisPerUnit = getMillisPerUnit(unit);
        long result = 0;
        
        // Fragments bigger than a day require a breakdown to days
        switch (fragment) {
            case Calendar.YEAR:
                result += (calendar.get(Calendar.DAY_OF_YEAR) * MILLIS_PER_DAY) / millisPerUnit;
                break;
            case Calendar.MONTH:
                result += (calendar.get(Calendar.DAY_OF_MONTH) * MILLIS_PER_DAY) / millisPerUnit;
                break;
        }

        switch (fragment) {
            // Number of days already calculated for these cases
            case Calendar.YEAR:
            case Calendar.MONTH:
            
            // The rest of the valid cases
            case Calendar.DAY_OF_YEAR:
            case Calendar.DATE:
                result += (calendar.get(Calendar.HOUR_OF_DAY) * MILLIS_PER_HOUR) / millisPerUnit;
                //$FALL-THROUGH$
            case Calendar.HOUR_OF_DAY:
                result += (calendar.get(Calendar.MINUTE) * MILLIS_PER_MINUTE) / millisPerUnit;
                //$FALL-THROUGH$
            case Calendar.MINUTE:
                result += (calendar.get(Calendar.SECOND) * MILLIS_PER_SECOND) / millisPerUnit;
                //$FALL-THROUGH$
            case Calendar.SECOND:
                result += (calendar.get(Calendar.MILLISECOND) * 1) / millisPerUnit;
                break;
            case Calendar.MILLISECOND: 
                break;//never useful
            default: 
                throw new IllegalArgumentException("The fragment " + fragment + " is not supported");
        }
        return result;
    }
    
   
    private static long getMillisPerUnit(int unit) {
        long result = Long.MAX_VALUE;
        switch (unit) {
            case Calendar.DAY_OF_YEAR:
            case Calendar.DATE:
                result = MILLIS_PER_DAY;
                break;
            case Calendar.HOUR_OF_DAY:
                result = MILLIS_PER_HOUR;
                break;
            case Calendar.MINUTE:
                result = MILLIS_PER_MINUTE;
                break;
            case Calendar.SECOND:
                result = MILLIS_PER_SECOND;
                break;
            case Calendar.MILLISECOND:
                result = 1;
                break;
            default: throw new IllegalArgumentException("The unit " + unit + " cannot be represented is milleseconds");
        }
        return result;
    }

    /**
     * <p>Date iterator.</p>
     */
    @SuppressWarnings("rawtypes")
	static class DateIterator implements Iterator {
        private final Calendar endFinal;
        private final Calendar spot;
        

        DateIterator(Calendar startFinal, Calendar endFinal) {
            super();
            this.endFinal = endFinal;
            spot = startFinal;
            spot.add(Calendar.DATE, -1);
        }


        public boolean hasNext() {
            return spot.before(endFinal);
        }


        public Object next() {
            if (spot.equals(endFinal)) {
                throw new NoSuchElementException();
            }
            spot.add(Calendar.DATE, 1);
            return spot.clone();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
    
}
/*------------------------------------------------------------------------------*/
/*
 * 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 com.accu.common.util.time;

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

import com.accu.common.util.StringUtils;



/**
 * 时间跨度格式化工具
 * <p>Duration formatting utilities and constants. The following table describes the tokens 
 * used in the pattern language for formatting. </p>
 * <table border="1">
 *  <tr><th>character</th><th>duration element</th></tr>
 *  <tr><td>y</td><td>years</td></tr>
 *  <tr><td>M</td><td>months</td></tr>
 *  <tr><td>d</td><td>days</td></tr>
 *  <tr><td>H</td><td>hours</td></tr>
 *  <tr><td>m</td><td>minutes</td></tr>
 *  <tr><td>s</td><td>seconds</td></tr>
 *  <tr><td>S</td><td>milliseconds</td></tr>
 * </table>
 *
 * @author Apache Software Foundation
 * @author Apache Ant - DateUtils
 * @author <a href="mailto:sbailliez@apache.org">Stephane Bailliez</a>
 * @author <a href="mailto:stefan.bodewig@epost.de">Stefan Bodewig</a>
 * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
 * @since 2.1
 * @version $Id: DurationFormatUtils.java 905684 2010-02-02 16:03:07Z niallp $
 */
public class DurationFormatUtils {

    /**
     * <p>DurationFormatUtils instances should NOT be constructed in standard programming.</p>
     *
     * <p>This constructor is public to permit tools that require a JavaBean instance
     * to operate.</p>
     */
    public DurationFormatUtils() {
        super();
    }

    /**
     * <p>Pattern used with <code>FastDateFormat</code> and <code>SimpleDateFormat</code>
     * for the ISO8601 period format used in durations.</p>
     * 
     * @see org.apache.commons.lang.time.FastDateFormat
     * @see java.text.SimpleDateFormat
     */
    public static final String ISO_EXTENDED_FORMAT_PATTERN = "'P'yyyy'Y'M'M'd'DT'H'H'm'M's.S'S'";

    //-----------------------------------------------------------------------
    /**
     * <p>Formats the time gap as a string.</p>
     * 
     * <p>The format used is ISO8601-like:
     * <i>H</i>:<i>m</i>:<i>s</i>.<i>S</i>.</p>
     * 
     * @param durationMillis  the duration to format
     * @return the time as a String
     */
    public static String formatDurationHMS(long durationMillis) {
        return formatDuration(durationMillis, "H:mm:ss.SSS");
    }

    /**
     * <p>Formats the time gap as a string.</p>
     * 
     * <p>The format used is the ISO8601 period format.</p>
     * 
     * <p>This method formats durations using the days and lower fields of the
     * ISO format pattern, such as P7D6TH5M4.321S.</p>
     * 
     * @param durationMillis  the duration to format
     * @return the time as a String
     */
    public static String formatDurationISO(long durationMillis) {
        return formatDuration(durationMillis, ISO_EXTENDED_FORMAT_PATTERN, false);
    }

    /**
     * <p>Formats the time gap as a string, using the specified format, and padding with zeros and 
     * using the default timezone.</p>
     * 
     * <p>This method formats durations using the days and lower fields of the
     * format pattern. Months and larger are not used.</p>
     * 
     * @param durationMillis  the duration to format
     * @param format  the way in which to format the duration
     * @return the time as a String
     */
    public static String formatDuration(long durationMillis, String format) {
        return formatDuration(durationMillis, format, true);
    }

    /**
     * <p>Formats the time gap as a string, using the specified format.
     * Padding the left hand side of numbers with zeroes is optional and 
     * the timezone may be specified.</p>
     * 
     * <p>This method formats durations using the days and lower fields of the
     * format pattern. Months and larger are not used.</p>
     * 
     * @param durationMillis  the duration to format
     * @param format  the way in which to format the duration
     * @param padWithZeros  whether to pad the left hand side of numbers with 0's
     * @return the time as a String
     */
    public static String formatDuration(long durationMillis, String format, boolean padWithZeros) {

        Token[] tokens = lexx(format);

        int days         = 0;
        int hours        = 0;
        int minutes      = 0;
        int seconds      = 0;
        int milliseconds = 0;
        
        if (Token.containsTokenWithValue(tokens, d) ) {
            days = (int) (durationMillis / DateUtils.MILLIS_PER_DAY);
            durationMillis = durationMillis - (days * DateUtils.MILLIS_PER_DAY);
        }
        if (Token.containsTokenWithValue(tokens, H) ) {
            hours = (int) (durationMillis / DateUtils.MILLIS_PER_HOUR);
            durationMillis = durationMillis - (hours * DateUtils.MILLIS_PER_HOUR);
        }
        if (Token.containsTokenWithValue(tokens, m) ) {
            minutes = (int) (durationMillis / DateUtils.MILLIS_PER_MINUTE);
            durationMillis = durationMillis - (minutes * DateUtils.MILLIS_PER_MINUTE);
        }
        if (Token.containsTokenWithValue(tokens, s) ) {
            seconds = (int) (durationMillis / DateUtils.MILLIS_PER_SECOND);
            durationMillis = durationMillis - (seconds * DateUtils.MILLIS_PER_SECOND);
        }
        if (Token.containsTokenWithValue(tokens, S) ) {
            milliseconds = (int) durationMillis;
        }

        return format(tokens, 0, 0, days, hours, minutes, seconds, milliseconds, padWithZeros);
    }

    /**
     * <p>Formats an elapsed time into a plurialization correct string.</p>
     * 
     * <p>This method formats durations using the days and lower fields of the
     * format pattern. Months and larger are not used.</p>
     * 
     * @param durationMillis  the elapsed time to report in milliseconds
     * @param suppressLeadingZeroElements  suppresses leading 0 elements
     * @param suppressTrailingZeroElements  suppresses trailing 0 elements
     * @return the formatted text in days/hours/minutes/seconds
     */
    public static String formatDurationWords(
        long durationMillis,
        boolean suppressLeadingZeroElements,
        boolean suppressTrailingZeroElements) {

        // This method is generally replacable by the format method, but 
        // there are a series of tweaks and special cases that require 
        // trickery to replicate.
        String duration = formatDuration(durationMillis, "d' days 'H' hours 'm' minutes 's' seconds'");
        if (suppressLeadingZeroElements) {
            // this is a temporary marker on the front. Like ^ in regexp.
            duration = " " + duration;
            String tmp = StringUtils.replaceOnce(duration, " 0 days", "");
            if (tmp.length() != duration.length()) {
                duration = tmp;
                tmp = StringUtils.replaceOnce(duration, " 0 hours", "");
                if (tmp.length() != duration.length()) {
                    duration = tmp;
                    tmp = StringUtils.replaceOnce(duration, " 0 minutes", "");
                    duration = tmp;
                    if (tmp.length() != duration.length()) {
                        duration = StringUtils.replaceOnce(tmp, " 0 seconds", "");
                    }
                }
            }
            if (duration.length() != 0) {
                // strip the space off again
                duration = duration.substring(1);
            }
        }
        if (suppressTrailingZeroElements) {
            String tmp = StringUtils.replaceOnce(duration, " 0 seconds", "");
            if (tmp.length() != duration.length()) {
                duration = tmp;
                tmp = StringUtils.replaceOnce(duration, " 0 minutes", "");
                if (tmp.length() != duration.length()) {
                    duration = tmp;
                    tmp = StringUtils.replaceOnce(duration, " 0 hours", "");
                    if (tmp.length() != duration.length()) {
                        duration = StringUtils.replaceOnce(tmp, " 0 days", "");
                    }
                }
            }
        }
        // handle plurals
        duration = " " + duration;
        duration = StringUtils.replaceOnce(duration, " 1 seconds", " 1 second");
        duration = StringUtils.replaceOnce(duration, " 1 minutes", " 1 minute");
        duration = StringUtils.replaceOnce(duration, " 1 hours", " 1 hour");
        duration = StringUtils.replaceOnce(duration, " 1 days", " 1 day");
        return duration.trim();
    }

    //-----------------------------------------------------------------------
    /**
     * <p>Formats the time gap as a string.</p>
     * 
     * <p>The format used is the ISO8601 period format.</p>
     * 
     * @param startMillis  the start of the duration to format
     * @param endMillis  the end of the duration to format
     * @return the time as a String
     */
    public static String formatPeriodISO(long startMillis, long endMillis) {
        return formatPeriod(startMillis, endMillis, ISO_EXTENDED_FORMAT_PATTERN, false, TimeZone.getDefault());
    }

    /**
     * <p>Formats the time gap as a string, using the specified format.
     * Padding the left hand side of numbers with zeroes is optional.
     * 
     * @param startMillis  the start of the duration
     * @param endMillis  the end of the duration
     * @param format  the way in which to format the duration
     * @return the time as a String
     */
    public static String formatPeriod(long startMillis, long endMillis, String format) {
        return formatPeriod(startMillis, endMillis, format, true, TimeZone.getDefault());
    }

    /**
     * <p>Formats the time gap as a string, using the specified format.
     * Padding the left hand side of numbers with zeroes is optional and 
     * the timezone may be specified. </p>
     *
     * <p>When calculating the difference between months/days, it chooses to 
     * calculate months first. So when working out the number of months and 
     * days between January 15th and March 10th, it choose 1 month and 
     * 23 days gained by choosing January->February = 1 month and then 
     * calculating days forwards, and not the 1 month and 26 days gained by 
     * choosing March -> February = 1 month and then calculating days 
     * backwards. </p>
     *
     * <p>For more control, the <a href="http://joda-time.sf.net/">Joda-Time</a>
     * library is recommended.</p>
     * 
     * @param startMillis  the start of the duration
     * @param endMillis  the end of the duration
     * @param format  the way in which to format the duration
     * @param padWithZeros whether to pad the left hand side of numbers with 0's
     * @param timezone the millis are defined in
     * @return the time as a String
     */
    public static String formatPeriod(long startMillis, long endMillis, String format, boolean padWithZeros, 
            TimeZone timezone) {

        // Used to optimise for differences under 28 days and 
        // called formatDuration(millis, format); however this did not work 
        // over leap years. 
        // TODO: Compare performance to see if anything was lost by 
        // losing this optimisation. 
        
        Token[] tokens = lexx(format);

        // timezones get funky around 0, so normalizing everything to GMT 
        // stops the hours being off
        Calendar start = Calendar.getInstance(timezone);
        start.setTime(new Date(startMillis));
        Calendar end = Calendar.getInstance(timezone);
        end.setTime(new Date(endMillis));

        // initial estimates
        int milliseconds = end.get(Calendar.MILLISECOND) - start.get(Calendar.MILLISECOND);
        int seconds = end.get(Calendar.SECOND) - start.get(Calendar.SECOND);
        int minutes = end.get(Calendar.MINUTE) - start.get(Calendar.MINUTE);
        int hours = end.get(Calendar.HOUR_OF_DAY) - start.get(Calendar.HOUR_OF_DAY);
        int days = end.get(Calendar.DAY_OF_MONTH) - start.get(Calendar.DAY_OF_MONTH);
        int months = end.get(Calendar.MONTH) - start.get(Calendar.MONTH);
        int years = end.get(Calendar.YEAR) - start.get(Calendar.YEAR);

        // each initial estimate is adjusted in case it is under 0
        while (milliseconds < 0) {
            milliseconds += 1000;
            seconds -= 1;
        }
        while (seconds < 0) {
            seconds += 60;
            minutes -= 1;
        }
        while (minutes < 0) {
            minutes += 60;
            hours -= 1;
        }
        while (hours < 0) {
            hours += 24;
            days -= 1;
        }
       
        if (Token.containsTokenWithValue(tokens, M)) {
            while (days < 0) {
                days += start.getActualMaximum(Calendar.DAY_OF_MONTH);
                months -= 1;
                start.add(Calendar.MONTH, 1);
            }

            while (months < 0) {
                months += 12;
                years -= 1;
            }

            if (!Token.containsTokenWithValue(tokens, y) && years != 0) {
                while (years != 0) {
                    months += 12 * years;
                    years = 0;
                }
            }
        } else {
            // there are no M's in the format string

            if( !Token.containsTokenWithValue(tokens, y) ) {
                int target = end.get(Calendar.YEAR);
                if (months < 0) {
                    // target is end-year -1
                    target -= 1;
                }
                
                while ( (start.get(Calendar.YEAR) != target)) {
                    days += start.getActualMaximum(Calendar.DAY_OF_YEAR) - start.get(Calendar.DAY_OF_YEAR);
                    
                    // Not sure I grok why this is needed, but the brutal tests show it is
                    if(start instanceof GregorianCalendar) {
                        if( (start.get(Calendar.MONTH) == Calendar.FEBRUARY) &&
                            (start.get(Calendar.DAY_OF_MONTH) == 29 ) )
                        {
                            days += 1;
                        }
                    }
                    
                    start.add(Calendar.YEAR, 1);
                    
                    days += start.get(Calendar.DAY_OF_YEAR);
                }
                
                years = 0;
            }
            
            while( start.get(Calendar.MONTH) != end.get(Calendar.MONTH) ) {
                days += start.getActualMaximum(Calendar.DAY_OF_MONTH);
                start.add(Calendar.MONTH, 1);
            }
            
            months = 0;            

            while (days < 0) {
                days += start.getActualMaximum(Calendar.DAY_OF_MONTH);
                months -= 1;
                start.add(Calendar.MONTH, 1);
            }
            
        }

        // The rest of this code adds in values that 
        // aren't requested. This allows the user to ask for the 
        // number of months and get the real count and not just 0->11.

        if (!Token.containsTokenWithValue(tokens, d)) {
            hours += 24 * days;
            days = 0;
        }
        if (!Token.containsTokenWithValue(tokens, H)) {
            minutes += 60 * hours;
            hours = 0;
        }
        if (!Token.containsTokenWithValue(tokens, m)) {
            seconds += 60 * minutes;
            minutes = 0;
        }
        if (!Token.containsTokenWithValue(tokens, s)) {
            milliseconds += 1000 * seconds;
            seconds = 0;
        }

        return format(tokens, years, months, days, hours, minutes, seconds, milliseconds, padWithZeros);
    }

    //-----------------------------------------------------------------------
    /**
     * <p>The internal method to do the formatting.</p>
     * 
     * @param tokens  the tokens
     * @param years  the number of years
     * @param months  the number of months
     * @param days  the number of days
     * @param hours  the number of hours
     * @param minutes  the number of minutes
     * @param seconds  the number of seconds
     * @param milliseconds  the number of millis
     * @param padWithZeros  whether to pad
     * @return the formatted string
     */
    static String format(Token[] tokens, int years, int months, int days, int hours, int minutes, int seconds,
            int milliseconds, boolean padWithZeros) {
        StringBuffer buffer = new StringBuffer();
        boolean lastOutputSeconds = false;
        int sz = tokens.length;
        for (int i = 0; i < sz; i++) {
            Token token = tokens[i];
            Object value = token.getValue();
            int count = token.getCount();
            if (value instanceof StringBuffer) {
                buffer.append(value.toString());
            } else {
                if (value == y) {
                    buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(years), count, '0') : Integer
                            .toString(years));
                    lastOutputSeconds = false;
                } else if (value == M) {
                    buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(months), count, '0') : Integer
                            .toString(months));
                    lastOutputSeconds = false;
                } else if (value == d) {
                    buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(days), count, '0') : Integer
                            .toString(days));
                    lastOutputSeconds = false;
                } else if (value == H) {
                    buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(hours), count, '0') : Integer
                            .toString(hours));
                    lastOutputSeconds = false;
                } else if (value == m) {
                    buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(minutes), count, '0') : Integer
                            .toString(minutes));
                    lastOutputSeconds = false;
                } else if (value == s) {
                    buffer.append(padWithZeros ? StringUtils.leftPad(Integer.toString(seconds), count, '0') : Integer
                            .toString(seconds));
                    lastOutputSeconds = true;
                } else if (value == S) {
                    if (lastOutputSeconds) {
                        milliseconds += 1000;
                        String str = padWithZeros
                                ? StringUtils.leftPad(Integer.toString(milliseconds), count, '0')
                                : Integer.toString(milliseconds);
                        buffer.append(str.substring(1));
                    } else {
                        buffer.append(padWithZeros
                                ? StringUtils.leftPad(Integer.toString(milliseconds), count, '0')
                                : Integer.toString(milliseconds));
                    }
                    lastOutputSeconds = false;
                }
            }
        }
        return buffer.toString();
    }

    static final Object y = "y";
    static final Object M = "M";
    static final Object d = "d";
    static final Object H = "H";
    static final Object m = "m";
    static final Object s = "s";
    static final Object S = "S";
    
    /**
     * Parses a classic date format string into Tokens
     *
     * @param format to parse
     * @return array of Token[]
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
	static Token[] lexx(String format) {
        char[] array = format.toCharArray();
        ArrayList list = new ArrayList(array.length);

        boolean inLiteral = false;
        StringBuffer buffer = null;
        Token previous = null;
        int sz = array.length;
        for(int i=0; i<sz; i++) {
            char ch = array[i];
            if(inLiteral && ch != '\'') {
                buffer.append(ch); // buffer can't be null if inLiteral is true
                continue;
            }
            Object value = null;
            switch(ch) {
                // TODO: Need to handle escaping of '
                case '\'' : 
                  if(inLiteral) {
                      buffer = null;
                      inLiteral = false;
                  } else {
                      buffer = new StringBuffer();
                      list.add(new Token(buffer));
                      inLiteral = true;
                  }
                  break;
                case 'y'  : value = y; break;
                case 'M'  : value = M; break;
                case 'd'  : value = d; break;
                case 'H'  : value = H; break;
                case 'm'  : value = m; break;
                case 's'  : value = s; break;
                case 'S'  : value = S; break;
                default   : 
                  if(buffer == null) {
                      buffer = new StringBuffer();
                      list.add(new Token(buffer));
                  }
                  buffer.append(ch);
            }

            if(value != null) {
                if(previous != null && previous.getValue() == value) {
                    previous.increment();
                } else {
                    Token token = new Token(value);
                    list.add(token); 
                    previous = token;
                }
                buffer = null; 
            }
        }
        return (Token[]) list.toArray( new Token[list.size()] );
    }

    /**
     * Element that is parsed from the format pattern.
     */
    static class Token {

        /**
         * Helper method to determine if a set of tokens contain a value
         *
         * @param tokens set to look in
         * @param value to look for
         * @return boolean <code>true</code> if contained
         */
        static boolean containsTokenWithValue(Token[] tokens, Object value) {
            int sz = tokens.length;
            for (int i = 0; i < sz; i++) {
                if (tokens[i].getValue() == value) {
                    return true;
                }
            }
            return false;
        }

        private Object value;
        private int count;

        /**
         * Wraps a token around a value. A value would be something like a 'Y'.
         *
         * @param value to wrap
         */
        Token(Object value) {
            this.value = value;
            this.count = 1;
        }

        /**
         * Wraps a token around a repeated number of a value, for example it would 
         * store 'yyyy' as a value for y and a count of 4.
         *
         * @param value to wrap
         * @param count to wrap
         */
        Token(Object value, int count) {
            this.value = value;
            this.count = count;
        }

        /**
         * Adds another one of the value
         */
        void increment() { 
            count++;
        }

        /**
         * Gets the current number of values represented
         *
         * @return int number of values represented
         */
        int getCount() {
            return count;
        }

        /**
         * Gets the particular value this token represents.
         * 
         * @return Object value
         */
        Object getValue() {
            return value;
        }

        /**
         * Supports equality of this Token to another Token.
         *
         * @param obj2 Object to consider equality of
         * @return boolean <code>true</code> if equal
         */
        @Override
		public boolean equals(Object obj2) {
            if (obj2 instanceof Token) {
                Token tok2 = (Token) obj2;
                if (this.value.getClass() != tok2.value.getClass()) {
                    return false;
                }
                if (this.count != tok2.count) {
                    return false;
                }
                if (this.value instanceof StringBuffer) {
                    return this.value.toString().equals(tok2.value.toString());
                } else if (this.value instanceof Number) {
                    return this.value.equals(tok2.value);
                } else {
                    return this.value == tok2.value;
                }
            }
            return false;
        }

        /**
         * Returns a hashcode for the token equal to the 
         * hashcode for the token's value. Thus 'TT' and 'TTTT' 
         * will have the same hashcode. 
         *
         * @return The hashcode for the token
         */
        @Override
		public int hashCode() {
            return this.value.hashCode();
        }

        /**
         * Represents this token as a String.
         *
         * @return String representation of the token
         */
        @Override
		public String toString() {
            return StringUtils.repeat(this.value.toString(), this.count);
        }
    }

}
/*------------------------------------------------------------------------------*/
/*
 * 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 com.accu.common.util.time;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.text.DateFormat;
import java.text.DateFormatSymbols;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;

/**
 * 将java.text.SimpleDateFormat包装一层,提供线程安全
 * <p>FastDateFormat is a fast and thread-safe version of
 * {@link java.text.SimpleDateFormat}.</p>
 * 
 * <p>This class can be used as a direct replacement to
 * <code>SimpleDateFormat</code> in most formatting situations.
 * This class is especially useful in multi-threaded server environments.
 * <code>SimpleDateFormat</code> is not thread-safe in any JDK version,
 * nor will it be as Sun have closed the bug/RFE.
 * </p>
 *
 * <p>Only formatting is supported, but all patterns are compatible with
 * SimpleDateFormat (except time zones - see below).</p>
 *
 * <p>Java 1.4 introduced a new pattern letter, <code>'Z'</code>, to represent
 * time zones in RFC822 format (eg. <code>+0800</code> or <code>-1100</code>).
 * This pattern letter can be used here (on all JDK versions).</p>
 *
 * <p>In addition, the pattern <code>'ZZ'</code> has been made to represent
 * ISO8601 full format time zones (eg. <code>+08:00</code> or <code>-11:00</code>).
 * This introduces a minor incompatibility with Java 1.4, but at a gain of
 * useful functionality.</p>
 *
 * @author Apache Software Foundation
 * @author TeaTrove project
 * @author Brian S O'Neill
 * @author Sean Schofield
 * @author Gary Gregory
 * @author Nikolay Metchev
 * @since 2.0
 * @version $Id: FastDateFormat.java 905684 2010-02-02 16:03:07Z niallp $
 */
public class FastDateFormat extends Format {
    // A lot of the speed in this class comes from caching, but some comes
    // from the special int to StringBuffer conversion.
    //
    // The following produces a padded 2 digit number:
    //   buffer.append((char)(value / 10 + '0'));
    //   buffer.append((char)(value % 10 + '0'));
    //
    // Note that the fastest append to StringBuffer is a single char (used here).
    // Note that Integer.toString() is not called, the conversion is simply
    // taking the value and adding (mathematically) the ASCII value for '0'.
    // So, don't change this code! It works and is very fast.
    
    /**
     * Required for serialization support.
     * 
     * @see java.io.Serializable
     */
    private static final long serialVersionUID = 1L;

    /**
     * FULL locale dependent date or time style.
     */
    public static final int FULL = DateFormat.FULL;
    /**
     * LONG locale dependent date or time style.
     */
    public static final int LONG = DateFormat.LONG;
    /**
     * MEDIUM locale dependent date or time style.
     */
    public static final int MEDIUM = DateFormat.MEDIUM;
    /**
     * SHORT locale dependent date or time style.
     */
    public static final int SHORT = DateFormat.SHORT;
    
    private static String cDefaultPattern; // lazily initialised by getInstance()

    @SuppressWarnings("rawtypes")
	private static final Map cInstanceCache = new HashMap(7);
    @SuppressWarnings("rawtypes")
    private static final Map cDateInstanceCache = new HashMap(7);
    @SuppressWarnings("rawtypes")
    private static final Map cTimeInstanceCache = new HashMap(7);
    @SuppressWarnings("rawtypes")
    private static final Map cDateTimeInstanceCache = new HashMap(7);
    @SuppressWarnings("rawtypes")
    private static final Map cTimeZoneDisplayCache = new HashMap(7);

    /**
     * The pattern.
     */
    private final String mPattern;
    /**
     * The time zone.
     */
    private final TimeZone mTimeZone;
    /**
     * Whether the time zone overrides any on Calendars.
     */
    private final boolean mTimeZoneForced;
    /**
     * The locale.
     */
    private final Locale mLocale;
    /**
     * Whether the locale overrides the default.
     */
    private final boolean mLocaleForced;
    /**
     * The parsed rules.
     */
    private transient Rule[] mRules;
    /**
     * The estimated maximum length.
     */
    private transient int mMaxLengthEstimate;

    //-----------------------------------------------------------------------
    /**
     * <p>Gets a formatter instance using the default pattern in the
     * default locale.</p>
     * 
     * @return a date/time formatter
     */
    public static FastDateFormat getInstance() {
        return getInstance(getDefaultPattern(), null, null);
    }

    /**
     * <p>Gets a formatter instance using the specified pattern in the
     * default locale.</p>
     * 
     * @param pattern  {@link java.text.SimpleDateFormat} compatible
     *  pattern
     * @return a pattern based date/time formatter
     * @throws IllegalArgumentException if pattern is invalid
     */
    public static FastDateFormat getInstance(String pattern) {
        return getInstance(pattern, null, null);
    }

    /**
     * <p>Gets a formatter instance using the specified pattern and
     * time zone.</p>
     * 
     * @param pattern  {@link java.text.SimpleDateFormat} compatible
     *  pattern
     * @param timeZone  optional time zone, overrides time zone of
     *  formatted date
     * @return a pattern based date/time formatter
     * @throws IllegalArgumentException if pattern is invalid
     */
    public static FastDateFormat getInstance(String pattern, TimeZone timeZone) {
        return getInstance(pattern, timeZone, null);
    }

    /**
     * <p>Gets a formatter instance using the specified pattern and
     * locale.</p>
     * 
     * @param pattern  {@link java.text.SimpleDateFormat} compatible
     *  pattern
     * @param locale  optional locale, overrides system locale
     * @return a pattern based date/time formatter
     * @throws IllegalArgumentException if pattern is invalid
     */
    public static FastDateFormat getInstance(String pattern, Locale locale) {
        return getInstance(pattern, null, locale);
    }

    /**
     * <p>Gets a formatter instance using the specified pattern, time zone
     * and locale.</p>
     * 
     * @param pattern  {@link java.text.SimpleDateFormat} compatible
     *  pattern
     * @param timeZone  optional time zone, overrides time zone of
     *  formatted date
     * @param locale  optional locale, overrides system locale
     * @return a pattern based date/time formatter
     * @throws IllegalArgumentException if pattern is invalid
     *  or <code>null</code>
     */
    @SuppressWarnings("unchecked")
	public static synchronized FastDateFormat getInstance(String pattern, TimeZone timeZone, Locale locale) {
        FastDateFormat emptyFormat = new FastDateFormat(pattern, timeZone, locale);
        FastDateFormat format = (FastDateFormat) cInstanceCache.get(emptyFormat);
        if (format == null) {
            format = emptyFormat;
            format.init();  // convert shell format into usable one
            cInstanceCache.put(format, format);  // this is OK!
        }
        return format;
    }

    //-----------------------------------------------------------------------
    /**
     * <p>Gets a date formatter instance using the specified style in the
     * default time zone and locale.</p>
     * 
     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
     * @return a localized standard date formatter
     * @throws IllegalArgumentException if the Locale has no date
     *  pattern defined
     * @since 2.1
     */
    public static FastDateFormat getDateInstance(int style) {
        return getDateInstance(style, null, null);
    }

    /**
     * <p>Gets a date formatter instance using the specified style and
     * locale in the default time zone.</p>
     * 
     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
     * @param locale  optional locale, overrides system locale
     * @return a localized standard date formatter
     * @throws IllegalArgumentException if the Locale has no date
     *  pattern defined
     * @since 2.1
     */
    public static FastDateFormat getDateInstance(int style, Locale locale) {
        return getDateInstance(style, null, locale);
    }

    /**
     * <p>Gets a date formatter instance using the specified style and
     * time zone in the default locale.</p>
     * 
     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
     * @param timeZone  optional time zone, overrides time zone of
     *  formatted date
     * @return a localized standard date formatter
     * @throws IllegalArgumentException if the Locale has no date
     *  pattern defined
     * @since 2.1
     */
    public static FastDateFormat getDateInstance(int style, TimeZone timeZone) {
        return getDateInstance(style, timeZone, null);
    }
    /**
     * <p>Gets a date formatter instance using the specified style, time
     * zone and locale.</p>
     * 
     * @param style  date style: FULL, LONG, MEDIUM, or SHORT
     * @param timeZone  optional time zone, overrides time zone of
     *  formatted date
     * @param locale  optional locale, overrides system locale
     * @return a localized standard date formatter
     * @throws IllegalArgumentException if the Locale has no date
     *  pattern defined
     */
    @SuppressWarnings("unchecked")
	public static synchronized FastDateFormat getDateInstance(int style, TimeZone timeZone, Locale locale) {
        Object key = new Integer(style);
        if (timeZone != null) {
            key = new Pair(key, timeZone);
        }

        if (locale == null) {
            locale = Locale.getDefault();
        }

        key = new Pair(key, locale);

        FastDateFormat format = (FastDateFormat) cDateInstanceCache.get(key);
        if (format == null) {
            try {
                SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getDateInstance(style, locale);
                String pattern = formatter.toPattern();
                format = getInstance(pattern, timeZone, locale);
                cDateInstanceCache.put(key, format);
                
            } catch (ClassCastException ex) {
                throw new IllegalArgumentException("No date pattern for locale: " + locale);
            }
        }
        return format;
    }

    //-----------------------------------------------------------------------
    /**
     * <p>Gets a time formatter instance using the specified style in the
     * default time zone and locale.</p>
     * 
     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
     * @return a localized standard time formatter
     * @throws IllegalArgumentException if the Locale has no time
     *  pattern defined
     * @since 2.1
     */
    public static FastDateFormat getTimeInstance(int style) {
        return getTimeInstance(style, null, null);
    }

    /**
     * <p>Gets a time formatter instance using the specified style and
     * locale in the default time zone.</p>
     * 
     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
     * @param locale  optional locale, overrides system locale
     * @return a localized standard time formatter
     * @throws IllegalArgumentException if the Locale has no time
     *  pattern defined
     * @since 2.1
     */
    public static FastDateFormat getTimeInstance(int style, Locale locale) {
        return getTimeInstance(style, null, locale);
    }
    
    /**
     * <p>Gets a time formatter instance using the specified style and
     * time zone in the default locale.</p>
     * 
     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
     * @param timeZone  optional time zone, overrides time zone of
     *  formatted time
     * @return a localized standard time formatter
     * @throws IllegalArgumentException if the Locale has no time
     *  pattern defined
     * @since 2.1
     */
    public static FastDateFormat getTimeInstance(int style, TimeZone timeZone) {
        return getTimeInstance(style, timeZone, null);
    }
    
    /**
     * <p>Gets a time formatter instance using the specified style, time
     * zone and locale.</p>
     * 
     * @param style  time style: FULL, LONG, MEDIUM, or SHORT
     * @param timeZone  optional time zone, overrides time zone of
     *  formatted time
     * @param locale  optional locale, overrides system locale
     * @return a localized standard time formatter
     * @throws IllegalArgumentException if the Locale has no time
     *  pattern defined
     */
    @SuppressWarnings("unchecked")
	public static synchronized FastDateFormat getTimeInstance(int style, TimeZone timeZone, Locale locale) {
        Object key = new Integer(style);
        if (timeZone != null) {
            key = new Pair(key, timeZone);
        }
        if (locale != null) {
            key = new Pair(key, locale);
        }

        FastDateFormat format = (FastDateFormat) cTimeInstanceCache.get(key);
        if (format == null) {
            if (locale == null) {
                locale = Locale.getDefault();
            }

            try {
                SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getTimeInstance(style, locale);
                String pattern = formatter.toPattern();
                format = getInstance(pattern, timeZone, locale);
                cTimeInstanceCache.put(key, format);
            
            } catch (ClassCastException ex) {
                throw new IllegalArgumentException("No date pattern for locale: " + locale);
            }
        }
        return format;
    }

    //-----------------------------------------------------------------------
    /**
     * <p>Gets a date/time formatter instance using the specified style
     * in the default time zone and locale.</p>
     * 
     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
     * @return a localized standard date/time formatter
     * @throws IllegalArgumentException if the Locale has no date/time
     *  pattern defined
     * @since 2.1
     */
    public static FastDateFormat getDateTimeInstance(
            int dateStyle, int timeStyle) {
        return getDateTimeInstance(dateStyle, timeStyle, null, null);
    }
    
    /**
     * <p>Gets a date/time formatter instance using the specified style and
     * locale in the default time zone.</p>
     * 
     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
     * @param locale  optional locale, overrides system locale
     * @return a localized standard date/time formatter
     * @throws IllegalArgumentException if the Locale has no date/time
     *  pattern defined
     * @since 2.1
     */
    public static FastDateFormat getDateTimeInstance(
            int dateStyle, int timeStyle, Locale locale) {
        return getDateTimeInstance(dateStyle, timeStyle, null, locale);
    }
    
    /**
     * <p>Gets a date/time formatter instance using the specified style and
     * time zone in the default locale.</p>
     * 
     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
     * @param timeZone  optional time zone, overrides time zone of
     *  formatted date
     * @return a localized standard date/time formatter
     * @throws IllegalArgumentException if the Locale has no date/time
     *  pattern defined
     * @since 2.1
     */
    public static FastDateFormat getDateTimeInstance(
            int dateStyle, int timeStyle, TimeZone timeZone) {
        return getDateTimeInstance(dateStyle, timeStyle, timeZone, null);
    }    
    /**
     * <p>Gets a date/time formatter instance using the specified style,
     * time zone and locale.</p>
     * 
     * @param dateStyle  date style: FULL, LONG, MEDIUM, or SHORT
     * @param timeStyle  time style: FULL, LONG, MEDIUM, or SHORT
     * @param timeZone  optional time zone, overrides time zone of
     *  formatted date
     * @param locale  optional locale, overrides system locale
     * @return a localized standard date/time formatter
     * @throws IllegalArgumentException if the Locale has no date/time
     *  pattern defined
     */
    @SuppressWarnings("unchecked")
	public static synchronized FastDateFormat getDateTimeInstance(int dateStyle, int timeStyle, TimeZone timeZone,
            Locale locale) {

        Object key = new Pair(new Integer(dateStyle), new Integer(timeStyle));
        if (timeZone != null) {
            key = new Pair(key, timeZone);
        }
        if (locale == null) {
            locale = Locale.getDefault();
        }
        key = new Pair(key, locale);

        FastDateFormat format = (FastDateFormat) cDateTimeInstanceCache.get(key);
        if (format == null) {
            try {
                SimpleDateFormat formatter = (SimpleDateFormat) DateFormat.getDateTimeInstance(dateStyle, timeStyle,
                        locale);
                String pattern = formatter.toPattern();
                format = getInstance(pattern, timeZone, locale);
                cDateTimeInstanceCache.put(key, format);

            } catch (ClassCastException ex) {
                throw new IllegalArgumentException("No date time pattern for locale: " + locale);
            }
        }
        return format;
    }

    //-----------------------------------------------------------------------
    /**
     * <p>Gets the time zone display name, using a cache for performance.</p>
     * 
     * @param tz  the zone to query
     * @param daylight  true if daylight savings
     * @param style  the style to use <code>TimeZone.LONG</code>
     *  or <code>TimeZone.SHORT</code>
     * @param locale  the locale to use
     * @return the textual name of the time zone
     */
    @SuppressWarnings("unchecked")
	static synchronized String getTimeZoneDisplay(TimeZone tz, boolean daylight, int style, Locale locale) {
        Object key = new TimeZoneDisplayKey(tz, daylight, style, locale);
        String value = (String) cTimeZoneDisplayCache.get(key);
        if (value == null) {
            // This is a very slow call, so cache the results.
            value = tz.getDisplayName(daylight, style, locale);
            cTimeZoneDisplayCache.put(key, value);
        }
        return value;
    }

    /**
     * <p>Gets the default pattern.</p>
     * 
     * @return the default pattern
     */
    private static synchronized String getDefaultPattern() {
        if (cDefaultPattern == null) {
            cDefaultPattern = new SimpleDateFormat().toPattern();
        }
        return cDefaultPattern;
    }

    // Constructor
    //-----------------------------------------------------------------------
    /**
     * <p>Constructs a new FastDateFormat.</p>
     * 
     * @param pattern  {@link java.text.SimpleDateFormat} compatible
     *  pattern
     * @param timeZone  time zone to use, <code>null</code> means use
     *  default for <code>Date</code> and value within for
     *  <code>Calendar</code>
     * @param locale  locale, <code>null</code> means use system
     *  default
     * @throws IllegalArgumentException if pattern is invalid or
     *  <code>null</code>
     */
    protected FastDateFormat(String pattern, TimeZone timeZone, Locale locale) {
        super();
        if (pattern == null) {
            throw new IllegalArgumentException("The pattern must not be null");
        }
        mPattern = pattern;
        
        mTimeZoneForced = (timeZone != null);
        if (timeZone == null) {
            timeZone = TimeZone.getDefault();
        }
        mTimeZone = timeZone;
        
        mLocaleForced = (locale != null);
        if (locale == null) {
            locale = Locale.getDefault();
        }
        mLocale = locale;
    }

    /**
     * <p>Initializes the instance for first use.</p>
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
	protected void init() {
        List rulesList = parsePattern();
        mRules = (Rule[]) rulesList.toArray(new Rule[rulesList.size()]);

        int len = 0;
        for (int i=mRules.length; --i >= 0; ) {
            len += mRules[i].estimateLength();
        }

        mMaxLengthEstimate = len;
    }

    // Parse the pattern
    //-----------------------------------------------------------------------
    /**
     * <p>Returns a list of Rules given a pattern.</p>
     * 
     * @return a <code>List</code> of Rule objects
     * @throws IllegalArgumentException if pattern is invalid
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
	protected List parsePattern() {
        DateFormatSymbols symbols = new DateFormatSymbols(mLocale);
        List rules = new ArrayList();

        String[] ERAs = symbols.getEras();
        String[] months = symbols.getMonths();
        String[] shortMonths = symbols.getShortMonths();
        String[] weekdays = symbols.getWeekdays();
        String[] shortWeekdays = symbols.getShortWeekdays();
        String[] AmPmStrings = symbols.getAmPmStrings();

        int length = mPattern.length();
        int[] indexRef = new int[1];

        for (int i = 0; i < length; i++) {
            indexRef[0] = i;
            String token = parseToken(mPattern, indexRef);
            i = indexRef[0];

            int tokenLen = token.length();
            if (tokenLen == 0) {
                break;
            }

            Rule rule;
            char c = token.charAt(0);

            switch (c) {
            case 'G': // era designator (text)
                rule = new TextField(Calendar.ERA, ERAs);
                break;
            case 'y': // year (number)
                if (tokenLen >= 4) {
                    rule = selectNumberRule(Calendar.YEAR, tokenLen);
                } else {
                    rule = TwoDigitYearField.INSTANCE;
                }
                break;
            case 'M': // month in year (text and number)
                if (tokenLen >= 4) {
                    rule = new TextField(Calendar.MONTH, months);
                } else if (tokenLen == 3) {
                    rule = new TextField(Calendar.MONTH, shortMonths);
                } else if (tokenLen == 2) {
                    rule = TwoDigitMonthField.INSTANCE;
                } else {
                    rule = UnpaddedMonthField.INSTANCE;
                }
                break;
            case 'd': // day in month (number)
                rule = selectNumberRule(Calendar.DAY_OF_MONTH, tokenLen);
                break;
            case 'h': // hour in am/pm (number, 1..12)
                rule = new TwelveHourField(selectNumberRule(Calendar.HOUR, tokenLen));
                break;
            case 'H': // hour in day (number, 0..23)
                rule = selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen);
                break;
            case 'm': // minute in hour (number)
                rule = selectNumberRule(Calendar.MINUTE, tokenLen);
                break;
            case 's': // second in minute (number)
                rule = selectNumberRule(Calendar.SECOND, tokenLen);
                break;
            case 'S': // millisecond (number)
                rule = selectNumberRule(Calendar.MILLISECOND, tokenLen);
                break;
            case 'E': // day in week (text)
                rule = new TextField(Calendar.DAY_OF_WEEK, tokenLen < 4 ? shortWeekdays : weekdays);
                break;
            case 'D': // day in year (number)
                rule = selectNumberRule(Calendar.DAY_OF_YEAR, tokenLen);
                break;
            case 'F': // day of week in month (number)
                rule = selectNumberRule(Calendar.DAY_OF_WEEK_IN_MONTH, tokenLen);
                break;
            case 'w': // week in year (number)
                rule = selectNumberRule(Calendar.WEEK_OF_YEAR, tokenLen);
                break;
            case 'W': // week in month (number)
                rule = selectNumberRule(Calendar.WEEK_OF_MONTH, tokenLen);
                break;
            case 'a': // am/pm marker (text)
                rule = new TextField(Calendar.AM_PM, AmPmStrings);
                break;
            case 'k': // hour in day (1..24)
                rule = new TwentyFourHourField(selectNumberRule(Calendar.HOUR_OF_DAY, tokenLen));
                break;
            case 'K': // hour in am/pm (0..11)
                rule = selectNumberRule(Calendar.HOUR, tokenLen);
                break;
            case 'z': // time zone (text)
                if (tokenLen >= 4) {
                    rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.LONG);
                } else {
                    rule = new TimeZoneNameRule(mTimeZone, mTimeZoneForced, mLocale, TimeZone.SHORT);
                }
                break;
            case 'Z': // time zone (value)
                if (tokenLen == 1) {
                    rule = TimeZoneNumberRule.INSTANCE_NO_COLON;
                } else {
                    rule = TimeZoneNumberRule.INSTANCE_COLON;
                }
                break;
            case '\'': // literal text
                String sub = token.substring(1);
                if (sub.length() == 1) {
                    rule = new CharacterLiteral(sub.charAt(0));
                } else {
                    rule = new StringLiteral(sub);
                }
                break;
            default:
                throw new IllegalArgumentException("Illegal pattern component: " + token);
            }

            rules.add(rule);
        }

        return rules;
    }

    /**
     * <p>Performs the parsing of tokens.</p>
     * 
     * @param pattern  the pattern
     * @param indexRef  index references
     * @return parsed token
     */
    protected String parseToken(String pattern, int[] indexRef) {
        StringBuffer buf = new StringBuffer();

        int i = indexRef[0];
        int length = pattern.length();

        char c = pattern.charAt(i);
        if (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z') {
            // Scan a run of the same character, which indicates a time
            // pattern.
            buf.append(c);

            while (i + 1 < length) {
                char peek = pattern.charAt(i + 1);
                if (peek == c) {
                    buf.append(c);
                    i++;
                } else {
                    break;
                }
            }
        } else {
            // This will identify token as text.
            buf.append('\'');

            boolean inLiteral = false;

            for (; i < length; i++) {
                c = pattern.charAt(i);

                if (c == '\'') {
                    if (i + 1 < length && pattern.charAt(i + 1) == '\'') {
                        // '' is treated as escaped '
                        i++;
                        buf.append(c);
                    } else {
                        inLiteral = !inLiteral;
                    }
                } else if (!inLiteral &&
                         (c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')) {
                    i--;
                    break;
                } else {
                    buf.append(c);
                }
            }
        }

        indexRef[0] = i;
        return buf.toString();
    }

    /**
     * <p>Gets an appropriate rule for the padding required.</p>
     * 
     * @param field  the field to get a rule for
     * @param padding  the padding required
     * @return a new rule with the correct padding
     */
    protected NumberRule selectNumberRule(int field, int padding) {
        switch (padding) {
        case 1:
            return new UnpaddedNumberField(field);
        case 2:
            return new TwoDigitNumberField(field);
        default:
            return new PaddedNumberField(field, padding);
        }
    }

    // Format methods
    //-----------------------------------------------------------------------
    /**
     * <p>Formats a <code>Date</code>, <code>Calendar</code> or
     * <code>Long</code> (milliseconds) object.</p>
     * 
     * @param obj  the object to format
     * @param toAppendTo  the buffer to append to
     * @param pos  the position - ignored
     * @return the buffer passed in
     */
    @Override
	public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
        if (obj instanceof Date) {
            return format((Date) obj, toAppendTo);
        } else if (obj instanceof Calendar) {
            return format((Calendar) obj, toAppendTo);
        } else if (obj instanceof Long) {
            return format(((Long) obj).longValue(), toAppendTo);
        } else {
            throw new IllegalArgumentException("Unknown class: " +
                (obj == null ? "<null>" : obj.getClass().getName()));
        }
    }

    /**
     * <p>Formats a millisecond <code>long</code> value.</p>
     * 
     * @param millis  the millisecond value to format
     * @return the formatted string
     * @since 2.1
     */
    public String format(long millis) {
        return format(new Date(millis));
    }

    /**
     * <p>Formats a <code>Date</code> object.</p>
     * 
     * @param date  the date to format
     * @return the formatted string
     */
    public String format(Date date) {
        Calendar c = new GregorianCalendar(mTimeZone);
        c.setTime(date);
        return applyRules(c, new StringBuffer(mMaxLengthEstimate)).toString();
    }

    /**
     * <p>Formats a <code>Calendar</code> object.</p>
     * 
     * @param calendar  the calendar to format
     * @return the formatted string
     */
    public String format(Calendar calendar) {
        return format(calendar, new StringBuffer(mMaxLengthEstimate)).toString();
    }

    /**
     * <p>Formats a milliseond <code>long</code> value into the
     * supplied <code>StringBuffer</code>.</p>
     * 
     * @param millis  the millisecond value to format
     * @param buf  the buffer to format into
     * @return the specified string buffer
     * @since 2.1
     */
    public StringBuffer format(long millis, StringBuffer buf) {
        return format(new Date(millis), buf);
    }

    /**
     * <p>Formats a <code>Date</code> object into the
     * supplied <code>StringBuffer</code>.</p>
     * 
     * @param date  the date to format
     * @param buf  the buffer to format into
     * @return the specified string buffer
     */
    public StringBuffer format(Date date, StringBuffer buf) {
        Calendar c = new GregorianCalendar(mTimeZone);
        c.setTime(date);
        return applyRules(c, buf);
    }

    /**
     * <p>Formats a <code>Calendar</code> object into the
     * supplied <code>StringBuffer</code>.</p>
     * 
     * @param calendar  the calendar to format
     * @param buf  the buffer to format into
     * @return the specified string buffer
     */
    public StringBuffer format(Calendar calendar, StringBuffer buf) {
        if (mTimeZoneForced) {
            calendar.getTime(); /// LANG-538
            calendar = (Calendar) calendar.clone();
            calendar.setTimeZone(mTimeZone);
        }
        return applyRules(calendar, buf);
    }

    /**
     * <p>Performs the formatting by applying the rules to the
     * specified calendar.</p>
     * 
     * @param calendar  the calendar to format
     * @param buf  the buffer to format into
     * @return the specified string buffer
     */
    protected StringBuffer applyRules(Calendar calendar, StringBuffer buf) {
        Rule[] rules = mRules;
        int len = mRules.length;
        for (int i = 0; i < len; i++) {
            rules[i].appendTo(buf, calendar);
        }
        return buf;
    }

    // Parsing
    //-----------------------------------------------------------------------
    /**
     * <p>Parsing is not supported.</p>
     * 
     * @param source  the string to parse
     * @param pos  the parsing position
     * @return <code>null</code> as not supported
     */
    @Override
	public Object parseObject(String source, ParsePosition pos) {
        pos.setIndex(0);
        pos.setErrorIndex(0);
        return null;
    }
    
    // Accessors
    //-----------------------------------------------------------------------
    /**
     * <p>Gets the pattern used by this formatter.</p>
     * 
     * @return the pattern, {@link java.text.SimpleDateFormat} compatible
     */
    public String getPattern() {
        return mPattern;
    }

    /**
     * <p>Gets the time zone used by this formatter.</p>
     *
     * <p>This zone is always used for <code>Date</code> formatting.
     * If a <code>Calendar</code> is passed in to be formatted, the
     * time zone on that may be used depending on
     * {@link #getTimeZoneOverridesCalendar()}.</p>
     * 
     * @return the time zone
     */
    public TimeZone getTimeZone() {
        return mTimeZone;
    }

    /**
     * <p>Returns <code>true</code> if the time zone of the
     * calendar overrides the time zone of the formatter.</p>
     * 
     * @return <code>true</code> if time zone of formatter
     *  overridden for calendars
     */
    public boolean getTimeZoneOverridesCalendar() {
        return mTimeZoneForced;
    }

    /**
     * <p>Gets the locale used by this formatter.</p>
     * 
     * @return the locale
     */
    public Locale getLocale() {
        return mLocale;
    }

    /**
     * <p>Gets an estimate for the maximum string length that the
     * formatter will produce.</p>
     *
     * <p>The actual formatted length will almost always be less than or
     * equal to this amount.</p>
     * 
     * @return the maximum formatted length
     */
    public int getMaxLengthEstimate() {
        return mMaxLengthEstimate;
    }

    // Basics
    //-----------------------------------------------------------------------
    /**
     * <p>Compares two objects for equality.</p>
     * 
     * @param obj  the object to compare to
     * @return <code>true</code> if equal
     */
    @Override
	public boolean equals(Object obj) {
        if (obj instanceof FastDateFormat == false) {
            return false;
        }
        FastDateFormat other = (FastDateFormat) obj;
        if (
            (mPattern == other.mPattern || mPattern.equals(other.mPattern)) &&
            (mTimeZone == other.mTimeZone || mTimeZone.equals(other.mTimeZone)) &&
            (mLocale == other.mLocale || mLocale.equals(other.mLocale)) &&
            (mTimeZoneForced == other.mTimeZoneForced) &&
            (mLocaleForced == other.mLocaleForced)
            ) {
            return true;
        }
        return false;
    }

    /**
     * <p>Returns a hashcode compatible with equals.</p>
     * 
     * @return a hashcode compatible with equals
     */
    @Override
	public int hashCode() {
        int total = 0;
        total += mPattern.hashCode();
        total += mTimeZone.hashCode();
        total += (mTimeZoneForced ? 1 : 0);
        total += mLocale.hashCode();
        total += (mLocaleForced ? 1 : 0);
        return total;
    }

    /**
     * <p>Gets a debugging string version of this formatter.</p>
     * 
     * @return a debugging string
     */
    @Override
	public String toString() {
        return "FastDateFormat[" + mPattern + "]";
    }

    // Serializing
    //-----------------------------------------------------------------------
    /**
     * Create the object after serialization. This implementation reinitializes the 
     * transient properties.
     *
     * @param in ObjectInputStream from which the object is being deserialized.
     * @throws IOException if there is an IO issue.
     * @throws ClassNotFoundException if a class cannot be found.
     */
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        init();
    }
    
    // Rules
    //-----------------------------------------------------------------------
    /**
     * <p>Inner class defining a rule.</p>
     */
    private interface Rule {
        /**
         * Returns the estimated lentgh of the result.
         * 
         * @return the estimated length
         */
        int estimateLength();
        
        /**
         * Appends the value of the specified calendar to the output buffer based on the rule implementation.
         * 
         * @param buffer the output buffer
         * @param calendar calendar to be appended
         */
        void appendTo(StringBuffer buffer, Calendar calendar);
    }

    /**
     * <p>Inner class defining a numeric rule.</p>
     */
    private interface NumberRule extends Rule {
        /**
         * Appends the specified value to the output buffer based on the rule implementation.
         * 
         * @param buffer the output buffer
         * @param value the value to be appended
         */
        void appendTo(StringBuffer buffer, int value);
    }

    /**
     * <p>Inner class to output a constant single character.</p>
     */
    private static class CharacterLiteral implements Rule {
        private final char mValue;

        /**
         * Constructs a new instance of <code>CharacterLiteral</code>
         * to hold the specified value.
         * 
         * @param value the character literal
         */
        CharacterLiteral(char value) {
            mValue = value;
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public int estimateLength() {
            return 1;
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public void appendTo(StringBuffer buffer, Calendar calendar) {
            buffer.append(mValue);
        }
    }

    /**
     * <p>Inner class to output a constant string.</p>
     */
    private static class StringLiteral implements Rule {
        private final String mValue;

        /**
         * Constructs a new instance of <code>StringLiteral</code>
         * to hold the specified value.
         * 
         * @param value the string literal
         */
        StringLiteral(String value) {
            mValue = value;
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public int estimateLength() {
            return mValue.length();
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public void appendTo(StringBuffer buffer, Calendar calendar) {
            buffer.append(mValue);
        }
    }

    /**
     * <p>Inner class to output one of a set of values.</p>
     */
    private static class TextField implements Rule {
        private final int mField;
        private final String[] mValues;

        /**
         * Constructs an instance of <code>TextField</code>
         * with the specified field and values.
         * 
         * @param field the field
         * @param values the field values
         */
        TextField(int field, String[] values) {
            mField = field;
            mValues = values;
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public int estimateLength() {
            int max = 0;
            for (int i=mValues.length; --i >= 0; ) {
                int len = mValues[i].length();
                if (len > max) {
                    max = len;
                }
            }
            return max;
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public void appendTo(StringBuffer buffer, Calendar calendar) {
            buffer.append(mValues[calendar.get(mField)]);
        }
    }

    /**
     * <p>Inner class to output an unpadded number.</p>
     */
    private static class UnpaddedNumberField implements NumberRule {
        private final int mField;

        /**
         * Constructs an instance of <code>UnpadedNumberField</code> with the specified field.
         * 
         * @param field the field
         */
        UnpaddedNumberField(int field) {
            mField = field;
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public int estimateLength() {
            return 4;
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public void appendTo(StringBuffer buffer, Calendar calendar) {
            appendTo(buffer, calendar.get(mField));
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public final void appendTo(StringBuffer buffer, int value) {
            if (value < 10) {
                buffer.append((char)(value + '0'));
            } else if (value < 100) {
                buffer.append((char)(value / 10 + '0'));
                buffer.append((char)(value % 10 + '0'));
            } else {
                buffer.append(Integer.toString(value));
            }
        }
    }

    /**
     * <p>Inner class to output an unpadded month.</p>
     */
    private static class UnpaddedMonthField implements NumberRule {
        static final UnpaddedMonthField INSTANCE = new UnpaddedMonthField();

        /**
         * Constructs an instance of <code>UnpaddedMonthField</code>.
         *
         */
        UnpaddedMonthField() {
            super();
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public int estimateLength() {
            return 2;
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public void appendTo(StringBuffer buffer, Calendar calendar) {
            appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public final void appendTo(StringBuffer buffer, int value) {
            if (value < 10) {
                buffer.append((char)(value + '0'));
            } else {
                buffer.append((char)(value / 10 + '0'));
                buffer.append((char)(value % 10 + '0'));
            }
        }
    }

    /**
     * <p>Inner class to output a padded number.</p>
     */
    private static class PaddedNumberField implements NumberRule {
        private final int mField;
        private final int mSize;

        /**
         * Constructs an instance of <code>PaddedNumberField</code>.
         * 
         * @param field the field
         * @param size size of the output field
         */
        PaddedNumberField(int field, int size) {
            if (size < 3) {
                // Should use UnpaddedNumberField or TwoDigitNumberField.
                throw new IllegalArgumentException();
            }
            mField = field;
            mSize = size;
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public int estimateLength() {
            return 4;
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public void appendTo(StringBuffer buffer, Calendar calendar) {
            appendTo(buffer, calendar.get(mField));
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public final void appendTo(StringBuffer buffer, int value) {
            if (value < 100) {
                for (int i = mSize; --i >= 2; ) {
                    buffer.append('0');
                }
                buffer.append((char)(value / 10 + '0'));
                buffer.append((char)(value % 10 + '0'));
            } else {
                int digits;
                if (value < 1000) {
                    digits = 3;
                } else {
                    if ((value > -1) == false) {
                        throw new IllegalArgumentException("Negative values should not be possible " + value);
                    }
                    digits = Integer.toString(value).length();
                }
                for (int i = mSize; --i >= digits; ) {
                    buffer.append('0');
                }
                buffer.append(Integer.toString(value));
            }
        }
    }

    /**
     * <p>Inner class to output a two digit number.</p>
     */
    private static class TwoDigitNumberField implements NumberRule {
        private final int mField;

        /**
         * Constructs an instance of <code>TwoDigitNumberField</code> with the specified field.
         * 
         * @param field the field
         */
        TwoDigitNumberField(int field) {
            mField = field;
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public int estimateLength() {
            return 2;
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public void appendTo(StringBuffer buffer, Calendar calendar) {
            appendTo(buffer, calendar.get(mField));
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public final void appendTo(StringBuffer buffer, int value) {
            if (value < 100) {
                buffer.append((char)(value / 10 + '0'));
                buffer.append((char)(value % 10 + '0'));
            } else {
                buffer.append(Integer.toString(value));
            }
        }
    }

    /**
     * <p>Inner class to output a two digit year.</p>
     */
    private static class TwoDigitYearField implements NumberRule {
        static final TwoDigitYearField INSTANCE = new TwoDigitYearField();

        /**
         * Constructs an instance of <code>TwoDigitYearField</code>.
         */
        TwoDigitYearField() {
            super();
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public int estimateLength() {
            return 2;
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public void appendTo(StringBuffer buffer, Calendar calendar) {
            appendTo(buffer, calendar.get(Calendar.YEAR) % 100);
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public final void appendTo(StringBuffer buffer, int value) {
            buffer.append((char)(value / 10 + '0'));
            buffer.append((char)(value % 10 + '0'));
        }
    }

    /**
     * <p>Inner class to output a two digit month.</p>
     */
    private static class TwoDigitMonthField implements NumberRule {
        static final TwoDigitMonthField INSTANCE = new TwoDigitMonthField();

        /**
         * Constructs an instance of <code>TwoDigitMonthField</code>.
         */
        TwoDigitMonthField() {
            super();
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public int estimateLength() {
            return 2;
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public void appendTo(StringBuffer buffer, Calendar calendar) {
            appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public final void appendTo(StringBuffer buffer, int value) {
            buffer.append((char)(value / 10 + '0'));
            buffer.append((char)(value % 10 + '0'));
        }
    }

    /**
     * <p>Inner class to output the twelve hour field.</p>
     */
    private static class TwelveHourField implements NumberRule {
        private final NumberRule mRule;

        /**
         * Constructs an instance of <code>TwelveHourField</code> with the specified
         * <code>NumberRule</code>.
         * 
         * @param rule the rule
         */
        TwelveHourField(NumberRule rule) {
            mRule = rule;
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public int estimateLength() {
            return mRule.estimateLength();
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public void appendTo(StringBuffer buffer, Calendar calendar) {
            int value = calendar.get(Calendar.HOUR);
            if (value == 0) {
                value = calendar.getLeastMaximum(Calendar.HOUR) + 1;
            }
            mRule.appendTo(buffer, value);
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public void appendTo(StringBuffer buffer, int value) {
            mRule.appendTo(buffer, value);
        }
    }

    /**
     * <p>Inner class to output the twenty four hour field.</p>
     */
    private static class TwentyFourHourField implements NumberRule {
        private final NumberRule mRule;

        /**
         * Constructs an instance of <code>TwentyFourHourField</code> with the specified
         * <code>NumberRule</code>.
         * 
         * @param rule the rule
         */
        TwentyFourHourField(NumberRule rule) {
            mRule = rule;
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public int estimateLength() {
            return mRule.estimateLength();
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public void appendTo(StringBuffer buffer, Calendar calendar) {
            int value = calendar.get(Calendar.HOUR_OF_DAY);
            if (value == 0) {
                value = calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1;
            }
            mRule.appendTo(buffer, value);
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public void appendTo(StringBuffer buffer, int value) {
            mRule.appendTo(buffer, value);
        }
    }

    /**
     * <p>Inner class to output a time zone name.</p>
     */
    private static class TimeZoneNameRule implements Rule {
        private final TimeZone mTimeZone;
        private final boolean mTimeZoneForced;
        private final Locale mLocale;
        private final int mStyle;
        private final String mStandard;
        private final String mDaylight;

        /**
         * Constructs an instance of <code>TimeZoneNameRule</code> with the specified properties.
         * 
         * @param timeZone the time zone
         * @param timeZoneForced if <code>true</code> the time zone is forced into standard and daylight
         * @param locale the locale
         * @param style the style
         */
        TimeZoneNameRule(TimeZone timeZone, boolean timeZoneForced, Locale locale, int style) {
            mTimeZone = timeZone;
            mTimeZoneForced = timeZoneForced;
            mLocale = locale;
            mStyle = style;

            if (timeZoneForced) {
                mStandard = getTimeZoneDisplay(timeZone, false, style, locale);
                mDaylight = getTimeZoneDisplay(timeZone, true, style, locale);
            } else {
                mStandard = null;
                mDaylight = null;
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public int estimateLength() {
            if (mTimeZoneForced) {
                return Math.max(mStandard.length(), mDaylight.length());
            } else if (mStyle == TimeZone.SHORT) {
                return 4;
            } else {
                return 40;
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public void appendTo(StringBuffer buffer, Calendar calendar) {
            if (mTimeZoneForced) {
                if (mTimeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
                    buffer.append(mDaylight);
                } else {
                    buffer.append(mStandard);
                }
            } else {
                TimeZone timeZone = calendar.getTimeZone();
                if (timeZone.useDaylightTime() && calendar.get(Calendar.DST_OFFSET) != 0) {
                    buffer.append(getTimeZoneDisplay(timeZone, true, mStyle, mLocale));
                } else {
                    buffer.append(getTimeZoneDisplay(timeZone, false, mStyle, mLocale));
                }
            }
        }
    }

    /**
     * <p>Inner class to output a time zone as a number <code>+/-HHMM</code>
     * or <code>+/-HH:MM</code>.</p>
     */
    private static class TimeZoneNumberRule implements Rule {
        static final TimeZoneNumberRule INSTANCE_COLON = new TimeZoneNumberRule(true);
        static final TimeZoneNumberRule INSTANCE_NO_COLON = new TimeZoneNumberRule(false);
        
        final boolean mColon;
        
        /**
         * Constructs an instance of <code>TimeZoneNumberRule</code> with the specified properties.
         * 
         * @param colon add colon between HH and MM in the output if <code>true</code>
         */
        TimeZoneNumberRule(boolean colon) {
            mColon = colon;
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public int estimateLength() {
            return 5;
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public void appendTo(StringBuffer buffer, Calendar calendar) {
            int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
            
            if (offset < 0) {
                buffer.append('-');
                offset = -offset;
            } else {
                buffer.append('+');
            }
            
            int hours = offset / (60 * 60 * 1000);
            buffer.append((char)(hours / 10 + '0'));
            buffer.append((char)(hours % 10 + '0'));
            
            if (mColon) {
                buffer.append(':');
            }
            
            int minutes = offset / (60 * 1000) - 60 * hours;
            buffer.append((char)(minutes / 10 + '0'));
            buffer.append((char)(minutes % 10 + '0'));
        }            
    }

    // ----------------------------------------------------------------------
    /**
     * <p>Inner class that acts as a compound key for time zone names.</p>
     */
    private static class TimeZoneDisplayKey {
        private final TimeZone mTimeZone;
        private final int mStyle;
        private final Locale mLocale;

        /**
         * Constructs an instance of <code>TimeZoneDisplayKey</code> with the specified properties.
         *  
         * @param timeZone the time zone
         * @param daylight adjust the style for daylight saving time if <code>true</code>
         * @param style the timezone style
         * @param locale the timezone locale
         */
        TimeZoneDisplayKey(TimeZone timeZone,
                           boolean daylight, int style, Locale locale) {
            mTimeZone = timeZone;
            if (daylight) {
                style |= 0x80000000;
            }
            mStyle = style;
            mLocale = locale;
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public int hashCode() {
            return mStyle * 31 + mLocale.hashCode();
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj instanceof TimeZoneDisplayKey) {
                TimeZoneDisplayKey other = (TimeZoneDisplayKey)obj;
                return
                    mTimeZone.equals(other.mTimeZone) &&
                    mStyle == other.mStyle &&
                    mLocale.equals(other.mLocale);
            }
            return false;
        }
    }

    // ----------------------------------------------------------------------
    /**
     * <p>Helper class for creating compound objects.</p>
     *
     * <p>One use for this class is to create a hashtable key
     * out of multiple objects.</p>
     */
    private static class Pair {
        private final Object mObj1;
        private final Object mObj2;

        /**
         * Constructs an instance of <code>Pair</code> to hold the specified objects.
         * @param obj1 one object in the pair
         * @param obj2 second object in the pair
         */
        public Pair(Object obj1, Object obj2) {
            mObj1 = obj1;
            mObj2 = obj2;
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }

            if (!(obj instanceof Pair)) {
                return false;
            }

            Pair key = (Pair)obj;

            return
                (mObj1 == null ?
                 key.mObj1 == null : mObj1.equals(key.mObj1)) &&
                (mObj2 == null ?
                 key.mObj2 == null : mObj2.equals(key.mObj2));
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public int hashCode() {
            return
                (mObj1 == null ? 0 : mObj1.hashCode()) +
                (mObj2 == null ? 0 : mObj2.hashCode());
        }

        /**
         * {@inheritDoc}
         */
        @Override
		public String toString() {
            return "[" + mObj1 + ':' + mObj2 + ']';
        }
    }

}