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: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html 015 */ 016package com.kosherjava.zmanim.util; 017 018import java.text.SimpleDateFormat; 019import java.util.Comparator; 020import java.util.Date; 021 022/** 023 * A wrapper class for astronomical times / <em>zmanim</em> that is mostly intended to allow sorting collections of astronomical times. 024 * It has fields for both date/time and duration based <em>zmanim</em>, name / labels as well as a longer description or explanation of a 025 * <em>zman</em>. 026 * <p> 027 * Here is an example of various ways of sorting <em>zmanim</em>. 028 * <p>First create the Calendar for the location you would like to calculate: 029 * 030 * <pre style="background: #FEF0C9; display: inline-block;"> 031 * String locationName = "Lakewood, NJ"; 032 * double latitude = 40.0828; // Lakewood, NJ 033 * double longitude = -74.2094; // Lakewood, NJ 034 * double elevation = 20; // optional elevation correction in Meters 035 * // the String parameter in getTimeZone() has to be a valid timezone listed in {@link java.util.TimeZone#getAvailableIDs()} 036 * TimeZone timeZone = TimeZone.getTimeZone("America/New_York"); 037 * GeoLocation location = new GeoLocation(locationName, latitude, longitude, elevation, timeZone); 038 * ComplexZmanimCalendar czc = new ComplexZmanimCalendar(location); 039 * Zman sunset = new Zman(czc.getSunset(), "Sunset"); 040 * Zman shaah16 = new Zman(czc.getShaahZmanis16Point1Degrees(), "Shaah zmanis 16.1"); 041 * Zman sunrise = new Zman(czc.getSunrise(), "Sunrise"); 042 * Zman shaah = new Zman(czc.getShaahZmanisGra(), "Shaah zmanis GRA"); 043 * ArrayList<Zman> zl = new ArrayList<Zman>(); 044 * zl.add(sunset); 045 * zl.add(shaah16); 046 * zl.add(sunrise); 047 * zl.add(shaah); 048 * //will sort sunset, shaah 1.6, sunrise, shaah GRA 049 * System.out.println(zl); 050 * Collections.sort(zl, Zman.DATE_ORDER); 051 * // will sort sunrise, sunset, shaah, shaah 1.6 (the last 2 are not in any specific order) 052 * Collections.sort(zl, Zman.DURATION_ORDER); 053 * // will sort sunrise, sunset (the first 2 are not in any specific order), shaah GRA, shaah 1.6 054 * Collections.sort(zl, Zman.NAME_ORDER); 055 * // will sort shaah 1.6, shaah GRA, sunrise, sunset 056 * </pre> 057 * 058 * @author © Eliyahu Hershfeld 2007-2025 059 * @todo Add secondary sorting. As of now the {@code Comparator}s in this class do not sort by secondary order. This means that when sorting a 060 * {@link java.util.Collection} of <em>zmanim</em> and using the {@link #DATE_ORDER} {@code Comparator} will have the duration based <em>zmanim</em> 061 * at the end, but they will not be sorted by duration. This should be N/A for label based sorting. 062 */ 063public class Zman { 064 /** 065 * The name / label of the <em>zman</em>. 066 */ 067 private String label; 068 069 /** 070 * The {@link Date} of the <em>zman</em> 071 */ 072 private Date zman; 073 074 /** 075 * The duration if the <em>zman</em> is a {@link com.kosherjava.zmanim.AstronomicalCalendar#getTemporalHour() temporal hour} (or the various 076 * <em>shaah zmanis</em> base times such as {@link com.kosherjava.zmanim.ZmanimCalendar#getShaahZmanisGra() <em>shaah Zmanis GRA</em>} or 077 * {@link com.kosherjava.zmanim.ComplexZmanimCalendar#getShaahZmanis16Point1Degrees() <em>shaah Zmanis 16.1°</em>}). 078 */ 079 private long duration; 080 081 /** 082 * A longer description or explanation of a <em>zman</em>. 083 */ 084 private String description; 085 086 /** 087 * The location information of the <em>zman</em>. 088 */ 089 private GeoLocation geoLocation; 090 091 /** 092 * The constructor setting a {@link Date} based <em>zman</em> and a label. In most cases you will likely want to call 093 * {@link #Zman(Date, GeoLocation, String)} that also sets the location. 094 * @param date the Date of the <em>zman</em>. 095 * @param label the label of the <em>zman</em> such as "<em>Sof Zman Krias Shema GRA</em>". 096 * @see #Zman(Date, GeoLocation, String) 097 */ 098 public Zman(Date date, String label) { 099 this(date, null, label); 100 } 101 102 /** 103 * The constructor setting a {@link Date} based <em>zman</em> and a label. In most cases you will likely want to call 104 * {@link #Zman(Date, GeoLocation, String)} that also sets the geo location. 105 * @param date the Date of the <em>zman</em>. 106 * @param geoLocation the {@link GeoLocation} of the <em>zman</em>. 107 * @param label the label of the <em>zman</em> such as "<em>Sof Zman Krias Shema GRA</em>". 108 */ 109 public Zman(Date date, GeoLocation geoLocation, String label) { 110 this.zman = date; 111 this.geoLocation = geoLocation; 112 this.label = label; 113 } 114 115 /** 116 * The constructor setting a duration based <em>zman</em> such as 117 * {@link com.kosherjava.zmanim.AstronomicalCalendar#getTemporalHour() temporal hour} (or the various <em>shaah zmanis</em> times such as 118 * {@link com.kosherjava.zmanim.ZmanimCalendar#getShaahZmanisGra() <em>shaah zmanis GRA</em>} or 119 * {@link com.kosherjava.zmanim.ComplexZmanimCalendar#getShaahZmanis16Point1Degrees() <em>shaah Zmanis 16.1°</em>}) and label. 120 * @param duration a duration based <em>zman</em> such as ({@link com.kosherjava.zmanim.AstronomicalCalendar#getTemporalHour()} 121 * @param label the label of the <em>zman</em> such as "<em>Shaah Zmanis GRA</em>". 122 * @see #Zman(Date, String) 123 */ 124 public Zman(long duration, String label) { 125 this.label = label; 126 this.duration = duration; 127 } 128 129 /** 130 * Returns the {@code Date} based <em>zman</em>. 131 * @return the <em>zman</em>. 132 * @see #setZman(Date) 133 */ 134 public Date getZman() { 135 return this.zman; 136 } 137 138 /** 139 * Sets a {@code Date} based <em>zman</em>. 140 * @param date a {@code Date} based <em>zman</em> 141 * @see #getZman() 142 */ 143 public void setZman(Date date) { 144 this.zman = date; 145 } 146 147 /** 148 * Returns the {link TimeZone} of the <em>zman</em>. 149 * @return the time zone 150 */ 151 public GeoLocation getGeoLocation() { 152 return geoLocation; 153 } 154 155 /** 156 * Sets the {@code GeoLocation} of the <em>zman</em>. 157 * @param geoLocation the {@code GeoLocation} of the <em>zman</em>. 158 */ 159 public void setGeoLocation(GeoLocation geoLocation) { 160 this.geoLocation = geoLocation; 161 } 162 163 /** 164 * Returns a duration based <em>zman</em> such as {@link com.kosherjava.zmanim.AstronomicalCalendar#getTemporalHour() temporal hour} 165 * (or the various <em>shaah zmanis</em> times such as {@link com.kosherjava.zmanim.ZmanimCalendar#getShaahZmanisGra() <em>shaah zmanis GRA</em>} 166 * or {@link com.kosherjava.zmanim.ComplexZmanimCalendar#getShaahZmanis16Point1Degrees() <em>shaah zmanis 16.1°</em>}). 167 * @return the duration based <em>zman</em>. 168 * @see #setDuration(long) 169 */ 170 public long getDuration() { 171 return this.duration; 172 } 173 174 /** 175 * Sets a duration based <em>zman</em> such as {@link com.kosherjava.zmanim.AstronomicalCalendar#getTemporalHour() temporal hour} 176 * (or the various <em>shaah zmanis</em> times as {@link com.kosherjava.zmanim.ZmanimCalendar#getShaahZmanisGra() <em>shaah zmanis GRA</em>} or 177 * {@link com.kosherjava.zmanim.ComplexZmanimCalendar#getShaahZmanis16Point1Degrees() <em>shaah zmanis 16.1°</em>}). 178 * @param duration duration based <em>zman</em> such as {@link com.kosherjava.zmanim.AstronomicalCalendar#getTemporalHour()}. 179 * @see #getDuration() 180 */ 181 public void setDuration(long duration) { 182 this.duration = duration; 183 } 184 185 /** 186 * Returns the name / label of the <em>zman</em> such as "<em>Sof Zman Krias Shema GRA</em>". There are no automatically set labels 187 * and you must set them using {@link #setLabel(String)}. 188 * @return the name/label of the <em>zman</em>. 189 * @see #setLabel(String) 190 */ 191 public String getLabel() { 192 return this.label; 193 } 194 195 /** 196 * Sets the name / label of the <em>zman</em> such as "<em>Sof Zman Krias Shema GRA</em>". 197 * @param label the name / label to set for the <em>zman</em>. 198 * @see #getLabel() 199 */ 200 public void setLabel(String label) { 201 this.label = label; 202 } 203 204 /** 205 * Returns the longer description or explanation of a <em>zman</em>. There is no default value for this and it must be set using 206 * {@link #setDescription(String)} 207 * @return the description or explanation of a <em>zman</em>. 208 * @see #setDescription(String) 209 */ 210 public String getDescription() { 211 return this.description; 212 } 213 214 /** 215 * Sets the longer description or explanation of a <em>zman</em>. 216 * @param description 217 * the <em>zman</em> description to set. 218 * @see #getDescription() 219 */ 220 public void setDescription(String description) { 221 this.description = description; 222 } 223 224 /** 225 * A {@link Comparator} that will compare and sort <em>zmanim</em> by date/time order. Compares its two arguments by the zman's date/time 226 * order. Returns a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater 227 * than the second. 228 * Please note that this class will handle cases where either the {@code Zman} is a null or {@link #getZman()} returns a null. 229 */ 230 public static final Comparator<Zman> DATE_ORDER = new Comparator<Zman>() { 231 public int compare(Zman zman1, Zman zman2) { 232 long firstTime = (zman1 == null || zman1.getZman() == null) ? Long.MAX_VALUE : zman1.getZman().getTime(); 233 long secondTime = (zman2 == null || zman2.getZman() == null) ? Long.MAX_VALUE : zman2.getZman().getTime(); 234 return Long.valueOf(firstTime).compareTo(Long.valueOf(secondTime)); 235 } 236 }; 237 238 /** 239 * A {@link Comparator} that will compare and sort zmanim by zmanim label order. Compares its two arguments by the zmanim label 240 * name order. Returns a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater 241 * than the second. 242 * Please note that this class will sort cases where either the {@code Zman} is a null or {@link #label} returns a null 243 * as empty {@code String}s. 244 */ 245 public static final Comparator<Zman> NAME_ORDER = new Comparator<Zman>() { 246 public int compare(Zman zman1, Zman zman2) { 247 String firstLabel = (zman1 == null || zman1.getLabel() == null) ? "" : zman1.getLabel(); 248 String secondLabel = (zman2 == null || zman2.getLabel() == null) ? "" : zman2.getLabel(); 249 return firstLabel.compareTo(secondLabel); 250 } 251 }; 252 253 /** 254 * A {@link Comparator} that will compare and sort duration based <em>zmanim</em> such as 255 * {@link com.kosherjava.zmanim.AstronomicalCalendar#getTemporalHour() temporal hour} (or the various <em>shaah zmanis</em> times 256 * such as <em>{@link com.kosherjava.zmanim.ZmanimCalendar#getShaahZmanisGra() shaah zmanis GRA}</em> or 257 * {@link com.kosherjava.zmanim.ComplexZmanimCalendar#getShaahZmanis16Point1Degrees() <em>shaah zmanis 16.1°</em>}). Returns a negative 258 * integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second. 259 * Please note that this class will sort cases where {@code Zman} is a null. 260 */ 261 public static final Comparator<Zman> DURATION_ORDER = new Comparator<Zman>() { 262 public int compare(Zman zman1, Zman zman2) { 263 long firstDuration = zman1 == null ? Long.MAX_VALUE : zman1.getDuration(); 264 long secondDuration = zman2 == null ? Long.MAX_VALUE : zman2.getDuration(); 265 return firstDuration == secondDuration ? 0 : firstDuration > secondDuration ? 1 : -1; 266 } 267 }; 268 269 /** 270 * A method that returns an XML formatted <code>String</code> representing the serialized <code>Object</code>. Very 271 * similar to the toString method but the return value is in an xml format. The format currently used (subject to 272 * change) is: 273 * 274 * <pre> 275 * <Zman> 276 * <Label>Sof Zman Krias Shema GRA</Label> 277 * <Zman>1969-02-08T09:37:56.820</Zman> 278 * <TimeZone> 279 * <TimezoneName>America/Montreal</TimezoneName> 280 * <TimeZoneDisplayName>Eastern Standard Time</TimeZoneDisplayName> 281 * <TimezoneGMTOffset>-5</TimezoneGMTOffset> 282 * <TimezoneDSTOffset>1</TimezoneDSTOffset> 283 * </TimeZone> 284 * <Duration>0</Duration> 285 * <Description>Sof Zman Krias Shema GRA is 3 sha'os zmaniyos calculated from sunrise to sunset.</Description> 286 * </Zman> 287 * </pre> 288 * @return The XML formatted <code>String</code>. 289 */ 290 public String toXML() { 291 SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); 292 StringBuilder sb = new StringBuilder(); 293 sb.append("<Zman>\n"); 294 sb.append("\t<Label>").append(getLabel()).append("</Label>\n"); 295 sb.append("\t<Zman>").append(getZman() == null ? "": formatter.format(getZman())).append("</Zman>\n"); 296 sb.append("\t" + getGeoLocation().toXML().replaceAll("\n", "\n\t")); 297 sb.append("\n\t<Duration>").append(getDuration()).append("</Duration>\n"); 298 sb.append("\t<Description>").append(getDescription()).append("</Description>\n"); 299 sb.append("</Zman>"); 300 return sb.toString(); 301 } 302 303 /** 304 * @see java.lang.Object#toString() 305 */ 306 public String toString() { 307 StringBuilder sb = new StringBuilder(); 308 sb.append("\nLabel:\t").append(this.getLabel()); 309 sb.append("\nZman:\t").append(getZman()); 310 sb.append("\nGeoLocation:\t").append(getGeoLocation().toString().replaceAll("\n", "\n\t")); 311 sb.append("\nDuration:\t").append(getDuration()); 312 sb.append("\nDescription:\t").append(getDescription()); 313 return sb.toString(); 314 } 315}