2525import java .util .BitSet ;
2626import java .util .Calendar ;
2727import java .util .Map ;
28+ import java .util .TimeZone ;
2829
2930/**
3031 * Whole class is basically a mix of <a href="https://code.google.com/p/open-replicator">open-replicator</a>'s
3132 * AbstractRowEventParser and MySQLUtils. Main purpose here is to ease rows deserialization.<p>
3233 *
3334 * Current {@link ColumnType} to java type mapping is following:
3435 * <pre>
35- * Integer: {@link ColumnType#TINY}, {@link ColumnType#SHORT}, {@link ColumnType#LONG}, {@link ColumnType#INT24},
36- * {@link ColumnType#YEAR}, {@link ColumnType#ENUM}, {@link ColumnType#SET},
37- * Long: {@link ColumnType#LONGLONG},
38- * Float: {@link ColumnType#FLOAT},
39- * Double: {@link ColumnType#DOUBLE},
40- * String: {@link ColumnType#VARCHAR}, {@link ColumnType#VAR_STRING}, {@link ColumnType#STRING},
41- * java.util.BitSet: {@link ColumnType#BIT},
42- * java.util.Date: {@link ColumnType#DATETIME}, {@link ColumnType#DATETIME_V2},
43- * java.math.BigDecimal: {@link ColumnType#NEWDECIMAL},
44- * java.sql.Timestamp: {@link ColumnType#TIMESTAMP}, {@link ColumnType#TIMESTAMP_V2},
45- * java.sql.Date: {@link ColumnType#DATE},
46- * java.sql.Time: {@link ColumnType#TIME}, {@link ColumnType#TIME_V2},
47- * byte[]: {@link ColumnType#BLOB},
36+ * {@link ColumnType#TINY}: Integer
37+ * {@link ColumnType#SHORT}: Integer
38+ * {@link ColumnType#LONG}: Integer
39+ * {@link ColumnType#INT24}: Integer
40+ * {@link ColumnType#YEAR}: Integer
41+ * {@link ColumnType#ENUM}: Integer
42+ * {@link ColumnType#SET}: Long
43+ * {@link ColumnType#LONGLONG}: Long
44+ * {@link ColumnType#FLOAT}: Float
45+ * {@link ColumnType#DOUBLE}: Double
46+ * {@link ColumnType#BIT}: java.util.BitSet
47+ * {@link ColumnType#DATETIME}: java.util.Date
48+ * {@link ColumnType#DATETIME_V2}: java.util.Date
49+ * {@link ColumnType#NEWDECIMAL}: java.math.BigDecimal
50+ * {@link ColumnType#TIMESTAMP}: java.sql.Timestamp
51+ * {@link ColumnType#TIMESTAMP_V2}: java.sql.Timestamp
52+ * {@link ColumnType#DATE}: java.sql.Date
53+ * {@link ColumnType#TIME}: java.sql.Time
54+ * {@link ColumnType#TIME_V2}: java.sql.Time
55+ * {@link ColumnType#VARCHAR}: String
56+ * {@link ColumnType#VAR_STRING}: String
57+ * {@link ColumnType#STRING}: String
58+ * {@link ColumnType#BLOB}: byte[]
59+ * {@link ColumnType#GEOMETRY}: byte[]
4860 * </pre>
4961 *
5062 * At the moment {@link ColumnType#GEOMETRY} is unsupported.
@@ -208,101 +220,83 @@ protected Serializable deserializeDate(ByteArrayInputStream inputStream) throws
208220 value >>>= 5 ;
209221 int month = value % 16 ;
210222 int year = value >> 4 ;
211- Calendar cal = Calendar .getInstance ();
212- cal .clear ();
213- cal .set (Calendar .YEAR , year );
214- cal .set (Calendar .MONTH , month - 1 );
215- cal .set (Calendar .DATE , day );
216- return new java .sql .Date (cal .getTimeInMillis ());
223+ Long timestamp = asUnixTime (year , month , day , 0 , 0 , 0 , 0 );
224+ return timestamp != null ? new java .sql .Date (timestamp ) : null ;
217225 }
218226
219227 protected Serializable deserializeTime (ByteArrayInputStream inputStream ) throws IOException {
220228 int value = inputStream .readInteger (3 );
221229 int [] split = split (value , 100 , 3 );
222- Calendar c = Calendar .getInstance ();
223- c .clear ();
224- c .set (Calendar .HOUR_OF_DAY , split [2 ]);
225- c .set (Calendar .MINUTE , split [1 ]);
226- c .set (Calendar .SECOND , split [0 ]);
227- return new java .sql .Time (c .getTimeInMillis ());
230+ Long timestamp = asUnixTime (1970 , 1 , 1 , split [2 ], split [1 ], split [0 ], 0 );
231+ return timestamp != null ? new java .sql .Time (timestamp ) : null ;
228232 }
229233
230234 protected Serializable deserializeTimeV2 (int meta , ByteArrayInputStream inputStream ) throws IOException {
231235 /*
232- in big endian:
236+ ( in big endian)
233237
234238 1 bit sign (1= non-negative, 0= negative)
235239 1 bit unused (reserved for future extensions)
236240 10 bits hour (0-838)
237241 6 bits minute (0-59)
238242 6 bits second (0-59)
239- = (3 bytes in total)
240- +
241- fractional-seconds storage (size depends on meta)
243+
244+ (3 bytes in total)
245+
246+ + fractional-seconds storage (size depends on meta)
242247 */
243248 long time = bigEndianLong (inputStream .read (3 ), 0 , 3 );
244- Calendar c = Calendar . getInstance ();
245- c . clear ();
246- c . set ( Calendar . HOUR_OF_DAY , extractBits (time , 2 , 10 , 24 ));
247- c . set ( Calendar . MINUTE , extractBits (time , 12 , 6 , 24 ));
248- c . set ( Calendar . SECOND , extractBits ( time , 18 , 6 , 24 ));
249- c . set ( Calendar . MILLISECOND , deserializeFractionalSeconds ( meta , inputStream ) );
250- return new java .sql .Time (c . getTimeInMillis ()) ;
249+ Long timestamp = asUnixTime ( 1970 , 1 , 1 ,
250+ bitSlice ( time , 2 , 10 , 24 ),
251+ bitSlice (time , 12 , 6 , 24 ),
252+ bitSlice (time , 18 , 6 , 24 ),
253+ deserializeFractionalSeconds ( meta , inputStream )
254+ );
255+ return timestamp != null ? new java .sql .Time (timestamp ) : null ;
251256 }
252257
253258 protected Serializable deserializeTimestamp (ByteArrayInputStream inputStream ) throws IOException {
254- long value = inputStream .readLong (4 );
255- return new java .sql .Timestamp (value * 1000L );
259+ return new java .sql .Timestamp (inputStream .readLong (4 ) * 1000 );
256260 }
257261
258262 protected Serializable deserializeTimestampV2 (int meta , ByteArrayInputStream inputStream ) throws IOException {
259- // big endian
260- long timestamp = bigEndianLong (inputStream .read (4 ), 0 , 4 );
261- Calendar c = Calendar .getInstance ();
262- c .setTimeInMillis (timestamp * 1000 );
263- c .set (Calendar .MILLISECOND , deserializeFractionalSeconds (meta , inputStream ));
264- return new java .sql .Timestamp (c .getTimeInMillis ());
263+ return new java .sql .Timestamp (bigEndianLong (inputStream .read (4 ), 0 , 4 ) * 1000 +
264+ deserializeFractionalSeconds (meta , inputStream ));
265265 }
266266
267267 protected Serializable deserializeDatetime (ByteArrayInputStream inputStream ) throws IOException {
268- long value = inputStream .readLong (8 );
269- int [] split = split (value , 100 , 6 );
270- Calendar c = Calendar .getInstance ();
271- c .set (Calendar .YEAR , split [5 ]);
272- c .set (Calendar .MONTH , split [4 ] - 1 );
273- c .set (Calendar .DAY_OF_MONTH , split [3 ]);
274- c .set (Calendar .HOUR_OF_DAY , split [2 ]);
275- c .set (Calendar .MINUTE , split [1 ]);
276- c .set (Calendar .SECOND , split [0 ]);
277- c .set (Calendar .MILLISECOND , 0 );
278- return c .getTime ();
268+ int [] split = split (inputStream .readLong (8 ), 100 , 6 );
269+ Long timestamp = asUnixTime (split [5 ], split [4 ], split [3 ], split [2 ], split [1 ], split [0 ], 0 );
270+ return timestamp != null ? new java .util .Date (timestamp ) : null ;
279271 }
280272
281273 protected Serializable deserializeDatetimeV2 (int meta , ByteArrayInputStream inputStream ) throws IOException {
282274 /*
283- in big endian:
275+ ( in big endian)
284276
285277 1 bit sign (1= non-negative, 0= negative)
286278 17 bits year*13+month (year 0-9999, month 0-12)
287279 5 bits day (0-31)
288280 5 bits hour (0-23)
289281 6 bits minute (0-59)
290282 6 bits second (0-59)
291- = (5 bytes in total)
292- +
293- fractional-seconds storage (size depends on meta)
283+
284+ (5 bytes in total)
285+
286+ + fractional-seconds storage (size depends on meta)
294287 */
295288 long datetime = bigEndianLong (inputStream .read (5 ), 0 , 5 );
296- int yearMonth = extractBits (datetime , 1 , 17 , 40 );
297- Calendar c = Calendar .getInstance ();
298- c .set (Calendar .YEAR , yearMonth / 13 );
299- c .set (Calendar .MONTH , yearMonth % 13 - 1 );
300- c .set (Calendar .DAY_OF_MONTH , extractBits (datetime , 18 , 5 , 40 ));
301- c .set (Calendar .HOUR_OF_DAY , extractBits (datetime , 23 , 5 , 40 ));
302- c .set (Calendar .MINUTE , extractBits (datetime , 28 , 6 , 40 ));
303- c .set (Calendar .SECOND , extractBits (datetime , 34 , 6 , 40 ));
304- c .set (Calendar .MILLISECOND , deserializeFractionalSeconds (meta , inputStream ));
305- return c .getTime ();
289+ int yearMonth = bitSlice (datetime , 1 , 17 , 40 );
290+ Long timestamp = asUnixTime (
291+ yearMonth / 13 ,
292+ yearMonth % 13 ,
293+ bitSlice (datetime , 18 , 5 , 40 ),
294+ bitSlice (datetime , 23 , 5 , 40 ),
295+ bitSlice (datetime , 28 , 6 , 40 ),
296+ bitSlice (datetime , 34 , 6 , 40 ),
297+ deserializeFractionalSeconds (meta , inputStream )
298+ );
299+ return timestamp != null ? new java .util .Date (timestamp ) : null ;
306300 }
307301
308302 protected Serializable deserializeYear (ByteArrayInputStream inputStream ) throws IOException {
@@ -339,6 +333,15 @@ protected Serializable deserializeGeometry(int meta, ByteArrayInputStream inputS
339333 return inputStream .read (dataLength );
340334 }
341335
336+ // checkstyle, please ignore ParameterNumber for the next line
337+ protected Long asUnixTime (int year , int month , int day , int hour , int minute , int second , int millis ) {
338+ // https://dev.mysql.com/doc/refman/5.0/en/datetime.html
339+ if (year == 0 || month == 0 || day == 0 ) {
340+ return null ;
341+ }
342+ return UnixTime .from (year , month , day , hour , minute , second , millis );
343+ }
344+
342345 protected int deserializeFractionalSeconds (int meta , ByteArrayInputStream inputStream ) throws IOException {
343346 int length = (meta + 1 ) / 2 ;
344347 if (length > 0 ) {
@@ -348,7 +351,7 @@ protected int deserializeFractionalSeconds(int meta, ByteArrayInputStream inputS
348351 return 0 ;
349352 }
350353
351- private static int extractBits (long value , int bitOffset , int numberOfBits , int payloadSize ) {
354+ private static int bitSlice (long value , int bitOffset , int numberOfBits , int payloadSize ) {
352355 long result = value >> payloadSize - (bitOffset + numberOfBits );
353356 return (int ) (result & ((1 << numberOfBits ) - 1 ));
354357 }
@@ -424,4 +427,75 @@ private static long bigEndianLong(byte[] bytes, int offset, int length) {
424427 return result ;
425428 }
426429
430+ /**
431+ * Class for working with Unix time.
432+ */
433+ static class UnixTime {
434+
435+ private static final int [] YEAR_DAYS_BY_MONTH = new int [] {
436+ 0 , 31 , 59 , 90 , 120 , 151 , 181 , 212 , 243 , 273 , 304 , 334 , 365
437+ };
438+ private static final int [] LEAP_YEAR_DAYS_BY_MONTH = new int [] {
439+ 0 , 31 , 60 , 91 , 121 , 152 , 182 , 213 , 244 , 274 , 305 , 335 , 366
440+ };
441+
442+ /**
443+ * Calendar::getTimeInMillis but magnitude faster for all dates starting from October 15, 1582
444+ * (Gregorian Calendar cutover).
445+ *
446+ * @param year year
447+ * @param month month [1..12]
448+ * @param day day [1..)
449+ * @param hour hour [0..23]
450+ * @param minute [0..59]
451+ * @param second [0..59]
452+ * @param millis [0..999]
453+ *
454+ * @return Unix time (number of seconds that have elapsed since 00:00:00 (UTC), Thursday,
455+ * 1 January 1970, not counting leap seconds)
456+ */
457+ // checkstyle, please ignore ParameterNumber for the next line
458+ public static long from (int year , int month , int day , int hour , int minute , int second , int millis ) {
459+ if (year < 1582 || (year == 1582 && (month < 10 || (month == 10 && day < 15 )))) {
460+ return fallbackToGC (year , month , day , hour , minute , second , millis );
461+ }
462+ long timestamp = 0 ;
463+ int numberOfLeapYears = leapYears (1970 , year );
464+ timestamp += 366L * 24 * 60 * 60 * numberOfLeapYears ;
465+ timestamp += 365L * 24 * 60 * 60 * (year - 1970 - numberOfLeapYears );
466+ long daysUpToMonth = isLeapYear (year ) ? LEAP_YEAR_DAYS_BY_MONTH [month - 1 ] : YEAR_DAYS_BY_MONTH [month - 1 ];
467+ timestamp += ((daysUpToMonth + day - 1 ) * 24 * 60 * 60 ) +
468+ (hour * 60 * 60 ) + (minute * 60 ) + (second );
469+ timestamp = timestamp * 1000 + millis ;
470+ return timestamp ;
471+ }
472+
473+ // checkstyle, please ignore ParameterNumber for the next line
474+ private static long fallbackToGC (int year , int month , int dayOfMonth , int hourOfDay ,
475+ int minute , int second , int millis ) {
476+ Calendar c = Calendar .getInstance (TimeZone .getTimeZone ("GMT" ));
477+ c .set (Calendar .YEAR , year );
478+ c .set (Calendar .MONTH , month - 1 );
479+ c .set (Calendar .DAY_OF_MONTH , dayOfMonth );
480+ c .set (Calendar .HOUR_OF_DAY , hourOfDay );
481+ c .set (Calendar .MINUTE , minute );
482+ c .set (Calendar .SECOND , second );
483+ c .set (Calendar .MILLISECOND , millis );
484+ return c .getTimeInMillis ();
485+ }
486+
487+ private static int leapYears (int from , int end ) {
488+ return leapYearsBefore (end ) - leapYearsBefore (from + 1 );
489+ }
490+
491+ private static int leapYearsBefore (int year ) {
492+ year --; return (year / 4 ) - (year / 100 ) + (year / 400 );
493+ }
494+
495+ private static boolean isLeapYear (int year ) {
496+ return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0 );
497+ }
498+
499+ }
500+
427501}
0 commit comments