001    package biweekly.util;
002    
003    import java.text.DecimalFormat;
004    import java.text.NumberFormat;
005    import java.util.Calendar;
006    import java.util.Date;
007    import java.util.TimeZone;
008    import java.util.regex.Matcher;
009    import java.util.regex.Pattern;
010    
011    /*
012     Copyright (c) 2013, Michael Angstadt
013     All rights reserved.
014    
015     Redistribution and use in source and binary forms, with or without
016     modification, are permitted provided that the following conditions are met: 
017    
018     1. Redistributions of source code must retain the above copyright notice, this
019     list of conditions and the following disclaimer. 
020     2. Redistributions in binary form must reproduce the above copyright notice,
021     this list of conditions and the following disclaimer in the documentation
022     and/or other materials provided with the distribution. 
023    
024     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
025     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
026     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
027     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
028     ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
029     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
030     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
031     ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
032     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
033     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
034     */
035    
036    /**
037     * <p>
038     * Contains the raw components of a date-time value.
039     * </p>
040     * <p>
041     * <b>Examples:</b>
042     * 
043     * <pre class="brush:java">
044     * //July 22, 2013 at 17:25
045     * DateTimeComponents components = new DateTimeComponents(2013, 07, 22, 17, 25, 0, false);
046     * 
047     * //parsing a date string (accepts basic and extended formats)
048     * DateTimeComponents components = DateTimeComponents.parse(&quot;20130722T172500&quot;);
049     * 
050     * //converting to date string
051     * DateTimeComponents components = new DateTimeComponents(2013, 07, 22, 17, 25, 0, false);
052     * String str = components.toString(true); //&quot;2013-07-22T17:25:00&quot;
053     * 
054     * //converting to a Date object
055     * DateTimeComponents components = new DateTimeComponents(2013, 07, 22, 17, 25, 0, false);
056     * Date date = components.toDate();
057     * 
058     * </pre>
059     * 
060     * </p>
061     * @author Michael Angstadt
062     */
063    public final class DateTimeComponents {
064            private static final Pattern regex = Pattern.compile("^(\\d{4})-?(\\d{2})-?(\\d{2})(T(\\d{2}):?(\\d{2}):?(\\d{2})(Z?))?.*");
065            private final int year, month, date, hour, minute, second;
066            private final boolean utc;
067    
068            /**
069             * Parses the components out of a date-time string.
070             * @param dateString the date-time string (basic and extended formats
071             * supported, e.g. "20130331T020000" or "2013-03-31T02:00:00")
072             * @return the parsed components
073             * @throws IllegalArgumentException if the date string cannot be parsed
074             */
075            public static DateTimeComponents parse(String dateString) {
076                    Matcher m = regex.matcher(dateString);
077                    if (!m.find()) {
078                            throw new IllegalArgumentException("Cannot parse date: " + dateString);
079                    }
080    
081                    int i = 1;
082    
083                    int year = Integer.parseInt(m.group(i++));
084    
085                    int month = Integer.parseInt(m.group(i++));
086    
087                    int date = Integer.parseInt(m.group(i++));
088    
089                    i++; //skip
090    
091                    String hourStr = m.group(i++);
092                    int hour = (hourStr == null) ? 0 : Integer.parseInt(hourStr);
093    
094                    String minuteStr = m.group(i++);
095                    int minute = (minuteStr == null) ? 0 : Integer.parseInt(minuteStr);
096    
097                    String secondStr = m.group(i++);
098                    int second = (secondStr == null) ? 0 : Integer.parseInt(secondStr);
099    
100                    boolean utc = "Z".equals(m.group(i++));
101    
102                    return new DateTimeComponents(year, month, date, hour, minute, second, utc);
103            }
104    
105            /**
106             * Copies an existing DateTimeComponents object.
107             * @param original the object to copy from
108             * @param year the new year value or null not to change
109             * @param month the new month value or null not to change
110             * @param date the new date value or null not to change
111             * @param hour the new hour value or null not to change
112             * @param minute the new minute value or null not to change
113             * @param second the new second value or null not to change
114             * @param utc true if the time is in UTC, false if not, or null not to
115             * change
116             */
117            public DateTimeComponents(DateTimeComponents original, Integer year, Integer month, Integer date, Integer hour, Integer minute, Integer second, Boolean utc) {
118                    //@formatter:off
119                    this(
120                            (year == null) ? original.year : year,
121                            (month == null) ? original.month : month,
122                            (date == null) ? original.date : date,
123                            (hour == null) ? original.hour : hour,
124                            (minute == null) ? original.minute : minute,
125                            (second == null) ? original.second : second,
126                            (utc == null) ? original.utc : utc
127                    );
128                    //@formatter:on
129            }
130    
131            /**
132             * Creates a new set of date-time components.
133             * @param year the year (e.g. "2013")
134             * @param month the month (e.g. "1" for January)
135             * @param date the date of the month (e.g. "15")
136             * @param hour the hour (e.g. "13")
137             * @param minute the minute
138             * @param second the second
139             * @param utc true if the time is in UTC, false if not
140             */
141            public DateTimeComponents(int year, int month, int date, int hour, int minute, int second, boolean utc) {
142                    this.year = year;
143                    this.month = month;
144                    this.date = date;
145                    this.hour = hour;
146                    this.minute = minute;
147                    this.second = second;
148                    this.utc = utc;
149            }
150    
151            /**
152             * Gets the year component.
153             * @return the year
154             */
155            public int getYear() {
156                    return year;
157            }
158    
159            /**
160             * Gets the month component.
161             * @return the month (e.g. "1" for January)
162             */
163            public int getMonth() {
164                    return month;
165            }
166    
167            /**
168             * Gets the date component
169             * @return the date
170             */
171            public int getDate() {
172                    return date;
173            }
174    
175            /**
176             * Gets the hour component
177             * @return the hour
178             */
179            public int getHour() {
180                    return hour;
181            }
182    
183            /**
184             * Gets the minute component.
185             * @return the minute
186             */
187            public int getMinute() {
188                    return minute;
189            }
190    
191            /**
192             * Gets the second component.
193             * @return the second
194             */
195            public int getSecond() {
196                    return second;
197            }
198    
199            /**
200             * Gets whether the time is in UTC or not
201             * @return true if the time is in UTC, false if not
202             */
203            public boolean isUtc() {
204                    return utc;
205            }
206    
207            /**
208             * Converts the date-time components to a string using "basic" format.
209             * @return the date string
210             */
211            @Override
212            public String toString() {
213                    return toString(false);
214            }
215    
216            /**
217             * Converts the date-time components to a string.
218             * @param extended true to use extended format, false to use basic
219             * @return the date string
220             */
221            public String toString(boolean extended) {
222                    NumberFormat nf = new DecimalFormat("00");
223                    String dash = extended ? "-" : "";
224                    String colon = extended ? ":" : "";
225                    String z = utc ? "Z" : "";
226    
227                    return year + dash + nf.format(month) + dash + nf.format(date) + "T" + nf.format(hour) + colon + nf.format(minute) + colon + nf.format(second) + z;
228            }
229    
230            /**
231             * Converts the date-time components to a {@link Date} object.
232             * @return the date object
233             */
234            public Date toDate() {
235                    TimeZone tz = utc ? TimeZone.getTimeZone("UTC") : TimeZone.getDefault();
236                    Calendar c = Calendar.getInstance(tz);
237                    c.clear();
238                    c.set(Calendar.YEAR, year);
239                    c.set(Calendar.MONTH, month - 1);
240                    c.set(Calendar.DATE, date);
241                    c.set(Calendar.HOUR_OF_DAY, hour);
242                    c.set(Calendar.MINUTE, minute);
243                    c.set(Calendar.SECOND, second);
244                    return c.getTime();
245            }
246    
247            @Override
248            public int hashCode() {
249                    final int prime = 31;
250                    int result = 1;
251                    result = prime * result + date;
252                    result = prime * result + hour;
253                    result = prime * result + minute;
254                    result = prime * result + month;
255                    result = prime * result + second;
256                    result = prime * result + (utc ? 1231 : 1237);
257                    result = prime * result + year;
258                    return result;
259            }
260    
261            @Override
262            public boolean equals(Object obj) {
263                    if (this == obj)
264                            return true;
265                    if (obj == null)
266                            return false;
267                    if (getClass() != obj.getClass())
268                            return false;
269                    DateTimeComponents other = (DateTimeComponents) obj;
270                    if (date != other.date)
271                            return false;
272                    if (hour != other.hour)
273                            return false;
274                    if (minute != other.minute)
275                            return false;
276                    if (month != other.month)
277                            return false;
278                    if (second != other.second)
279                            return false;
280                    if (utc != other.utc)
281                            return false;
282                    if (year != other.year)
283                            return false;
284                    return true;
285            }
286    }