001package biweekly.util;
002
003import java.text.DecimalFormat;
004import java.text.NumberFormat;
005import java.util.Calendar;
006import java.util.Date;
007import java.util.TimeZone;
008import java.util.regex.Matcher;
009import 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 */
063public 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}