001 package biweekly.util; 002 003 import java.text.DateFormat; 004 import java.text.ParseException; 005 import java.util.Date; 006 import java.util.TimeZone; 007 008 /* 009 Copyright (c) 2013, Michael Angstadt 010 All rights reserved. 011 012 Redistribution and use in source and binary forms, with or without 013 modification, are permitted provided that the following conditions are met: 014 015 1. Redistributions of source code must retain the above copyright notice, this 016 list of conditions and the following disclaimer. 017 2. Redistributions in binary form must reproduce the above copyright notice, 018 this list of conditions and the following disclaimer in the documentation 019 and/or other materials provided with the distribution. 020 021 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 022 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 023 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 024 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 025 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 026 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 027 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 028 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 029 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 030 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 031 */ 032 033 /** 034 * Helper class that formats and parses iCalendar dates. iCalendar dates adhere 035 * to the ISO8601 date format standard. 036 * @author Michael Angstadt 037 */ 038 public class ICalDateFormatter { 039 /** 040 * Formats a date for inclusion in an iCalendar object. 041 * @param date the date to format 042 * @param format the format to use 043 * @return the formatted date 044 */ 045 public static String format(Date date, ISOFormat format) { 046 return format(date, format, null); 047 } 048 049 /** 050 * Formats a date for inclusion in an iCalendar object. 051 * @param date the date to format 052 * @param format the format to use 053 * @param timeZone the timezone to format the date in or null to use the 054 * JVM's default timezone (ignored with "UTC" formats) 055 * @return the formatted date 056 */ 057 public static String format(Date date, ISOFormat format, TimeZone timeZone) { 058 switch (format) { 059 case UTC_TIME_BASIC: 060 case UTC_TIME_EXTENDED: 061 timeZone = TimeZone.getTimeZone("UTC"); 062 break; 063 } 064 065 DateFormat df = format.getFormatDateFormat(); 066 if (timeZone != null) { 067 df.setTimeZone(timeZone); 068 } 069 String str = df.format(date); 070 071 switch (format) { 072 case TIME_EXTENDED: 073 //add a colon to the timezone 074 //example: converts "2012-07-05T22:31:41-0400" to "2012-07-05T22:31:41-04:00" 075 str = str.replaceAll("([-\\+]\\d{2})(\\d{2})$", "$1:$2"); 076 break; 077 } 078 079 return str; 080 } 081 082 /** 083 * Parses an iCalendar date. 084 * @param dateStr the date string to parse (e.g. "20130609T181023Z") 085 * @return the parsed date 086 * @throws IllegalArgumentException if the date string isn't in one of the 087 * accepted ISO8601 formats 088 */ 089 public static Date parse(String dateStr) { 090 return parse(dateStr, null); 091 } 092 093 /** 094 * Parses an iCalendar date. 095 * @param dateStr the date string to parse (e.g. "20130609T181023Z") 096 * @param timezone the timezone to parse the date as or null to use the 097 * JVM's default timezone (if the date string contains its own timezone, 098 * then that timezone will be used instead) 099 * @return the parsed date 100 * @throws IllegalArgumentException if the date string isn't in one of the 101 * accepted ISO8601 formats 102 */ 103 public static Date parse(String dateStr, TimeZone timezone) { 104 //find out what ISOFormat the date is in 105 ISOFormat format = null; 106 for (ISOFormat f : ISOFormat.values()) { 107 if (f.matches(dateStr)) { 108 format = f; 109 break; 110 } 111 } 112 if (format == null) { 113 throw new IllegalArgumentException("Date string is not in a valid ISO-8601 format."); 114 } 115 116 //tweak the date string to make it work with SimpleDateFormat 117 switch (format) { 118 case TIME_EXTENDED: 119 case HCARD_TIME_TAG: 120 //SimpleDateFormat doesn't recognize timezone offsets that have colons 121 //so remove the colon from the timezone offset 122 dateStr = dateStr.replaceAll("([-\\+]\\d{2}):(\\d{2})$", "$1$2"); 123 break; 124 case UTC_TIME_BASIC: 125 case UTC_TIME_EXTENDED: 126 //SimpleDateFormat doesn't recognize "Z" 127 dateStr = dateStr.replace("Z", "+0000"); 128 break; 129 } 130 131 //parse the date 132 DateFormat df = format.getParseDateFormat(); 133 if (timezone != null) { 134 df.setTimeZone(timezone); 135 } 136 try { 137 return df.parse(dateStr); 138 } catch (ParseException e) { 139 //should never be thrown because the string is checked against a regex 140 throw new IllegalArgumentException("Date string is not in a valid ISO-8601 format."); 141 } 142 } 143 144 /** 145 * Determines whether a date string has a time component. 146 * @param dateStr the date string (e.g. "20130601T120000") 147 * @return true if it has a time component, false if not 148 */ 149 public static boolean dateHasTime(String dateStr) { 150 return dateStr.contains("T"); 151 } 152 153 /** 154 * Determines whether a date string is in UTC time or has a timezone offset. 155 * @param dateStr the date string (e.g. "20130601T120000Z", 156 * "20130601T120000-0400") 157 * @return true if it has a timezone, false if not 158 */ 159 public static boolean dateHasTimezone(String dateStr) { 160 return dateStr.endsWith("Z") || dateStr.matches(".*?[-+]\\d\\d:?\\d\\d"); 161 } 162 163 /** 164 * Gets the {@link TimeZone} object that corresponds to the given ID. 165 * @param timezoneId the timezone ID (e.g. "America/New_York") 166 * @return the timezone object or null if not found 167 */ 168 public static TimeZone parseTimeZoneId(String timezoneId) { 169 TimeZone timezone = TimeZone.getTimeZone(timezoneId); 170 return "GMT".equals(timezone.getID()) ? null : timezone; 171 } 172 173 private ICalDateFormatter() { 174 //hide constructor 175 } 176 }