View Javadoc
1   /*
2    *
3    * The DbUnit Database Testing Framework
4    * Copyright (C)2002-2004, DbUnit.org
5    *
6    * This library is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public
8    * License as published by the Free Software Foundation; either
9    * version 2.1 of the License, or (at your option) any later version.
10   *
11   * This library is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Lesser General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public
17   * License along with this library; if not, write to the Free Software
18   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19   *
20   */
21  
22  package org.dbunit.dataset.datatype;
23  
24  import java.math.BigInteger;
25  import java.sql.PreparedStatement;
26  import java.sql.ResultSet;
27  import java.sql.SQLException;
28  import java.sql.Timestamp;
29  import java.sql.Types;
30  import java.time.LocalDateTime;
31  import java.time.format.DateTimeParseException;
32  import java.util.Calendar;
33  import java.util.TimeZone;
34  import java.util.regex.Matcher;
35  import java.util.regex.Pattern;
36  
37  import org.dbunit.dataset.ITable;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  /**
42   * @author Manuel Laflamme
43   * @author Last changed by: $Author$
44   * @version $Revision$ $Date$
45   * @since 1.0 (Feb 19, 2002)
46   */
47  public class TimestampDataType extends AbstractDataType
48  {
49      private static final BigInteger ONE_BILLION = new BigInteger("1000000000");
50      private static final Pattern TIMEZONE_REGEX =
51              Pattern.compile("(.*)(?:\\W([+-][0-2][0-9][0-5][0-9]))");
52  
53      /**
54       * Logger for this class
55       */
56      private static final Logger logger =
57              LoggerFactory.getLogger(TimestampDataType.class);
58  
59      TimestampDataType()
60      {
61          super("TIMESTAMP", Types.TIMESTAMP, Timestamp.class, false);
62      }
63  
64      ////////////////////////////////////////////////////////////////////////////
65      // DataType class
66  
67      @Override
68      public Object typeCast(final Object value) throws TypeCastException
69      {
70          logger.debug("typeCast(value={}) - start", value);
71  
72          if (value == null || value == ITable.NO_VALUE)
73          {
74              return null;
75          }
76  
77          if (value instanceof java.sql.Timestamp)
78          {
79              return value;
80          }
81  
82          if (value instanceof java.util.Date)
83          {
84              final java.util.Date date = (java.util.Date) value;
85              return new java.sql.Timestamp(date.getTime());
86          }
87  
88          if (value instanceof Long)
89          {
90              final Long date = (Long) value;
91              return new java.sql.Timestamp(date);
92          }
93  
94          if (value instanceof String)
95          {
96              String stringValue = value.toString();
97  
98              if (isExtendedSyntax(stringValue))
99              {
100                 // Relative date.
101                 try
102                 {
103                     final LocalDateTime datetime =
104                             RELATIVE_DATE_TIME_PARSER.parse(stringValue);
105                     return java.sql.Timestamp.valueOf(datetime);
106                 } catch (IllegalArgumentException | DateTimeParseException e)
107                 {
108                     throw new TypeCastException(value, this, e);
109                 }
110             }
111 
112             String zoneValue = null;
113 
114             final Matcher tzMatcher = TIMEZONE_REGEX.matcher(stringValue);
115             if (tzMatcher.matches() && tzMatcher.group(2) != null)
116             {
117                 stringValue = tzMatcher.group(1);
118                 zoneValue = tzMatcher.group(2);
119             }
120 
121             Timestamp ts = null;
122             if (stringValue.length() == 10)
123             {
124                 try
125                 {
126                     final long time =
127                             java.sql.Date.valueOf(stringValue).getTime();
128                     ts = new java.sql.Timestamp(time);
129                 } catch (final IllegalArgumentException e)
130                 {
131                     // Was not a java.sql.Date, let Timestamp handle this value
132                 }
133             }
134             if (ts == null)
135             {
136                 try
137                 {
138                     ts = java.sql.Timestamp.valueOf(stringValue);
139                 } catch (final IllegalArgumentException e)
140                 {
141                     throw new TypeCastException(value, this, e);
142                 }
143             }
144 
145             // Apply zone if any
146             if (zoneValue != null)
147             {
148                 final long tsTime = ts.getTime();
149 
150                 final TimeZone localTZ = java.util.TimeZone.getDefault();
151                 final int offset = localTZ.getOffset(tsTime);
152                 final BigInteger localTZOffset = BigInteger.valueOf(offset);
153                 BigInteger time = BigInteger.valueOf(tsTime / 1000 * 1000)
154                         .add(localTZOffset).multiply(ONE_BILLION)
155                         .add(BigInteger.valueOf(ts.getNanos()));
156                 final int hours = Integer.parseInt(zoneValue.substring(1, 3));
157                 final int minutes = Integer.parseInt(zoneValue.substring(3, 5));
158                 final BigInteger offsetAsSeconds =
159                         BigInteger.valueOf((hours * 3600) + (minutes * 60));
160                 final BigInteger offsetAsNanos =
161                         offsetAsSeconds.multiply(BigInteger.valueOf(1000))
162                                 .multiply(ONE_BILLION);
163                 if (zoneValue.charAt(0) == '+')
164                 {
165                     time = time.subtract(offsetAsNanos);
166                 } else
167                 {
168                     time = time.add(offsetAsNanos);
169                 }
170                 final BigInteger[] components =
171                         time.divideAndRemainder(ONE_BILLION);
172                 ts = new Timestamp(components[0].longValue());
173                 ts.setNanos(components[1].intValue());
174             }
175 
176             return ts;
177         }
178 
179         throw new TypeCastException(value, this);
180     }
181 
182     @Override
183     public boolean isDateTime()
184     {
185         logger.debug("isDateTime() - start");
186 
187         return true;
188     }
189 
190     @Override
191     public Object getSqlValue(final int column, final ResultSet resultSet)
192             throws SQLException, TypeCastException
193     {
194         logger.debug("getSqlValue(column={}, resultSet={}) - start", column,
195                 resultSet);
196         final Timestamp rawValue = resultSet.getTimestamp(column);
197         final Timestamp value = resultSet.wasNull() ? null : rawValue;
198         logger.debug("getSqlValue: column={}, value={}", column, value);
199         return value;
200     }
201 
202     @Override
203     public void setSqlValue(final Object value, final int column,
204             final PreparedStatement statement)
205             throws SQLException, TypeCastException
206     {
207         logger.debug("setSqlValue(value={}, column={}, statement={}) - start",
208                 value, column, statement);
209         final Timestamp ts = (Timestamp) typeCast(value);
210         if (value instanceof String)
211         {
212             final String stringValue = (String) value;
213             setSqlValueFromString(stringValue, column, statement, ts);
214         } else
215         {
216             statement.setTimestamp(column, ts);
217         }
218     }
219 
220     private void setSqlValueFromString(final String value, final int column,
221             final PreparedStatement statement, final Timestamp ts)
222             throws SQLException
223     {
224         final Matcher timezoneMatcher = TIMEZONE_REGEX.matcher(value);
225         if (timezoneMatcher.matches() && timezoneMatcher.group(2) != null)
226         {
227             final Calendar cal = makeCalendar(timezoneMatcher);
228             statement.setTimestamp(column, ts, cal);
229         } else
230         {
231             statement.setTimestamp(column, ts);
232         }
233     }
234 
235     private Calendar makeCalendar(final Matcher timezoneMatcher)
236     {
237         final String zoneValue = timezoneMatcher.group(2);
238         final String sign = zoneValue.substring(0, 1);
239         final int hours = Integer.parseInt(zoneValue.substring(1, 3));
240         final int minutes = Integer.parseInt(zoneValue.substring(3, 5));
241         final String timezoneId =
242                 String.format("GMT%s%02d:%02d", sign, hours, minutes);
243         final TimeZone timeZone = TimeZone.getTimeZone(timezoneId);
244         final Calendar cal = Calendar.getInstance(timeZone);
245         return cal;
246     }
247 }