1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.value; |
7 | |
8 | import java.math.BigDecimal; |
9 | import java.sql.Date; |
10 | import java.sql.PreparedStatement; |
11 | import java.sql.SQLException; |
12 | import java.sql.Timestamp; |
13 | import java.util.Calendar; |
14 | import java.util.TimeZone; |
15 | import org.h2.api.ErrorCode; |
16 | import org.h2.message.DbException; |
17 | import org.h2.util.DateTimeUtils; |
18 | import org.h2.util.MathUtils; |
19 | |
20 | /** |
21 | * Implementation of the TIMESTAMP data type. |
22 | */ |
23 | public class ValueTimestamp extends Value { |
24 | |
25 | /** |
26 | * The precision in digits. |
27 | */ |
28 | public static final int PRECISION = 23; |
29 | |
30 | /** |
31 | * The display size of the textual representation of a timestamp. |
32 | * Example: 2001-01-01 23:59:59.000 |
33 | */ |
34 | static final int DISPLAY_SIZE = 23; |
35 | |
36 | /** |
37 | * The default scale for timestamps. |
38 | */ |
39 | static final int DEFAULT_SCALE = 10; |
40 | |
41 | /** |
42 | * A bit field with bits for the year, month, and day (see DateTimeUtils for |
43 | * encoding) |
44 | */ |
45 | private final long dateValue; |
46 | /** |
47 | * The nanoseconds since midnight. |
48 | */ |
49 | private final long timeNanos; |
50 | |
51 | private ValueTimestamp(long dateValue, long timeNanos) { |
52 | this.dateValue = dateValue; |
53 | if (timeNanos < 0 || timeNanos >= 24L * 60 * 60 * 1000 * 1000 * 1000) { |
54 | throw new IllegalArgumentException("timeNanos out of range " + timeNanos); |
55 | } |
56 | this.timeNanos = timeNanos; |
57 | } |
58 | |
59 | /** |
60 | * Get or create a date value for the given date. |
61 | * |
62 | * @param dateValue the date value, a bit field with bits for the year, |
63 | * month, and day |
64 | * @param timeNanos the nanoseconds since midnight |
65 | * @return the value |
66 | */ |
67 | public static ValueTimestamp fromDateValueAndNanos(long dateValue, long timeNanos) { |
68 | return (ValueTimestamp) Value.cache(new ValueTimestamp(dateValue, timeNanos)); |
69 | } |
70 | |
71 | /** |
72 | * Get or create a timestamp value for the given timestamp. |
73 | * |
74 | * @param timestamp the timestamp |
75 | * @return the value |
76 | */ |
77 | public static ValueTimestamp get(Timestamp timestamp) { |
78 | long ms = timestamp.getTime(); |
79 | long nanos = timestamp.getNanos() % 1000000; |
80 | long dateValue = DateTimeUtils.dateValueFromDate(ms); |
81 | nanos += DateTimeUtils.nanosFromDate(ms); |
82 | return fromDateValueAndNanos(dateValue, nanos); |
83 | } |
84 | |
85 | /** |
86 | * Get or create a timestamp value for the given date/time in millis. |
87 | * |
88 | * @param ms the milliseconds |
89 | * @param nanos the nanoseconds |
90 | * @return the value |
91 | */ |
92 | public static ValueTimestamp fromMillisNanos(long ms, int nanos) { |
93 | long dateValue = DateTimeUtils.dateValueFromDate(ms); |
94 | long timeNanos = nanos + DateTimeUtils.nanosFromDate(ms); |
95 | return fromDateValueAndNanos(dateValue, timeNanos); |
96 | } |
97 | |
98 | /** |
99 | * Get or create a timestamp value for the given date/time in millis. |
100 | * |
101 | * @param ms the milliseconds |
102 | * @return the value |
103 | */ |
104 | public static ValueTimestamp fromMillis(long ms) { |
105 | long dateValue = DateTimeUtils.dateValueFromDate(ms); |
106 | long nanos = DateTimeUtils.nanosFromDate(ms); |
107 | return fromDateValueAndNanos(dateValue, nanos); |
108 | } |
109 | |
110 | /** |
111 | * Parse a string to a ValueTimestamp. This method supports the format |
112 | * +/-year-month-day hour:minute:seconds.fractional and an optional timezone |
113 | * part. |
114 | * |
115 | * @param s the string to parse |
116 | * @return the date |
117 | */ |
118 | public static ValueTimestamp parse(String s) { |
119 | try { |
120 | return parseTry(s); |
121 | } catch (Exception e) { |
122 | throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2, |
123 | e, "TIMESTAMP", s); |
124 | } |
125 | } |
126 | |
127 | private static ValueTimestamp parseTry(String s) { |
128 | int dateEnd = s.indexOf(' '); |
129 | if (dateEnd < 0) { |
130 | // ISO 8601 compatibility |
131 | dateEnd = s.indexOf('T'); |
132 | } |
133 | int timeStart; |
134 | if (dateEnd < 0) { |
135 | dateEnd = s.length(); |
136 | timeStart = -1; |
137 | } else { |
138 | timeStart = dateEnd + 1; |
139 | } |
140 | long dateValue = DateTimeUtils.parseDateValue(s, 0, dateEnd); |
141 | long nanos; |
142 | if (timeStart < 0) { |
143 | nanos = 0; |
144 | } else { |
145 | int timeEnd = s.length(); |
146 | TimeZone tz = null; |
147 | if (s.endsWith("Z")) { |
148 | tz = TimeZone.getTimeZone("UTC"); |
149 | timeEnd--; |
150 | } else { |
151 | int timeZoneStart = s.indexOf('+', dateEnd); |
152 | if (timeZoneStart < 0) { |
153 | timeZoneStart = s.indexOf('-', dateEnd); |
154 | } |
155 | if (timeZoneStart >= 0) { |
156 | String tzName = "GMT" + s.substring(timeZoneStart); |
157 | tz = TimeZone.getTimeZone(tzName); |
158 | if (!tz.getID().startsWith(tzName)) { |
159 | throw new IllegalArgumentException( |
160 | tzName + " (" + tz.getID() + "?)"); |
161 | } |
162 | timeEnd = timeZoneStart; |
163 | } else { |
164 | timeZoneStart = s.indexOf(' ', dateEnd + 1); |
165 | if (timeZoneStart > 0) { |
166 | String tzName = s.substring(timeZoneStart + 1); |
167 | tz = TimeZone.getTimeZone(tzName); |
168 | if (!tz.getID().startsWith(tzName)) { |
169 | throw new IllegalArgumentException(tzName); |
170 | } |
171 | timeEnd = timeZoneStart; |
172 | } |
173 | } |
174 | } |
175 | nanos = DateTimeUtils.parseTimeNanos(s, dateEnd + 1, timeEnd, true); |
176 | if (tz != null) { |
177 | int year = DateTimeUtils.yearFromDateValue(dateValue); |
178 | int month = DateTimeUtils.monthFromDateValue(dateValue); |
179 | int day = DateTimeUtils.dayFromDateValue(dateValue); |
180 | long ms = nanos / 1000000; |
181 | nanos -= ms * 1000000; |
182 | long second = ms / 1000; |
183 | ms -= second * 1000; |
184 | int minute = (int) (second / 60); |
185 | second -= minute * 60; |
186 | int hour = minute / 60; |
187 | minute -= hour * 60; |
188 | long millis = DateTimeUtils.getMillis( |
189 | tz, year, month, day, hour, minute, (int) second, (int) ms); |
190 | ms = DateTimeUtils.convertToLocal( |
191 | new Date(millis), |
192 | Calendar.getInstance(TimeZone.getTimeZone("UTC"))); |
193 | long md = DateTimeUtils.MILLIS_PER_DAY; |
194 | long absoluteDay = (ms >= 0 ? ms : ms - md + 1) / md; |
195 | dateValue = DateTimeUtils.dateValueFromAbsoluteDay(absoluteDay); |
196 | ms -= absoluteDay * md; |
197 | nanos += ms * 1000000; |
198 | } |
199 | } |
200 | return ValueTimestamp.fromDateValueAndNanos(dateValue, nanos); |
201 | } |
202 | |
203 | /** |
204 | * A bit field with bits for the year, month, and day (see DateTimeUtils for |
205 | * encoding). |
206 | * |
207 | * @return the data value |
208 | */ |
209 | public long getDateValue() { |
210 | return dateValue; |
211 | } |
212 | |
213 | /** |
214 | * The nanoseconds since midnight. |
215 | * |
216 | * @return the nanoseconds |
217 | */ |
218 | public long getTimeNanos() { |
219 | return timeNanos; |
220 | } |
221 | |
222 | @Override |
223 | public Timestamp getTimestamp() { |
224 | return DateTimeUtils.convertDateValueToTimestamp(dateValue, timeNanos); |
225 | } |
226 | |
227 | @Override |
228 | public int getType() { |
229 | return Value.TIMESTAMP; |
230 | } |
231 | |
232 | @Override |
233 | public String getString() { |
234 | StringBuilder buff = new StringBuilder(DISPLAY_SIZE); |
235 | ValueDate.appendDate(buff, dateValue); |
236 | buff.append(' '); |
237 | ValueTime.appendTime(buff, timeNanos, true); |
238 | return buff.toString(); |
239 | } |
240 | |
241 | @Override |
242 | public String getSQL() { |
243 | return "TIMESTAMP '" + getString() + "'"; |
244 | } |
245 | |
246 | @Override |
247 | public long getPrecision() { |
248 | return PRECISION; |
249 | } |
250 | |
251 | @Override |
252 | public int getScale() { |
253 | return DEFAULT_SCALE; |
254 | } |
255 | |
256 | @Override |
257 | public int getDisplaySize() { |
258 | return DISPLAY_SIZE; |
259 | } |
260 | |
261 | @Override |
262 | public Value convertScale(boolean onlyToSmallerScale, int targetScale) { |
263 | if (targetScale >= DEFAULT_SCALE) { |
264 | return this; |
265 | } |
266 | if (targetScale < 0) { |
267 | throw DbException.getInvalidValueException("scale", targetScale); |
268 | } |
269 | long n = timeNanos; |
270 | BigDecimal bd = BigDecimal.valueOf(n); |
271 | bd = bd.movePointLeft(9); |
272 | bd = ValueDecimal.setScale(bd, targetScale); |
273 | bd = bd.movePointRight(9); |
274 | long n2 = bd.longValue(); |
275 | if (n2 == n) { |
276 | return this; |
277 | } |
278 | return fromDateValueAndNanos(dateValue, n2); |
279 | } |
280 | |
281 | @Override |
282 | protected int compareSecure(Value o, CompareMode mode) { |
283 | ValueTimestamp t = (ValueTimestamp) o; |
284 | int c = MathUtils.compareLong(dateValue, t.dateValue); |
285 | if (c != 0) { |
286 | return c; |
287 | } |
288 | return MathUtils.compareLong(timeNanos, t.timeNanos); |
289 | } |
290 | |
291 | @Override |
292 | public boolean equals(Object other) { |
293 | if (this == other) { |
294 | return true; |
295 | } else if (!(other instanceof ValueTimestamp)) { |
296 | return false; |
297 | } |
298 | ValueTimestamp x = (ValueTimestamp) other; |
299 | return dateValue == x.dateValue && timeNanos == x.timeNanos; |
300 | } |
301 | |
302 | @Override |
303 | public int hashCode() { |
304 | return (int) (dateValue ^ (dateValue >>> 32) ^ timeNanos ^ (timeNanos >>> 32)); |
305 | } |
306 | |
307 | @Override |
308 | public Object getObject() { |
309 | return getTimestamp(); |
310 | } |
311 | |
312 | @Override |
313 | public void set(PreparedStatement prep, int parameterIndex) |
314 | throws SQLException { |
315 | prep.setTimestamp(parameterIndex, getTimestamp()); |
316 | } |
317 | |
318 | @Override |
319 | public Value add(Value v) { |
320 | ValueTimestamp t = (ValueTimestamp) v.convertTo(Value.TIMESTAMP); |
321 | long d1 = DateTimeUtils.absoluteDayFromDateValue(dateValue); |
322 | long d2 = DateTimeUtils.absoluteDayFromDateValue(t.dateValue); |
323 | return DateTimeUtils.normalizeTimestamp(d1 + d2, timeNanos + t.timeNanos); |
324 | } |
325 | |
326 | @Override |
327 | public Value subtract(Value v) { |
328 | ValueTimestamp t = (ValueTimestamp) v.convertTo(Value.TIMESTAMP); |
329 | long d1 = DateTimeUtils.absoluteDayFromDateValue(dateValue); |
330 | long d2 = DateTimeUtils.absoluteDayFromDateValue(t.dateValue); |
331 | return DateTimeUtils.normalizeTimestamp(d1 - d2, timeNanos - t.timeNanos); |
332 | } |
333 | |
334 | } |