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.TimeZone;
33  import java.util.regex.Matcher;
34  import java.util.regex.Pattern;
35  
36  import org.dbunit.dataset.ITable;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  
40  /**
41   * @author Manuel Laflamme
42   * @author Last changed by: $Author$
43   * @version $Revision$ $Date$
44   * @since 1.0 (Feb 19, 2002)
45   */
46  public class TimestampDataType extends AbstractDataType
47  {
48      private static final BigInteger ONE_BILLION = new BigInteger("1000000000");
49      private static final Pattern TIMEZONE_REGEX =
50              Pattern.compile("(.*)(?:\\W([+-][0-2][0-9][0-5][0-9]))");
51  
52      /**
53       * Logger for this class
54       */
55      private static final Logger logger =
56              LoggerFactory.getLogger(TimestampDataType.class);
57  
58      TimestampDataType()
59      {
60          super("TIMESTAMP", Types.TIMESTAMP, Timestamp.class, false);
61      }
62  
63      ////////////////////////////////////////////////////////////////////////////
64      // DataType class
65  
66      @Override
67      public Object typeCast(final Object value) throws TypeCastException
68      {
69          logger.debug("typeCast(value={}) - start", value);
70  
71          if (value == null || value == ITable.NO_VALUE)
72          {
73              return null;
74          }
75  
76          if (value instanceof java.sql.Timestamp)
77          {
78              return value;
79          }
80  
81          if (value instanceof java.util.Date)
82          {
83              final java.util.Date date = (java.util.Date) value;
84              return new java.sql.Timestamp(date.getTime());
85          }
86  
87          if (value instanceof Long)
88          {
89              final Long date = (Long) value;
90              return new java.sql.Timestamp(date);
91          }
92  
93          if (value instanceof String)
94          {
95              String stringValue = value.toString();
96  
97              if (isExtendedSyntax(stringValue))
98              {
99                  // Relative date.
100                 try
101                 {
102                     final LocalDateTime datetime =
103                             RELATIVE_DATE_TIME_PARSER.parse(stringValue);
104                     return java.sql.Timestamp.valueOf(datetime);
105                 } catch (IllegalArgumentException | DateTimeParseException e)
106                 {
107                     throw new TypeCastException(value, this, e);
108                 }
109             }
110 
111             String zoneValue = null;
112 
113             final Matcher tzMatcher = TIMEZONE_REGEX.matcher(stringValue);
114             if (tzMatcher.matches() && tzMatcher.group(2) != null)
115             {
116                 stringValue = tzMatcher.group(1);
117                 zoneValue = tzMatcher.group(2);
118             }
119 
120             Timestamp ts = null;
121             if (stringValue.length() == 10)
122             {
123                 try
124                 {
125                     final long time =
126                             java.sql.Date.valueOf(stringValue).getTime();
127                     ts = new java.sql.Timestamp(time);
128                 } catch (final IllegalArgumentException e)
129                 {
130                     // Was not a java.sql.Date, let Timestamp handle this value
131                 }
132             }
133             if (ts == null)
134             {
135                 try
136                 {
137                     ts = java.sql.Timestamp.valueOf(stringValue);
138                 } catch (final IllegalArgumentException e)
139                 {
140                     throw new TypeCastException(value, this, e);
141                 }
142             }
143 
144             // Apply zone if any
145             if (zoneValue != null)
146             {
147                 final long tsTime = ts.getTime();
148 
149                 final TimeZone localTZ = java.util.TimeZone.getDefault();
150                 final int offset = localTZ.getOffset(tsTime);
151                 final BigInteger localTZOffset = BigInteger.valueOf(offset);
152                 BigInteger time = BigInteger.valueOf(tsTime / 1000 * 1000)
153                         .add(localTZOffset).multiply(ONE_BILLION)
154                         .add(BigInteger.valueOf(ts.getNanos()));
155                 final int hours = Integer.parseInt(zoneValue.substring(1, 3));
156                 final int minutes = Integer.parseInt(zoneValue.substring(3, 5));
157                 final BigInteger offsetAsSeconds =
158                         BigInteger.valueOf((hours * 3600) + (minutes * 60));
159                 final BigInteger offsetAsNanos =
160                         offsetAsSeconds.multiply(BigInteger.valueOf(1000))
161                                 .multiply(ONE_BILLION);
162                 if (zoneValue.charAt(0) == '+')
163                 {
164                     time = time.subtract(offsetAsNanos);
165                 } else
166                 {
167                     time = time.add(offsetAsNanos);
168                 }
169                 final BigInteger[] components =
170                         time.divideAndRemainder(ONE_BILLION);
171                 ts = new Timestamp(components[0].longValue());
172                 ts.setNanos(components[1].intValue());
173             }
174 
175             return ts;
176         }
177 
178         throw new TypeCastException(value, this);
179     }
180 
181     @Override
182     public boolean isDateTime()
183     {
184         logger.debug("isDateTime() - start");
185 
186         return true;
187     }
188 
189     @Override
190     public Object getSqlValue(final int column, final ResultSet resultSet)
191             throws SQLException, TypeCastException
192     {
193         logger.debug("getSqlValue(column={}, resultSet={}) - start", column,
194                 resultSet);
195         final Timestamp rawValue = resultSet.getTimestamp(column);
196         final Timestamp value = resultSet.wasNull() ? null : rawValue;
197         logger.debug("getSqlValue: column={}, value={}", column, value);
198         return value;
199     }
200 
201     @Override
202     public void setSqlValue(final Object value, final int column,
203             final PreparedStatement statement)
204             throws SQLException, TypeCastException
205     {
206         logger.debug("setSqlValue(value={}, column={}, statement={}) - start",
207                 value, column, statement);
208 
209         statement.setTimestamp(column, (java.sql.Timestamp) typeCast(value));
210     }
211 }