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