001/* 002 * Zmanim Java API 003 * Copyright © 2004-2026 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; version 2.1 of the License. 007 * 008 * This library is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied 009 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 010 * details. 011 * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to 012 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA, 013 * or connect to: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html 014 */ 015package com.kosherjava.zmanim.util; 016 017import java.lang.reflect.Method; 018import java.text.DecimalFormat; 019import java.time.LocalDate; 020import java.time.LocalTime; 021import java.util.ArrayList; 022import java.util.List; 023import java.util.Locale; 024import java.time.Duration; 025import java.time.Instant; 026import java.time.ZoneId; 027import java.time.ZonedDateTime; 028import java.time.format.DateTimeFormatter; 029import com.kosherjava.zmanim.AstronomicalCalendar; 030 031/** 032 * A class used to format both {@link Instant} times generated by the Zmanim package as well as {@link Duration} used for temporal 033 * hours / <em>sha'os zmaniyos</em>. For example the {@link com.kosherjava.zmanim.AstronomicalCalendar#getTemporalHour()} returns the 034 * length of the hour as a {@code Duration}. 035 * 036 * @author © Eliyahu Hershfeld 2004 - 2026 037 */ 038public class ZmanimFormatter { 039 /** 040 * Setting to prepend a zero to single digit hours. 041 * @see #setSettings(boolean, boolean, boolean) 042 */ 043 private boolean prependZeroHours = false; 044 045 /** 046 * Should seconds be used in formatting time. 047 * @see #setSettings(boolean, boolean, boolean) 048 */ 049 private boolean useSeconds = false; 050 051 /** 052 * Should milliseconds be used in formatting time. 053 * @see #setSettings(boolean, boolean, boolean) 054 */ 055 private boolean useMillis = false; 056 057 /** 058 * the formatter for minutes as seconds. 059 */ 060 private static final DecimalFormat minuteSecondNF = new DecimalFormat("00"); 061 062 /** 063 * the formatter for hours. 064 */ 065 private DecimalFormat hourNF = new DecimalFormat("0"); 066 067 /** 068 * the formatter for minutes as milliseconds. 069 */ 070 private static final DecimalFormat milliNF = new DecimalFormat("000"); 071 072 /** 073 * The {@link DateTimeFormatter} class used by the formatter. 074 * @see #setDateTimeFormatter(DateTimeFormatter) 075 */ 076 private DateTimeFormatter dateTimeFormatter; 077 078 /** 079 * The TimeZone class. 080 * @see #setZoneId(ZoneId) 081 */ 082 private ZoneId zoneId = null; 083 084 085 /** 086 * Method to return the {@code ZoneId}. 087 * @return the ZoneId 088 */ 089 public ZoneId getZoneId() { 090 return zoneId; 091 } 092 093 /** 094 * Method to set the {@code ZoneId}. 095 * @param zoneId the {@code ZoneId} to set 096 */ 097 public void setZoneId(ZoneId zoneId) { 098 this.zoneId = zoneId; 099 } 100 101 /** 102 * Format using hours, minutes, seconds and milliseconds using the xsd:time format. This format will return 103 * {@code 00.00.00.0} when formatting 0. 104 */ 105 public static final int SEXAGESIMAL_XSD_FORMAT = 0; 106 107 /** 108 * Set by the constructor; defaults to {@link #SEXAGESIMAL_SECONDS_FORMAT} when using the single-argument constructor. 109 * @see #setTimeFormat(int) 110 */ 111 private int timeFormat; 112 113 /** Format using hours and minutes. */ 114 public static final int SEXAGESIMAL_FORMAT = 1; 115 116 /** Format using hours, minutes and seconds. */ 117 public static final int SEXAGESIMAL_SECONDS_FORMAT = 2; 118 119 /** Format using hours, minutes, seconds and milliseconds. */ 120 public static final int SEXAGESIMAL_MILLIS_FORMAT = 3; 121 122 /** 123 * Format using the XSD Duration format. This is in the format of PT1H6M7.869S (P for period (duration), T for time, 124 * H, M and S indicate hours, minutes and seconds. 125 */ 126 public static final int XSD_DURATION_FORMAT = 4; 127 128 /** 129 * Default constructor that uses the format "{@code h:mm:ss}" for dates and "{@code 0:00:00}" for {@link Duration}s. 130 * @param zoneId the {@code ZoneId} {@code Object}. 131 */ 132 public ZmanimFormatter(ZoneId zoneId) { 133 this(SEXAGESIMAL_SECONDS_FORMAT, DateTimeFormatter.ofPattern("h:mm:ss"), zoneId); 134 } 135 136 /** 137 * ZmanimFormatter constructor using various formatting options. 138 * 139 * @param timeFormat The formatting style to use for {@link Duration} used to represent <em>shaos zmaniyos</em>. The formatting 140 * will be as follows.<ul> 141 * <li>{@link #SEXAGESIMAL_SECONDS_FORMAT} (the default) - Will format an hour and a half as "{@code 1:30:00}", and half 142 * an hour as "{@code 0:30:00}".</li> 143 * <li>{@link #SEXAGESIMAL_XSD_FORMAT} - Will format an hour and a half as "{@code 01:30:00.000}", and half an 144 * hour as "{@code 00:30:00.000}".</li> 145 * <li>{@link #SEXAGESIMAL_MILLIS_FORMAT} - Will format an hour and a half as "{@code 1:30:00.000}", and half an hour as 146 * "{@code 0:30:00.000}".</li> 147 * <li>{@link #SEXAGESIMAL_FORMAT} will format an hour and a half as "{@code 1:30}", and half an hour as 148 * "{@code 0:30}".</li> 149 * <li>{@link #XSD_DURATION_FORMAT} will format the time as : "{@code PT1H30M}", and half an hour as "{@code PT30M}".</li> 150 * </ul> 151 * @param dateTimeFormatter the {@link DateTimeFormatter} {@code Object}. 152 * @param zoneId the {@code ZoneId} {@code Object}. 153 */ 154 public ZmanimFormatter(int timeFormat, DateTimeFormatter dateTimeFormatter, ZoneId zoneId) { 155 setZoneId(zoneId); 156 setTimeFormat(timeFormat); 157 setDateTimeFormatter(dateTimeFormatter.withZone(zoneId)); 158 } 159 160 /** 161 * Sets the time format to use for formatting {@link Duration}s. 162 * 163 * @param format the format constant to use. See {@link #ZmanimFormatter(int, DateTimeFormatter, ZoneId)} for documentation on 164 * the formats. 165 */ 166 public void setTimeFormat(int format) { 167 this.timeFormat = format; 168 switch (format) { 169 case SEXAGESIMAL_XSD_FORMAT: 170 setSettings(true, true, true); 171 break; 172 case SEXAGESIMAL_FORMAT: 173 setSettings(false, false, false); 174 break; 175 case SEXAGESIMAL_SECONDS_FORMAT: 176 setSettings(false, true, false); 177 break; 178 case SEXAGESIMAL_MILLIS_FORMAT: 179 setSettings(false, true, true); 180 break; 181 case XSD_DURATION_FORMAT: 182 break; // the settings are N/A for this 183 default: throw new IllegalArgumentException("An invalid time format of " + format + 184 " was set. Please see the documentation for the list of valid formats."); 185 } 186 } 187 188 /** 189 * Sets the {@link DateTimeFormatter} {@code Object}. 190 * @param dateTimeFormatter the {@code DateTimeFormatter} {@code Object} to set. 191 */ 192 public void setDateTimeFormatter(DateTimeFormatter dateTimeFormatter) { 193 this.dateTimeFormatter = dateTimeFormatter; 194 } 195 196 /** 197 * returns the {@link DateTimeFormatter} {@code Object}. 198 * @return the {@link DateTimeFormatter} {@code Object}. 199 */ 200 public DateTimeFormatter getDateTimeFormatter() { 201 return this.dateTimeFormatter; 202 } 203 204 /** 205 * Sets various format settings. 206 * @param prependZeroHours if to prepend a zero for single digit hours (so that 1 o'clock is displayed as 01). 207 * @param useSeconds should seconds be used in the time format. 208 * @param useMillis should milliseconds be used in formatting time. 209 */ 210 private void setSettings(boolean prependZeroHours, boolean useSeconds, boolean useMillis) { 211 this.prependZeroHours = prependZeroHours; 212 this.useSeconds = useSeconds; 213 this.useMillis = useMillis; 214 this.hourNF = new DecimalFormat(this.prependZeroHours ? "00" : "0"); 215 } 216 217 /** 218 * A method that formats a {@link Duration} into a {@code String} format of "{@code 00:00}", "{@code 00:00:00}" or 219 * "{@code 00:00:00.000}" depending on the settings of the formatter. See the documentation on 220 * {@link ZmanimFormatter#ZmanimFormatter(int, DateTimeFormatter, ZoneId)} for details. 221 * 222 * @param duration The {@code Duration} to format. 223 * @return String The formatted {@code String}. 224 */ 225 public String format(Duration duration) { 226 if (duration == null) { 227 duration = Duration.ZERO; 228 } 229 230 if (this.timeFormat == XSD_DURATION_FORMAT) { 231 return formatXSDDurationTime(duration); 232 } 233 234 StringBuilder sb = new StringBuilder(); 235 if (duration.isNegative()) { 236 sb.append("-"); 237 } 238 239 Duration absDuration = duration.abs(); 240 241 long hours = absDuration.toHours(); 242 long minutes = absDuration.toMinutes() % 60; 243 long seconds = absDuration.toSeconds() % 60; 244 long milliseconds = absDuration.toMillis() % 1000; 245 sb.append(this.hourNF.format(hours)); 246 sb.append(":"); 247 sb.append(minuteSecondNF.format(minutes)); 248 249 if (this.useSeconds) { 250 sb.append(":"); 251 sb.append(minuteSecondNF.format(seconds)); 252 } 253 if (this.useMillis) { 254 sb.append("."); 255 sb.append(milliNF.format(milliseconds)); 256 } 257 258 return sb.toString(); 259 } 260 261 /** 262 * Formats an {@link Instant} using this class's {@link #getDateTimeFormatter()}. 263 * 264 * @param instant the {@code Instant} to format. 265 * @param zoneId the {@link ZoneId} used to help format based on the {@code Instant}'s DST and other settings. 266 * @return the formatted String 267 * @see #formatXSDateTime(Instant) 268 */ 269 public String formatInstant(Instant instant, ZoneId zoneId) { 270 ZonedDateTime dateTime = instant.atZone(zoneId); 271 return this.getDateTimeFormatter().format(dateTime); 272 } 273 274 /** 275 * Format the {@code Instant} using the format "{@code yyyy-MM-dd'T'HH:mm:ssXXX}". 276 * @param instant the {@code Instant} to format. 277 * @return the {@code Instant} formatted using the format "{@code yyyy-MM-dd'T'HH:mm:ssXXX}". 278 */ 279 public String formatXSDateTime(Instant instant) { 280 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX").withZone(getZoneId()); 281 return formatter.format(instant); 282 } 283 284 /** 285 * This returns the xml representation of an xsd:duration object. This simply uses {@link Duration#toString()} that formats it 286 * in the standard XSD ISO-8601 (e.g., "{@code PT1H30M}") 287 * 288 * @param duration the duration to format 289 * @return the xsd:duration ISO-8601 formatted String 290 */ 291 public String formatXSDDurationTime(Duration duration) { 292 if (duration == null) { 293 return ""; 294 } 295 return duration.toString(); // Java's native toString() outputs a standard XSD ISO-8601 (e.g., "PT1H30M") 296 } 297 298 /** 299 * A method that returns an XML formatted {@code String} representing the serialized {@code Object}. The 300 * format used is: 301 * 302 * {@snippet lang='xml' : 303 * <AstronomicalTimes date="1969-02-08" type="com.kosherjava.zmanim.AstronomicalCalendar" 304 * algorithm="US Naval Almanac Algorithm" location="Montreal, Quebec" latitude="45.497" longitude="-73.63" 305 * elevation="85.0" timeZoneName="Eastern Standard Time" timeZoneID="America/New_York" timeZoneOffset="-5"> 306 * <SeaLevelSunset>1969-02-08T17:11:26-05:00</SeaLevelSunset> 307 * <TemporalHour>PT50M23.259S</TemporalHour> 308 * ... 309 * </AstronomicalTimes> 310 * } 311 * 312 * If a zman does not occur, the value "N/A" will be returned. 313 * 314 * Note that the output uses the <a href="http://www.w3.org/TR/xmlschema11-2/#dateTime">xsd:dateTime</a> format for 315 * times such as sunrise, and <a href="http://www.w3.org/TR/xmlschema11-2/#duration">xsd:duration</a> format for 316 * times that are a duration such as the length of a 317 * {@link com.kosherjava.zmanim.AstronomicalCalendar#getTemporalHour() temporal hour}. The output of this method is 318 * returned by the {@link #toString() toString}. 319 * 320 * @param astronomicalCalendar the AstronomicalCalendar Object 321 * 322 * @return The XML {@code String} formatted as described above. 323 * 324 * @todo Add proper schema, and support for nulls. XSD duration (for solar hours), should probably return nil and not P. 325 */ 326 public static String toXML(AstronomicalCalendar astronomicalCalendar) { 327 ZmanimFormatter formatter = new ZmanimFormatter(ZmanimFormatter.XSD_DURATION_FORMAT, DateTimeFormatter.ofPattern( 328 "yyyy-MM-dd'T'HH:mm:ss"), astronomicalCalendar.getGeoLocation().getZoneId()); 329 DateTimeFormatter xsdFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX") 330 .withZone(astronomicalCalendar.getGeoLocation().getZoneId()); 331 DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd"); 332 df = df.withZone(astronomicalCalendar.getGeoLocation().getZoneId()); 333 334 LocalDate localDate = astronomicalCalendar.getLocalDate(); 335 GeoLocation geoLocation = astronomicalCalendar.getGeoLocation(); 336 ZonedDateTime lastMidnight = ZonedDateTime.of(astronomicalCalendar.getLocalDate(), LocalTime.MIDNIGHT, astronomicalCalendar.getGeoLocation().getZoneId()); 337 double offsetHours = lastMidnight.getOffset().getTotalSeconds() / 3600.0; 338 String timeZoneName = lastMidnight.format(DateTimeFormatter.ofPattern("zzzz", Locale.getDefault())); 339 340 StringBuilder sb = new StringBuilder("<"); 341 boolean isAstronomicalCalendar = astronomicalCalendar.getClass().getName().equals("com.kosherjava.zmanim.AstronomicalCalendar"); 342 boolean isComprehensiveZmanimCalendar = astronomicalCalendar.getClass().getName().equals("com.kosherjava.zmanim.ComprehensiveZmanimCalendar"); 343 boolean isZmanimCalendar = astronomicalCalendar.getClass().getName().equals("com.kosherjava.zmanim.ZmanimCalendar"); 344 if (isAstronomicalCalendar) { 345 sb.append("AstronomicalTimes"); 346 } else if (isComprehensiveZmanimCalendar) { 347 sb.append("Zmanim"); 348 } else if (isZmanimCalendar) { 349 sb.append("BasicZmanim"); 350 } 351 sb.append(" date=\"").append(df.format(localDate)).append("\""); 352 sb.append(" type=\"").append(astronomicalCalendar.getClass().getName()).append("\""); 353 sb.append(" algorithm=\"").append(astronomicalCalendar.getAstronomicalCalculator().getCalculatorName()).append("\""); 354 sb.append(" location=\"").append(astronomicalCalendar.getGeoLocation().getLocationName()).append("\""); 355 sb.append(" latitude=\"").append(astronomicalCalendar.getGeoLocation().getLatitude()).append("\""); 356 sb.append(" longitude=\"").append(astronomicalCalendar.getGeoLocation().getLongitude()).append("\""); 357 sb.append(" elevation=\"").append(astronomicalCalendar.getGeoLocation().getElevation()).append("\""); 358 sb.append(" timeZoneName=\"").append(timeZoneName).append("\""); 359 sb.append(" timeZoneID=\"").append(geoLocation.getZoneId().getId()).append("\""); 360 sb.append(" timeZoneOffset=\"").append(offsetHours).append("\""); 361 //sb.append(" useElevationAllZmanim=\"").append(astronomicalCalendar.useElevationAllZmanim()).append("\""); //TODO likely using reflection 362 sb.append(">\n"); 363 364 Method[] theMethods = astronomicalCalendar.getClass().getMethods(); 365 String tagName; 366 Object value; 367 List<Zman> dateList = new ArrayList<>(); 368 List<Zman> durationList = new ArrayList<>(); 369 List<String> otherList = new ArrayList<>(); 370 for (Method theMethod : theMethods) { 371 if (includeMethod(theMethod)) { 372 tagName = theMethod.getName().substring(3); 373 // String returnType = theMethods[i].getReturnType().getName(); 374 try { 375 value = theMethod.invoke(astronomicalCalendar, (Object[]) null); 376 if (value == null) { 377 otherList.add("<" + tagName + ">N/A</" + tagName + ">"); 378 } else if (value instanceof Instant) { 379 dateList.add(new Zman((Instant) value, tagName)); 380 } else if (value instanceof Duration) {// shaah zmanis 381 durationList.add(new Zman((Duration)value, tagName)); 382 } else { // will probably never enter this block, but is present to be future-proof 383 otherList.add("<" + tagName + ">" + value + "</" + tagName + ">"); 384 } 385 } catch (Exception e) { 386 e.printStackTrace(); 387 } 388 } 389 } 390 Zman zman; 391 dateList.sort(Zman.DATE_ORDER); 392 393 for (int i = 0; i < dateList.size(); i++) { 394 zman = dateList.get(i); 395 sb.append("\t<").append(zman.getLabel()).append(">"); 396 sb.append(xsdFormatter.format(zman.getZman())); 397 sb.append("</").append(zman.getLabel()).append(">\n"); 398 } 399 durationList.sort(Zman.DURATION_ORDER); 400 for (int i = 0; i < durationList.size(); i++) { 401 zman = durationList.get(i); 402 sb.append("\t<" + zman.getLabel()).append(">"); 403 sb.append(formatter.format(zman.getDuration())).append("</").append(zman.getLabel()) 404 .append(">\n"); 405 } 406 407 for (int i = 0; i < otherList.size(); i++) {// will probably never enter this block 408 sb.append("\t").append(otherList.get(i)).append("\n"); 409 } 410 411 if (isAstronomicalCalendar) { 412 sb.append("</AstronomicalTimes>"); 413 } else if (isComprehensiveZmanimCalendar) { 414 sb.append("</Zmanim>"); 415 } else if (isZmanimCalendar) { 416 sb.append("</BasicZmanim>"); 417 } 418 return sb.toString(); 419 } 420 421 /** 422 * A method that returns a JSON formatted {@code String} representing the serialized {@code Object}. The format used is: 423 * {@snippet lang='json' : 424 * { 425 * "metadata":{ 426 * "date":"1969-02-08", 427 * "type":"com.kosherjava.zmanim.AstronomicalCalendar", 428 * "algorithm":"US Naval Almanac Algorithm", 429 * "location":"Montreal, Quebec", 430 * "latitude":"45.497", 431 * "longitude":"-73.63", 432 * "elevation":"85.0", 433 * "timeZoneName":"Eastern Standard Time", 434 * "timeZoneID":"America/New_York", 435 * "timeZoneOffset":"-5"}, 436 * "AstronomicalTimes":{ 437 * "SeaLevelSunset":"1969-02-08T17:11:26-05:00", 438 * "TemporalHour":"PT50M23.259S" 439 * ... 440 * } 441 * } 442 * } 443 * 444 * Note that the output uses the <a href="http://www.w3.org/TR/xmlschema11-2/#dateTime">xsd:dateTime</a> format for 445 * times such as sunrise, and <a href="http://www.w3.org/TR/xmlschema11-2/#duration">xsd:duration</a> format for 446 * times that are a duration such as the length of a 447 * {@link com.kosherjava.zmanim.AstronomicalCalendar#getTemporalHour() temporal hour}. 448 * If a zman does not occur, the value "N/A" will be returned. 449 * 450 * @param astronomicalCalendar the AstronomicalCalendar Object 451 * 452 * @return The JSON {@code String} formatted as described above. 453 */ 454 public static String toJSON(AstronomicalCalendar astronomicalCalendar) { 455 ZmanimFormatter formatter = new ZmanimFormatter(ZmanimFormatter.XSD_DURATION_FORMAT, DateTimeFormatter.ofPattern( 456 "yyyy-MM-dd'T'HH:mm:ss"), astronomicalCalendar.getGeoLocation().getZoneId()); 457 DateTimeFormatter xsdFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX") 458 .withZone(astronomicalCalendar.getGeoLocation().getZoneId()); 459 DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd") 460 .withZone(astronomicalCalendar.getGeoLocation().getZoneId()); 461 462 LocalDate localDate = astronomicalCalendar.getLocalDate(); 463 GeoLocation geoLocation = astronomicalCalendar.getGeoLocation(); 464 ZonedDateTime lastMidnight = ZonedDateTime.of(astronomicalCalendar.getLocalDate(), LocalTime.MIDNIGHT, 465 astronomicalCalendar.getGeoLocation().getZoneId()); 466 double offsetHours = lastMidnight.getOffset().getTotalSeconds() / 3600.0; 467 String timeZoneName = lastMidnight.format(DateTimeFormatter.ofPattern("zzzz", Locale.getDefault())); 468 469 StringBuilder sb = new StringBuilder("{\n\"metadata\":{\n"); 470 sb.append("\t\"date\":\"").append(df.format(localDate)).append("\",\n"); 471 sb.append("\t\"type\":\"").append(astronomicalCalendar.getClass().getName()).append("\",\n"); 472 sb.append("\t\"algorithm\":\"").append(astronomicalCalendar.getAstronomicalCalculator().getCalculatorName()).append("\",\n"); 473 sb.append("\t\"location\":\"").append(geoLocation.getLocationName()).append("\",\n"); 474 sb.append("\t\"latitude\":\"").append(geoLocation.getLatitude()).append("\",\n"); 475 sb.append("\t\"longitude\":\"").append(geoLocation.getLongitude()).append("\",\n"); 476 sb.append("\t\"elevation\":\"").append(geoLocation.getElevation()).append("\",\n"); 477 sb.append("\t\"timeZoneName\":\"").append(timeZoneName).append("\",\n"); 478 sb.append("\t\"timeZoneID\":\"").append(geoLocation.getZoneId().getId()).append("\",\n"); 479 sb.append("\t\"timeZoneOffset\":\"").append(offsetHours).append("\""); 480 sb.append("},\n\""); 481 482 switch (astronomicalCalendar.getClass().getName()) { 483 case "com.kosherjava.zmanim.AstronomicalCalendar": 484 sb.append("AstronomicalTimes"); 485 break; 486 case "com.kosherjava.zmanim.ComprehensiveZmanimCalendar": 487 sb.append("Zmanim"); 488 break; 489 case "com.kosherjava.zmanim.ZmanimCalendar": 490 sb.append("BasicZmanim"); 491 break; 492 } 493 sb.append("\":{\n"); 494 Method[] theMethods = astronomicalCalendar.getClass().getMethods(); 495 String tagName; 496 Object value; 497 List<Zman> dateList = new ArrayList<>(); 498 List<Zman> durationList = new ArrayList<>(); 499 List<String> otherList = new ArrayList<>(); 500 for (Method theMethod : theMethods) { 501 if (includeMethod(theMethod)) { 502 tagName = theMethod.getName().substring(3); 503 // String returnType = theMethods[i].getReturnType().getName(); 504 try { 505 value = theMethod.invoke(astronomicalCalendar, (Object[]) null); 506 if (value == null) { 507 otherList.add("\"" + tagName + "\":\"N/A\","); 508 } else if (value instanceof Instant) { 509 dateList.add(new Zman((Instant) value, tagName)); 510 } else if (value instanceof Duration) {// shaah zmanis 511 durationList.add(new Zman((Duration)value, tagName)); 512 } else { // will probably never enter this block, but is present to be future-proof 513 otherList.add("\"" + tagName + "\":\"" + value + "\","); 514 } 515 } catch (Exception e) { 516 e.printStackTrace(); 517 } 518 } 519 } 520 Zman zman; 521 dateList.sort(Zman.DATE_ORDER); 522 for (int i = 0; i < dateList.size(); i++) { 523 zman = dateList.get(i); 524 sb.append("\t\"").append(zman.getLabel()).append("\":\""); 525 sb.append(xsdFormatter.format(zman.getZman())); 526 sb.append("\",\n"); 527 } 528 durationList.sort(Zman.DURATION_ORDER); 529 for (int i = 0; i < durationList.size(); i++) { 530 zman = durationList.get(i); 531 sb.append("\t\"" + zman.getLabel()).append("\":\""); 532 sb.append(formatter.format(zman.getDuration())).append("\",\n"); 533 } 534 535 for (int i = 0; i < otherList.size(); i++) {// will probably never enter this block 536 sb.append("\t").append(otherList.get(i)).append("\n"); 537 } 538 sb.setLength(sb.length() - 2); 539 sb.append("}\n}"); 540 return sb.toString(); 541 } 542 543 /** 544 * Determines if a method should be output by the {@link #toXML(AstronomicalCalendar)} 545 * 546 * @param method the method in question 547 * @return if the method should be included in serialization 548 */ 549 private static boolean includeMethod(Method method) { 550 if (method.getParameterTypes().length > 0) 551 return false; // Skip get methods with parameters since we do not know what value to pass 552 if (!method.getName().startsWith("get")) 553 return false; 554 555 return method.getReturnType().getName().endsWith("Instant") || method.getReturnType().getName().endsWith("Duration"); 556 } 557}