001    package ezvcard.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     The views and conclusions contained in the software and documentation are those
033     of the authors and should not be interpreted as representing official policies, 
034     either expressed or implied, of the FreeBSD Project.
035     */
036    
037    /**
038     * Helper class that formats and parses vCard dates. vCard dates adhere to the
039     * ISO8601 date format standard.
040     * @author Michael Angstadt
041     */
042    public class VCardDateFormatter {
043            /**
044             * Formats a date for inclusion in a vCard.
045             * @param date the date to format
046             * @param format the format to use
047             * @return the formatted date
048             */
049            public static String format(Date date, ISOFormat format) {
050                    return format(date, format, TimeZone.getDefault());
051            }
052    
053            /**
054             * Formats a date for inclusion in a vCard.
055             * @param date the date to format
056             * @param format the format to use
057             * @param timeZone the time zone to format the date in. This will be ignored
058             * if the specified ISOFormat is a "UTC" format
059             * @return the formatted date
060             */
061            public static String format(Date date, ISOFormat format, TimeZone timeZone) {
062                    switch (format) {
063                    case UTC_TIME_BASIC:
064                    case UTC_TIME_EXTENDED:
065                            timeZone = TimeZone.getTimeZone("UTC");
066                            break;
067                    }
068    
069                    DateFormat df = format.getFormatDateFormat();
070                    df.setTimeZone(timeZone);
071                    String str = df.format(date);
072    
073                    switch (format) {
074                    case TIME_EXTENDED:
075                            //add a colon to the timezone
076                            //example: converts "2012-07-05T22:31:41-0400" to "2012-07-05T22:31:41-04:00"
077                            str = str.replaceAll("([-\\+]\\d{2})(\\d{2})$", "$1:$2");
078                            break;
079                    }
080    
081                    return str;
082            }
083    
084            /**
085             * Parses a vCard date.
086             * @param dateStr the date string to parse
087             * @return the parsed date
088             * @throws IllegalArgumentException if the date string isn't in one of the
089             * accepted ISO8601 formats
090             */
091            public static Date parse(String dateStr) {
092                    //find out what ISOFormat the date is in
093                    ISOFormat format = null;
094                    for (ISOFormat f : ISOFormat.values()) {
095                            if (f.matches(dateStr)) {
096                                    format = f;
097                                    break;
098                            }
099                    }
100                    if (format == null) {
101                            throw new IllegalArgumentException("Date string is not in a valid ISO-8601 format.");
102                    }
103    
104                    //tweak the date string to make it work with SimpleDateFormat
105                    switch (format) {
106                    case TIME_EXTENDED:
107                    case HCARD_TIME_TAG:
108                            //SimpleDateFormat doesn't recognize timezone offsets that have colons
109                            //so remove the colon from the timezone offset
110                            dateStr = dateStr.replaceAll("([-\\+]\\d{2}):(\\d{2})$", "$1$2");
111                            break;
112                    case UTC_TIME_BASIC:
113                    case UTC_TIME_EXTENDED:
114                            //SimpleDateFormat doesn't recognize "Z"
115                            dateStr = dateStr.replace("Z", "+0000");
116                            break;
117                    }
118    
119                    //parse the date
120                    DateFormat df = format.getParseDateFormat();
121                    try {
122                            return df.parse(dateStr);
123                    } catch (ParseException e) {
124                            //should never be thrown because the string is checked against a regex
125                            throw new RuntimeException(e);
126                    }
127            }
128    
129            /**
130             * Gets the {@link TimeZone} object that corresponds to the given ID.
131             * @param timezoneId the timezone ID (e.g. "America/New_York")
132             * @return the timezone object or null if not found
133             */
134            public static TimeZone parseTimeZoneId(String timezoneId) {
135                    TimeZone timezone = TimeZone.getTimeZone(timezoneId);
136                    return "GMT".equals(timezone.getID()) ? null : timezone;
137            }
138    
139            private VCardDateFormatter() {
140                    //hide constructor
141            }
142    }