1
/*****************************************************************************
2
* Copyright (C) 2008 EnterpriseDB Corporation.
3
* Copyright (C) 2011 Stado Global Development Group.
5
* This file is part of Stado.
7
* Stado is free software: you can redistribute it and/or modify
8
* it under the terms of the GNU General Public License as published by
9
* the Free Software Foundation, either version 3 of the License, or
10
* (at your option) any later version.
12
* Stado is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
* GNU General Public License for more details.
17
* You should have received a copy of the GNU General Public License
18
* along with Stado. If not, see <http://www.gnu.org/licenses/>.
20
* You can find Stado at http://www.stado.us
22
****************************************************************************/
23
package org.postgresql.driver.util;
25
import java.io.Serializable;
26
import java.sql.SQLException;
27
import java.text.DecimalFormat;
28
import java.text.DecimalFormatSymbols;
29
import java.util.Calendar;
30
import java.util.Date;
31
import java.util.StringTokenizer;
34
* This implements a class that handles the PostgreSQL interval type
36
public class PGInterval extends PGobject implements Serializable, Cloneable
44
private double seconds;
46
private final static DecimalFormat secondsFormat;
48
secondsFormat = new DecimalFormat("0.00####");
49
DecimalFormatSymbols dfs = secondsFormat.getDecimalFormatSymbols();
50
dfs.setDecimalSeparator('.');
51
secondsFormat.setDecimalFormatSymbols(dfs);
56
* required by the driver
64
* Initialize a interval with a given interval string representation
66
* @param value String representated interval (e.g. '3 years 2 mons')
67
* @throws SQLException Is thrown if the string representation has an unknown format
68
* @see #setValue(String)
70
public PGInterval(String value)
78
* Initializes all values of this interval to the specified values
80
* @see #setValue(int, int, int, int, int, double)
82
public PGInterval(int years, int months, int days, int hours, int minutes, double seconds)
85
setValue(years, months, days, hours, minutes, seconds);
89
* Sets a interval string represented value to this instance.
90
* This method only recognize the format, that Postgres returns -
91
* not all input formats are supported (e.g. '1 yr 2 m 3 s')!
93
* @param value String representated interval (e.g. '3 years 2 mons')
94
* @throws SQLException Is thrown if the string representation has an unknown format
96
public void setValue(String value)
99
final boolean ISOFormat = !value.startsWith("@");
102
if (!ISOFormat && value.length() == 3 && value.charAt(2) == '0')
104
setValue(0, 0, 0, 0, 0, 0.0);
117
String valueToken = null;
119
value = value.replace('+', ' ').replace('@', ' ');
120
final StringTokenizer st = new StringTokenizer(value);
121
for (int i = 1; st.hasMoreTokens(); i++)
123
String token = st.nextToken();
127
int endHours = token.indexOf(':');
134
// This handles hours, minutes, seconds and microseconds for
136
int offset = (token.charAt(0) == '-') ? 1 : 0;
138
hours = nullSafeIntGet(token.substring(offset+0, endHours));
139
minutes = nullSafeIntGet(token.substring(endHours+1, endHours+3));
141
// Pre 7.4 servers do not put second information into the results
142
// unless it is non-zero.
143
int endMinutes = token.indexOf(':', endHours+1);
144
if (endMinutes != -1)
145
seconds = nullSafeDoubleGet(token.substring(endMinutes+1));
158
// This handles years, months, days for both, ISO and
159
// Non-ISO intervals. Hours, minutes, seconds and microseconds
160
// are handled for Non-ISO intervals here.
162
if (token.startsWith("year"))
163
years = nullSafeIntGet(valueToken);
164
else if (token.startsWith("mon"))
165
months = nullSafeIntGet(valueToken);
166
else if (token.startsWith("day"))
167
days = nullSafeIntGet(valueToken);
168
else if (token.startsWith("hour"))
169
hours = nullSafeIntGet(valueToken);
170
else if (token.startsWith("min"))
171
minutes = nullSafeIntGet(valueToken);
172
else if (token.startsWith("sec"))
173
seconds = nullSafeDoubleGet(valueToken);
177
catch (NumberFormatException e)
179
throw new PSQLException(GT.tr("Conversion of interval failed"), PSQLState.NUMERIC_CONSTANT_OUT_OF_RANGE, e);
182
if (!ISOFormat && value.endsWith("ago"))
184
// Inverse the leading sign
185
setValue(-years, -months, -days, -hours, -minutes, -seconds);
189
setValue(years, months, days, hours, minutes, seconds);
194
* Set all values of this interval to the specified values
196
public void setValue(int years, int months, int days, int hours, int minutes, double seconds)
207
* Returns the stored interval information as a string
209
* @return String represented interval
211
public String getValue()
213
return years + " years " +
218
secondsFormat.format(seconds) + " secs";
222
* Returns the years represented by this interval
224
public int getYears()
230
* Set the years of this interval to the specified value
232
public void setYears(int years)
238
* Returns the months represented by this interval
240
public int getMonths()
246
* Set the months of this interval to the specified value
248
public void setMonths(int months)
250
this.months = months;
254
* Returns the days represented by this interval
262
* Set the days of this interval to the specified value
264
public void setDays(int days)
270
* Returns the hours represented by this interval
272
public int getHours()
278
* Set the hours of this interval to the specified value
280
public void setHours(int hours)
286
* Returns the minutes represented by this interval
288
public int getMinutes()
294
* Set the minutes of this interval to the specified value
296
public void setMinutes(int minutes)
298
this.minutes = minutes;
302
* Returns the seconds represented by this interval
304
public double getSeconds()
310
* Set the seconds of this interval to the specified value
312
public void setSeconds(double seconds)
314
this.seconds = seconds;
318
* Rolls this interval on a given calendar
320
* @param cal Calendar instance to add to
322
public void add(Calendar cal)
324
// Avoid precision loss
325
// Be aware postgres doesn't return more than 60 seconds - no overflow can happen
326
final int microseconds = (int)(getSeconds() * 1000000.0);
327
final int milliseconds = (microseconds + ((microseconds < 0) ? -500 : 500)) / 1000;
329
cal.add(Calendar.MILLISECOND, milliseconds);
330
cal.add(Calendar.MINUTE, getMinutes());
331
cal.add(Calendar.HOUR, getHours());
332
cal.add(Calendar.DAY_OF_MONTH, getDays());
333
cal.add(Calendar.MONTH, getMonths());
334
cal.add(Calendar.YEAR, getYears());
338
* Rolls this interval on a given date
340
* @param date Date instance to add to
342
public void add(Date date)
344
final Calendar cal = Calendar.getInstance();
347
date.setTime(cal.getTime().getTime());
351
* Add this interval's value to the passed interval.
352
* This is backwards to what I would expect, but
353
* this makes it match the other existing add methods.
355
public void add(PGInterval interval)
357
interval.setYears(interval.getYears() + getYears());
358
interval.setMonths(interval.getMonths() + getMonths());
359
interval.setDays(interval.getDays() + getDays());
360
interval.setHours(interval.getHours() + getHours());
361
interval.setMinutes(interval.getMinutes() + getMinutes());
362
interval.setSeconds(interval.getSeconds() + getSeconds());
366
* Scale this interval by an integer factor. The server
367
* can scale by arbitrary factors, but that would require
368
* adjusting the call signatures for all the existing methods
369
* like getDays() or providing our own justification of fractional
370
* intervals. Neither of these seem like a good idea without a
373
public void scale(int factor)
375
setYears(factor * getYears());
376
setMonths(factor * getMonths());
377
setDays(factor * getDays());
378
setHours(factor * getHours());
379
setMinutes(factor * getMinutes());
380
setSeconds(factor * getSeconds());
384
* Returns integer value of value or 0 if value is null
386
* @param value integer as string value
387
* @return integer parsed from string value
388
* @throws NumberFormatException if the string contains invalid chars
390
private int nullSafeIntGet(String value)
391
throws NumberFormatException
393
return (value == null) ? 0 : Integer.parseInt(value);
397
* Returns double value of value or 0 if value is null
399
* @param value double as string value
400
* @return double parsed from string value
401
* @throws NumberFormatException if the string contains invalid chars
403
private double nullSafeDoubleGet(String value)
404
throws NumberFormatException
406
return (value == null) ? 0 : Double.parseDouble(value);
410
* Returns whether an object is equal to this one or not
412
* @param obj Object to compare with
413
* @return true if the two intervals are identical
415
public boolean equals(Object obj)
423
if (!(obj instanceof PGInterval))
426
final PGInterval pgi = (PGInterval)obj;
429
pgi.years == years &&
430
pgi.months == months &&
432
pgi.hours == hours &&
433
pgi.minutes == minutes &&
434
Double.doubleToLongBits(pgi.seconds) == Double.doubleToLongBits(seconds);
438
* Returns a hashCode for this object
442
public int hashCode()
444
return ((((((7 * 31 +
445
(int)Double.doubleToLongBits(seconds)) * 31 +