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.util;
018
019 import java.lang.reflect.Method;
020 import java.text.DateFormat;
021 import java.text.DecimalFormat;
022 import java.util.ArrayList;
023 import java.util.Collections;
024 import java.util.Date;
025 import java.util.Calendar;
026 import java.util.List;
027 import java.text.SimpleDateFormat;
028 import net.sourceforge.zmanim.*;
029
030
031 /**
032 * A class used to format non {@link java.util.Date} times generated by the
033 * Zmanim package. For example the
034 * {@link net.sourceforge.zmanim.AstronomicalCalendar#getTemporalHour()} returns
035 * the length of the hour in milliseconds. This class can format this time.
036 *
037 * @author © Eliyahu Hershfeld 2004 - 2007
038 * @version 0.9.0
039 */
040 public class ZmanimFormatter {
041 private boolean prependZeroHours;
042
043 private boolean useSeconds;
044
045 private boolean useMillis;
046
047 boolean useDecimal;
048
049 private static DecimalFormat minuteSecondNF = new DecimalFormat("00");;
050
051 private DecimalFormat hourNF;
052
053 private static DecimalFormat milliNF = new DecimalFormat("000");
054
055 private SimpleDateFormat dateFormat;
056
057 // private DecimalFormat decimalNF;
058
059 /**
060 * Format using hours, minutes, seconds and milliseconds using the xsd:time
061 * format. This format will return 00.00.00.0 when formatting 0.
062 */
063 public static final int SEXAGESIMAL_XSD_FORMAT = 0;
064
065 private int timeFormat = SEXAGESIMAL_XSD_FORMAT;
066
067 /**
068 * Format using standard decimal format with 5 positions after the decimal.
069 */
070 public static final int DECIMAL_FORMAT = 1;
071
072 /** Format using hours and minutes. */
073 public static final int SEXAGESIMAL_FORMAT = 2;
074
075 /** Format using hours, minutes and seconds. */
076 public static final int SEXAGESIMAL_SECONDS_FORMAT = 3;
077
078 /** Format using hours, minutes, seconds and milliseconds. */
079 public static final int SEXAGESIMAL_MILLIS_FORMAT = 4;
080
081 /** constant for milliseconds in a minute (60,000) */
082 static final long MINUTE_MILLIS = 60 * 1000;
083
084 /** constant for milliseconds in an hour (3,600,000) */
085 static final long HOUR_MILLIS = MINUTE_MILLIS * 60;
086
087 /**
088 * Format using the XSD Duration format. This is in the format of
089 * PT1H6M7.869S (P for period (duration), T for time, H, M and S indicate
090 * hours, minutes and seconds.
091 */
092 public static final int XSD_DURATION_FORMAT = 5;
093
094 public ZmanimFormatter() {
095 this(0, new SimpleDateFormat("h:mm:ss"));
096 }
097
098 /**
099 * ZmanimFormatter constructor using a formatter
100 *
101 * @param format
102 * int The formatting style to use. Using
103 * ZmanimFormatter.SEXAGESIMAL_SECONDS_FORMAT will format the
104 * time time of 90*60*1000 + 1 as 1:30:00
105 */
106 public ZmanimFormatter(int format, SimpleDateFormat dateFormat) {
107 String hourFormat = "0";
108 if (prependZeroHours) {
109 hourFormat = "00";
110 }
111 hourNF = new DecimalFormat(hourFormat);
112 // decimalNF = new DecimalFormat("0.0####");
113 setTimeFormat(format);
114 this.setDateFormat(dateFormat);
115 }
116
117 /**
118 * Sets the format to use for formatting.
119 *
120 * @param format
121 * int the format constant to use.
122 */
123 public void setTimeFormat(int format) {
124 timeFormat = format;
125 switch (format) {
126 case SEXAGESIMAL_XSD_FORMAT:
127 setSettings(true, true, true);
128 break;
129 case SEXAGESIMAL_FORMAT:
130 setSettings(false, false, false);
131 break;
132 case SEXAGESIMAL_SECONDS_FORMAT:
133 setSettings(false, true, false);
134 break;
135 case SEXAGESIMAL_MILLIS_FORMAT:
136 setSettings(false, true, true);
137 break;
138 case DECIMAL_FORMAT:
139 default:
140 useDecimal = true;
141 }
142 }
143
144 public void setDateFormat(SimpleDateFormat sdf) {
145 dateFormat = sdf;
146 }
147
148 public SimpleDateFormat getDateFormat() {
149 return dateFormat;
150 }
151
152 private void setSettings(boolean prependZeroHours, boolean useSeconds,
153 boolean useMillis) {
154 this.prependZeroHours = prependZeroHours;
155 this.useSeconds = useSeconds;
156 this.useMillis = useMillis;
157 }
158
159 /**
160 * A method that formats milliseconds into a time format.
161 *
162 * @param milliseconds
163 * The time in milliseconds.
164 * @return String The formatted <code>String</code>
165 */
166 public String format(double milliseconds) {
167 return format((int) milliseconds);
168 }
169
170 /**
171 * A method that formats milliseconds into a time format.
172 *
173 * @param millis
174 * The time in milliseconds.
175 * @return String The formatted <code>String</code>
176 */
177 public String format(int millis) {
178 return format(new Time(millis));
179 }
180
181 /**
182 * A method that formats {@link Time}objects.
183 *
184 * @param time
185 * The time <code>Object</code> to be formatted.
186 * @return String The formatted <code>String</code>
187 */
188 public String format(Time time) {
189 if (timeFormat == XSD_DURATION_FORMAT) {
190 return formatXSDDurationTime(time);
191 }
192 StringBuffer sb = new StringBuffer();
193 sb.append(hourNF.format(time.getHours()));
194 sb.append(":");
195 sb.append(minuteSecondNF.format(time.getMinutes()));
196 if (useSeconds) {
197 sb.append(":");
198 sb.append(minuteSecondNF.format(time.getSeconds()));
199 }
200 if (useMillis) {
201 sb.append(".");
202 sb.append(milliNF.format(time.getMilliseconds()));
203 }
204 return sb.toString();
205 }
206
207 /**
208 * Formats a date using this classe's {@link #getDateFormat() date format}.
209 *
210 * @param dateTime
211 * the date to format
212 * @param calendar
213 * the {@link java.util.Calendar Calendar} used to help format
214 * based on the Calendar's DST and other settings.
215 * @return the formatted String
216 */
217 public String formatDateTime(Date dateTime, Calendar calendar) {
218 dateFormat.setCalendar(calendar);
219 if (this.dateFormat.toPattern().equals("yyyy-MM-dd'T'HH:mm:ss")) {
220 return getXSDateTime(dateTime, calendar);
221 } else {
222 return dateFormat.format(dateTime);
223 }
224
225 }
226
227 /**
228 * The date:date-time function returns the current date and time as a
229 * date/time string. The date/time string that's returned must be a string
230 * in the format defined as the lexical representation of xs:dateTime in <a
231 * href="http://www.w3.org/TR/xmlschema11-2/#dateTime">[3.3.8 dateTime]</a>
232 * of <a href="http://www.w3.org/TR/xmlschema11-2/">[XML Schema 1.1 Part 2:
233 * Datatypes]</a>. The date/time format is basically CCYY-MM-DDThh:mm:ss,
234 * although implementers should consult <a
235 * href="http://www.w3.org/TR/xmlschema11-2/">[XML Schema 1.1 Part 2:
236 * Datatypes]</a> and <a href="http://www.iso.ch/markete/8601.pdf">[ISO
237 * 8601]</a> for details. The date/time string format must include a time
238 * zone, either a Z to indicate Coordinated Universal Time or a + or -
239 * followed by the difference between the difference from UTC represented as
240 * hh:mm.
241 */
242 public String getXSDateTime(Date dateTime, Calendar cal) {
243 String xsdDateTimeFormat = "yyyy-MM-dd'T'HH:mm:ss";
244 /*
245 * if (xmlDateFormat == null || xmlDateFormat.trim().equals("")) {
246 * xmlDateFormat = xsdDateTimeFormat; }
247 */
248 SimpleDateFormat dateFormat = new SimpleDateFormat(xsdDateTimeFormat);
249
250 StringBuffer buff = new StringBuffer(dateFormat.format(dateTime));
251 // Must also include offset from UTF.
252 // Get the offset (in milliseconds).
253 int offset = cal.get(Calendar.ZONE_OFFSET)
254 + cal.get(Calendar.DST_OFFSET);
255 // If there is no offset, we have "Coordinated
256 // Universal Time."
257 if (offset == 0)
258 buff.append("Z");
259 else {
260 // Convert milliseconds to hours and minutes
261 int hrs = offset / (60 * 60 * 1000);
262 // In a few cases, the time zone may be +/-hh:30.
263 int min = offset % (60 * 60 * 1000);
264 char posneg = hrs < 0 ? '-' : '+';
265 buff.append(posneg + formatDigits(hrs) + ':' + formatDigits(min));
266 }
267 return buff.toString();
268 }
269
270 /**
271 * Represent the hours and minutes with two-digit strings.
272 *
273 * @param digits hours or minutes.
274 * @return two-digit String representation of hrs or minutes.
275 */
276 private static String formatDigits(int digits) {
277 String dd = String.valueOf(Math.abs(digits));
278 return dd.length() == 1 ? '0' + dd : dd;
279 }
280
281 /**
282 * This returns the xml representation of an xsd:duration object.
283 *
284 * @param millis the duration in milliseconds
285 * @return the xsd:duration formatted String
286 */
287 public String formatXSDDurationTime(long millis) {
288 return formatXSDDurationTime(new Time(millis));
289 }
290
291 /**
292 * This returns the xml representation of an xsd:duration object.
293 *
294 * @param time the duration as a Time object
295 * @return the xsd:duration formatted String
296 */
297 public String formatXSDDurationTime(Time time) {
298 StringBuffer duration = new StringBuffer();
299
300 duration.append("P");
301
302 if (time.getHours() != 0 || time.getMinutes() != 0
303 || time.getSeconds() != 0 || time.getMilliseconds() != 0) {
304 duration.append("T");
305
306 if (time.getHours() != 0)
307 duration.append(time.getHours() + "H");
308
309 if (time.getMinutes() != 0)
310 duration.append(time.getMinutes() + "M");
311
312 if (time.getSeconds() != 0 || time.getMilliseconds() != 0) {
313 duration.append(time.getSeconds() + "."
314 + milliNF.format(time.getMilliseconds()));
315 duration.append("S");
316 }
317 if (duration.length() == 1) // zero seconds
318 duration.append("T0S");
319 if (time.isNegative())
320 duration.insert(0, "-");
321 }
322 return duration.toString();
323 }
324
325 /**
326 * A method that returns an XML formatted <code>String</code> representing
327 * the serialized <code>Object</code>. The format used is:
328 *
329 * <pre>
330 * <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">
331 * <Sunrise>2007-02-18T06:45:27-05:00</Sunrise>
332 * <TemporalHour>PT54M17.529S</TemporalHour>
333 * ...
334 * </AstronomicalTimes>
335 * </pre>
336 *
337 * Note that the output uses the <a
338 * href="http://www.w3.org/TR/xmlschema11-2/#dateTime">xsd:dateTime</a>
339 * format for times such as sunrise, and <a
340 * href="http://www.w3.org/TR/xmlschema11-2/#duration">xsd:duration</a>
341 * format for times that are a duration such as the length of a
342 * {@link net.sourceforge.zmanim.AstronomicalCalendar#getTemporalHour() temporal hour}. The output of this method is
343 * returned by the {@link #toString() toString} }.
344 *
345 * @return The XML formatted <code>String</code>. The format will be:
346 *
347 * <pre>
348 * <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">
349 * <Sunrise>2007-02-18T06:45:27-05:00</Sunrise>
350 * <TemporalHour>PT54M17.529S</TemporalHour>
351 * ...
352 * </AstronomicalTimes>
353 * </pre>
354 *
355 */
356 public static String toXML(AstronomicalCalendar ac) {
357 ZmanimFormatter formatter = new ZmanimFormatter(
358 ZmanimFormatter.XSD_DURATION_FORMAT, new SimpleDateFormat(
359 "yyyy-MM-dd'T'HH:mm:ss"));
360 DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
361 String output = "<";
362 if (ac.getClass().getName().endsWith("AstronomicalCalendar")) {
363 output += "AstronomicalTimes";
364 } else if (ac.getClass().getName().endsWith("ZmanimCalendar")) {
365 output += "Zmanim";
366 }
367 output += " date=\"" + df.format(ac.getCalendar().getTime()) + "\"";
368 output += " type=\"" + ac.getClass().getName() + "\"";
369 output += " algorithm=\""
370 + ac.getAstronomicalCalculator().getCalculatorName() + "\"";
371 output += " location=\"" + ac.getGeoLocation().getLocationName() + "\"";
372 output += " latitude=\"" + ac.getGeoLocation().getLatitude() + "\"";
373 output += " longitude=\"" + ac.getGeoLocation().getLongitude() + "\"";
374 output += " elevation=\"" + ac.getGeoLocation().getElevation() + "\"";
375 output += " timeZoneName=\""
376 + ac.getGeoLocation().getTimeZone().getDisplayName() + "\"";
377 output += " timeZoneID=\"" + ac.getGeoLocation().getTimeZone().getID()
378 + "\"";
379 output += " timeZoneOffset=\""
380 + (ac.getGeoLocation().getTimeZone().getOffset(
381 ac.getCalendar().getTimeInMillis()) / (HOUR_MILLIS))
382 + "\"";
383
384 output += ">\n";
385
386 Method[] theMethods = ac.getClass().getMethods();
387 String tagName = "";
388 Object value = null;
389 List dateList = new ArrayList();
390 List durationList = new ArrayList();
391 List otherList = new ArrayList();
392 for (int i = 0; i < theMethods.length; i++) {
393 if (includeMethod(theMethods[i])) {
394 tagName = theMethods[i].getName().substring(3);
395 //String returnType = theMethods[i].getReturnType().getName();
396 try {
397 value = theMethods[i].invoke(ac, (Object[]) null);
398 if (value == null) {//FIXME: use reflection to determine what the return type is, not the value
399 otherList.add("<" + tagName + ">N/A</" + tagName + ">");
400 } else if (value instanceof Date) {
401 dateList.add(new Zman((Date) value, tagName));
402 } else if (value instanceof Long) {// shaah zmanis
403 durationList.add(new Zman((int) ((Long) value)
404 .longValue(), tagName));
405 } else { // will probably never enter this block, but is
406 // present to be future proof
407 otherList.add("<" + tagName + ">" + value + "</"
408 + tagName + ">");
409 }
410 } catch (Exception e) {
411 e.printStackTrace();
412 }
413 }
414 }
415 Zman zman;
416 Collections.sort(dateList, Zman.DATE_ORDER);
417 for (int i = 0; i < dateList.size(); i++) {
418 zman = (Zman) dateList.get(i);
419 output += "\t<" + zman.getZmanLabel();
420 output += ">";
421 output += formatter.formatDateTime(zman.getZman(), ac.getCalendar())
422 + "</" + zman.getZmanLabel() + ">\n";
423 }
424 Collections.sort(durationList, Zman.DURATION_ORDER);
425 for (int i = 0; i < durationList.size(); i++) {
426 zman = (Zman) durationList.get(i);
427 output += "\t<" + zman.getZmanLabel();
428 output += ">";
429 output += formatter.format((int) zman.getDuration()) + "</"
430 + zman.getZmanLabel() + ">\n";
431 }
432
433 for (int i = 0; i < otherList.size(); i++) {// will probably never enter
434 // this block
435 output += "\t" + otherList.get(i) + "\n";
436 }
437
438 if (ac.getClass().getName().endsWith("AstronomicalCalendar")) {
439 output += "</AstronomicalTimes>";
440 } else if (ac.getClass().getName().endsWith("ZmanimCalendar")) {
441 output += "</Zmanim>";
442 }
443 return output;
444 }
445
446 /**
447 * Determines if a method should be output by the {@link #toXML(AstronomicalCalendar)}
448 *
449 * @param method
450 * @return
451 */
452 private static boolean includeMethod(Method method) {
453 List methodWhiteList = new ArrayList();
454 // methodWhiteList.add("getName");
455
456 List methodBlackList = new ArrayList();
457 // methodBlackList.add("getGregorianChange");
458
459 if (methodWhiteList.contains(method.getName()))
460 return true;
461 if (methodBlackList.contains(method.getName()))
462 return false;
463
464 if (method.getParameterTypes().length > 0)
465 return false; // Skip get methods with parameters since we do not
466 // know what value to pass
467 if (!method.getName().startsWith("get"))
468 return false;
469
470 if (method.getReturnType().getName().endsWith("Date")
471 || method.getReturnType().getName().endsWith("long")) {
472 return true;
473 }
474 return false;
475 }
476 }