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("20130722T172500");
049 *
050 * //converting to date string
051 * DateTimeComponents components = new DateTimeComponents(2013, 07, 22, 17, 25, 0, false);
052 * String str = components.toString(true); //"2013-07-22T17:25:00"
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 }