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