001    
002    /*
003     * Copyright (C) 2011 Archie L. Cobbs. All rights reserved.
004     *
005     * $Id: DateEncoder.java 311 2012-03-26 16:18:17Z archie.cobbs $
006     */
007    
008    package org.dellroad.stuff.string;
009    
010    import java.text.ParseException;
011    import java.text.SimpleDateFormat;
012    import java.util.Date;
013    import java.util.TimeZone;
014    import java.util.regex.Matcher;
015    import java.util.regex.Pattern;
016    
017    /**
018     * Encodes {@code Date} objects to and from strings.
019     */
020    public final class DateEncoder {
021    
022        /**
023         * Regular expression matching properly encoded strings.
024         */
025        public static final String PATTERN = "[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{3})?(Z)?";
026    
027        private static final String FORMAT_SECONDS = "yyyy-MM-dd'T'HH:mm:ss";
028        private static final String FORMAT_MILLIS = "yyyy-MM-dd'T'HH:mm:ss.SSS";
029        private static final String FORMAT_Z_SUFFIX = "'Z'";
030    
031        private DateEncoder() {
032        }
033    
034        /**
035         * Encode the given date as a string of the form {@code 2009-12-01T15:33:07.763Z}.
036         * If the fractional seconds portion is zero, it will be omitted.
037         *
038         * @param date date to encode
039         * @throws NullPointerException if {@code date} is {@code null}
040         */
041        public static String encode(Date date) {
042            String format = (date.getTime() % 1000 != 0 ? FORMAT_MILLIS : FORMAT_SECONDS) + FORMAT_Z_SUFFIX;
043            return DateEncoder.getDateFormat(format).format(date);
044        }
045    
046        /**
047         * Decode the given date.
048         *
049         * @param string encoded date
050         * @throws IllegalArgumentException if {@code string} is malformed
051         * @throws NullPointerException if {@code string} is {@code null}
052         */
053        public static Date decode(String string) {
054            Matcher matcher = Pattern.compile(PATTERN).matcher(string);
055            if (!matcher.matches())
056                throw new IllegalArgumentException("malformed date string");
057            String format = matcher.group(1) != null ? FORMAT_MILLIS : FORMAT_SECONDS;
058            if (matcher.group(2) != null)
059                format += FORMAT_Z_SUFFIX;
060            try {
061                return DateEncoder.getDateFormat(format).parse(matcher.group());
062            } catch (ParseException e) {
063                throw new RuntimeException("unexpected");
064            }
065        }
066    
067        /**
068         * Get a {@link SimpleDateFormat} configured with the given format and for the UTC time zone and strict parsing.
069         *
070         * @param format date format string
071         */
072        public static SimpleDateFormat getDateFormat(String format) {
073            SimpleDateFormat dateFormat = new SimpleDateFormat(format);
074            dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
075            dateFormat.setLenient(false);
076            return dateFormat;
077        }
078    }
079