Skip to content

Commit 5352d6b

Browse files
committed
[CONJ-1226] Dates containing zero day or month results into DateTimeException
1 parent 2d48120 commit 5352d6b

File tree

2 files changed

+179
-52
lines changed

2 files changed

+179
-52
lines changed

src/main/java/org/mariadb/jdbc/client/column/TimestampColumn.java

Lines changed: 163 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.sql.*;
99
import java.text.DecimalFormat;
1010
import java.text.DecimalFormatSymbols;
11+
import java.time.DateTimeException;
1112
import java.time.LocalDateTime;
1213
import java.time.ZonedDateTime;
1314
import java.time.format.DateTimeFormatter;
@@ -144,24 +145,30 @@ public String decodeStringText(
144145
final Context context)
145146
throws SQLDataException {
146147
if (length.get() == 0) return buildZeroDate();
148+
int initialPos = buf.pos();
147149
int initialLength = length.get();
148-
LocalDateTime ldt = parseText(buf, length);
149-
if (ldt == null) {
150-
if (initialLength > 0) return buildZeroDate();
151-
return null;
152-
}
153-
LocalDateTime modifiedLdt =
154-
localDateTimeToZoneDateTime(ldt, providedCal, context).toLocalDateTime();
155-
String timestampWithoutMicro = dateTimeFormatter.format(modifiedLdt);
156-
if (context.getConf().oldModeNoPrecisionTimestamp()) {
157-
// for compatibility with 2.2.0 and before, micro precision use .0##### format
150+
try {
151+
LocalDateTime ldt = parseText(buf, length);
152+
if (ldt == null) {
153+
if (initialLength > 0) return buildZeroDate();
154+
return null;
155+
}
156+
LocalDateTime modifiedLdt =
157+
localDateTimeToZoneDateTime(ldt, providedCal, context).toLocalDateTime();
158+
String timestampWithoutMicro = dateTimeFormatter.format(modifiedLdt);
159+
if (context.getConf().oldModeNoPrecisionTimestamp()) {
160+
// for compatibility with 2.2.0 and before, micro precision use .0##### format
161+
return timestampWithoutMicro
162+
+ oldDecimalFormat.format(((double) modifiedLdt.getNano()) / 1000000000);
163+
}
164+
if (this.decimals == 0) return timestampWithoutMicro;
158165
return timestampWithoutMicro
159-
+ oldDecimalFormat.format(((double) modifiedLdt.getNano()) / 1000000000);
166+
+ "."
167+
+ String.format(Locale.US, "%0" + this.decimals + "d", modifiedLdt.getNano() / 1000);
168+
} catch (DateTimeException e) {
169+
buf.pos(initialPos);
170+
return buf.readString(length.get());
160171
}
161-
if (this.decimals == 0) return timestampWithoutMicro;
162-
return timestampWithoutMicro
163-
+ "."
164-
+ String.format(Locale.US, "%0" + this.decimals + "d", modifiedLdt.getNano() / 1000);
165172
}
166173

167174
private String buildZeroDate() {
@@ -181,24 +188,72 @@ public String decodeStringBinary(
181188
final Context context)
182189
throws SQLDataException {
183190
if (length.get() == 0) return buildZeroDate();
191+
int initialPos = buf.pos();
184192
int initialLength = length.get();
185-
LocalDateTime ldt = parseBinary(buf, length);
186-
if (ldt == null) {
187-
if (initialLength > 0) return buildZeroDate();
188-
return null;
189-
}
190-
LocalDateTime modifiedLdt =
191-
localDateTimeToZoneDateTime(ldt, providedCal, context).toLocalDateTime();
192-
String timestampWithoutMicro = dateTimeFormatter.format(modifiedLdt);
193-
if (context.getConf().oldModeNoPrecisionTimestamp()) {
194-
// for compatibility with 2.2.0 and before, micro precision use .0##### format
193+
try {
194+
LocalDateTime ldt = parseBinary(buf, length);
195+
if (ldt == null) {
196+
if (initialLength > 0) return buildZeroDate();
197+
return null;
198+
}
199+
LocalDateTime modifiedLdt =
200+
localDateTimeToZoneDateTime(ldt, providedCal, context).toLocalDateTime();
201+
String timestampWithoutMicro = dateTimeFormatter.format(modifiedLdt);
202+
if (context.getConf().oldModeNoPrecisionTimestamp()) {
203+
// for compatibility with 2.2.0 and before, micro precision use .0##### format
204+
return timestampWithoutMicro
205+
+ oldDecimalFormat.format(((double) modifiedLdt.getNano()) / 1000000000);
206+
}
207+
if (this.decimals == 0) return timestampWithoutMicro;
195208
return timestampWithoutMicro
196-
+ oldDecimalFormat.format(((double) modifiedLdt.getNano()) / 1000000000);
209+
+ "."
210+
+ String.format(Locale.US, "%0" + this.decimals + "d", modifiedLdt.getNano() / 1000);
211+
} catch (DateTimeException e) {
212+
buf.pos(initialPos);
213+
int year = buf.readUnsignedShort();
214+
int month = buf.readByte();
215+
int dayOfMonth = buf.readByte();
216+
int hour = 0;
217+
int minutes = 0;
218+
int seconds = 0;
219+
long microseconds = 0;
220+
221+
if (length.get() > 4) {
222+
hour = buf.readByte();
223+
minutes = buf.readByte();
224+
seconds = buf.readByte();
225+
226+
if (length.get() > 7) {
227+
microseconds = buf.readUnsignedInt();
228+
}
229+
}
230+
StringBuilder sb = new StringBuilder();
231+
fill(year, 4, sb);
232+
sb.append("-");
233+
fill(month, 2, sb);
234+
sb.append("-");
235+
fill(dayOfMonth, 2, sb);
236+
sb.append(" ");
237+
fill(hour, 2, sb);
238+
sb.append(":");
239+
fill(minutes, 2, sb);
240+
sb.append(":");
241+
fill(seconds, 2, sb);
242+
243+
if (getDecimals() == 0) return sb.toString();
244+
sb.append(".");
245+
fill((int) (microseconds / Math.pow(10, 6 - getDecimals())), getDecimals(), sb);
246+
return sb.toString();
247+
}
248+
}
249+
250+
private void fill(int val, int size, StringBuilder sb) {
251+
String valSt = String.valueOf(val);
252+
long zeroToAdd = size - valSt.length();
253+
while (zeroToAdd-- > 0) {
254+
sb.append("0");
197255
}
198-
if (this.decimals == 0) return timestampWithoutMicro;
199-
return timestampWithoutMicro
200-
+ "."
201-
+ String.format(Locale.US, "%0" + this.decimals + "d", modifiedLdt.getNano() / 1000);
256+
sb.append(valSt);
202257
}
203258

204259
@Override
@@ -320,22 +375,90 @@ public Time decodeTimeBinary(
320375
public Timestamp decodeTimestampText(
321376
final ReadableByteBuf buf, final MutableInt length, Calendar calParam, final Context context)
322377
throws SQLDataException {
323-
LocalDateTime ldt = parseText(buf, length);
324-
if (ldt == null) return null;
325-
Timestamp res = new Timestamp(localDateTimeToInstant(ldt, calParam, context));
326-
res.setNanos(ldt.getNano());
327-
return res;
378+
int[] parts = LocalDateTimeCodec.parseTextTimestamp(buf, length);
379+
if (LocalDateTimeCodec.isZeroTimestamp(parts)) {
380+
length.set(NULL_LENGTH);
381+
return null;
382+
}
383+
384+
try {
385+
LocalDateTime ldt = LocalDateTime.of(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5])
386+
.plusNanos(parts[6]);
387+
Timestamp res = new Timestamp(localDateTimeToInstant(ldt, calParam, context));
388+
res.setNanos(ldt.getNano());
389+
return res;
390+
} catch (DateTimeException e) {
391+
Timestamp timestamp;
392+
Calendar cal = calParam == null ? Calendar.getInstance() : calParam;
393+
synchronized (cal) {
394+
cal.setLenient(true);
395+
cal.clear();
396+
cal.set(Calendar.YEAR, parts[0]);
397+
cal.set(Calendar.MONTH, parts[1] - 1);
398+
cal.set(Calendar.DAY_OF_MONTH, parts[2]);
399+
cal.set(Calendar.HOUR_OF_DAY, parts[3]);
400+
cal.set(Calendar.MINUTE, parts[4]);
401+
cal.set(Calendar.SECOND, parts[5]);
402+
cal.set(Calendar.MILLISECOND, parts[6] / 1000000);
403+
timestamp = new Timestamp(cal.getTime().getTime());
404+
}
405+
timestamp.setNanos(parts[6]);
406+
return timestamp;
407+
}
408+
328409
}
329410

330411
@Override
331412
public Timestamp decodeTimestampBinary(
332413
final ReadableByteBuf buf, final MutableInt length, Calendar calParam, final Context context)
333414
throws SQLDataException {
334-
LocalDateTime ldt = parseBinary(buf, length);
335-
if (ldt == null) return null;
336-
Timestamp res = new Timestamp(localDateTimeToInstant(ldt, calParam, context));
337-
res.setNanos(ldt.getNano());
338-
return res;
415+
if (length.get() == 0) {
416+
length.set(NULL_LENGTH);
417+
return null;
418+
}
419+
420+
int year = buf.readUnsignedShort();
421+
int month = buf.readByte();
422+
int dayOfMonth = buf.readByte();
423+
int hour = 0;
424+
int minutes = 0;
425+
int seconds = 0;
426+
long microseconds = 0;
427+
428+
if (length.get() > 4) {
429+
hour = buf.readByte();
430+
minutes = buf.readByte();
431+
seconds = buf.readByte();
432+
433+
if (length.get() > 7) {
434+
microseconds = buf.readUnsignedInt();
435+
}
436+
}
437+
try {
438+
LocalDateTime ldt = LocalDateTime.of(year, month, dayOfMonth, hour, minutes, seconds)
439+
.plusNanos(microseconds * 1000);
440+
if (ldt == null) return null;
441+
Timestamp res = new Timestamp(localDateTimeToInstant(ldt, calParam, context));
442+
res.setNanos(ldt.getNano());
443+
return res;
444+
} catch (DateTimeException e) {
445+
Timestamp timestamp;
446+
Calendar cal = calParam == null ? Calendar.getInstance() : calParam;
447+
synchronized (cal) {
448+
cal.setLenient(true);
449+
cal.clear();
450+
cal.set(Calendar.YEAR, year);
451+
cal.set(Calendar.MONTH, month - 1);
452+
cal.set(Calendar.DAY_OF_MONTH, dayOfMonth);
453+
cal.set(Calendar.HOUR_OF_DAY, hour);
454+
cal.set(Calendar.MINUTE, minutes);
455+
cal.set(Calendar.SECOND, seconds);
456+
cal.set(Calendar.MILLISECOND, (int) (microseconds / 1000000));
457+
timestamp = new Timestamp(cal.getTime().getTime());
458+
}
459+
timestamp.setNanos((int) (microseconds * 1000));
460+
return timestamp;
461+
}
339462
}
340463

341464
private LocalDateTime parseText(final ReadableByteBuf buf, final MutableInt length) {
@@ -371,18 +494,6 @@ private LocalDateTime parseBinary(final ReadableByteBuf buf, final MutableInt le
371494
microseconds = buf.readUnsignedInt();
372495
}
373496
}
374-
375-
// xpand workaround https://jira.mariadb.org/browse/XPT-274
376-
if (year == 0
377-
&& month == 0
378-
&& dayOfMonth == 0
379-
&& hour == 0
380-
&& minutes == 0
381-
&& seconds == 0
382-
&& microseconds == 0) {
383-
length.set(NULL_LENGTH);
384-
return null;
385-
}
386497
return LocalDateTime.of(year, month, dayOfMonth, hour, minutes, seconds)
387498
.plusNanos(microseconds * 1000);
388499
}

src/test/java/org/mariadb/jdbc/integration/codec/DateTimeCodecTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ public static void drop() throws SQLException {
3434
public static void beforeAll2() throws SQLException {
3535
drop();
3636
Statement stmt = sharedConn.createStatement();
37+
38+
// ensure not setting NO_ZERO_DATE and NO_ZERO_IN_DATE
39+
stmt.execute("set sql_mode='STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'");
40+
3741
stmt.execute(
3842
"CREATE TABLE DateTimeCodec (t1 DATETIME , t2 DATETIME(6), t3 DATETIME(6), t4"
3943
+ " DATETIME(6))");
@@ -42,6 +46,7 @@ public static void beforeAll2() throws SQLException {
4246
+ " '9999-12-31 18:30:12.55', null)"
4347
+ (isMariaDBServer()
4448
? ",('0000-00-00 00:00:00', '0000-00-00 00:00:00', '9999-12-31 00:00:00.00', null)"
49+
+ ",('1980-00-10 00:00:00', '1970-10-00 00:00:00.0123', null, null)"
4550
: ""));
4651
stmt.execute(
4752
"CREATE TABLE DateTimeCodec2 (id int not null primary key auto_increment, t1 DATETIME(6))");
@@ -216,6 +221,14 @@ public void getObject(ResultSet rs) throws SQLException {
216221
assertTrue(rs.wasNull());
217222
assertNull(rs.getObject(1, LocalTime.class));
218223
assertTrue(rs.wasNull());
224+
rs.next();
225+
226+
assertEquals(
227+
Timestamp.valueOf("1979-12-10 00:00:00").getTime(),
228+
((Timestamp) rs.getObject(1)).getTime());
229+
assertEquals(
230+
Timestamp.valueOf("1970-09-30 00:00:00.012300").getTime(),
231+
((Timestamp) rs.getObject(2)).getTime());
219232
}
220233
}
221234

@@ -312,6 +325,9 @@ public void getString(ResultSet rs, boolean text, boolean oldModeNoPrecisionTime
312325
assertEquals("0000-00-00 00:00:00.000000", rs.getString(2));
313326
assertEquals(
314327
"9999-12-31 00:00:00.0" + (oldModeNoPrecisionTimestamp ? "" : "00000"), rs.getString(3));
328+
rs.next();
329+
assertEquals("1980-00-10 00:00:00", rs.getString(1));
330+
assertEquals("1970-10-00 00:00:00.012300", rs.getString(2));
315331
}
316332
}
317333

0 commit comments

Comments
 (0)