001 /* 002 * Zmanim Java API 003 * Copyright (C) 2004-2007 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; 018 019 import java.util.ArrayList; 020 import java.util.Collections; 021 import java.util.Calendar; 022 import java.util.Date; 023 import java.util.GregorianCalendar; 024 import java.util.TimeZone; 025 //import java.util.InvalidPropertiesFormatException; 026 import java.util.List; 027 //import java.util.TimeZone; 028 // import java.io.FileInputStream; 029 //import java.io.IOException; 030 import java.lang.reflect.Method; 031 import java.text.DateFormat; 032 import java.text.SimpleDateFormat; 033 034 import net.sourceforge.zmanim.util.AstronomicalCalculator; 035 import net.sourceforge.zmanim.util.NOAACalculator; 036 import net.sourceforge.zmanim.util.GeoLocation; 037 import net.sourceforge.zmanim.util.ZmanimFormatter; 038 import net.sourceforge.zmanim.util.Zman; 039 040 /** 041 * A Java calendar that calculates astronomical time calculations such as 042 * {@link #getSunrise() sunrise} and {@link #getSunset() sunset} times. This 043 * class contains a {@link #getCalendar() Calendar} and can therefore use the 044 * standard Calendar functionality to change dates etc. The calculation engine 045 * used to calculate the astronomical times can be changed to a different 046 * implementation by implementing the {@link AstronomicalCalculator} and setting 047 * it with the {@link #setAstronomicalCalculator(AstronomicalCalculator)}. A 048 * number of different implementations are included in the util package <br /> 049 * <b>Note:</b> There are times when the algorithms can't calculate proper 050 * values for sunrise and sunset. This is usually caused by trying to calculate 051 * times for areas either very far North or South, where sunrise / sunset never 052 * happen on that date. This is common when calculating twilight with a deep dip 053 * below the horizon for locations as south as London in the northern 054 * hemisphere. When the calculations encounter this condition a null will be 055 * returned when a <code>{@link java.util.Date}</code> is expected and {@link Double#NaN} will 056 * be returned when a double is expected. The reason that <code>Exception</code>s 057 * are not thrown in these cases is due to the fact that such problems are 058 * expected and will often be encountered when a calendar for a full year is 059 * being generated. 060 * 061 * @author © Eliyahu Hershfeld 2004 - 2007 062 * @version 1.1 063 */ 064 public class AstronomicalCalendar /* extends GregorianCalendar */{ 065 private static final long serialVersionUID = 1; 066 067 /** 068 * 90° below the vertical. Used for certain calculations.<br /> 069 * <b>Note </b>: it is important to note the distinction between this zenith 070 * and the {@link AstronomicalCalculator#adjustZenith adjusted zenith} used 071 * for some solar calculations. This 90 zenith is only used because some 072 * calculations in some subclasses are historically calculated as an offset 073 * in reference to 90. 074 */ 075 public static final double GEOMETRIC_ZENITH = 90; 076 077 /** 078 * Default value for Sun's zenith and true rise/set Zenith (used in this 079 * class and subclasses) is the angle that the center of the Sun makes to a 080 * line perpendicular to the Earth's surface. If the Sun were a point and 081 * the Earth were without an atmosphere, true sunset and sunrise would 082 * correspond to a 90° zenith. Because the Sun is not a point, and 083 * because the atmosphere refracts light, this 90° zenith does not, in 084 * fact, correspond to true sunset or sunrise, instead the center of the 085 * Sun's disk must lie just below the horizon for the upper edge to be 086 * obscured. This means that a zenith of just above 90° must be used. 087 * The Sun subtends an angle of 16 minutes of arc (this can be changed via 088 * the {@link #setSunRadius(double)} method , and atmospheric refraction 089 * accounts for 34 minutes or so (this can be changed via the 090 * {@link #setRefraction(double)} method), giving a total of 50 arcminutes. 091 * The total value for ZENITH is 90+(5/6) or 90.8333333° for true 092 * sunrise/sunset. 093 */ 094 // public static double ZENITH = GEOMETRIC_ZENITH + 5.0 / 6.0; 095 /** Sun's zenith at civil twilight (96°). */ 096 public static final double CIVIL_ZENITH = 96; 097 098 /** Sun's zenith at nautical twilight (102°). */ 099 public static final double NAUTICAL_ZENITH = 102; 100 101 /** Sun's zenith at astronomical twilight (108°). */ 102 public static final double ASTRONOMICAL_ZENITH = 108; 103 104 /** constant for milliseconds in a minute (60,000) */ 105 static final long MINUTE_MILLIS = 60 * 1000; 106 107 private Calendar calendar; 108 109 private GeoLocation geoLocation; 110 111 private AstronomicalCalculator astronomicalCalculator; 112 113 /** 114 * The getSunrise method Returns a <code>Date</code> representing the 115 * sunrise time. The zenith used for the calculation uses 116 * {@link #GEOMETRIC_ZENITH geometric zenith} of 90°. This is adjusted by 117 * the {@link AstronomicalCalculator} that adds approximately 50/60 of a 118 * degree to account for 34 archminutes of refraction and 16 archminutes for 119 * the sun's radius for a total of 120 * {@link AstronomicalCalculator#adjustZenith 90.83333°}. See documentation 121 * for the specific implementation of the {@link AstronomicalCalculator} 122 * that you are using. 123 * 124 * @return the <code>Date</code> representing the exact sunrise time. If 125 * the calculation can not be computed null will be returned. 126 * @see AstronomicalCalculator#adjustZenith 127 */ 128 public Date getSunrise() { 129 double sunrise = getUTCSunrise(GEOMETRIC_ZENITH); 130 if (Double.isNaN(sunrise)) { 131 return null; 132 } else { 133 sunrise = getOffsetTime(sunrise); 134 return getDateFromTime(sunrise); 135 } 136 } 137 138 /** 139 * Method that returns the sunrise without correction for elevation. 140 * Non-sunrise and sunset calculations such as dawn and dusk, depend on the 141 * amount of visible light, something that is not affected by elevation. 142 * This method returns sunrise calculated at sea level. This forms the base 143 * for dawn calculations that are calculated as a dip below the horizon 144 * before sunrise. 145 * 146 * @return the <code>Date</code> representing the exact sea-level sunrise 147 * time. If the calculation can not be computed null will be 148 * returned. 149 * @see AstronomicalCalendar#getSunrise 150 * @see AstronomicalCalendar#getUTCSeaLevelSunrise 151 */ 152 public Date getSeaLevelSunrise() { 153 double sunrise = getUTCSeaLevelSunrise(GEOMETRIC_ZENITH); 154 if (Double.isNaN(sunrise)) { 155 return null; 156 } else { 157 sunrise = getOffsetTime(sunrise); 158 return getDateFromTime(sunrise); 159 } 160 } 161 162 /** 163 * A method to return the the beginning of civil twilight (dawn) using a 164 * zenith of {@link #CIVIL_ZENITH 96°}. 165 * 166 * @return The <code>Date</code> of the beginning of civil twilight using 167 * a zenith of 96°. If the calculation can not be computed null will 168 * be returned. 169 * @see #CIVIL_ZENITH 170 */ 171 public Date getBeginCivilTwilight() { 172 return getSunriseOffsetByDegrees(CIVIL_ZENITH); 173 } 174 175 /** 176 * A method to return the the beginning of nautical twilight using a zenith 177 * of {@link #NAUTICAL_ZENITH 102°}. 178 * 179 * @return The <code>Date</code> of the beginning of nautical twilight 180 * using a zenith of 102°. If the calculation can not be computed 181 * null will be returned. 182 * @see #NAUTICAL_ZENITH 183 */ 184 public Date getBeginNauticalTwilight() { 185 return getSunriseOffsetByDegrees(NAUTICAL_ZENITH); 186 } 187 188 /** 189 * A method that returns the the beginning of astronomical twilight using a 190 * zenith of {@link #ASTRONOMICAL_ZENITH 108°}. 191 * 192 * @return The <code>Date</code> of the beginning of astronomical twilight 193 * using a zenith of 108°. If the calculation can not be computed 194 * null will be returned. 195 * @see #ASTRONOMICAL_ZENITH 196 */ 197 public Date getBeginAstronomicalTwilight() { 198 return getSunriseOffsetByDegrees(ASTRONOMICAL_ZENITH); 199 } 200 201 /** 202 * The getSunset method Returns a <code>Date</code> representing the 203 * sunset time. The zenith used for the calculation uses 204 * {@link #GEOMETRIC_ZENITH geometric zenith} of 90°. This is adjusted by 205 * the {@link AstronomicalCalculator} that adds approximately 50/60 of a 206 * degree to account for 34 archminutes of refraction and 16 archminutes for 207 * the sun's radius for a total of 208 * {@link AstronomicalCalculator#adjustZenith 90.83333°}. See documentation 209 * for the specific implementation of the {@link AstronomicalCalculator} 210 * that you are using. 211 * Note: In certain cases the calculates sunset will occur before sunrise. This 212 * will typically happen when a timezone other than the local timezone is used 213 * (calculating Los Angeles sunset using a GMT timezone for example). In this case 214 * the sunset date will be incremented to the following date. 215 * 216 * @return the <code>Date</code> representing the exact sunset time. If 217 * the calculation can not be computed null will be returned. If the 218 * time calculation 219 * @see AstronomicalCalculator#adjustZenith 220 */ 221 public Date getSunset() { 222 double sunset = getUTCSunset(GEOMETRIC_ZENITH); 223 if (Double.isNaN(sunset)) { 224 return null; 225 } else { 226 sunset = getOffsetTime(sunset); 227 return getAdjustedSunsetDate(getDateFromTime(sunset), getSunrise()); 228 } 229 } 230 231 /** 232 * A method that will roll the sunset time forward a day if sunset occurs before 233 * sunrise. This will typically happen when a timezone other than the local timezone 234 * is used (calculating Los Angeles sunset using a GMT timezone for example). 235 * In this case the sunset date will be incremented to the following date. 236 * @param sunset the sunset date to adjust if needed 237 * @param sunrise the sunrise to compare to the sunset 238 * @return the adjusted sunset date 239 */ 240 private Date getAdjustedSunsetDate(Date sunset, Date sunrise){ 241 if(sunset != null && sunrise != null && sunrise.compareTo(sunset) >= 0){ 242 Calendar clonedCalendar = (GregorianCalendar)getCalendar().clone(); 243 clonedCalendar.setTime(sunset); 244 clonedCalendar.add(Calendar.DAY_OF_MONTH, 1); 245 return clonedCalendar.getTime(); 246 } else { 247 return sunset; 248 } 249 250 } 251 252 /** 253 * Method that returns the sunset without correction for elevation. 254 * Non-sunrise and sunset calculations such as dawn and dusk, depend on the 255 * amount of visible light, something that is not affected by elevation. 256 * This method returns sunset calculated at sea level. This forms the base 257 * for dusk calculations that are calculated as a dip below the horizon 258 * after sunset. 259 * 260 * @return the <code>Date</code> representing the exact sea-level sunset 261 * time. If the calculation can not be computed null will be 262 * returned. 263 * @see AstronomicalCalendar#getSunset 264 * @see AstronomicalCalendar#getUTCSeaLevelSunset 265 */ 266 public Date getSeaLevelSunset() { 267 double sunset = getUTCSeaLevelSunset(GEOMETRIC_ZENITH); 268 if (Double.isNaN(sunset)) { 269 return null; 270 } else { 271 sunset = getOffsetTime(sunset); 272 //return getDateFromTime(sunset); 273 return getAdjustedSunsetDate(getDateFromTime(sunset), getSeaLevelSunrise()); 274 } 275 } 276 277 /** 278 * A method to return the the end of civil twilight using a zenith of 279 * {@link #CIVIL_ZENITH 96°}. 280 * 281 * @return The <code>Date</code> of the end of civil twilight using a 282 * zenith of {@link #CIVIL_ZENITH 96°}. If the calculation can not 283 * be computed null will be returned. 284 * @see #CIVIL_ZENITH 285 */ 286 public Date getEndCivilTwilight() { 287 return getSunsetOffsetByDegrees(CIVIL_ZENITH); 288 } 289 290 /** 291 * A method to return the the end of nautical twilight using a zenith of 292 * {@link #NAUTICAL_ZENITH 102°}. 293 * 294 * @return The <code>Date</code> of the end of nautical twilight using a 295 * zenith of {@link #NAUTICAL_ZENITH 102°}. If the calculation can not be computed null will 296 * be returned. 297 * @see #NAUTICAL_ZENITH 298 */ 299 public Date getEndNauticalTwilight() { 300 return getSunsetOffsetByDegrees(NAUTICAL_ZENITH); 301 } 302 303 /** 304 * A method to return the the end of astronomical twilight using a zenith of 305 * {@link #ASTRONOMICAL_ZENITH 108°}. 306 * 307 * @return The The <code>Date</code> of the end of astronomical twilight 308 * using a zenith of {@link #ASTRONOMICAL_ZENITH 108°}. If the calculation can not be computed 309 * null will be returned. 310 * @see #ASTRONOMICAL_ZENITH 311 */ 312 public Date getEndAstronomicalTwilight() { 313 return getSunsetOffsetByDegrees(ASTRONOMICAL_ZENITH); 314 } 315 316 /** 317 * Utility method that returns a time offset. This method casts the offset 318 * as a <code>long</code> and calls {@link #getTimeOffset(Date, long)}. 319 * 320 * @param time 321 * the start time 322 * @param offset 323 * the offset in milliseconds to add to the time 324 * @return the {@link java.util.Date}with the offset added to it 325 */ 326 public Date getTimeOffset(Date time, double offset) { 327 return getTimeOffset(time, (long) offset); 328 } 329 330 /** 331 * A utility method to return a time offset. 332 * 333 * @param time 334 * the start time 335 * @param offset 336 * the offset in milliseconds to add to the time. 337 * @return the {@link java.util.Date} with the offset in milliseconds added 338 * to it 339 */ 340 public Date getTimeOffset(Date time, long offset) { 341 if (time == null || offset == Long.MIN_VALUE) { 342 return null; 343 } 344 return new Date(time.getTime() + offset); 345 } 346 347 /** 348 * A utility method to return the time of an offset by degrees below or 349 * above the horizon of {@link #getSunrise() sunrise}. 350 * 351 * @param offsetZenith 352 * the degrees before {@link #getSunrise()} to use in the 353 * calculation. For time after sunrise use negative numbers. 354 * @return The {@link java.util.Date} of the offset after (or before) 355 * {@link #getSunrise()}. If the calculation can not be computed 356 * null will be returned. 357 */ 358 public Date getSunriseOffsetByDegrees(double offsetZenith) { 359 double alos = getUTCSunrise(offsetZenith); 360 if (Double.isNaN(alos)) { 361 return null; 362 } else { 363 alos = getOffsetTime(alos); 364 return getDateFromTime(alos); 365 } 366 } 367 368 /** 369 * A utility method to return the time of an offset by degrees below or 370 * above the horizon of {@link #getSunset() sunset}. 371 * 372 * @param offsetZenith 373 * the degrees after {@link #getSunset()} to use in the 374 * calculation. For time before sunset use negative numbers. 375 * @return The {@link java.util.Date}of the offset after (or before) 376 * {@link #getSunset()}. If the calculation can not be computed 377 * null will be returned. 378 */ 379 public Date getSunsetOffsetByDegrees(double offsetZenith) { 380 double sunset = getUTCSunset(offsetZenith); 381 if (Double.isNaN(sunset)) { 382 return null; 383 } else { 384 sunset = getOffsetTime(sunset); 385 //return getDateFromTime(sunset); 386 return getAdjustedSunsetDate(getDateFromTime(sunset), getSunriseOffsetByDegrees(offsetZenith)); 387 388 } 389 } 390 391 /** 392 * Default constructor will set a default {@link GeoLocation}, use the 393 * default ({@link AstronomicalCalculator#getDefault()}) 394 * AstronomicalCalculator and default the calendar to the current time. 395 */ 396 public AstronomicalCalendar() { 397 // setGeoLocation(new GeoLocation()); //PMD 398 geoLocation = new GeoLocation(); 399 calendar = Calendar.getInstance(geoLocation.getTimeZone()); 400 astronomicalCalculator = AstronomicalCalculator.getDefault(); 401 } 402 403 /** 404 * A constructor that takes in as a parameter geolocation information 405 * 406 * @param geoLocation 407 * The location information used for astronomical calculating sun 408 * times. 409 */ 410 public AstronomicalCalendar(GeoLocation geoLocation) { 411 this(); // call default constructor to initialize astronomicalGeometryCalculator 412 setGeoLocation(geoLocation); 413 calendar = Calendar.getInstance(geoLocation.getTimeZone()); 414 } 415 416 /** 417 * Method that returns the sunrise in UTC time without correction for time 418 * zone offset from GMT and without using daylight savings time. 419 * 420 * @param zenith 421 * the degrees below the horizon. For time after sunrise use 422 * negative numbers. 423 * @return The time in the format: 18.75 for 18:45:00 UTC/GMT. If the 424 * calculation can not be computed {@link Double#NaN} will be 425 * returned. 426 */ 427 public double getUTCSunrise(double zenith) { 428 return getAstronomicalCalculator().getUTCSunrise(this, zenith, true); 429 } 430 431 /** 432 * Method that returns the sunrise in UTC time without correction for time 433 * zone offset from GMT and without using daylight savings time. Non-sunrise 434 * and sunset calculations such as dawn and dusk, depend on the amount of 435 * visible light, something that is not affected by elevation. This method 436 * returns UTC sunrise calculated at sea level. This forms the base for dawn 437 * calculations that are calculated as a dip below the horizon before 438 * sunrise. 439 * 440 * @param zenith 441 * the degrees below the horizon. For time after sunrise use 442 * negative numbers. 443 * @return The time in the format: 18.75 for 18:45:00 UTC/GMT. If the 444 * calculation can not be computed {@link Double#NaN} will be 445 * returned. 446 * @see AstronomicalCalendar#getUTCSunrise 447 * @see AstronomicalCalendar#getUTCSeaLevelSunset 448 */ 449 public double getUTCSeaLevelSunrise(double zenith) { 450 return getAstronomicalCalculator().getUTCSunrise(this, zenith, false); 451 } 452 453 /** 454 * Method that returns the sunset in UTC time without correction for time 455 * zone offset from GMT and without using daylight savings time. 456 * 457 * @param zenith 458 * the degrees below the horizon. For time after before sunset 459 * use negative numbers. 460 * @return The time in the format: 18.75 for 18:45:00 UTC/GMT. If the 461 * calculation can not be computed {@link Double#NaN} will be 462 * returned. 463 * @see AstronomicalCalendar#getUTCSeaLevelSunset 464 */ 465 public double getUTCSunset(double zenith) { 466 return getAstronomicalCalculator().getUTCSunset(this, zenith, true); 467 } 468 469 /** 470 * Method that returns the sunset in UTC time without correction for 471 * elevation, time zone offset from GMT and without using daylight savings 472 * time. Non-sunrise and sunset calculations such as dawn and dusk, depend 473 * on the amount of visible light, something that is not affected by 474 * elevation. This method returns UTC sunset calculated at sea level. This 475 * forms the base for dusk calculations that are calculated as a dip below 476 * the horizon after sunset. 477 * 478 * @param zenith 479 * the degrees below the horizon. For time before sunset use 480 * negative numbers. 481 * @return The time in the format: 18.75 for 18:45:00 UTC/GMT. If the 482 * calculation can not be computed {@link Double#NaN} will be 483 * returned. 484 * @see AstronomicalCalendar#getUTCSunset 485 * @see AstronomicalCalendar#getUTCSeaLevelSunrise 486 */ 487 public double getUTCSeaLevelSunset(double zenith) { 488 return getAstronomicalCalculator().getUTCSunset(this, zenith, false); 489 } 490 491 /** 492 * A method that adds time zone offset and daylight savings time to the raw 493 * UTC time. 494 * 495 * @param time 496 * The UTC time to be adjusted. 497 * @return The time adjusted for the time zone offset and daylight savings 498 * time. 499 */ 500 private double getOffsetTime(double time) { 501 boolean dst = getCalendar().getTimeZone().inDaylightTime( 502 getCalendar().getTime()); 503 double dstOffset = 0; 504 // be nice to Newfies and use a double 505 double gmtOffset = getCalendar().getTimeZone().getRawOffset() 506 / (60 * MINUTE_MILLIS); 507 if (dst) { 508 dstOffset = getCalendar().getTimeZone().getDSTSavings() 509 / (60 * MINUTE_MILLIS); 510 } 511 return time + gmtOffset + dstOffset; 512 } 513 514 /** 515 * Method to return a temporal (solar) hour. The day from sunrise to sunset 516 * is split into 12 equal parts with each one being a temporal hour. 517 * 518 * @return the <code>long</code> millisecond length of a temporal hour. If 519 * the calculation can not be computed {@link Long#MIN_VALUE} will 520 * be returned. 521 */ 522 public long getTemporalHour() { 523 return getTemporalHour(getSunrise(), getSunset()); 524 } 525 526 /** 527 * Utility method that will allow the calculation of a temporal (solar) hour 528 * based on the sunrise and sunset passed to this method. 529 * 530 * @param sunrise 531 * The start of the day. 532 * @param sunset 533 * The end of the day. 534 * @see #getTemporalHour() 535 * @return the <code>long</code> millisecond length of the temporal hour. 536 * If the calculation can not be computed {@link Long#MIN_VALUE} 537 * will be returned. 538 */ 539 public long getTemporalHour(Date sunrise, Date sunset) { 540 if (sunrise == null || sunset == null) { 541 return Long.MIN_VALUE; 542 } 543 return (sunset.getTime() - sunrise.getTime()) / 12; 544 } 545 546 /** 547 * method that returns sundial noon. This is calculated as halfway between 548 * sunrise and sunset. 549 * 550 * @return the <code>Date</code> representing Sun's transit. If the 551 * calculation can not be computed null will be returned. 552 */ 553 public Date getSunTransit() { 554 return getTimeOffset(getSunrise(), getTemporalHour() * 6); 555 } 556 557 /** 558 * A method that returns a <code>Date</code> from the time passed in 559 * 560 * @param time 561 * The time to be set as the time for the <code>Date</code>. 562 * The time expected is in the format: 18.75 for 6:45:00 PM 563 * @return The Date. 564 */ 565 private Date getDateFromTime(double time) { 566 //System.out.println(time); 567 if (Double.isNaN(time)) { 568 return null; 569 } 570 Calendar cal = new GregorianCalendar(); 571 cal.clear(); 572 cal.set(Calendar.YEAR, getCalendar().get(Calendar.YEAR)); 573 cal.set(Calendar.MONTH, getCalendar().get(Calendar.MONTH)); 574 cal.set(Calendar.DAY_OF_MONTH, getCalendar().get(Calendar.DAY_OF_MONTH)); 575 // sunset time (usually offset) is later than midnight GMT 576 //FIXME, with the use of getCorrectedSunsetDate(), the following might not be needed 577 if ((!getCalendar().getTimeZone().inDaylightTime(getCalendar().getTime()) && time < 0) 578 || (getCalendar().getTimeZone().inDaylightTime(getCalendar().getTime()) && time < 1)) { 579 cal.add(Calendar.DAY_OF_MONTH, 1); 580 } 581 582 int hours = (int) time; // cut off minutes 583 boolean prob = false; 584 if (hours < 1) { 585 prob = true; 586 } 587 time -= hours; 588 589 int minutes = (int) (time *= 60); 590 time -= minutes; 591 int seconds = (int) (time *= 60); 592 time -= seconds; // milliseconds 593 594 cal.set(Calendar.HOUR_OF_DAY, hours); 595 cal.set(Calendar.MINUTE, minutes); 596 cal.set(Calendar.SECOND, seconds); 597 cal.set(Calendar.MILLISECOND, (int) (time * 1000)); 598 if (prob == true) {//FIXME: might not be an issue with the new getCorrectedSunsetDate 599 // System.out.println(cal); 600 } 601 return cal.getTime(); 602 } 603 604 /** 605 * @return an XML formatted representation of the class. It returns the 606 * default output of the {@link #toXML() toXML} 607 * method. 608 * @see #toXML() 609 * @see java.lang.Object#toString() 610 */ 611 public String toString() { 612 return toXML(); 613 } 614 615 /** 616 * @see java.lang.Object#equals(Object) 617 */ 618 public boolean equals(Object object) { 619 if (this == object) 620 return true; 621 if (!(object instanceof AstronomicalCalendar)) 622 return false; 623 AstronomicalCalendar aCal = (AstronomicalCalendar) object; 624 return getCalendar().equals(aCal.getCalendar()) 625 && getGeoLocation().equals(aCal.getGeoLocation()) 626 && getAstronomicalCalculator().equals( 627 aCal.getAstronomicalCalculator()); 628 } 629 630 /** 631 * @see java.lang.Object#hashCode() 632 */ 633 public int hashCode() { 634 int result = 17; 635 // needed or this and subclasses will return identical hash 636 result = 37 * result + getClass().hashCode(); 637 result += 37 * result + getCalendar().hashCode(); 638 result += 37 * result + getGeoLocation().hashCode(); 639 result += 37 * result + getAstronomicalCalculator().hashCode(); 640 return result; 641 } 642 643 /** 644 * @return Returns the geoLocation. 645 */ 646 public GeoLocation getGeoLocation() { 647 return geoLocation; 648 } 649 650 /** 651 * @param geoLocation 652 * The geoLocation to set. 653 */ 654 public void setGeoLocation(GeoLocation geoLocation) { 655 this.geoLocation = geoLocation; 656 getCalendar().setTimeZone(geoLocation.getTimeZone());// TODO might 657 // not be needed 658 } 659 660 /** 661 * A method to return the current AstronomicalCalculator set. 662 * 663 * @return Returns the astronimicalCalculator. 664 * @see #setAstronomicalCalculator(AstronomicalCalculator) 665 */ 666 public AstronomicalCalculator getAstronomicalCalculator() { 667 return astronomicalCalculator; 668 } 669 670 /** 671 * A method to set the {@link AstronomicalCalculator} used for astronomical 672 * calculations. The Zmanim package ships with a number of different 673 * implementations of the <code>abstract</code> 674 * {@link AstronomicalCalculator} based on different algorithms, including 675 * {@link net.sourceforge.zmanim.util.SunTimesCalculator one implementation} 676 * based on the <a href = "http://aa.usno.navy.mil/">US Naval Observatory's</a> 677 * algorithm, and 678 * {@link net.sourceforge.zmanim.util.JSuntimeCalculator another} based on 679 * <a href=""http://noaa.gov">NOAA's</a> algorithm. This allows easy 680 * runtime switching and comparison of different algorithms. 681 * 682 * @param astronomicalCalculator 683 * The astronimicalCalculator to set. 684 */ 685 public void setAstronomicalCalculator( 686 AstronomicalCalculator astronomicalCalculator) { 687 this.astronomicalCalculator = astronomicalCalculator; 688 } 689 690 /** 691 * returns the Calendar object encapsulated by this class. 692 * @return Returns the calendar. 693 */ 694 public Calendar getCalendar() { 695 return calendar; 696 } 697 698 /** 699 * @param calendar 700 * The calendar to set. 701 */ 702 public void setCalendar(Calendar calendar) { 703 this.calendar = calendar; 704 } 705 706 /** 707 * A method that returns an XML formatted <code>String</code> representing 708 * the serialized <code>Object</code>. 709 * The format used is: 710 * 711 * <pre> 712 * <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"> 713 * <Sunrise>2007-02-18T06:45:27-05:00</Sunrise> 714 * <TemporalHour>PT54M17.529S</TemporalHour> 715 * ... 716 * </AstronomicalTimes> 717 * </pre> 718 * 719 * Note that the output uses the <a 720 * href="http://www.w3.org/TR/xmlschema11-2/#dateTime">xsd:dateTime</a> 721 * format for times such as sunrise, and <a 722 * href="http://www.w3.org/TR/xmlschema11-2/#duration">xsd:duration</a> 723 * format for times that are a duration such as the length of a 724 * {@link #getTemporalHour() temporal hour}. The output of this 725 * method is returned by the {@link #toString() toString} }. 726 * 727 * @return The XML formatted <code>String</code>. The format will be: 728 * 729 * <pre> 730 * <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"> 731 * <Sunrise>2007-02-18T06:45:27-05:00</Sunrise> 732 * <TemporalHour>PT54M17.529S</TemporalHour> 733 * ... 734 * </AstronomicalTimes> 735 * </pre> 736 * 737 */ 738 public String toXML() { 739 ZmanimFormatter formatter = new ZmanimFormatter(ZmanimFormatter.XSD_DURATION_FORMAT, new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")); 740 DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); 741 String output = "<"; 742 if(getClass().getName().endsWith("AstronomicalCalendar")){ 743 output += "AstronomicalTimes"; 744 } else if(getClass().getName().endsWith("ZmanimCalendar")){ 745 output += "Zmanim"; 746 } 747 output += " date=\"" + df.format(this.getCalendar().getTime()) + "\""; 748 output += " type=\"" + getClass().getName() + "\""; 749 output += " algorithm=\"" 750 + getAstronomicalCalculator().getCalculatorName() + "\""; 751 output += " location=\"" + getGeoLocation().getLocationName() + "\""; 752 output += " latitude=\"" + getGeoLocation().getLatitude() + "\""; 753 output += " longitude=\"" + getGeoLocation().getLongitude() + "\""; 754 output += " elevation=\"" + getGeoLocation().getElevation() + "\""; 755 output += " timeZoneName=\"" 756 + getGeoLocation().getTimeZone().getDisplayName() + "\""; 757 output += " timeZoneID=\"" + getGeoLocation().getTimeZone().getID() 758 + "\""; 759 output += " timeZoneOffset=\"" 760 + (getGeoLocation().getTimeZone().getOffset( 761 getCalendar().getTimeInMillis()) / (60 * 60 * 1000)) 762 + "\""; 763 764 output += ">\n"; 765 766 Method[] theMethods = getClass().getMethods(); 767 String tagName = ""; 768 Object value = null; 769 List dateList = new ArrayList(); 770 List durationList = new ArrayList(); 771 for (int i = 0; i < theMethods.length; i++) { 772 if (includeMethod(theMethods[i])) { 773 tagName = theMethods[i].getName().substring(3); 774 try { 775 value = theMethods[i].invoke(this, (Object[]) null); 776 if (value instanceof Date) { 777 778 dateList.add(new Zman((Date) value, tagName)); 779 // output += "\t<" + tagName; 780 // output += ">"; 781 // output += formatter.formatDateTime((Date) value, 782 // getCalendar()) 783 // + "</" + tagName + ">\n"; 784 } else if (value instanceof Long) {// shaah zmanis 785 // output += "\t<" + tagName; 786 // output += ">" 787 // + formatter.format((int) ((Long) value) 788 // .longValue()) + "</" + tagName + ">\n"; 789 durationList.add(new Zman((int) ((Long) value).longValue(), tagName)); 790 } else { // will probably never enter this block 791 output += "<" + tagName + ">" + value + "</" + tagName 792 + ">\n"; 793 } 794 } catch (Exception e) { 795 e.printStackTrace(); 796 } 797 } 798 } 799 Zman zman; 800 Collections.sort(dateList, Zman.DATE_ORDER); 801 for(int i=0; i< dateList.size(); i++){ 802 zman = (Zman)dateList.get(i); 803 output += "\t<" + zman.getZmanLabel(); 804 output += ">"; 805 output += formatter.formatDateTime(zman.getZman(), getCalendar()) 806 + "</" + zman.getZmanLabel() + ">\n"; 807 } 808 for(int i=0; i< durationList.size(); i++){ 809 zman = (Zman)durationList.get(i); 810 output += "\t<" + zman.getZmanLabel(); 811 output += ">"; 812 output += formatter.format((int) zman.getDuration()) 813 + "</" + zman.getZmanLabel() + ">\n"; 814 } 815 816 if(getClass().getName().endsWith("AstronomicalCalendar")){ 817 output += "</AstronomicalTimes>"; 818 } else if(getClass().getName().endsWith("ZmanimCalendar")){ 819 output += "</Zmanim>"; 820 } 821 return output; 822 } 823 824 public String toXML2() { 825 ZmanimFormatter formatter = new ZmanimFormatter(ZmanimFormatter.XSD_DURATION_FORMAT, new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")); 826 DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); 827 String output = "<"; 828 if(getClass().getName().endsWith("AstronomicalCalendar")){ 829 output += "AstronomicalTimes"; 830 } else if(getClass().getName().endsWith("ZmanimCalendar")){ 831 output += "Zmanim"; 832 } 833 output += " date=\"" + df.format(this.getCalendar().getTime()) + "\""; 834 output += " type=\"" + getClass().getName() + "\""; 835 output += " algorithm=\"" 836 + getAstronomicalCalculator().getCalculatorName() + "\""; 837 output += " location=\"" + getGeoLocation().getLocationName() + "\""; 838 output += " latitude=\"" + getGeoLocation().getLatitude() + "\""; 839 output += " longitude=\"" + getGeoLocation().getLongitude() + "\""; 840 output += " elevation=\"" + getGeoLocation().getElevation() + "\""; 841 output += " timeZoneName=\"" 842 + getGeoLocation().getTimeZone().getDisplayName() + "\""; 843 output += " timeZoneID=\"" + getGeoLocation().getTimeZone().getID() 844 + "\""; 845 output += " timeZoneOffset=\"" 846 + (getGeoLocation().getTimeZone().getOffset( 847 getCalendar().getTimeInMillis()) / (60 * 60 * 1000)) 848 + "\""; 849 850 output += ">\n"; 851 852 Method[] theMethods = getClass().getMethods(); 853 String tagName = ""; 854 Object value = null; 855 for (int i = 0; i < theMethods.length; i++) { 856 if (includeMethod(theMethods[i])) { 857 tagName = theMethods[i].getName().substring(3); 858 try { 859 value = theMethods[i].invoke(this, (Object[]) null); 860 if (value instanceof Date) { 861 output += "\t<" + tagName; 862 // if(props!= null){ 863 // String zmanimDescription = 864 // props.getProperty(tagName); 865 // if(zmanimDescription != null){ 866 // output += " description=\"" + zmanimDescription + 867 // "\""; 868 // } 869 // } 870 output += ">"; 871 output += formatter.formatDateTime((Date) value, 872 getCalendar()) 873 + "</" + tagName + ">\n"; 874 } else if (value instanceof Long) {// shaah zmanis 875 output += "\t<" + tagName; 876 // if(props!= null){ 877 // String zmanimDescription = 878 // props.getProperty(tagName); 879 // if(zmanimDescription != null){ 880 // output += " description=\"" + zmanimDescription + 881 // "\""; 882 // } 883 // } 884 output += ">" 885 + formatter.format((int) ((Long) value) 886 .longValue()) + "</" + tagName + ">\n"; 887 } else { // will probably never enter this block 888 output += "<" + tagName + ">" + value + "</" + tagName 889 + ">\n"; 890 } 891 } catch (Exception e) { 892 e.printStackTrace(); 893 } 894 } 895 } 896 897 if(getClass().getName().endsWith("AstronomicalCalendar")){ 898 output += "</AstronomicalTimes>"; 899 } else if(getClass().getName().endsWith("ZmanimCalendar")){ 900 output += "</Zmanim>"; 901 } 902 return output; 903 } 904 905 private static boolean includeMethod(Method method) { 906 List methodWhiteList = new ArrayList(); 907 // methodWhiteList.add("getName"); 908 909 List methodBlackList = new ArrayList(); 910 // methodBlackList.add("getGregorianChange"); 911 912 if (methodWhiteList.contains(method.getName())) 913 return true; 914 if (methodBlackList.contains(method.getName())) 915 return false; 916 917 if (method.getParameterTypes().length > 0) 918 return false; // skip get methods with parameters 919 if (!method.getName().startsWith("get")) 920 return false; 921 922 if (method.getReturnType().getName().endsWith("Date") 923 || method.getReturnType().getName().endsWith("long")) { 924 return true; 925 } 926 return false; 927 } 928 929 public static void main(String [] args){ 930 931 String locationName = "Lakewood, NJ"; 932 // locationName = "Gateshead, United Kingdom"; 933 // locationName = "Whippany, NJ"; 934 // locationName = "Thule"; 935 double latitude = 40.095965; // Lakewood, NJ 936 double longitude = -74.222130; // Lakewood, NJ 937 938 //latitude = 34.052187; //LA, CA 939 //longitude = -118.243425;// LA, CA 940 // latitude = 54.98333333; //Gateshead, United Kingdom 941 // longitude = -1.583333333; //Gateshead, United Kingdom 942 // latitude = 40.82444; //Whippany, NJ 943 // longitude = -74.4175; //Whippany, NJ 944 //latitude = 76.32; //thule 945 // longitude = 68.5; 946 TimeZone timeZone = TimeZone.getTimeZone("America/New_York"); 947 // timeZone = TimeZone.getTimeZone("Europe/London"); 948 //timeZone = TimeZone.getTimeZone("America/Thule"); 949 //timeZone = TimeZone.getTimeZone("GMT"); 950 951 GeoLocation location = new GeoLocation(locationName, latitude, longitude, 952 timeZone); 953 AstronomicalCalendar ac = new AstronomicalCalendar(location); 954 ac.setAstronomicalCalculator(new NOAACalculator()); 955 ac.getCalendar().set(Calendar.YEAR, 2000); 956 //System.out.println(ac); 957 958 Date sunrise = ac.getSunrise(); 959 //Date sunset = ac.getCorrectedSunsetDate(sunrise, sunrise); 960 961 System.out.println(sunrise); 962 Date other = ac.getBeginAstronomicalTwilight(); 963 System.out.println(other); 964 } 965 }