001/* 002 * Zmanim Java API 003 * Copyright (C) 2004-2025 Eliyahu Hershfeld 004 * 005 * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General 006 * Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) 007 * any later version. 008 * 009 * This library is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied 010 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 011 * details. 012 * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to 013 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA, 014 * or connect to: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html 015 */ 016package com.kosherjava.zmanim.util; 017 018import java.lang.reflect.Method; 019import java.text.DateFormat; 020import java.text.DecimalFormat; 021import java.util.ArrayList; 022import java.util.Collections; 023import java.util.Date; 024import java.util.Calendar; 025import java.util.List; 026import java.util.TimeZone; 027import java.text.SimpleDateFormat; 028import com.kosherjava.zmanim.AstronomicalCalendar; 029 030/** 031 * A class used to format both non {@link java.util.Date} times generated by the Zmanim package as well as Dates. For 032 * example the {@link com.kosherjava.zmanim.AstronomicalCalendar#getTemporalHour()} returns the length of the hour in 033 * milliseconds. This class can format this time. 034 * 035 * @author © Eliyahu Hershfeld 2004 - 2025 036 */ 037public class ZmanimFormatter { 038 /** 039 * Setting to prepend a zero to single digit hours. 040 * @see #setSettings(boolean, boolean, boolean) 041 */ 042 private boolean prependZeroHours = false; 043 044 /** 045 * Should seconds be used in formatting time. 046 * @see #setSettings(boolean, boolean, boolean) 047 */ 048 private boolean useSeconds = false; 049 050 /** 051 * Should milliseconds be used in formatting time. 052 * @see #setSettings(boolean, boolean, boolean) 053 */ 054 private boolean useMillis = false; 055 056 /** 057 * the formatter for minutes as seconds. 058 */ 059 private static DecimalFormat minuteSecondNF = new DecimalFormat("00"); 060 061 /** 062 * the formatter for hours. 063 */ 064 private DecimalFormat hourNF; 065 066 /** 067 * the formatter for minutes as milliseconds. 068 */ 069 private static DecimalFormat milliNF = new DecimalFormat("000"); 070 071 /** 072 * The SimpleDateFormat class. 073 * @see #setDateFormat(SimpleDateFormat) 074 */ 075 private SimpleDateFormat dateFormat; 076 077 /** 078 * The TimeZone class. 079 * @see #setTimeZone(TimeZone) 080 */ 081 private TimeZone timeZone = null; 082 083 084 /** 085 * Method to return the TimeZone. 086 * @return the timeZone 087 */ 088 public TimeZone getTimeZone() { 089 return timeZone; 090 } 091 092 /** 093 * Method to set the TimeZone. 094 * @param timeZone 095 * the timeZone to set 096 */ 097 public void setTimeZone(TimeZone timeZone) { 098 this.timeZone = timeZone; 099 } 100 101 /** 102 * Format using hours, minutes, seconds and milliseconds using the xsd:time format. This format will return 103 * 00.00.00.0 when formatting 0. 104 */ 105 public static final int SEXAGESIMAL_XSD_FORMAT = 0; 106 107 /** 108 * Defaults to {@link #SEXAGESIMAL_XSD_FORMAT}. 109 * @see #setTimeFormat(int) 110 */ 111 private int timeFormat = SEXAGESIMAL_XSD_FORMAT; 112 113 /** 114 * Format using standard decimal format with 5 positions after the decimal. 115 */ 116 public static final int DECIMAL_FORMAT = 1; 117 118 /** Format using hours and minutes. */ 119 public static final int SEXAGESIMAL_FORMAT = 2; 120 121 /** Format using hours, minutes and seconds. */ 122 public static final int SEXAGESIMAL_SECONDS_FORMAT = 3; 123 124 /** Format using hours, minutes, seconds and milliseconds. */ 125 public static final int SEXAGESIMAL_MILLIS_FORMAT = 4; 126 127 /** constant for milliseconds in a minute (60,000) */ 128 static final long MINUTE_MILLIS = 60 * 1000; 129 130 /** constant for milliseconds in an hour (3,600,000) */ 131 public static final long HOUR_MILLIS = MINUTE_MILLIS * 60; 132 133 /** 134 * Format using the XSD Duration format. This is in the format of PT1H6M7.869S (P for period (duration), T for time, 135 * H, M and S indicate hours, minutes and seconds. 136 */ 137 public static final int XSD_DURATION_FORMAT = 5; 138 139 /** 140 * Constructor that defaults to this will use the format "h:mm:ss" for dates and 00.00.00.0 for {@link Time}. 141 * @param timeZone the TimeZone Object 142 */ 143 public ZmanimFormatter(TimeZone timeZone) { 144 this(0, new SimpleDateFormat("h:mm:ss"), timeZone); 145 } 146 147 /** 148 * ZmanimFormatter constructor using a formatter 149 * 150 * @param format 151 * int The formatting style to use. Using ZmanimFormatter.SEXAGESIMAL_SECONDS_FORMAT will format the 152 * time of 90*60*1000 + 1 as 1:30:00 153 * @param dateFormat the SimpleDateFormat Object 154 * @param timeZone the TimeZone Object 155 */ 156 public ZmanimFormatter(int format, SimpleDateFormat dateFormat, TimeZone timeZone) { 157 setTimeZone(timeZone); 158 String hourFormat = "0"; 159 if (prependZeroHours) { 160 hourFormat = "00"; 161 } 162 this.hourNF = new DecimalFormat(hourFormat); 163 setTimeFormat(format); 164 dateFormat.setTimeZone(timeZone); 165 setDateFormat(dateFormat); 166 } 167 168 /** 169 * Sets the format to use for formatting. 170 * 171 * @param format 172 * int the format constant to use. 173 */ 174 public void setTimeFormat(int format) { 175 this.timeFormat = format; 176 switch (format) { 177 case SEXAGESIMAL_XSD_FORMAT: 178 setSettings(true, true, true); 179 break; 180 case SEXAGESIMAL_FORMAT: 181 setSettings(false, false, false); 182 break; 183 case SEXAGESIMAL_SECONDS_FORMAT: 184 setSettings(false, true, false); 185 break; 186 case SEXAGESIMAL_MILLIS_FORMAT: 187 setSettings(false, true, true); 188 break; 189 // case DECIMAL_FORMAT: 190 // default: 191 } 192 } 193 194 /** 195 * Sets the SimpleDateFormat Object 196 * @param simpleDateFormat the SimpleDateFormat Object to set 197 */ 198 public void setDateFormat(SimpleDateFormat simpleDateFormat) { 199 this.dateFormat = simpleDateFormat; 200 } 201 202 /** 203 * returns the SimpleDateFormat Object 204 * @return the SimpleDateFormat Object 205 */ 206 public SimpleDateFormat getDateFormat() { 207 return this.dateFormat; 208 } 209 210 /** 211 * Sets various format settings. 212 * @param prependZeroHours if to prepend a zero for single digit hours (so that 1 o'clock is displayed as 01) 213 * @param useSeconds should seconds be used in the time format 214 * @param useMillis should milliseconds be used in formatting time. 215 */ 216 private void setSettings(boolean prependZeroHours, boolean useSeconds, boolean useMillis) { 217 this.prependZeroHours = prependZeroHours; 218 this.useSeconds = useSeconds; 219 this.useMillis = useMillis; 220 } 221 222 /** 223 * A method that formats milliseconds into a time format. 224 * 225 * @param milliseconds 226 * The time in milliseconds. 227 * @return String The formatted <code>String</code> 228 */ 229 public String format(double milliseconds) { 230 return format((int) milliseconds); 231 } 232 233 /** 234 * A method that formats milliseconds into a time format. 235 * 236 * @param millis 237 * The time in milliseconds. 238 * @return String The formatted <code>String</code> 239 */ 240 public String format(int millis) { 241 return format(new Time(millis)); 242 } 243 244 /** 245 * A method that formats {@link Time} objects. 246 * 247 * @param time 248 * The time <code>Object</code> to be formatted. 249 * @return String The formatted <code>String</code> 250 */ 251 public String format(Time time) { 252 if (this.timeFormat == XSD_DURATION_FORMAT) { 253 return formatXSDDurationTime(time); 254 } 255 StringBuilder sb = new StringBuilder(); 256 sb.append(this.hourNF.format(time.getHours())); 257 sb.append(":"); 258 sb.append(minuteSecondNF.format(time.getMinutes())); 259 if (this.useSeconds) { 260 sb.append(":"); 261 sb.append(minuteSecondNF.format(time.getSeconds())); 262 } 263 if (this.useMillis) { 264 sb.append("."); 265 sb.append(milliNF.format(time.getMilliseconds())); 266 } 267 return sb.toString(); 268 } 269 270 /** 271 * Formats a date using this class's {@link #getDateFormat() date format}. 272 * 273 * @param dateTime 274 * the date to format 275 * @param calendar 276 * the {@link java.util.Calendar Calendar} used to help format based on the Calendar's DST and other 277 * settings. 278 * @return the formatted String 279 */ 280 public String formatDateTime(Date dateTime, Calendar calendar) { 281 this.dateFormat.setCalendar(calendar); 282 if (this.dateFormat.toPattern().equals("yyyy-MM-dd'T'HH:mm:ss")) { 283 return getXSDateTime(dateTime); 284 } else { 285 return this.dateFormat.format(dateTime); 286 } 287 288 } 289 290 /** 291 * The date:date-time function returns the current date and time as a date/time string. The date/time string that's 292 * returned must be a string in the format defined as the lexical representation of xs:dateTime in <a 293 * href="http://www.w3.org/TR/xmlschema11-2/#dateTime">[3.3.8 dateTime]</a> of <a 294 * href="http://www.w3.org/TR/xmlschema11-2/">[XML Schema 1.1 Part 2: Datatypes]</a>. The date/time format is 295 * basically CCYY-MM-DDThh:mm:ss, although implementers should consult <a 296 * href="http://www.w3.org/TR/xmlschema11-2/">[XML Schema 1.1 Part 2: Datatypes]</a> and <a 297 * href="http://www.iso.ch/markete/8601.pdf">[ISO 8601]</a> for details. The date/time string format must include a 298 * time zone, either a Z to indicate Coordinated Universal Time or a + or - followed by the difference between the 299 * difference from UTC represented as hh:mm. 300 * @param date Date Object 301 * @param calendar Calendar Object that is now ignored. 302 * @return the XSD dateTime 303 * @deprecated This method will be removed in v3.0 304 */ 305 @Deprecated // (since="2.5", forRemoval=true)// add back once Java 9 is the minimum supported version 306 public String getXSDateTime(Date date, Calendar calendar) { 307 return getXSDateTime(date); 308 } 309 310 /** 311 * Format the Date using the format "yyyy-MM-dd'T'HH:mm:ssXXX" 312 * @param date the Date to format. 313 * @return the Date formatted using the format "yyyy-MM-dd'T'HH:mm:ssXXX 314 */ 315 public String getXSDateTime(Date date) { 316 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); 317 dateFormat.setTimeZone(getTimeZone()); 318 return new StringBuilder(dateFormat.format(date)).toString(); 319 } 320 321 /** 322 * This returns the xml representation of an xsd:duration object. 323 * 324 * @param millis 325 * the duration in milliseconds 326 * @return the xsd:duration formatted String 327 */ 328 public String formatXSDDurationTime(long millis) { 329 return formatXSDDurationTime(new Time(millis)); 330 } 331 332 /** 333 * This returns the xml representation of an xsd:duration object. 334 * 335 * @param time 336 * the duration as a Time object 337 * @return the xsd:duration formatted String 338 */ 339 public String formatXSDDurationTime(Time time) { 340 StringBuilder duration = new StringBuilder(); 341 if (time.getHours() != 0 || time.getMinutes() != 0 || time.getSeconds() != 0 || time.getMilliseconds() != 0) { 342 duration.append("P"); 343 duration.append("T"); 344 345 if (time.getHours() != 0) 346 duration.append(time.getHours() + "H"); 347 348 if (time.getMinutes() != 0) 349 duration.append(time.getMinutes() + "M"); 350 351 if (time.getSeconds() != 0 || time.getMilliseconds() != 0) { 352 duration.append(time.getSeconds() + "." + milliNF.format(time.getMilliseconds())); 353 duration.append("S"); 354 } 355 if (duration.length() == 1) // zero seconds 356 duration.append("T0S"); 357 if (time.isNegative()) 358 duration.insert(0, "-"); 359 } 360 return duration.toString(); 361 } 362 363 /** 364 * A method that returns an XML formatted <code>String</code> representing the serialized <code>Object</code>. The 365 * format used is: 366 * 367 * <pre> 368 * <AstronomicalTimes date="1969-02-08" type="com.kosherjava.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"> 369 * <Sunrise>2007-02-18T06:45:27-05:00</Sunrise> 370 * <TemporalHour>PT54M17.529S</TemporalHour> 371 * ... 372 * </AstronomicalTimes> 373 * </pre> 374 * 375 * Note that the output uses the <a href="http://www.w3.org/TR/xmlschema11-2/#dateTime">xsd:dateTime</a> format for 376 * times such as sunrise, and <a href="http://www.w3.org/TR/xmlschema11-2/#duration">xsd:duration</a> format for 377 * times that are a duration such as the length of a 378 * {@link com.kosherjava.zmanim.AstronomicalCalendar#getTemporalHour() temporal hour}. The output of this method is 379 * returned by the {@link #toString() toString}. 380 * 381 * @param astronomicalCalendar the AstronomicalCalendar Object 382 * 383 * @return The XML formatted <code>String</code>. The format will be: 384 * 385 * <pre> 386 * <AstronomicalTimes date="1969-02-08" type="com.kosherjava.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"> 387 * <Sunrise>2007-02-18T06:45:27-05:00</Sunrise> 388 * <TemporalHour>PT54M17.529S</TemporalHour> 389 * ... 390 * </AstronomicalTimes> 391 * </pre> 392 * 393 * @todo Add proper schema, and support for nulls. XSD duration (for solar hours), should probably return nil and not P. 394 */ 395 public static String toXML(AstronomicalCalendar astronomicalCalendar) { 396 ZmanimFormatter formatter = new ZmanimFormatter(ZmanimFormatter.XSD_DURATION_FORMAT, new SimpleDateFormat( 397 "yyyy-MM-dd'T'HH:mm:ss"), astronomicalCalendar.getGeoLocation().getTimeZone()); 398 DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); 399 df.setTimeZone(astronomicalCalendar.getGeoLocation().getTimeZone()); 400 401 Date date = astronomicalCalendar.getCalendar().getTime(); 402 TimeZone tz = astronomicalCalendar.getGeoLocation().getTimeZone(); 403 boolean daylight = tz.useDaylightTime() && tz.inDaylightTime(date); 404 405 StringBuilder sb = new StringBuilder("<"); 406 if (astronomicalCalendar.getClass().getName().equals("com.kosherjava.zmanim.AstronomicalCalendar")) { 407 sb.append("AstronomicalTimes"); 408 // TODO: use proper schema ref, and maybe build a real schema. 409 // output += "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "; 410 // output += xsi:schemaLocation="http://www.kosherjava.com/zmanim astronomical.xsd" 411 } else if (astronomicalCalendar.getClass().getName().equals("com.kosherjava.zmanim.ComplexZmanimCalendar")) { 412 sb.append("Zmanim"); 413 // TODO: use proper schema ref, and maybe build a real schema. 414 // output += "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "; 415 // output += xsi:schemaLocation="http://www.kosherjava.com/zmanim zmanim.xsd" 416 } else if (astronomicalCalendar.getClass().getName().equals("com.kosherjava.zmanim.ZmanimCalendar")) { 417 sb.append("BasicZmanim"); 418 // TODO: use proper schema ref, and maybe build a real schema. 419 // output += "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "; 420 // output += xsi:schemaLocation="http://www.kosherjava.com/zmanim basicZmanim.xsd" 421 } 422 sb.append(" date=\"").append(df.format(date)).append("\""); 423 sb.append(" type=\"").append(astronomicalCalendar.getClass().getName()).append("\""); 424 sb.append(" algorithm=\"").append(astronomicalCalendar.getAstronomicalCalculator().getCalculatorName()).append("\""); 425 sb.append(" location=\"").append(astronomicalCalendar.getGeoLocation().getLocationName()).append("\""); 426 sb.append(" latitude=\"").append(astronomicalCalendar.getGeoLocation().getLatitude()).append("\""); 427 sb.append(" longitude=\"").append(astronomicalCalendar.getGeoLocation().getLongitude()).append("\""); 428 sb.append(" elevation=\"").append(astronomicalCalendar.getGeoLocation().getElevation()).append("\""); 429 sb.append(" timeZoneName=\"").append(tz.getDisplayName(daylight, TimeZone.LONG)).append("\""); 430 sb.append(" timeZoneID=\"").append(tz.getID()).append("\""); 431 sb.append(" timeZoneOffset=\"") 432 .append((tz.getOffset(astronomicalCalendar.getCalendar().getTimeInMillis()) / ((double) HOUR_MILLIS))) 433 .append("\""); 434 // sb.append(" useElevationAllZmanim=\"").append(astronomicalCalendar.useElevationAllZmanim).append("\""); //TODO likely using reflection 435 436 sb.append(">\n"); 437 438 Method[] theMethods = astronomicalCalendar.getClass().getMethods(); 439 String tagName = ""; 440 Object value = null; 441 List<Zman> dateList = new ArrayList<Zman>(); 442 List<Zman> durationList = new ArrayList<Zman>(); 443 List<String> otherList = new ArrayList<String>(); 444 for (int i = 0; i < theMethods.length; i++) { 445 if (includeMethod(theMethods[i])) { 446 tagName = theMethods[i].getName().substring(3); 447 // String returnType = theMethods[i].getReturnType().getName(); 448 try { 449 value = theMethods[i].invoke(astronomicalCalendar, (Object[]) null); 450 if (value == null) {// TODO: Consider using reflection to determine the return type, not the value 451 otherList.add("<" + tagName + ">N/A</" + tagName + ">"); 452 // TODO: instead of N/A, consider return proper xs:nil. 453 // otherList.add("<" + tagName + " xs:nil=\"true\" />"); 454 } else if (value instanceof Date) { 455 dateList.add(new Zman((Date) value, tagName)); 456 } else if (value instanceof Long || value instanceof Integer) {// shaah zmanis 457 if (((Long) value).longValue() == Long.MIN_VALUE) { 458 otherList.add("<" + tagName + ">N/A</" + tagName + ">"); 459 // TODO: instead of N/A, consider return proper xs:nil. 460 // otherList.add("<" + tagName + " xs:nil=\"true\" />"); 461 } else { 462 durationList.add(new Zman((int) ((Long) value).longValue(), tagName)); 463 } 464 } else { // will probably never enter this block, but is present to be future-proof 465 otherList.add("<" + tagName + ">" + value + "</" + tagName + ">"); 466 } 467 } catch (Exception e) { 468 e.printStackTrace(); 469 } 470 } 471 } 472 Zman zman; 473 Collections.sort(dateList, Zman.DATE_ORDER); 474 475 for (int i = 0; i < dateList.size(); i++) { 476 zman = (Zman) dateList.get(i); 477 sb.append("\t<").append(zman.getLabel()).append(">"); 478 sb.append(formatter.formatDateTime(zman.getZman(), astronomicalCalendar.getCalendar())); 479 sb.append("</").append(zman.getLabel()).append(">\n"); 480 } 481 Collections.sort(durationList, Zman.DURATION_ORDER); 482 for (int i = 0; i < durationList.size(); i++) { 483 zman = (Zman) durationList.get(i); 484 sb.append("\t<" + zman.getLabel()).append(">"); 485 sb.append(formatter.format((int) zman.getDuration())).append("</").append(zman.getLabel()) 486 .append(">\n"); 487 } 488 489 for (int i = 0; i < otherList.size(); i++) {// will probably never enter this block 490 sb.append("\t").append(otherList.get(i)).append("\n"); 491 } 492 493 if (astronomicalCalendar.getClass().getName().equals("com.kosherjava.zmanim.AstronomicalCalendar")) { 494 sb.append("</AstronomicalTimes>"); 495 } else if (astronomicalCalendar.getClass().getName().equals("com.kosherjava.zmanim.ComplexZmanimCalendar")) { 496 sb.append("</Zmanim>"); 497 } else if (astronomicalCalendar.getClass().getName().equals("com.kosherjava.zmanim.ZmanimCalendar")) { 498 sb.append("</BasicZmanim>"); 499 } 500 return sb.toString(); 501 } 502 503 /** 504 * A method that returns a JSON formatted <code>String</code> representing the serialized <code>Object</code>. The 505 * format used is: 506 * <pre> 507 * { 508 * "metadata":{ 509 * "date":"1969-02-08", 510 * "type":"com.kosherjava.zmanim.AstronomicalCalendar", 511 * "algorithm":"US Naval Almanac Algorithm", 512 * "location":"Lakewood, NJ", 513 * "latitude":"40.095965", 514 * "longitude":"-74.22213", 515 * "elevation:"31.0", 516 * "timeZoneName":"Eastern Standard Time", 517 * "timeZoneID":"America/New_York", 518 * "timeZoneOffset":"-5"}, 519 * "AstronomicalTimes":{ 520 * "Sunrise":"2007-02-18T06:45:27-05:00", 521 * "TemporalHour":"PT54M17.529S" 522 * ... 523 * } 524 * } 525 * </pre> 526 * 527 * Note that the output uses the <a href="http://www.w3.org/TR/xmlschema11-2/#dateTime">xsd:dateTime</a> format for 528 * times such as sunrise, and <a href="http://www.w3.org/TR/xmlschema11-2/#duration">xsd:duration</a> format for 529 * times that are a duration such as the length of a 530 * {@link com.kosherjava.zmanim.AstronomicalCalendar#getTemporalHour() temporal hour}. 531 * 532 * @param astronomicalCalendar the AstronomicalCalendar Object 533 * 534 * @return The JSON formatted <code>String</code>. The format will be: 535 * <pre> 536 * { 537 * "metadata":{ 538 * "date":"1969-02-08", 539 * "type":"com.kosherjava.zmanim.AstronomicalCalendar", 540 * "algorithm":"US Naval Almanac Algorithm", 541 * "location":"Lakewood, NJ", 542 * "latitude":"40.095965", 543 * "longitude":"-74.22213", 544 * "elevation:"31.0", 545 * "timeZoneName":"Eastern Standard Time", 546 * "timeZoneID":"America/New_York", 547 * "timeZoneOffset":"-5"}, 548 * "AstronomicalTimes":{ 549 * "Sunrise":"2007-02-18T06:45:27-05:00", 550 * "TemporalHour":"PT54M17.529S" 551 * ... 552 * } 553 * } 554 * </pre> 555 */ 556 public static String toJSON(AstronomicalCalendar astronomicalCalendar) { 557 ZmanimFormatter formatter = new ZmanimFormatter(ZmanimFormatter.XSD_DURATION_FORMAT, new SimpleDateFormat( 558 "yyyy-MM-dd'T'HH:mm:ss"), astronomicalCalendar.getGeoLocation().getTimeZone()); 559 DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); 560 df.setTimeZone(astronomicalCalendar.getGeoLocation().getTimeZone()); 561 562 Date date = astronomicalCalendar.getCalendar().getTime(); 563 TimeZone tz = astronomicalCalendar.getGeoLocation().getTimeZone(); 564 boolean daylight = tz.useDaylightTime() && tz.inDaylightTime(date); 565 566 StringBuilder sb = new StringBuilder("{\n\"metadata\":{\n"); 567 sb.append("\t\"date\":\"").append(df.format(date)).append("\",\n"); 568 sb.append("\t\"type\":\"").append(astronomicalCalendar.getClass().getName()).append("\",\n"); 569 sb.append("\t\"algorithm\":\"").append(astronomicalCalendar.getAstronomicalCalculator().getCalculatorName()).append("\",\n"); 570 sb.append("\t\"location\":\"").append(astronomicalCalendar.getGeoLocation().getLocationName()).append("\",\n"); 571 sb.append("\t\"latitude\":\"").append(astronomicalCalendar.getGeoLocation().getLatitude()).append("\",\n"); 572 sb.append("\t\"longitude\":\"").append(astronomicalCalendar.getGeoLocation().getLongitude()).append("\",\n"); 573 sb.append("\t\"elevation\":\"").append(astronomicalCalendar.getGeoLocation().getElevation()).append("\",\n"); 574 sb.append("\t\"timeZoneName\":\"").append(tz.getDisplayName(daylight, TimeZone.LONG)).append("\",\n"); 575 sb.append("\t\"timeZoneID\":\"").append(tz.getID()).append("\",\n"); 576 sb.append("\t\"timeZoneOffset\":\"") 577 .append((tz.getOffset(astronomicalCalendar.getCalendar().getTimeInMillis()) / ((double) HOUR_MILLIS))) 578 .append("\""); 579 580 sb.append("},\n\""); 581 582 if (astronomicalCalendar.getClass().getName().equals("com.kosherjava.zmanim.AstronomicalCalendar")) { 583 sb.append("AstronomicalTimes"); 584 } else if (astronomicalCalendar.getClass().getName().equals("com.kosherjava.zmanim.ComplexZmanimCalendar")) { 585 sb.append("Zmanim"); 586 } else if (astronomicalCalendar.getClass().getName().equals("com.kosherjava.zmanim.ZmanimCalendar")) { 587 sb.append("BasicZmanim"); 588 } 589 sb.append("\":{\n"); 590 Method[] theMethods = astronomicalCalendar.getClass().getMethods(); 591 String tagName = ""; 592 Object value = null; 593 List<Zman> dateList = new ArrayList<Zman>(); 594 List<Zman> durationList = new ArrayList<Zman>(); 595 List<String> otherList = new ArrayList<String>(); 596 for (int i = 0; i < theMethods.length; i++) { 597 if (includeMethod(theMethods[i])) { 598 tagName = theMethods[i].getName().substring(3); 599 // String returnType = theMethods[i].getReturnType().getName(); 600 try { 601 value = theMethods[i].invoke(astronomicalCalendar, (Object[]) null); 602 if (value == null) {// TODO: Consider using reflection to determine the return type, not the value 603 otherList.add("\"" + tagName + "\":\"N/A\","); 604 } else if (value instanceof Date) { 605 dateList.add(new Zman((Date) value, tagName)); 606 } else if (value instanceof Long || value instanceof Integer) {// shaah zmanis 607 if (((Long) value).longValue() == Long.MIN_VALUE) { 608 otherList.add("\"" + tagName + "\":\"N/A\""); 609 } else { 610 durationList.add(new Zman((int) ((Long) value).longValue(), tagName)); 611 } 612 } else { // will probably never enter this block, but is present to be future-proof 613 otherList.add("\"" + tagName + "\":\"" + value + "\","); 614 } 615 } catch (Exception e) { 616 e.printStackTrace(); 617 } 618 } 619 } 620 Zman zman; 621 Collections.sort(dateList, Zman.DATE_ORDER); 622 for (int i = 0; i < dateList.size(); i++) { 623 zman = (Zman) dateList.get(i); 624 sb.append("\t\"").append(zman.getLabel()).append("\":\""); 625 sb.append(formatter.formatDateTime(zman.getZman(), astronomicalCalendar.getCalendar())); 626 sb.append("\",\n"); 627 } 628 Collections.sort(durationList, Zman.DURATION_ORDER); 629 for (int i = 0; i < durationList.size(); i++) { 630 zman = (Zman) durationList.get(i); 631 sb.append("\t\"" + zman.getLabel()).append("\":\""); 632 sb.append(formatter.format((int) zman.getDuration())).append("\",\n"); 633 } 634 635 for (int i = 0; i < otherList.size(); i++) {// will probably never enter this block 636 sb.append("\t").append(otherList.get(i)).append("\n"); 637 } 638 sb.setLength(sb.length() - 2); 639 sb.append("}\n}"); 640 return sb.toString(); 641 } 642 643 /** 644 * Determines if a method should be output by the {@link #toXML(AstronomicalCalendar)} 645 * 646 * @param method the method in question 647 * @return if the method should be included in serialization 648 */ 649 private static boolean includeMethod(Method method) { 650 List<String> methodWhiteList = new ArrayList<String>(); 651 // methodWhiteList.add("getName"); 652 653 List<String> methodBlackList = new ArrayList<String>(); 654 // methodBlackList.add("getGregorianChange"); 655 656 if (methodWhiteList.contains(method.getName())) 657 return true; 658 if (methodBlackList.contains(method.getName())) 659 return false; 660 661 if (method.getParameterTypes().length > 0) 662 return false; // Skip get methods with parameters since we do not know what value to pass 663 if (!method.getName().startsWith("get")) 664 return false; 665 666 if (method.getReturnType().getName().endsWith("Date") || method.getReturnType().getName().endsWith("long")) { 667 return true; 668 } 669 return false; 670 } 671}