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 = &quot;Lakewood, NJ&quot;;
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(&quot;America/New_York&quot;);
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&lt;Zman&gt; zl = new ArrayList&lt;Zman&gt;();
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 &copy; 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&deg;</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&deg;</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&deg;</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&deg;</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&deg;</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         * &lt;Zman&gt;
276         *      &lt;Label&gt;Sof Zman Krias Shema GRA&lt;/Label&gt;
277         *      &lt;Zman&gt;1969-02-08T09:37:56.820&lt;/Zman&gt;
278         *      &lt;TimeZone&gt;
279         *              &lt;TimezoneName&gt;America/Montreal&lt;/TimezoneName&gt;
280         *              &lt;TimeZoneDisplayName&gt;Eastern Standard Time&lt;/TimeZoneDisplayName&gt;
281         *              &lt;TimezoneGMTOffset&gt;-5&lt;/TimezoneGMTOffset&gt;
282         *              &lt;TimezoneDSTOffset&gt;1&lt;/TimezoneDSTOffset&gt;
283         *      &lt;/TimeZone&gt;
284         *      &lt;Duration&gt;0&lt;/Duration&gt;
285         *      &lt;Description&gt;Sof Zman Krias Shema GRA is 3 sha'os zmaniyos calculated from sunrise to sunset.&lt;/Description&gt;
286         * &lt;/Zman&gt;
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}