001package biweekly.util; 002 003import java.io.Serializable; 004import java.text.DecimalFormat; 005import java.text.NumberFormat; 006import java.util.Calendar; 007import java.util.Date; 008import java.util.TimeZone; 009import java.util.regex.Matcher; 010import java.util.regex.Pattern; 011 012/* 013 Copyright (c) 2013-2015, Michael Angstadt 014 All rights reserved. 015 016 Redistribution and use in source and binary forms, with or without 017 modification, are permitted provided that the following conditions are met: 018 019 1. Redistributions of source code must retain the above copyright notice, this 020 list of conditions and the following disclaimer. 021 2. Redistributions in binary form must reproduce the above copyright notice, 022 this list of conditions and the following disclaimer in the documentation 023 and/or other materials provided with the distribution. 024 025 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 026 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 027 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 028 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 029 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 030 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 031 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 032 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 033 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 034 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 035 */ 036 037/** 038 * <p> 039 * Contains the raw components of a date-time value. 040 * </p> 041 * <p> 042 * <b>Examples:</b> 043 * 044 * <pre class="brush:java"> 045 * //July 22, 2013 at 17:25 046 * DateTimeComponents components = new DateTimeComponents(2013, 07, 22, 17, 25, 0, false); 047 * 048 * //parsing a date string (accepts basic and extended formats) 049 * DateTimeComponents components = DateTimeComponents.parse("20130722T172500"); 050 * 051 * //converting to date string 052 * DateTimeComponents components = new DateTimeComponents(2013, 07, 22, 17, 25, 0, false); 053 * String str = components.toString(true); //"2013-07-22T17:25:00" 054 * 055 * //converting to a Date object 056 * DateTimeComponents components = new DateTimeComponents(2013, 07, 22, 17, 25, 0, false); 057 * Date date = components.toDate(); 058 * 059 * </pre> 060 * 061 * </p> 062 * @author Michael Angstadt 063 */ 064public final class DateTimeComponents implements Comparable<DateTimeComponents>, Serializable { 065 private static final long serialVersionUID = 7668029303206402368L; 066 private static final Pattern regex = Pattern.compile("^(\\d{4})-?(\\d{2})-?(\\d{2})(T(\\d{2}):?(\\d{2}):?(\\d{2})(Z?))?.*"); 067 private final int year, month, date, hour, minute, second; 068 private final boolean hasTime, utc; 069 070 /** 071 * Parses the components out of a date-time string. 072 * @param dateString the date-time string (basic and extended formats are 073 * supported, e.g. "20130331T020000" or "2013-03-31T02:00:00") 074 * @return the parsed components 075 * @throws IllegalArgumentException if the date string cannot be parsed 076 */ 077 public static DateTimeComponents parse(String dateString) { 078 return parse(dateString, null); 079 } 080 081 /** 082 * Parses the components out of a date-time string. 083 * @param dateString the date-time string (basic and extended formats are 084 * supported, e.g. "20130331T020000" or "2013-03-31T02:00:00") 085 * @param hasTime true to force the value to be parsed as a date-time value, 086 * false to force the value to be parsed as a date value, null to parse the 087 * value however it is 088 * @return the parsed components 089 * @throws IllegalArgumentException if the date string cannot be parsed 090 */ 091 public static DateTimeComponents parse(String dateString, Boolean hasTime) { 092 Matcher m = regex.matcher(dateString); 093 if (!m.find()) { 094 throw new IllegalArgumentException("Cannot parse date: " + dateString); 095 } 096 097 int i = 1; 098 int year = Integer.parseInt(m.group(i++)); 099 int month = Integer.parseInt(m.group(i++)); 100 int date = Integer.parseInt(m.group(i++)); 101 102 i++; //skip 103 104 String hourStr = m.group(i++); 105 if (hasTime == null) { 106 hasTime = (hourStr != null); 107 } 108 if (!hasTime) { 109 return new DateTimeComponents(year, month, date); 110 } 111 112 int hour = (hourStr == null) ? 0 : Integer.parseInt(hourStr); 113 114 String minuteStr = m.group(i++); 115 int minute = (minuteStr == null) ? 0 : Integer.parseInt(minuteStr); 116 117 String secondStr = m.group(i++); 118 int second = (secondStr == null) ? 0 : Integer.parseInt(secondStr); 119 120 boolean utc = "Z".equals(m.group(i++)); 121 122 return new DateTimeComponents(year, month, date, hour, minute, second, utc); 123 } 124 125 /** 126 * Copies an existing DateTimeComponents object. 127 * @param original the object to copy from 128 */ 129 public DateTimeComponents(DateTimeComponents original) { 130 this(original, null, null, null, null, null, null, null); 131 } 132 133 /** 134 * Copies an existing DateTimeComponents object. 135 * @param original the object to copy from 136 * @param year the new year value or null not to change 137 * @param month the new month value or null not to change 138 * @param date the new date value or null not to change 139 * @param hour the new hour value or null not to change 140 * @param minute the new minute value or null not to change 141 * @param second the new second value or null not to change 142 * @param utc true if the time is in UTC, false if not, or null not to 143 * change 144 */ 145 public DateTimeComponents(DateTimeComponents original, Integer year, Integer month, Integer date, Integer hour, Integer minute, Integer second, Boolean utc) { 146 //@formatter:off 147 this( 148 (year == null) ? original.year : year, 149 (month == null) ? original.month : month, 150 (date == null) ? original.date : date, 151 (hour == null) ? original.hour : hour, 152 (minute == null) ? original.minute : minute, 153 (second == null) ? original.second : second, 154 (utc == null) ? original.utc : utc 155 ); 156 //@formatter:on 157 } 158 159 /** 160 * Creates a set of date components. 161 * @param year the year (e.g. "2013") 162 * @param month the month (e.g. "1" for January) 163 * @param date the date of the month (e.g. "15") 164 */ 165 public DateTimeComponents(int year, int month, int date) { 166 this(year, month, date, 0, 0, 0, false, false); 167 } 168 169 /** 170 * Creates a set of date-time components. 171 * @param year the year (e.g. "2013") 172 * @param month the month (e.g. "1" for January) 173 * @param date the date of the month (e.g. "15") 174 * @param hour the hour (e.g. "13") 175 * @param minute the minute 176 * @param second the second 177 * @param utc true if the time is in UTC, false if not 178 */ 179 public DateTimeComponents(int year, int month, int date, int hour, int minute, int second, boolean utc) { 180 this(year, month, date, hour, minute, second, utc, true); 181 } 182 183 private DateTimeComponents(int year, int month, int date, int hour, int minute, int second, boolean utc, boolean hasTime) { 184 this.year = year; 185 this.month = month; 186 this.date = date; 187 this.hour = hour; 188 this.minute = minute; 189 this.second = second; 190 this.utc = utc; 191 this.hasTime = hasTime; 192 } 193 194 /** 195 * Creates a set of date-time components in the local timezone from a 196 * {@link Date} object. 197 * @param date the date object 198 */ 199 public DateTimeComponents(Date date) { 200 this(date, TimeZone.getDefault()); 201 } 202 203 /** 204 * Creates a set of date-time components from a {@link Date} object. 205 * @param date the date object 206 * @param timezone the timezone the date-time components will be in 207 */ 208 public DateTimeComponents(Date date, TimeZone timezone) { 209 Calendar cal = Calendar.getInstance(timezone); 210 cal.setTime(date); 211 212 year = cal.get(Calendar.YEAR); 213 month = cal.get(Calendar.MONTH) + 1; 214 this.date = cal.get(Calendar.DATE); 215 hour = cal.get(Calendar.HOUR_OF_DAY); 216 minute = cal.get(Calendar.MINUTE); 217 second = cal.get(Calendar.SECOND); 218 utc = false; 219 hasTime = true; 220 } 221 222 /** 223 * Gets the year component. 224 * @return the year 225 */ 226 public int getYear() { 227 return year; 228 } 229 230 /** 231 * Gets the month component. 232 * @return the month (e.g. "1" for January) 233 */ 234 public int getMonth() { 235 return month; 236 } 237 238 /** 239 * Gets the date component 240 * @return the date 241 */ 242 public int getDate() { 243 return date; 244 } 245 246 /** 247 * Gets whether these components contain a time component 248 * @return true if it has a time component, false if it's strictly a date 249 */ 250 public boolean hasTime() { 251 return hasTime; 252 } 253 254 /** 255 * Gets the hour component 256 * @return the hour 257 */ 258 public int getHour() { 259 return hour; 260 } 261 262 /** 263 * Gets the minute component. 264 * @return the minute 265 */ 266 public int getMinute() { 267 return minute; 268 } 269 270 /** 271 * Gets the second component. 272 * @return the second 273 */ 274 public int getSecond() { 275 return second; 276 } 277 278 /** 279 * Gets whether the time is in UTC or not 280 * @return true if the time is in UTC, false if not 281 */ 282 public boolean isUtc() { 283 return utc; 284 } 285 286 /** 287 * Converts the date-time components to a string using "basic" format. 288 * @return the date string 289 */ 290 @Override 291 public String toString() { 292 return toString(true, false); 293 } 294 295 /** 296 * Converts the date-time components to a string. 297 * @param includeTime true to include the time portion, false not to 298 * @param extended true to use extended format, false to use basic 299 * @return the date string 300 */ 301 public String toString(boolean includeTime, boolean extended) { 302 NumberFormat nf = new DecimalFormat("00"); 303 String dash = extended ? "-" : ""; 304 String colon = extended ? ":" : ""; 305 String z = utc ? "Z" : ""; 306 307 StringBuilder sb = new StringBuilder(); 308 sb.append(year).append(dash).append(nf.format(month)).append(dash).append(nf.format(date)); 309 if (includeTime) { 310 sb.append("T").append(nf.format(hour)).append(colon).append(nf.format(minute)).append(colon).append(nf.format(second)).append(z); 311 } 312 return sb.toString(); 313 } 314 315 /** 316 * Converts the date-time components to a {@link Date} object. 317 * @return the date object 318 */ 319 public Date toDate() { 320 TimeZone timezone = utc ? TimeZone.getTimeZone("UTC") : TimeZone.getDefault(); 321 return toDate(timezone); 322 } 323 324 /** 325 * Converts the date-time components to a {@link Date} object. 326 * @return the date object 327 */ 328 public Date toDate(TimeZone timezone) { 329 Calendar c = Calendar.getInstance(timezone); 330 c.clear(); 331 c.set(Calendar.YEAR, year); 332 c.set(Calendar.MONTH, month - 1); 333 c.set(Calendar.DATE, date); 334 c.set(Calendar.HOUR_OF_DAY, hour); 335 c.set(Calendar.MINUTE, minute); 336 c.set(Calendar.SECOND, second); 337 return c.getTime(); 338 } 339 340 @Override 341 public int hashCode() { 342 final int prime = 31; 343 int result = 1; 344 result = prime * result + date; 345 result = prime * result + (hasTime ? 1231 : 1237); 346 result = prime * result + hour; 347 result = prime * result + minute; 348 result = prime * result + month; 349 result = prime * result + second; 350 result = prime * result + (utc ? 1231 : 1237); 351 result = prime * result + year; 352 return result; 353 } 354 355 @Override 356 public boolean equals(Object obj) { 357 if (this == obj) return true; 358 if (obj == null) return false; 359 if (getClass() != obj.getClass()) return false; 360 DateTimeComponents other = (DateTimeComponents) obj; 361 if (date != other.date) return false; 362 if (hasTime != other.hasTime) return false; 363 if (hour != other.hour) return false; 364 if (minute != other.minute) return false; 365 if (month != other.month) return false; 366 if (second != other.second) return false; 367 if (utc != other.utc) return false; 368 if (year != other.year) return false; 369 return true; 370 } 371 372 public int compareTo(DateTimeComponents that) { 373 int c = this.year - that.year; 374 if (c != 0) { 375 return c; 376 } 377 378 c = this.month - that.month; 379 if (c != 0) { 380 return c; 381 } 382 383 c = this.date - that.date; 384 if (c != 0) { 385 return c; 386 } 387 388 c = this.hour - that.hour; 389 if (c != 0) { 390 return c; 391 } 392 393 c = this.minute - that.minute; 394 if (c != 0) { 395 return c; 396 } 397 398 c = this.second - that.second; 399 if (c != 0) { 400 return c; 401 } 402 403 return 0; 404 } 405 406 public boolean before(DateTimeComponents that) { 407 return this.compareTo(that) < 0; 408 } 409 410 public boolean after(DateTimeComponents that) { 411 return this.compareTo(that) > 0; 412 } 413}