001: /*-------------------------------------------------------------------------
002: *
003: * Copyright (c) 2004-2005, PostgreSQL Global Development Group
004: *
005: * IDENTIFICATION
006: * $PostgreSQL: pgjdbc/org/postgresql/util/PGInterval.java,v 1.13 2007/07/16 15:02:53 jurka Exp $
007: *
008: *-------------------------------------------------------------------------
009: */
010: package org.postgresql.util;
011:
012: import java.io.Serializable;
013: import java.sql.SQLException;
014: import java.text.DecimalFormat;
015: import java.text.DecimalFormatSymbols;
016: import java.util.Calendar;
017: import java.util.Date;
018: import java.util.StringTokenizer;
019:
020: /**
021: * This implements a class that handles the PostgreSQL interval type
022: */
023: public class PGInterval extends PGobject implements Serializable,
024: Cloneable {
025:
026: private int years;
027: private int months;
028: private int days;
029: private int hours;
030: private int minutes;
031: private double seconds;
032:
033: private final static DecimalFormat secondsFormat;
034: static {
035: secondsFormat = new DecimalFormat("0.00####");
036: DecimalFormatSymbols dfs = secondsFormat
037: .getDecimalFormatSymbols();
038: dfs.setDecimalSeparator('.');
039: secondsFormat.setDecimalFormatSymbols(dfs);
040: }
041:
042: /**
043: * required by the driver
044: */
045: public PGInterval() {
046: setType("interval");
047: }
048:
049: /**
050: * Initialize a interval with a given interval string representation
051: *
052: * @param value String representated interval (e.g. '3 years 2 mons')
053: * @throws SQLException Is thrown if the string representation has an unknown format
054: * @see #setValue(String)
055: */
056: public PGInterval(String value) throws SQLException {
057: this ();
058: setValue(value);
059: }
060:
061: /**
062: * Initializes all values of this interval to the specified values
063: *
064: * @see #setValue(int, int, int, int, int, double)
065: */
066: public PGInterval(int years, int months, int days, int hours,
067: int minutes, double seconds) {
068: this ();
069: setValue(years, months, days, hours, minutes, seconds);
070: }
071:
072: /**
073: * Sets a interval string represented value to this instance.
074: * This method only recognize the format, that Postgres returns -
075: * not all input formats are supported (e.g. '1 yr 2 m 3 s')!
076: *
077: * @param value String representated interval (e.g. '3 years 2 mons')
078: * @throws SQLException Is thrown if the string representation has an unknown format
079: */
080: public void setValue(String value) throws SQLException {
081: final boolean ISOFormat = !value.startsWith("@");
082:
083: // Just a simple '0'
084: if (!ISOFormat && value.length() == 3 && value.charAt(2) == '0') {
085: setValue(0, 0, 0, 0, 0, 0.0);
086: return;
087: }
088:
089: int years = 0;
090: int months = 0;
091: int days = 0;
092: int hours = 0;
093: int minutes = 0;
094: double seconds = 0;
095:
096: try {
097: String valueToken = null;
098:
099: value = value.replace('+', ' ').replace('@', ' ');
100: final StringTokenizer st = new StringTokenizer(value);
101: for (int i = 1; st.hasMoreTokens(); i++) {
102: String token = st.nextToken();
103:
104: if ((i & 1) == 1) {
105: int endHours = token.indexOf(':');
106: if (endHours == -1) {
107: valueToken = token;
108: continue;
109: }
110:
111: // This handles hours, minutes, seconds and microseconds for
112: // ISO intervals
113: int offset = (token.charAt(0) == '-') ? 1 : 0;
114:
115: hours = nullSafeIntGet(token.substring(offset + 0,
116: endHours));
117: minutes = nullSafeIntGet(token.substring(
118: endHours + 1, endHours + 3));
119:
120: // Pre 7.4 servers do not put second information into the results
121: // unless it is non-zero.
122: int endMinutes = token.indexOf(':', endHours + 1);
123: if (endMinutes != -1)
124: seconds = nullSafeDoubleGet(token
125: .substring(endMinutes + 1));
126:
127: if (offset == 1) {
128: hours = -hours;
129: minutes = -minutes;
130: seconds = -seconds;
131: }
132:
133: valueToken = null;
134: } else {
135: // This handles years, months, days for both, ISO and
136: // Non-ISO intervals. Hours, minutes, seconds and microseconds
137: // are handled for Non-ISO intervals here.
138:
139: if (token.startsWith("year"))
140: years = nullSafeIntGet(valueToken);
141: else if (token.startsWith("mon"))
142: months = nullSafeIntGet(valueToken);
143: else if (token.startsWith("day"))
144: days = nullSafeIntGet(valueToken);
145: else if (token.startsWith("hour"))
146: hours = nullSafeIntGet(valueToken);
147: else if (token.startsWith("min"))
148: minutes = nullSafeIntGet(valueToken);
149: else if (token.startsWith("sec"))
150: seconds = nullSafeDoubleGet(valueToken);
151: }
152: }
153: } catch (NumberFormatException e) {
154: throw new PSQLException(GT
155: .tr("Conversion of interval failed"),
156: PSQLState.NUMERIC_CONSTANT_OUT_OF_RANGE, e);
157: }
158:
159: if (!ISOFormat && value.endsWith("ago")) {
160: // Inverse the leading sign
161: setValue(-years, -months, -days, -hours, -minutes, -seconds);
162: } else {
163: setValue(years, months, days, hours, minutes, seconds);
164: }
165: }
166:
167: /**
168: * Set all values of this interval to the specified values
169: */
170: public void setValue(int years, int months, int days, int hours,
171: int minutes, double seconds) {
172: setYears(years);
173: setMonths(months);
174: setDays(days);
175: setHours(hours);
176: setMinutes(minutes);
177: setSeconds(seconds);
178: }
179:
180: /**
181: * Returns the stored interval information as a string
182: *
183: * @return String represented interval
184: */
185: public String getValue() {
186: return years + " years " + months + " mons " + days + " days "
187: + hours + " hours " + minutes + " mins "
188: + secondsFormat.format(seconds) + " secs";
189: }
190:
191: /**
192: * Returns the years represented by this interval
193: */
194: public int getYears() {
195: return years;
196: }
197:
198: /**
199: * Set the years of this interval to the specified value
200: */
201: public void setYears(int years) {
202: this .years = years;
203: }
204:
205: /**
206: * Returns the months represented by this interval
207: */
208: public int getMonths() {
209: return months;
210: }
211:
212: /**
213: * Set the months of this interval to the specified value
214: */
215: public void setMonths(int months) {
216: this .months = months;
217: }
218:
219: /**
220: * Returns the days represented by this interval
221: */
222: public int getDays() {
223: return days;
224: }
225:
226: /**
227: * Set the days of this interval to the specified value
228: */
229: public void setDays(int days) {
230: this .days = days;
231: }
232:
233: /**
234: * Returns the hours represented by this interval
235: */
236: public int getHours() {
237: return hours;
238: }
239:
240: /**
241: * Set the hours of this interval to the specified value
242: */
243: public void setHours(int hours) {
244: this .hours = hours;
245: }
246:
247: /**
248: * Returns the minutes represented by this interval
249: */
250: public int getMinutes() {
251: return minutes;
252: }
253:
254: /**
255: * Set the minutes of this interval to the specified value
256: */
257: public void setMinutes(int minutes) {
258: this .minutes = minutes;
259: }
260:
261: /**
262: * Returns the seconds represented by this interval
263: */
264: public double getSeconds() {
265: return seconds;
266: }
267:
268: /**
269: * Set the seconds of this interval to the specified value
270: */
271: public void setSeconds(double seconds) {
272: this .seconds = seconds;
273: }
274:
275: /**
276: * Rolls this interval on a given calendar
277: *
278: * @param cal Calendar instance to add to
279: */
280: public void add(Calendar cal) {
281: // Avoid precision loss
282: // Be aware postgres doesn't return more than 60 seconds - no overflow can happen
283: final int microseconds = (int) (getSeconds() * 1000000.0);
284: final int milliseconds = (microseconds + ((microseconds < 0) ? -500
285: : 500)) / 1000;
286:
287: cal.add(Calendar.MILLISECOND, milliseconds);
288: cal.add(Calendar.MINUTE, getMinutes());
289: cal.add(Calendar.HOUR, getHours());
290: cal.add(Calendar.DAY_OF_MONTH, getDays());
291: cal.add(Calendar.MONTH, getMonths());
292: cal.add(Calendar.YEAR, getYears());
293: }
294:
295: /**
296: * Rolls this interval on a given date
297: *
298: * @param date Date instance to add to
299: */
300: public void add(Date date) {
301: final Calendar cal = Calendar.getInstance();
302: cal.setTime(date);
303: add(cal);
304: date.setTime(cal.getTime().getTime());
305: }
306:
307: /**
308: * Add this interval's value to the passed interval.
309: * This is backwards to what I would expect, but
310: * this makes it match the other existing add methods.
311: */
312: public void add(PGInterval interval) {
313: interval.setYears(interval.getYears() + getYears());
314: interval.setMonths(interval.getMonths() + getMonths());
315: interval.setDays(interval.getDays() + getDays());
316: interval.setHours(interval.getHours() + getHours());
317: interval.setMinutes(interval.getMinutes() + getMinutes());
318: interval.setSeconds(interval.getSeconds() + getSeconds());
319: }
320:
321: /**
322: * Scale this interval by an integer factor. The server
323: * can scale by arbitrary factors, but that would require
324: * adjusting the call signatures for all the existing methods
325: * like getDays() or providing our own justification of fractional
326: * intervals. Neither of these seem like a good idea without a
327: * strong use case.
328: */
329: public void scale(int factor) {
330: setYears(factor * getYears());
331: setMonths(factor * getMonths());
332: setDays(factor * getDays());
333: setHours(factor * getHours());
334: setMinutes(factor * getMinutes());
335: setSeconds(factor * getSeconds());
336: }
337:
338: /**
339: * Returns integer value of value or 0 if value is null
340: *
341: * @param value integer as string value
342: * @return integer parsed from string value
343: * @throws NumberFormatException if the string contains invalid chars
344: */
345: private int nullSafeIntGet(String value)
346: throws NumberFormatException {
347: return (value == null) ? 0 : Integer.parseInt(value);
348: }
349:
350: /**
351: * Returns double value of value or 0 if value is null
352: *
353: * @param value double as string value
354: * @return double parsed from string value
355: * @throws NumberFormatException if the string contains invalid chars
356: */
357: private double nullSafeDoubleGet(String value)
358: throws NumberFormatException {
359: return (value == null) ? 0 : Double.parseDouble(value);
360: }
361:
362: /**
363: * Returns whether an object is equal to this one or not
364: *
365: * @param obj Object to compare with
366: * @return true if the two intervals are identical
367: */
368: public boolean equals(Object obj) {
369: if (obj == null)
370: return false;
371:
372: if (obj == this )
373: return true;
374:
375: if (!(obj instanceof PGInterval))
376: return false;
377:
378: final PGInterval pgi = (PGInterval) obj;
379:
380: return pgi.years == years
381: && pgi.months == months
382: && pgi.days == days
383: && pgi.hours == hours
384: && pgi.minutes == minutes
385: && Double.doubleToLongBits(pgi.seconds) == Double
386: .doubleToLongBits(seconds);
387: }
388:
389: /**
390: * Returns a hashCode for this object
391: *
392: * @return hashCode
393: */
394: public int hashCode() {
395: return ((((((7 * 31 + (int) Double.doubleToLongBits(seconds)) * 31 + minutes) * 31 + hours) * 31 + days) * 31 + months) * 31 + years) * 31;
396: }
397:
398: }
|