~sgdg/stado/stado25

« back to all changes in this revision

Viewing changes to src/org/postgresql/driver/util/PGInterval.java

  • Committer: Jim Mlodgenski
  • Date: 2011-08-30 22:39:37 UTC
  • mfrom: (1.1.3 stado)
  • Revision ID: jim@cirrusql.com-20110830223937-25q231a31x0e08b4
Merge from Spatial branch

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*****************************************************************************
 
2
 * Copyright (C) 2008 EnterpriseDB Corporation.
 
3
 * Copyright (C) 2011 Stado Global Development Group.
 
4
 *
 
5
 * This file is part of Stado.
 
6
 *
 
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.
 
11
 *
 
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.
 
16
 *
 
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/>.
 
19
 *
 
20
 * You can find Stado at http://www.stado.us
 
21
 *
 
22
 ****************************************************************************/
 
23
package org.postgresql.driver.util;
 
24
 
 
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;
 
32
 
 
33
/**
 
34
 * This implements a class that handles the PostgreSQL interval type
 
35
 */
 
36
public class PGInterval extends PGobject implements Serializable, Cloneable
 
37
{
 
38
 
 
39
    private int years;
 
40
    private int months;
 
41
    private int days;
 
42
    private int hours;
 
43
    private int minutes;
 
44
    private double seconds;
 
45
 
 
46
    private final static DecimalFormat secondsFormat;
 
47
    static {
 
48
        secondsFormat = new DecimalFormat("0.00####");
 
49
        DecimalFormatSymbols dfs = secondsFormat.getDecimalFormatSymbols();
 
50
        dfs.setDecimalSeparator('.');
 
51
        secondsFormat.setDecimalFormatSymbols(dfs);
 
52
    }
 
53
 
 
54
 
 
55
    /**
 
56
     * required by the driver
 
57
     */
 
58
    public PGInterval()
 
59
    {
 
60
        setType("interval");
 
61
    }
 
62
 
 
63
    /**
 
64
     * Initialize a interval with a given interval string representation
 
65
     *
 
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)
 
69
     */
 
70
    public PGInterval(String value)
 
71
    throws SQLException
 
72
    {
 
73
        this();
 
74
        setValue(value);
 
75
    }
 
76
 
 
77
    /**
 
78
     * Initializes all values of this interval to the specified values
 
79
     *
 
80
     * @see #setValue(int, int, int, int, int, double)
 
81
     */
 
82
    public PGInterval(int years, int months, int days, int hours, int minutes, double seconds)
 
83
    {
 
84
        this();
 
85
        setValue(years, months, days, hours, minutes, seconds);
 
86
    }
 
87
 
 
88
    /**
 
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')!
 
92
     *
 
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
 
95
     */
 
96
    public void setValue(String value)
 
97
    throws SQLException
 
98
    {
 
99
        final boolean ISOFormat = !value.startsWith("@");
 
100
 
 
101
        // Just a simple '0'
 
102
        if (!ISOFormat && value.length() == 3 && value.charAt(2) == '0')
 
103
        {
 
104
            setValue(0, 0, 0, 0, 0, 0.0);
 
105
            return;
 
106
        }
 
107
 
 
108
        int    years   = 0;
 
109
        int    months  = 0;
 
110
        int    days    = 0;
 
111
        int    hours   = 0;
 
112
        int    minutes = 0;
 
113
        double seconds = 0;
 
114
 
 
115
        try
 
116
        {
 
117
            String valueToken = null;
 
118
 
 
119
            value = value.replace('+', ' ').replace('@', ' ');
 
120
            final StringTokenizer st = new StringTokenizer(value);
 
121
            for (int i = 1; st.hasMoreTokens(); i++)
 
122
            {
 
123
                String token = st.nextToken();
 
124
 
 
125
                if ((i & 1) == 1)
 
126
                {
 
127
                    int endHours = token.indexOf(':'); 
 
128
                    if (endHours == -1)
 
129
                    {
 
130
                        valueToken = token;
 
131
                        continue;
 
132
                    }
 
133
 
 
134
                    // This handles hours, minutes, seconds and microseconds for
 
135
                    // ISO intervals
 
136
                    int offset = (token.charAt(0) == '-') ? 1 : 0;
 
137
 
 
138
                    hours    = nullSafeIntGet(token.substring(offset+0, endHours));
 
139
                    minutes  = nullSafeIntGet(token.substring(endHours+1, endHours+3));
 
140
 
 
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));
 
146
 
 
147
                    if (offset == 1)
 
148
                    {
 
149
                        hours   = -hours;
 
150
                        minutes = -minutes;
 
151
                        seconds = -seconds;
 
152
                    }
 
153
 
 
154
                    valueToken = null;
 
155
                }
 
156
                else
 
157
                {
 
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.
 
161
 
 
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);
 
174
                }
 
175
            }
 
176
        }
 
177
        catch (NumberFormatException e)
 
178
        {
 
179
            throw new PSQLException(GT.tr("Conversion of interval failed"), PSQLState.NUMERIC_CONSTANT_OUT_OF_RANGE, e);
 
180
        }
 
181
 
 
182
        if (!ISOFormat && value.endsWith("ago"))
 
183
        {
 
184
            // Inverse the leading sign
 
185
            setValue(-years, -months, -days, -hours, -minutes, -seconds);
 
186
        }
 
187
        else
 
188
        {
 
189
            setValue(years, months, days, hours, minutes, seconds);
 
190
        }
 
191
    }
 
192
 
 
193
    /**
 
194
     * Set all values of this interval to the specified values
 
195
     */
 
196
    public void setValue(int years, int months, int days, int hours, int minutes, double seconds)
 
197
    {
 
198
        setYears(years);
 
199
        setMonths(months);
 
200
        setDays(days);
 
201
        setHours(hours);
 
202
        setMinutes(minutes);
 
203
        setSeconds(seconds);
 
204
    }
 
205
 
 
206
    /**
 
207
     * Returns the stored interval information as a string
 
208
     *
 
209
     * @return String represented interval
 
210
     */
 
211
    public String getValue()
 
212
    {
 
213
        return years + " years " +
 
214
               months + " mons " +
 
215
               days + " days " +
 
216
               hours + " hours " +
 
217
               minutes + " mins " +
 
218
               secondsFormat.format(seconds) + " secs";
 
219
    }
 
220
 
 
221
    /**
 
222
     * Returns the years represented by this interval
 
223
     */
 
224
    public int getYears()
 
225
    {
 
226
        return years;
 
227
    }
 
228
 
 
229
    /**
 
230
     * Set the years of this interval to the specified value
 
231
     */
 
232
    public void setYears(int years)
 
233
    {
 
234
        this.years = years;
 
235
    }
 
236
 
 
237
    /**
 
238
     * Returns the months represented by this interval
 
239
     */
 
240
    public int getMonths()
 
241
    {
 
242
        return months;
 
243
    }
 
244
 
 
245
    /**
 
246
     * Set the months of this interval to the specified value
 
247
     */
 
248
    public void setMonths(int months)
 
249
    {
 
250
        this.months = months;
 
251
    }
 
252
 
 
253
    /**
 
254
     * Returns the days represented by this interval
 
255
     */
 
256
    public int getDays()
 
257
    {
 
258
        return days;
 
259
    }
 
260
 
 
261
    /**
 
262
     * Set the days of this interval to the specified value
 
263
     */
 
264
    public void setDays(int days)
 
265
    {
 
266
        this.days = days;
 
267
    }
 
268
 
 
269
    /**
 
270
     * Returns the hours represented by this interval
 
271
     */
 
272
    public int getHours()
 
273
    {
 
274
        return hours;
 
275
    }
 
276
 
 
277
    /**
 
278
     * Set the hours of this interval to the specified value
 
279
     */
 
280
    public void setHours(int hours)
 
281
    {
 
282
        this.hours = hours;
 
283
    }
 
284
 
 
285
    /**
 
286
     * Returns the minutes represented by this interval
 
287
     */
 
288
    public int getMinutes()
 
289
    {
 
290
        return minutes;
 
291
    }
 
292
 
 
293
    /**
 
294
     * Set the minutes of this interval to the specified value
 
295
     */
 
296
    public void setMinutes(int minutes)
 
297
    {
 
298
        this.minutes = minutes;
 
299
    }
 
300
 
 
301
    /**
 
302
     * Returns the seconds represented by this interval
 
303
     */
 
304
    public double getSeconds()
 
305
    {
 
306
        return seconds;
 
307
    }
 
308
 
 
309
    /**
 
310
     * Set the seconds of this interval to the specified value
 
311
     */
 
312
    public void setSeconds(double seconds)
 
313
    {
 
314
        this.seconds = seconds;
 
315
    }
 
316
 
 
317
    /**
 
318
     * Rolls this interval on a given calendar
 
319
     *
 
320
     * @param cal Calendar instance to add to
 
321
     */
 
322
    public void add(Calendar cal)
 
323
    {
 
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;
 
328
 
 
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());
 
335
    }
 
336
 
 
337
    /**
 
338
     * Rolls this interval on a given date
 
339
     *
 
340
     * @param date Date instance to add to
 
341
     */
 
342
    public void add(Date date)
 
343
    {
 
344
        final Calendar cal = Calendar.getInstance();
 
345
        cal.setTime(date);
 
346
        add(cal);
 
347
        date.setTime(cal.getTime().getTime());
 
348
    }
 
349
 
 
350
    /**
 
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.
 
354
     */
 
355
    public void add(PGInterval interval)
 
356
    {
 
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());
 
363
    }
 
364
 
 
365
    /**
 
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
 
371
     * strong use case.
 
372
     */
 
373
    public void scale(int factor)
 
374
    {
 
375
        setYears(factor * getYears());
 
376
        setMonths(factor * getMonths());
 
377
        setDays(factor * getDays());
 
378
        setHours(factor * getHours());
 
379
        setMinutes(factor * getMinutes());
 
380
        setSeconds(factor * getSeconds());
 
381
    }
 
382
 
 
383
    /**
 
384
     * Returns integer value of value or 0 if value is null
 
385
     *
 
386
     * @param value integer as string value
 
387
     * @return integer parsed from string value
 
388
     * @throws NumberFormatException if the string contains invalid chars
 
389
     */
 
390
    private int nullSafeIntGet(String value)
 
391
    throws NumberFormatException
 
392
    {
 
393
        return (value == null) ? 0 : Integer.parseInt(value);
 
394
    }
 
395
 
 
396
    /**
 
397
     * Returns double value of value or 0 if value is null
 
398
     *
 
399
     * @param value double as string value
 
400
     * @return double parsed from string value
 
401
     * @throws NumberFormatException if the string contains invalid chars
 
402
     */
 
403
    private double nullSafeDoubleGet(String value)
 
404
    throws NumberFormatException
 
405
    {
 
406
        return (value == null) ? 0 : Double.parseDouble(value);
 
407
    }
 
408
 
 
409
    /**
 
410
     * Returns whether an object is equal to this one or not
 
411
     *
 
412
     * @param obj Object to compare with
 
413
     * @return true if the two intervals are identical
 
414
     */
 
415
    public boolean equals(Object obj)
 
416
    {
 
417
        if (obj == null)
 
418
            return false;
 
419
 
 
420
        if (obj == this)
 
421
            return true;
 
422
 
 
423
        if (!(obj instanceof PGInterval))
 
424
            return false;
 
425
 
 
426
        final PGInterval pgi = (PGInterval)obj;
 
427
 
 
428
        return
 
429
            pgi.years        == years &&
 
430
            pgi.months       == months &&
 
431
            pgi.days         == days &&
 
432
            pgi.hours        == hours &&
 
433
            pgi.minutes      == minutes &&
 
434
            Double.doubleToLongBits(pgi.seconds) == Double.doubleToLongBits(seconds);
 
435
    }
 
436
 
 
437
    /**
 
438
     * Returns a hashCode for this object
 
439
     *
 
440
     * @return hashCode
 
441
     */
 
442
    public int hashCode()
 
443
    {
 
444
        return ((((((7 * 31 +
 
445
                (int)Double.doubleToLongBits(seconds)) * 31 +
 
446
                minutes) * 31 +
 
447
                hours) * 31 +
 
448
                days) * 31 +
 
449
                months) * 31 +
 
450
                years) * 31;
 
451
    }
 
452
 
 
453
}