/* * 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 + ']'; } } }