001/* ==================================================================== 002 * /util/DateConverter.java 003 * 004 * (c) by Dirk Lehmann 005 * ==================================================================== 006 */ 007package de.lehmannet.om.util; 008import java.util.Calendar; 009import java.util.GregorianCalendar; 010import java.util.SimpleTimeZone; 011import java.util.StringTokenizer; 012import java.util.TimeZone; 013/** 014 * 015 * The DateConverter is a helper class that provides methods for 016 * 017 * handling the all kind of date formats.<br> 018 * 019 * E.g. the ISO8601 date format (A short summary about the ISO8601 date 020 * 021 * format can be accessed at the 022 * 023 * <a href="http://www.w3.org/TR/NOTE-datetime">W3C</a>.), or the Julian 024 * 025 * date.<br> 026 * 027 * 028 * 029 * @author doergn@users.sourceforge.net 030 * 031 * @since 1.0 032 */ 033public class DateConverter { 034 // --------- 035 // Constants --------------------------------------------------------- 036 // --------- 037 /* Delimiter for ISO8601 date entries. Example: year-month-day */ 038 private static final String DATE_DELIMITER = "-"; 039 /* Delimiter for ISO8601 time entries. Example: hour-minute-second */ 040 private static final String TIME_DELIMITER = ":"; 041 /* Delimiter for ISO8601 date and time section. Example: dateTtime */ 042 private static final String DATETIME_DELIMITER = "T"; 043 /* Timezone symbol for UTC (zulu) time. Used when time is set in UTC. */ 044 private static final String UTC_TIMEZONE_OFFSET = "Z"; 045 // -------------- 046 // Public methods ---------------------------------------------------- 047 // -------------- 048 // ------------------------------------------------------------------- 049 /** 050 * 051 * Converts a gregorian date into a julian date. 052 * 053 * 054 * 055 * @param gregorianDate 056 * The gregorianDate date 057 * 058 * @return A julian date with seconds accuracy 059 */ 060 public static double toJulianDate(Calendar gregorianCalendar) { 061 if (gregorianCalendar == null) { 062 throw new IllegalArgumentException("Gregorian date has illegal value. (NULL)"); 063 } 064 int month = gregorianCalendar.get(Calendar.MONTH) + 1; // Java Month starts with 0! 065 int year = gregorianCalendar.get(Calendar.YEAR); 066 int day = gregorianCalendar.get(Calendar.DAY_OF_MONTH); 067 int hour = gregorianCalendar.get(Calendar.HOUR_OF_DAY); 068 int minute = gregorianCalendar.get(Calendar.MINUTE); 069 int seconds = gregorianCalendar.get(Calendar.SECOND); 070 // Timezone offset (including Daylight Saving Time) in hours 071 int tzOffset = (gregorianCalendar.get(Calendar.DST_OFFSET) + gregorianCalendar.get(Calendar.ZONE_OFFSET)) 072 / (60 * 60 * 1000); 073 if (month < 3) { 074 year--; 075 month = month + 12; 076 } 077 078 // Calculation of leap year 079 double leapYear = Double.NaN; 080 Calendar gregStartDate = Calendar.getInstance(); 081 gregStartDate.set(1583, 9, 16); // 16.10.1583 day of introduction of greg. Calendar. Before that there're no leap 082 // years 083 if (gregorianCalendar.after(gregStartDate)) { 084 leapYear = 2 - (Math.round(year / 100)) + Math.round((Math.round(year / 100)) / 4); 085 } 086 Calendar gregBeginDate = Calendar.getInstance(); 087 gregBeginDate.set(1583, 9, 4); // 04.10.1583 last day before gregorian calendar reformation 088 if ((gregorianCalendar.before(gregBeginDate)) 089 || (gregorianCalendar.equals(gregBeginDate)) 090 ) { 091 leapYear = 0; 092 } 093 if (Double.isNaN(leapYear)) { 094 throw new IllegalArgumentException( 095 "Date is not valid. Due to gregorian calendar reformation after the 04.10.1583 follows the 16.10.1583."); 096 } 097 098 099 //GMB APR12,2012 - fixing bug #3514617 ---------------------------------------------------------------------------- 100 double fracSecs = (double)(((hour - tzOffset) * 3600) + (minute * 60) + seconds) / 86400; 101 102 long c = (long)(365.25 * (year + 4716)); 103 long d = (long)(30.6001 * (month + 1)); 104 105 double julianDate = day + c + d + fracSecs + leapYear - 1524.5; 106 107 /* 108 * double julianDate = c + d + hourAndMinutes + leapYear - 1524.5; 109 110 111 if( (month == 5) || (month == 7) || (month == 10) || (month == 12) ) { // Those month have 31 days 112 julianDate = julianDate-1; 113 } 114 */ 115 116 117 118 //System.out.println("@@ day= " + Double.toString(day) + " month= " + Long.toString(month) + " year= " + Long.toString(year) + " hour= " + Long.toString(hour) + " mimute= " + Long.toString(minute) + " seconds= " + Long.toString(seconds) + " tzOffset= " + Long.toString(tzOffset)); 119 //System.out.println("@@ JULIAN DATE: " + Double.toString(julianDate) + " ==> c=" + Long.toString(c) + " d= " + Long.toString(d) + " fracSecs= " + Double.toString(fracSecs) + " leapYear= " + Double.toString(leapYear)); 120 121 122 //GMB APR12,2012 - end fix #3514617 ------------------------------------------------------------------------------- 123 124 return julianDate; 125 } 126 127 128 129 // ------------------------------------------------------------------- 130 /** 131 * 132 * Converts a julian date into a gregorian date.<br> 133 * 134 * 135 * 136 * @param julianDate 137 * The julian date 138 * 139 * @return A gregorian date with seconds accuracy (Timezone = GMT) 140 */ 141 public static Calendar toGregorianDate(double julianDate) { 142 return DateConverter.toGregorianDate(julianDate, TimeZone.getTimeZone("GMT")); 143 } 144 // ------------------------------------------------------------------- 145 /** 146 * 147 * Converts a julian date into a gregorian date.<br> 148 * 149 * 150 * 151 * @param julianDate 152 * The julian date 153 * 154 * @param zone 155 * The timzone for the returned gregorian date (if <code>NULL</code> is 156 * 157 * passed GMT will be taken) 158 * 159 * @return A gregorian date 160 */ 161 public static Calendar toGregorianDate(double julianDate, TimeZone zone) { 162 if ((julianDate == Double.NaN) 163 || (julianDate == Double.NEGATIVE_INFINITY) 164 || (julianDate == Double.POSITIVE_INFINITY)) { 165 throw new IllegalArgumentException("Julian Date has illegal value. (Value=" + julianDate + ")"); 166 } 167 if (zone == null) { 168 zone = TimeZone.getTimeZone("GMT"); 169 } 170 julianDate = julianDate + 0.5; 171 int onlyDays = (int) Math.round(julianDate); 172 double onlyMinutes = julianDate - onlyDays; 173 double hours = 24 * onlyMinutes; 174 int hour = (int) (Math.round(hours)); 175 int minute = (int) ((hours - hour) * 60); 176 // int sec = (int)Math.round((hours * 3600) - ((minute * 60) + (hour * 3600))); 177 int sec = (int) ((((hours - hour) * 60) - minute) * 60); 178 double leapYear100 = (int) ((onlyDays - 1867216.25) / 36524.25); 179 double daysLeapYear = onlyDays + 1 + leapYear100 - (int) (leapYear100 / 4); 180 if (onlyDays < 2299161) { 181 daysLeapYear = onlyDays; 182 } 183 double completeLeapDays = daysLeapYear + 1524; 184 double completeYear = (int) ((completeLeapDays - 122.1) / 365.25); 185 double completeDays = (int) (365.25 * completeYear); 186 double completeMonths = (int) ((completeLeapDays - completeDays) / 30.6001); 187 int day = (int) (completeLeapDays - completeDays - (int) (30.6001 * completeMonths) + onlyMinutes); 188 int month = 0; 189 if (completeMonths < 14) { 190 month = (int) completeMonths - 1; 191 } else { 192 month = (int) completeMonths - 13; 193 } 194 int year = 0; 195 if (month > 2) { 196 year = (int) completeYear - 4716; // only AD years 197 } else { 198 year = (int) completeYear - 4715; // only AD years 199 } 200 Calendar gregorianDate = Calendar.getInstance(zone); 201 gregorianDate.set(year, month - 1, day + 1); 202 // DST offset and timezone offset calculation 203 int offset = (gregorianDate.get(Calendar.ZONE_OFFSET) + gregorianDate.get(Calendar.DST_OFFSET)) / (3600 * 1000); 204 // Month-1 as January is 0 in JAVA dates/calendars 205 gregorianDate.set(year, month - 1, day + 1, hour + offset, minute, sec); 206 return gregorianDate; 207 } 208 // ------------------------------------------------------------------- 209 /** 210 * 211 * Converts a Date object into a String object that represents a 212 * 213 * ISO8601 conform string. 214 * 215 * 216 * 217 * @param calendar 218 * A java.util.Date object that has to be converted 219 * 220 * @return A ISO8601 conform String, or <code>null</code> if the 221 * 222 * given date was <code>null</code> 223 */ 224 public static String toISO8601(Calendar calendar) { 225 if (calendar == null) { 226 return null; 227 } 228 StringBuffer iso8601 = new StringBuffer(); 229 iso8601.append(calendar.get(Calendar.YEAR)); 230 iso8601.append(DATE_DELIMITER); 231 iso8601.append(setLeadingZero(calendar.get(Calendar.MONTH) + 1)); 232 iso8601.append(DATE_DELIMITER); 233 iso8601.append(setLeadingZero(calendar.get(Calendar.DAY_OF_MONTH))); 234 iso8601.append(DATETIME_DELIMITER); 235 iso8601.append(setLeadingZero(calendar.get(Calendar.HOUR_OF_DAY))); 236 iso8601.append(TIME_DELIMITER); 237 iso8601.append(setLeadingZero(calendar.get(Calendar.MINUTE))); 238 iso8601.append(TIME_DELIMITER); 239 iso8601.append(setLeadingZero(calendar.get(Calendar.SECOND))); 240 // Get Offset in minutes 241 int offset = ((calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET)) / 1000) / 60; 242 iso8601.append(formatTimezone(offset)); 243 return iso8601.toString(); 244 } 245 // ------------------------------------------------------------------- 246 /** 247 * 248 * Converts a String object that contains a ISO8601 conform value to an 249 * 250 * java.util.Calendar object. 251 * 252 * 253 * 254 * @param iso8601 255 * A String with a ISO8601 conform value 256 * 257 * @return The parameters date as java.util.Calendar, or <code>null</code> 258 * 259 * if the given string was <code>null</code> or empty. 260 * 261 * @throws NumberFormatException 262 * if given ISO8601 is malformed. 263 */ 264 public static Calendar toDate(String iso8601) throws NumberFormatException { 265 if (iso8601 == null 266 || "".equals(iso8601) 267 ) { 268 return null; 269 } 270 StringTokenizer tokenizer = new StringTokenizer(iso8601); 271 String year = tokenizer.nextToken(DATE_DELIMITER); 272 String month = tokenizer.nextToken(DATE_DELIMITER); 273 String day = tokenizer.nextToken(DATETIME_DELIMITER); 274 day = day.substring(1, day.length()); // cutoff '-' 275 String hour = tokenizer.nextToken(TIME_DELIMITER); 276 hour = hour.substring(1, hour.length()); // cutoff 'T' 277 hour = cutLeadingZeroAndPlus(hour); 278 String minute = tokenizer.nextToken(TIME_DELIMITER); 279 minute = cutLeadingZeroAndPlus(minute); 280 String secAndTZ = iso8601.substring(iso8601.indexOf(TIME_DELIMITER) + 4, iso8601.length()); 281 String second = secAndTZ.substring(0, 2); 282 second = cutLeadingZeroAndPlus(second); 283 String timeZone = secAndTZ.substring(2, secAndTZ.length()); 284 int i_year = 0; 285 int i_month = 0; 286 int i_day = 0; 287 int i_hour = 0; 288 int i_minute = 0; 289 int i_second = 0; 290 String step = ""; 291 try { 292 step = "year"; 293 i_year = Integer.parseInt(year); 294 step = "month"; 295 i_month = Integer.parseInt(month) - 1; 296 step = "day"; 297 i_day = Integer.parseInt(day); 298 step = "hour"; 299 i_hour = Integer.parseInt(hour); 300 step = "minute"; 301 i_minute = Integer.parseInt(minute); 302 step = "second"; 303 i_second = Integer.parseInt(second); 304 } catch (NumberFormatException nfe) { 305 throw new NumberFormatException("Cannot generate ISO8601 date because " + step + " is malformed. "); 306 } 307 Calendar calendar = null; 308 if ((UTC_TIMEZONE_OFFSET.equals(timeZone)) 309 || ("".equals(timeZone.trim())) 310 ) { 311 calendar = new GregorianCalendar(new SimpleTimeZone(0, "GMT")); 312 } else { 313 calendar = new GregorianCalendar(createTimezone(timeZone)); 314 } 315 calendar.set(Calendar.YEAR, i_year); 316 calendar.set(Calendar.MONTH, i_month); 317 calendar.set(Calendar.DAY_OF_MONTH, i_day); 318 calendar.set(Calendar.HOUR_OF_DAY, i_hour); 319 calendar.set(Calendar.MINUTE, i_minute); 320 calendar.set(Calendar.SECOND, i_second); 321 calendar.set(Calendar.MILLISECOND, 0); // make sure they have no meanings if we compare dates 322 return calendar; 323 } 324 // --------------- 325 // Private methods --------------------------------------------------- 326 // --------------- 327 // ------------------------------------------------------------------- 328 /* 329 * 330 * Creates a TimeZone object from the last part of a ISO8601 date 331 * 332 * representing String. (e.g. +1:00) 333 */ 334 private static TimeZone createTimezone(String timeZone) throws NumberFormatException { 335 String h = timeZone.substring(0, timeZone.indexOf(TIME_DELIMITER)); 336 if (h.startsWith("+")) { 337 h = cutLeadingZeroAndPlus(h); 338 } 339 String m = timeZone.substring(timeZone.indexOf(TIME_DELIMITER) + 1, timeZone.length()); 340 int hour = 0; 341 int minute = 0; 342 try { 343 hour = Integer.parseInt(h); 344 minute = Integer.parseInt(m); 345 } catch (NumberFormatException nfe) { 346 new NumberFormatException("Cannot generate ISO8601 date because timezones hour or minute value is malformed. "); 347 } 348 // Calculate Timezone Offset: 349 // Hour: hour * secOfHour(3600) * mSecOfSecond(1000) 350 // Minute: minute * mSecOfSecond(1000) * secOfMinute(60) 351 // Depending on negative or positiv offset the minutes have to be added or subtracted from hour offset. 352 // Therefore add always and set minutes positiv or negativ (depending on hour value (if it is set)). 353 SimpleTimeZone tz = null; 354 int offset = 0; 355 if (hour == 0) { 356 offset = (hour * 3600 * 1000) + ((minute * 1000 * 60)); 357 tz = new SimpleTimeZone(offset, ""); 358 } else { 359 offset = (hour * 3600 * 1000) + ((minute * 1000 * 60) * (hour / Math.abs(hour))); 360 tz = new SimpleTimeZone(offset, ""); 361 } 362 tz.setDSTSavings(1); // We request user to not enter DST 363 return tz; 364 } 365 // ------------------------------------------------------------------- 366 /* 367 * 368 * Sets a leading 0 to a given value, if the value has only one digit. 369 */ 370 public static String setLeadingZero(int value) { 371 if ((value <= 9) 372 && (value >= -9)) { 373 if (value < 0) { 374 return "-0" + Math.abs(value); 375 } 376 return "0" + Math.abs(value); 377 } 378 return "" + value; 379 } 380 381 // ------------------------------------------------------------------- 382 /* 383 * 384 * Sets a leading 0 to a given value, if the value has only one digit. 385 */ 386 public static String setLeadingZero(double value) { 387 if ((value <= 9) 388 && (value >= -9)) { 389 if (value < 0) { 390 return "-0" + Math.abs(value); 391 } 392 return "0" + Math.abs(value); 393 } 394 return "" + value; 395 } 396 397 // ------------------------------------------------------------------- 398 /* 399 * 400 * Cuts off leadings zeros (and the + sign, if given) from a string 401 */ 402 private static String cutLeadingZeroAndPlus(String value) { 403 if (value.startsWith("+0")) { 404 return value.substring(2, value.length()); 405 } else if (value.startsWith("-0")) { 406 return "-" + value.substring(2, value.length()); 407 } 408 if (value.startsWith("+")) { 409 value = value.substring(1, value.length()); 410 } 411 return value; 412 } 413 // ------------------------------------------------------------------- 414 /* 415 * 416 * Takes a value in minutes and formats it an ISO8601 timezone String. 417 * 418 * If the timezone is UTC, then the returned String will only contain 419 * 420 * a 'Z' (not 0:00). 421 */ 422 private static String formatTimezone(int min) { 423 // Get complete hours 424 int hour = (int) min / 60; 425 // Get minutes from not complete hour 426 int minutes = (hour * 60) - min; 427 // If hour and minutes equal 0 (UTC) the offset is given as Z 428 if ((hour == 0) 429 && (minutes == 0)) { 430 return UTC_TIMEZONE_OFFSET; 431 } 432 // Calculate the hour offset 433 String hourOffset = setLeadingZero(hour); 434 if (hour > -1) { 435 hourOffset = "+" + hourOffset; 436 } 437 // Calculate the minute offset 438 String minOffset = setLeadingZero(Math.abs(minutes)); 439 return hourOffset + TIME_DELIMITER + minOffset; 440 } 441}