001 /* 002 * Zmanim Java API 003 * Copyright (C) 2004-2010 Eliyahu Hershfeld 004 * 005 * This program is free software; you can redistribute it and/or modify it under the terms of the 006 * GNU General Public License as published by the Free Software Foundation; either version 2 of the 007 * License, or (at your option) any later version. 008 * 009 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 010 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 011 * General Public License for more details. 012 * 013 * You should have received a copy of the GNU General Public License along with this program; if 014 * not, write to the Free Software Foundation, Inc. 59 Temple Place - Suite 330, Boston, MA 015 * 02111-1307, USA or connect to: http://www.fsf.org/copyleft/gpl.html 016 */ 017 package net.sourceforge.zmanim.util; 018 019 import java.lang.reflect.Method; 020 import java.text.DateFormat; 021 import java.text.DecimalFormat; 022 import java.util.ArrayList; 023 import java.util.Collections; 024 import java.util.Date; 025 import java.util.Calendar; 026 import java.util.List; 027 import java.text.SimpleDateFormat; 028 import net.sourceforge.zmanim.*; 029 030 031 /** 032 * A class used to format non {@link java.util.Date} times generated by the 033 * Zmanim package. For example the 034 * {@link net.sourceforge.zmanim.AstronomicalCalendar#getTemporalHour()} returns 035 * the length of the hour in milliseconds. This class can format this time. 036 * 037 * @author © Eliyahu Hershfeld 2004 - 2010 038 * @version 1.2 039 */ 040 public class ZmanimFormatter { 041 private boolean prependZeroHours; 042 043 private boolean useSeconds; 044 045 private boolean useMillis; 046 047 boolean useDecimal; 048 049 private static DecimalFormat minuteSecondNF = new DecimalFormat("00");; 050 051 private DecimalFormat hourNF; 052 053 private static DecimalFormat milliNF = new DecimalFormat("000"); 054 055 private SimpleDateFormat dateFormat; 056 057 // private DecimalFormat decimalNF; 058 059 /** 060 * Format using hours, minutes, seconds and milliseconds using the xsd:time 061 * format. This format will return 00.00.00.0 when formatting 0. 062 */ 063 public static final int SEXAGESIMAL_XSD_FORMAT = 0; 064 065 private int timeFormat = SEXAGESIMAL_XSD_FORMAT; 066 067 /** 068 * Format using standard decimal format with 5 positions after the decimal. 069 */ 070 public static final int DECIMAL_FORMAT = 1; 071 072 /** Format using hours and minutes. */ 073 public static final int SEXAGESIMAL_FORMAT = 2; 074 075 /** Format using hours, minutes and seconds. */ 076 public static final int SEXAGESIMAL_SECONDS_FORMAT = 3; 077 078 /** Format using hours, minutes, seconds and milliseconds. */ 079 public static final int SEXAGESIMAL_MILLIS_FORMAT = 4; 080 081 /** constant for milliseconds in a minute (60,000) */ 082 static final long MINUTE_MILLIS = 60 * 1000; 083 084 /** constant for milliseconds in an hour (3,600,000) */ 085 public static final long HOUR_MILLIS = MINUTE_MILLIS * 60; 086 087 /** 088 * Format using the XSD Duration format. This is in the format of 089 * PT1H6M7.869S (P for period (duration), T for time, H, M and S indicate 090 * hours, minutes and seconds. 091 */ 092 public static final int XSD_DURATION_FORMAT = 5; 093 094 public ZmanimFormatter() { 095 this(0, new SimpleDateFormat("h:mm:ss")); 096 } 097 098 /** 099 * ZmanimFormatter constructor using a formatter 100 * 101 * @param format 102 * int The formatting style to use. Using 103 * ZmanimFormatter.SEXAGESIMAL_SECONDS_FORMAT will format the 104 * time time of 90*60*1000 + 1 as 1:30:00 105 */ 106 public ZmanimFormatter(int format, SimpleDateFormat dateFormat) { 107 String hourFormat = "0"; 108 if (prependZeroHours) { 109 hourFormat = "00"; 110 } 111 hourNF = new DecimalFormat(hourFormat); 112 // decimalNF = new DecimalFormat("0.0####"); 113 setTimeFormat(format); 114 this.setDateFormat(dateFormat); 115 } 116 117 /** 118 * Sets the format to use for formatting. 119 * 120 * @param format 121 * int the format constant to use. 122 */ 123 public void setTimeFormat(int format) { 124 timeFormat = format; 125 switch (format) { 126 case SEXAGESIMAL_XSD_FORMAT: 127 setSettings(true, true, true); 128 break; 129 case SEXAGESIMAL_FORMAT: 130 setSettings(false, false, false); 131 break; 132 case SEXAGESIMAL_SECONDS_FORMAT: 133 setSettings(false, true, false); 134 break; 135 case SEXAGESIMAL_MILLIS_FORMAT: 136 setSettings(false, true, true); 137 break; 138 case DECIMAL_FORMAT: 139 default: 140 useDecimal = true; 141 } 142 } 143 144 public void setDateFormat(SimpleDateFormat sdf) { 145 dateFormat = sdf; 146 } 147 148 public SimpleDateFormat getDateFormat() { 149 return dateFormat; 150 } 151 152 private void setSettings(boolean prependZeroHours, boolean useSeconds, 153 boolean useMillis) { 154 this.prependZeroHours = prependZeroHours; 155 this.useSeconds = useSeconds; 156 this.useMillis = useMillis; 157 } 158 159 /** 160 * A method that formats milliseconds into a time format. 161 * 162 * @param milliseconds 163 * The time in milliseconds. 164 * @return String The formatted <code>String</code> 165 */ 166 public String format(double milliseconds) { 167 return format((int) milliseconds); 168 } 169 170 /** 171 * A method that formats milliseconds into a time format. 172 * 173 * @param millis 174 * The time in milliseconds. 175 * @return String The formatted <code>String</code> 176 */ 177 public String format(int millis) { 178 return format(new Time(millis)); 179 } 180 181 /** 182 * A method that formats {@link Time}objects. 183 * 184 * @param time 185 * The time <code>Object</code> to be formatted. 186 * @return String The formatted <code>String</code> 187 */ 188 public String format(Time time) { 189 if (timeFormat == XSD_DURATION_FORMAT) { 190 return formatXSDDurationTime(time); 191 } 192 StringBuffer sb = new StringBuffer(); 193 sb.append(hourNF.format(time.getHours())); 194 sb.append(":"); 195 sb.append(minuteSecondNF.format(time.getMinutes())); 196 if (useSeconds) { 197 sb.append(":"); 198 sb.append(minuteSecondNF.format(time.getSeconds())); 199 } 200 if (useMillis) { 201 sb.append("."); 202 sb.append(milliNF.format(time.getMilliseconds())); 203 } 204 return sb.toString(); 205 } 206 207 /** 208 * Formats a date using this classe's {@link #getDateFormat() date format}. 209 * 210 * @param dateTime 211 * the date to format 212 * @param calendar 213 * the {@link java.util.Calendar Calendar} used to help format 214 * based on the Calendar's DST and other settings. 215 * @return the formatted String 216 */ 217 public String formatDateTime(Date dateTime, Calendar calendar) { 218 dateFormat.setCalendar(calendar); 219 if (this.dateFormat.toPattern().equals("yyyy-MM-dd'T'HH:mm:ss")) { 220 return getXSDateTime(dateTime, calendar); 221 } else { 222 return dateFormat.format(dateTime); 223 } 224 225 } 226 227 /** 228 * The date:date-time function returns the current date and time as a 229 * date/time string. The date/time string that's returned must be a string 230 * in the format defined as the lexical representation of xs:dateTime in <a 231 * href="http://www.w3.org/TR/xmlschema11-2/#dateTime">[3.3.8 dateTime]</a> 232 * of <a href="http://www.w3.org/TR/xmlschema11-2/">[XML Schema 1.1 Part 2: 233 * Datatypes]</a>. The date/time format is basically CCYY-MM-DDThh:mm:ss, 234 * although implementers should consult <a 235 * href="http://www.w3.org/TR/xmlschema11-2/">[XML Schema 1.1 Part 2: 236 * Datatypes]</a> and <a href="http://www.iso.ch/markete/8601.pdf">[ISO 237 * 8601]</a> for details. The date/time string format must include a time 238 * zone, either a Z to indicate Coordinated Universal Time or a + or - 239 * followed by the difference between the difference from UTC represented as 240 * hh:mm. 241 */ 242 public String getXSDateTime(Date dateTime, Calendar cal) { 243 String xsdDateTimeFormat = "yyyy-MM-dd'T'HH:mm:ss"; 244 /* 245 * if (xmlDateFormat == null || xmlDateFormat.trim().equals("")) { 246 * xmlDateFormat = xsdDateTimeFormat; } 247 */ 248 SimpleDateFormat dateFormat = new SimpleDateFormat(xsdDateTimeFormat); 249 250 StringBuffer buff = new StringBuffer(dateFormat.format(dateTime)); 251 // Must also include offset from UTF. 252 // Get the offset (in milliseconds). 253 int offset = cal.get(Calendar.ZONE_OFFSET) 254 + cal.get(Calendar.DST_OFFSET); 255 // If there is no offset, we have "Coordinated 256 // Universal Time." 257 if (offset == 0) 258 buff.append("Z"); 259 else { 260 // Convert milliseconds to hours and minutes 261 int hrs = offset / (60 * 60 * 1000); 262 // In a few cases, the time zone may be +/-hh:30. 263 int min = offset % (60 * 60 * 1000); 264 char posneg = hrs < 0 ? '-' : '+'; 265 buff.append(posneg + formatDigits(hrs) + ':' + formatDigits(min)); 266 } 267 return buff.toString(); 268 } 269 270 /** 271 * Represent the hours and minutes with two-digit strings. 272 * 273 * @param digits hours or minutes. 274 * @return two-digit String representation of hrs or minutes. 275 */ 276 private static String formatDigits(int digits) { 277 String dd = String.valueOf(Math.abs(digits)); 278 return dd.length() == 1 ? '0' + dd : dd; 279 } 280 281 /** 282 * This returns the xml representation of an xsd:duration object. 283 * 284 * @param millis the duration in milliseconds 285 * @return the xsd:duration formatted String 286 */ 287 public String formatXSDDurationTime(long millis) { 288 return formatXSDDurationTime(new Time(millis)); 289 } 290 291 /** 292 * This returns the xml representation of an xsd:duration object. 293 * 294 * @param time the duration as a Time object 295 * @return the xsd:duration formatted String 296 */ 297 public String formatXSDDurationTime(Time time) { 298 StringBuffer duration = new StringBuffer(); 299 300 duration.append("P"); 301 302 if (time.getHours() != 0 || time.getMinutes() != 0 303 || time.getSeconds() != 0 || time.getMilliseconds() != 0) { 304 duration.append("T"); 305 306 if (time.getHours() != 0) 307 duration.append(time.getHours() + "H"); 308 309 if (time.getMinutes() != 0) 310 duration.append(time.getMinutes() + "M"); 311 312 if (time.getSeconds() != 0 || time.getMilliseconds() != 0) { 313 duration.append(time.getSeconds() + "." 314 + milliNF.format(time.getMilliseconds())); 315 duration.append("S"); 316 } 317 if (duration.length() == 1) // zero seconds 318 duration.append("T0S"); 319 if (time.isNegative()) 320 duration.insert(0, "-"); 321 } 322 return duration.toString(); 323 } 324 325 /** 326 * A method that returns an XML formatted <code>String</code> representing 327 * the serialized <code>Object</code>. The format used is: 328 * 329 * <pre> 330 * <AstronomicalTimes date="1969-02-08" type="net.sourceforge.zmanim.AstronomicalCalendar algorithm="US Naval Almanac Algorithm" location="Lakewood, NJ" latitude="40.095965" longitude="-74.22213" elevation="31.0" timeZoneName="Eastern Standard Time" timeZoneID="America/New_York" timeZoneOffset="-5"> 331 * <Sunrise>2007-02-18T06:45:27-05:00</Sunrise> 332 * <TemporalHour>PT54M17.529S</TemporalHour> 333 * ... 334 * </AstronomicalTimes> 335 * </pre> 336 * 337 * Note that the output uses the <a 338 * href="http://www.w3.org/TR/xmlschema11-2/#dateTime">xsd:dateTime</a> 339 * format for times such as sunrise, and <a 340 * href="http://www.w3.org/TR/xmlschema11-2/#duration">xsd:duration</a> 341 * format for times that are a duration such as the length of a 342 * {@link net.sourceforge.zmanim.AstronomicalCalendar#getTemporalHour() temporal hour}. The output of this method is 343 * returned by the {@link #toString() toString} }. 344 * 345 * @return The XML formatted <code>String</code>. The format will be: 346 * 347 * <pre> 348 * <AstronomicalTimes date="1969-02-08" type="net.sourceforge.zmanim.AstronomicalCalendar algorithm="US Naval Almanac Algorithm" location="Lakewood, NJ" latitude="40.095965" longitude="-74.22213" elevation="31.0" timeZoneName="Eastern Standard Time" timeZoneID="America/New_York" timeZoneOffset="-5"> 349 * <Sunrise>2007-02-18T06:45:27-05:00</Sunrise> 350 * <TemporalHour>PT54M17.529S</TemporalHour> 351 * ... 352 * </AstronomicalTimes> 353 * </pre> 354 * 355 */ 356 public static String toXML(AstronomicalCalendar ac) { 357 ZmanimFormatter formatter = new ZmanimFormatter( 358 ZmanimFormatter.XSD_DURATION_FORMAT, new SimpleDateFormat( 359 "yyyy-MM-dd'T'HH:mm:ss")); 360 DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); 361 String output = "<"; 362 if (ac.getClass().getName().endsWith("AstronomicalCalendar")) { 363 output += "AstronomicalTimes"; 364 } else if (ac.getClass().getName().endsWith("ZmanimCalendar")) { 365 output += "Zmanim"; 366 } 367 output += " date=\"" + df.format(ac.getCalendar().getTime()) + "\""; 368 output += " type=\"" + ac.getClass().getName() + "\""; 369 output += " algorithm=\"" 370 + ac.getAstronomicalCalculator().getCalculatorName() + "\""; 371 output += " location=\"" + ac.getGeoLocation().getLocationName() + "\""; 372 output += " latitude=\"" + ac.getGeoLocation().getLatitude() + "\""; 373 output += " longitude=\"" + ac.getGeoLocation().getLongitude() + "\""; 374 output += " elevation=\"" + ac.getGeoLocation().getElevation() + "\""; 375 output += " timeZoneName=\"" 376 + ac.getGeoLocation().getTimeZone().getDisplayName() + "\""; 377 output += " timeZoneID=\"" + ac.getGeoLocation().getTimeZone().getID() 378 + "\""; 379 output += " timeZoneOffset=\"" 380 + (ac.getGeoLocation().getTimeZone().getOffset( 381 ac.getCalendar().getTimeInMillis()) / ((double)HOUR_MILLIS)) 382 + "\""; 383 384 output += ">\n"; 385 386 Method[] theMethods = ac.getClass().getMethods(); 387 String tagName = ""; 388 Object value = null; 389 List dateList = new ArrayList(); 390 List durationList = new ArrayList(); 391 List otherList = new ArrayList(); 392 for (int i = 0; i < theMethods.length; i++) { 393 if (includeMethod(theMethods[i])) { 394 tagName = theMethods[i].getName().substring(3); 395 //String returnType = theMethods[i].getReturnType().getName(); 396 try { 397 value = theMethods[i].invoke(ac, (Object[]) null); 398 if (value == null) {//FIXME: use reflection to determine what the return type is, not the value 399 otherList.add("<" + tagName + ">N/A</" + tagName + ">"); 400 } else if (value instanceof Date) { 401 dateList.add(new Zman((Date) value, tagName)); 402 } else if (value instanceof Long) {// shaah zmanis 403 durationList.add(new Zman((int) ((Long) value) 404 .longValue(), tagName)); 405 } else { // will probably never enter this block, but is 406 // present to be future proof 407 otherList.add("<" + tagName + ">" + value + "</" 408 + tagName + ">"); 409 } 410 } catch (Exception e) { 411 e.printStackTrace(); 412 } 413 } 414 } 415 Zman zman; 416 Collections.sort(dateList, Zman.DATE_ORDER); 417 for (int i = 0; i < dateList.size(); i++) { 418 zman = (Zman) dateList.get(i); 419 output += "\t<" + zman.getZmanLabel(); 420 output += ">"; 421 output += formatter.formatDateTime(zman.getZman(), ac.getCalendar()) 422 + "</" + zman.getZmanLabel() + ">\n"; 423 } 424 Collections.sort(durationList, Zman.DURATION_ORDER); 425 for (int i = 0; i < durationList.size(); i++) { 426 zman = (Zman) durationList.get(i); 427 output += "\t<" + zman.getZmanLabel(); 428 output += ">"; 429 output += formatter.format((int) zman.getDuration()) + "</" 430 + zman.getZmanLabel() + ">\n"; 431 } 432 433 for (int i = 0; i < otherList.size(); i++) {// will probably never enter 434 // this block 435 output += "\t" + otherList.get(i) + "\n"; 436 } 437 438 if (ac.getClass().getName().endsWith("AstronomicalCalendar")) { 439 output += "</AstronomicalTimes>"; 440 } else if (ac.getClass().getName().endsWith("ZmanimCalendar")) { 441 output += "</Zmanim>"; 442 } 443 return output; 444 } 445 446 /** 447 * Determines if a method should be output by the {@link #toXML(AstronomicalCalendar)} 448 * 449 * @param method 450 * @return 451 */ 452 private static boolean includeMethod(Method method) { 453 List methodWhiteList = new ArrayList(); 454 // methodWhiteList.add("getName"); 455 456 List methodBlackList = new ArrayList(); 457 // methodBlackList.add("getGregorianChange"); 458 459 if (methodWhiteList.contains(method.getName())) 460 return true; 461 if (methodBlackList.contains(method.getName())) 462 return false; 463 464 if (method.getParameterTypes().length > 0) 465 return false; // Skip get methods with parameters since we do not 466 // know what value to pass 467 if (!method.getName().startsWith("get")) 468 return false; 469 470 if (method.getReturnType().getName().endsWith("Date") 471 || method.getReturnType().getName().endsWith("long")) { 472 return true; 473 } 474 return false; 475 } 476 }