001/* ***** BEGIN LICENSE BLOCK ***** 002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 003 * 004 * The contents of this file are subject to the Mozilla Public License Version 005 * 1.1 (the "License"); you may not use this file except in compliance with 006 * the License. You may obtain a copy of the License at 007 * http://www.mozilla.org/MPL/ 008 * 009 * Software distributed under the License is distributed on an "AS IS" basis, 010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 011 * for the specific language governing rights and limitations under the 012 * License. 013 * 014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in 015 * Java(TM), hosted at https://github.com/gunterze/dcm4che. 016 * 017 * The Initial Developer of the Original Code is 018 * Agfa Healthcare. 019 * Portions created by the Initial Developer are Copyright (C) 2011 020 * the Initial Developer. All Rights Reserved. 021 * 022 * Contributor(s): 023 * See @authors listed below 024 * 025 * Alternatively, the contents of this file may be used under the terms of 026 * either the GNU General Public License Version 2 or later (the "GPL"), or 027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 028 * in which case the provisions of the GPL or the LGPL are applicable instead 029 * of those above. If you wish to allow use of your version of this file only 030 * under the terms of either the GPL or the LGPL, and not to allow others to 031 * use your version of this file under the terms of the MPL, indicate your 032 * decision by deleting the provisions above and replace them with the notice 033 * and other provisions required by the GPL or the LGPL. If you do not delete 034 * the provisions above, a recipient may use your version of this file under 035 * the terms of any one of the MPL, the GPL or the LGPL. 036 * 037 * ***** END LICENSE BLOCK ***** */ 038 039package org.dcm4che3.util; 040 041import java.util.Calendar; 042import java.util.Date; 043import java.util.GregorianCalendar; 044import java.util.TimeZone; 045 046import org.dcm4che3.data.DatePrecision; 047 048/** 049 * @author Gunter Zeilinger <gunterze@gmail.com> 050 */ 051public class DateUtils { 052 053 public static final Date[] EMPTY_DATES = {}; 054 055 private static TimeZone cachedTimeZone; 056 057 private static Calendar cal(TimeZone tz) { 058 Calendar cal = (tz != null) 059 ? new GregorianCalendar(tz) 060 : new GregorianCalendar(); 061 cal.clear(); 062 return cal; 063 } 064 065 private static Calendar cal(TimeZone tz, Date date) { 066 Calendar cal = (tz != null) 067 ? new GregorianCalendar(tz) 068 : new GregorianCalendar(); 069 cal.setTime(date); 070 return cal; 071 } 072 073 private static void ceil(Calendar cal, int field) { 074 cal.add(field, 1); 075 cal.add(Calendar.MILLISECOND, -1); 076 } 077 078 public static String formatDA(TimeZone tz, Date date) { 079 return formatDA(tz, date, new StringBuilder(8)).toString(); 080 } 081 082 public static StringBuilder formatDA(TimeZone tz, Date date, 083 StringBuilder toAppendTo) { 084 return formatDT(cal(tz, date), toAppendTo, Calendar.DAY_OF_MONTH); 085 } 086 087 public static String formatTM(TimeZone tz, Date date) { 088 return formatTM(tz, date, new DatePrecision()); 089 } 090 091 public static String formatTM(TimeZone tz, Date date, DatePrecision precision) { 092 return formatTM(cal(tz, date), new StringBuilder(10), 093 precision.lastField).toString(); 094 } 095 096 private static StringBuilder formatTM(Calendar cal, 097 StringBuilder toAppendTo, int lastField) { 098 appendXX(cal.get(Calendar.HOUR_OF_DAY), toAppendTo); 099 if (lastField > Calendar.HOUR_OF_DAY) { 100 appendXX(cal.get(Calendar.MINUTE), toAppendTo); 101 if (lastField > Calendar.MINUTE) { 102 appendXX(cal.get(Calendar.SECOND), toAppendTo); 103 if (lastField > Calendar.SECOND) { 104 toAppendTo.append('.'); 105 appendXXX(cal.get(Calendar.MILLISECOND), toAppendTo); 106 } 107 } 108 } 109 return toAppendTo; 110 } 111 112 public static String formatDT(TimeZone tz, Date date) { 113 return formatDT(tz, date, new DatePrecision()); 114 } 115 116 public static String formatDT(TimeZone tz, Date date, DatePrecision precision) { 117 return formatDT(tz, date, new StringBuilder(23), precision).toString(); 118 } 119 120 public static StringBuilder formatDT(TimeZone tz, Date date, 121 StringBuilder toAppendTo, DatePrecision precision) { 122 Calendar cal = cal(tz, date); 123 formatDT(cal, toAppendTo, precision.lastField); 124 if (precision.includeTimezone) { 125 int offset = cal.get(Calendar.ZONE_OFFSET) 126 + cal.get(Calendar.DST_OFFSET); 127 appendZZZZZ(offset, toAppendTo); 128 } 129 return toAppendTo; 130 } 131 132 private static StringBuilder appendZZZZZ(int offset, StringBuilder sb) { 133 if (offset < 0) { 134 offset = -offset; 135 sb.append('-'); 136 } else 137 sb.append('+'); 138 int min = offset / 60000; 139 appendXX(min / 60, sb); 140 appendXX(min % 60, sb); 141 return sb; 142 } 143 144 145 /** 146 * Returns Timezone Offset From UTC in format {@code (+|i)HHMM} of specified 147 * Timezone without concerning Daylight saving time (DST). 148 * 149 * @param tz Timezone 150 * @return Timezone Offset From UTC in format {@code (+|i)HHMM} 151 */ 152 public static String formatTimezoneOffsetFromUTC(TimeZone tz) { 153 return appendZZZZZ(tz.getRawOffset(), new StringBuilder(5)).toString(); 154 } 155 156 /** 157 * Returns Timezone Offset From UTC in format {@code (+|i)HHMM} of specified 158 * Timezone on specified date. If no date is specified, DST is considered 159 * for the current date. 160 * 161 * @param tz Timezone 162 * @param date Date or {@code null} 163 * @return Timezone Offset From UTC in format {@code (+|i)HHMM} 164 */ 165 public static String formatTimezoneOffsetFromUTC(TimeZone tz, Date date) { 166 return appendZZZZZ(tz.getOffset(date == null 167 ? System.currentTimeMillis() : date.getTime()), 168 new StringBuilder(5)).toString(); 169 } 170 171 private static StringBuilder formatDT(Calendar cal, StringBuilder toAppendTo, 172 int lastField) { 173 appendXXXX(cal.get(Calendar.YEAR), toAppendTo); 174 if (lastField > Calendar.YEAR) { 175 appendXX(cal.get(Calendar.MONTH) + 1, toAppendTo); 176 if (lastField > Calendar.MONTH) { 177 appendXX(cal.get(Calendar.DAY_OF_MONTH), toAppendTo); 178 if (lastField > Calendar.DAY_OF_MONTH) { 179 formatTM(cal, toAppendTo, lastField); 180 } 181 } 182 } 183 return toAppendTo; 184 } 185 186 private static void appendXXXX(int i, StringBuilder toAppendTo) { 187 if (i < 1000) 188 toAppendTo.append('0'); 189 appendXXX(i, toAppendTo); 190 } 191 192 private static void appendXXX(int i, StringBuilder toAppendTo) { 193 if (i < 100) 194 toAppendTo.append('0'); 195 appendXX(i, toAppendTo); 196 } 197 198 private static void appendXX(int i, StringBuilder toAppendTo) { 199 if (i < 10) 200 toAppendTo.append('0'); 201 toAppendTo.append(i); 202 } 203 204 public static Date parseDA(TimeZone tz, String s) { 205 return parseDA(tz, s, false); 206 } 207 208 public static Date parseDA(TimeZone tz, String s, boolean ceil) { 209 Calendar cal = cal(tz); 210 int length = s.length(); 211 if (!(length == 8 || length == 10 && !Character.isDigit(s.charAt(4)))) 212 throw new IllegalArgumentException(s); 213 try { 214 int pos = 0; 215 cal.set(Calendar.YEAR, 216 Integer.parseInt(s.substring(pos, pos + 4))); 217 pos += 4; 218 if (!Character.isDigit(s.charAt(pos))) 219 pos++; 220 cal.set(Calendar.MONTH, 221 Integer.parseInt(s.substring(pos, pos + 2)) - 1); 222 pos += 2; 223 if (!Character.isDigit(s.charAt(pos))) 224 pos++; 225 cal.set(Calendar.DAY_OF_MONTH, 226 Integer.parseInt(s.substring(pos))); 227 if (ceil) 228 ceil(cal, Calendar.DAY_OF_MONTH); 229 } catch (NumberFormatException e) { 230 throw new IllegalArgumentException(s); 231 } 232 return cal.getTime(); 233 } 234 235 public static Date parseTM(TimeZone tz, String s, DatePrecision precision) { 236 return parseTM(tz, s, false, precision); 237 } 238 239 public static Date parseTM(TimeZone tz, String s, boolean ceil, 240 DatePrecision precision) { 241 return parseTM(cal(tz), s, ceil, precision); 242 } 243 244 private static Date parseTM(Calendar cal, String s, boolean ceil, 245 DatePrecision precision) { 246 int length = s.length(); 247 int pos = 0; 248 if (pos + 2 > length) 249 throw new IllegalArgumentException(s); 250 251 try { 252 cal.set(precision.lastField = Calendar.HOUR_OF_DAY, 253 Integer.parseInt(s.substring(pos, pos + 2))); 254 pos += 2; 255 if (pos < length) { 256 if (!Character.isDigit(s.charAt(pos))) 257 pos++; 258 if (pos + 2 > length) 259 throw new IllegalArgumentException(s); 260 261 cal.set(precision.lastField = Calendar.MINUTE, 262 Integer.parseInt(s.substring(pos, pos + 2))); 263 pos += 2; 264 if (pos < length) { 265 if (!Character.isDigit(s.charAt(pos))) 266 pos++; 267 if (pos + 2 > length) 268 throw new IllegalArgumentException(s); 269 cal.set(precision.lastField = Calendar.SECOND, 270 Integer.parseInt(s.substring(pos, pos + 2))); 271 pos += 2; 272 if (pos < length) { 273 float f = Float.parseFloat(s.substring(pos)); 274 if (f >= 1 || f < 0) 275 throw new IllegalArgumentException(s); 276 cal.set(precision.lastField = Calendar.MILLISECOND, 277 (int) (f * 1000)); 278 return cal.getTime(); 279 } 280 } 281 } 282 if (ceil) 283 ceil(cal, precision.lastField); 284 } catch (NumberFormatException e) { 285 throw new IllegalArgumentException(s); 286 } 287 return cal.getTime(); 288 } 289 290 public static Date parseDT(TimeZone tz, String s, DatePrecision precision) { 291 return parseDT(tz, s, false, precision); 292 } 293 294 public static TimeZone timeZone(String s) { 295 TimeZone tz; 296 if (s.length() != 5 || (tz = safeTimeZone(s)) == null) 297 throw new IllegalArgumentException("Illegal Timezone Offset: " + s); 298 return tz; 299 } 300 301 private static TimeZone safeTimeZone(String s) { 302 String tzid = tzid(s); 303 if (tzid == null) 304 return null; 305 306 TimeZone tz = cachedTimeZone; 307 if (tz == null || !tz.getID().equals(tzid)) 308 cachedTimeZone = tz = TimeZone.getTimeZone(tzid); 309 310 return tz; 311 } 312 313 private static String tzid(String s) { 314 int length = s.length(); 315 if (length > 4) { 316 char[] tzid = { 'G', 'M', 'T', 0, 0, 0, ':', 0, 0 }; 317 s.getChars(length-5, length-2, tzid, 3); 318 s.getChars(length-2, length, tzid, 7); 319 if ((tzid[3] == '+' || tzid[3] == '-') 320 && Character.isDigit(tzid[4]) 321 && Character.isDigit(tzid[5]) 322 && Character.isDigit(tzid[7]) 323 && Character.isDigit(tzid[8])) { 324 return new String(tzid); 325 } 326 } 327 return null; 328 } 329 330 public static Date parseDT(TimeZone tz, String s, boolean ceil, 331 DatePrecision precision) { 332 int length = s.length(); 333 TimeZone tz1 = safeTimeZone(s); 334 if (precision.includeTimezone = tz1 != null) { 335 length -= 5; 336 tz = tz1; 337 } 338 Calendar cal = cal(tz); 339 try { 340 int pos = 0; 341 if (pos + 4 > length) 342 throw new IllegalArgumentException(s); 343 cal.set(precision.lastField = Calendar.YEAR, 344 Integer.parseInt(s.substring(pos, pos + 4))); 345 pos += 4; 346 if (pos < length) { 347 if (!Character.isDigit(s.charAt(pos))) 348 pos++; 349 if (pos + 2 > length) 350 throw new IllegalArgumentException(s); 351 cal.set(precision.lastField = Calendar.MONTH, 352 Integer.parseInt(s.substring(pos, pos + 2)) - 1); 353 pos += 2; 354 if (pos < length) { 355 if (!Character.isDigit(s.charAt(pos))) 356 pos++; 357 if (pos + 2 > length) 358 throw new IllegalArgumentException(s); 359 cal.set(precision.lastField = Calendar.DAY_OF_MONTH, 360 Integer.parseInt(s.substring(pos, pos + 2))); 361 pos += 2; 362 if (pos < length) 363 return parseTM(cal, s.substring(pos, length), ceil, 364 precision); 365 } 366 } 367 } catch (NumberFormatException e) { 368 throw new IllegalArgumentException(s); 369 } 370 if (ceil) 371 ceil(cal, precision.lastField); 372 return cal.getTime(); 373 } 374 375}