Skip to content

Commit 974e4a1

Browse files
authored
Fix getObject() conversion of DATETIMEOFFSET to LocalDateTime (#2204)
1 parent 69ad140 commit 974e4a1

File tree

9 files changed

+136
-24
lines changed

9 files changed

+136
-24
lines changed

src/main/java/com/microsoft/sqlserver/jdbc/DDC.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,9 @@ private static String fractionalSecondsString(long subSecondNanos, int scale) {
861861
*
862862
* java.sql.Date java.sql.Time java.sql.Timestamp java.lang.String
863863
*
864+
* @param connection
865+
* the JDBC connection from which value was read
866+
*
864867
* @param jdbcType
865868
* the JDBC type indicating the desired conversion
866869
*
@@ -890,7 +893,8 @@ private static String fractionalSecondsString(long subSecondNanos, int scale) {
890893
*
891894
* @return a Java object of the desired type.
892895
*/
893-
static final Object convertTemporalToObject(JDBCType jdbcType, SSType ssType, Calendar timeZoneCalendar,
896+
static final Object convertTemporalToObject(
897+
SQLServerConnection connection, JDBCType jdbcType, SSType ssType, Calendar timeZoneCalendar,
894898
int daysSinceBaseDate, long ticksSinceMidnight, int fractionalSecondsScale) throws SQLServerException {
895899

896900
// In cases where a Calendar object (and therefore Timezone) is not passed to the method,
@@ -1127,7 +1131,13 @@ static final Object convertTemporalToObject(JDBCType jdbcType, SSType ssType, Ca
11271131
java.sql.Timestamp ts2 = new java.sql.Timestamp(cal.getTimeInMillis());
11281132
ts2.setNanos(subSecondNanos);
11291133
if (jdbcType == JDBCType.LOCALDATETIME) {
1130-
return ts2.toLocalDateTime();
1134+
if (connection.getIgnoreOffsetOnDateTimeOffsetConversion()) {
1135+
return LocalDateTime.of(
1136+
cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH),
1137+
cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND), subSecondNanos);
1138+
} else {
1139+
return ts2.toLocalDateTime();
1140+
}
11311141
}
11321142
return ts2;
11331143

src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7229,7 +7229,7 @@ final Object readDateTime(int valueLength, Calendar appTimeZoneCalendar, JDBCTyp
72297229
}
72307230

72317231
// Convert the DATETIME/SMALLDATETIME value to the desired Java type.
7232-
return DDC.convertTemporalToObject(jdbcType, SSType.DATETIME, appTimeZoneCalendar, daysSinceSQLBaseDate,
7232+
return DDC.convertTemporalToObject(con, jdbcType, SSType.DATETIME, appTimeZoneCalendar, daysSinceSQLBaseDate,
72337233
msecSinceMidnight, 0); // scale
72347234
// (ignored
72357235
// for
@@ -7246,7 +7246,7 @@ final Object readDate(int valueLength, Calendar appTimeZoneCalendar, JDBCType jd
72467246
int localDaysIntoCE = readDaysIntoCE();
72477247

72487248
// Convert the DATE value to the desired Java type.
7249-
return DDC.convertTemporalToObject(jdbcType, SSType.DATE, appTimeZoneCalendar, localDaysIntoCE, 0, // midnight
7249+
return DDC.convertTemporalToObject(con, jdbcType, SSType.DATE, appTimeZoneCalendar, localDaysIntoCE, 0, // midnight
72507250
// local to
72517251
// app time
72527252
// zone
@@ -7262,7 +7262,7 @@ final Object readTime(int valueLength, TypeInfo typeInfo, Calendar appTimeZoneCa
72627262
long localNanosSinceMidnight = readNanosSinceMidnight(typeInfo.getScale());
72637263

72647264
// Convert the TIME value to the desired Java type.
7265-
return DDC.convertTemporalToObject(jdbcType, SSType.TIME, appTimeZoneCalendar, 0, localNanosSinceMidnight,
7265+
return DDC.convertTemporalToObject(con, jdbcType, SSType.TIME, appTimeZoneCalendar, 0, localNanosSinceMidnight,
72667266
typeInfo.getScale());
72677267
}
72687268

@@ -7276,7 +7276,7 @@ final Object readDateTime2(int valueLength, TypeInfo typeInfo, Calendar appTimeZ
72767276
int localDaysIntoCE = readDaysIntoCE();
72777277

72787278
// Convert the DATETIME2 value to the desired Java type.
7279-
return DDC.convertTemporalToObject(jdbcType, SSType.DATETIME2, appTimeZoneCalendar, localDaysIntoCE,
7279+
return DDC.convertTemporalToObject(con, jdbcType, SSType.DATETIME2, appTimeZoneCalendar, localDaysIntoCE,
72807280
localNanosSinceMidnight, typeInfo.getScale());
72817281
}
72827282

@@ -7291,7 +7291,7 @@ final Object readDateTimeOffset(int valueLength, TypeInfo typeInfo, JDBCType jdb
72917291
int localMinutesOffset = readShort();
72927292

72937293
// Convert the DATETIMEOFFSET value to the desired Java type.
7294-
return DDC.convertTemporalToObject(jdbcType, SSType.DATETIMEOFFSET,
7294+
return DDC.convertTemporalToObject(con, jdbcType, SSType.DATETIMEOFFSET,
72957295
new GregorianCalendar(new SimpleTimeZone(localMinutesOffset * 60 * 1000, ""), Locale.US), utcDaysIntoCE,
72967296
utcNanosSinceMidnight, typeInfo.getScale());
72977297
}

src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,21 @@ CallableStatement prepareCall(String sql, int nType, int nConcur, int nHold,
416416
*/
417417
void setDelayLoadingLobs(boolean delayLoadingLobs);
418418

419+
/**
420+
* Returns the current flag value for ignoreOffsetOnDateTimeOffsetConversion.
421+
*
422+
* @return 'ignoreOffsetOnDateTimeOffsetConversion' property value.
423+
*/
424+
boolean getIgnoreOffsetOnDateTimeOffsetConversion();
425+
426+
/**
427+
* Specifies the flag to ignore offset when converting DATETIMEOFFSET to LocalDateTime.
428+
*
429+
* @param ignoreOffsetOnDateTimeOffsetConversion
430+
* boolean value for 'ignoreOffsetOnDateTimeOffsetConversion'.
431+
*/
432+
void setIgnoreOffsetOnDateTimeOffsetConversion(boolean ignoreOffsetOnDateTimeOffsetConversion);
433+
419434
/**
420435
* Sets the name of the preferred type of IP Address.
421436
*

src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,19 @@ public void setDelayLoadingLobs(boolean b) {
973973
delayLoadingLobs = b;
974974
}
975975

976+
/** Boolean that indicates whether datetime types are converted to java.time objects using java.time rules */
977+
private boolean ignoreOffsetOnDateTimeOffsetConversion = SQLServerDriverBooleanProperty.IGNORE_OFFSET_ON_DATE_TIME_OFFSET_CONVERSION.getDefaultValue();
978+
979+
@Override
980+
public boolean getIgnoreOffsetOnDateTimeOffsetConversion() {
981+
return ignoreOffsetOnDateTimeOffsetConversion;
982+
}
983+
984+
@Override
985+
public void setIgnoreOffsetOnDateTimeOffsetConversion(boolean ignoreOffsetOnDateTimeOffsetConversion) {
986+
this.ignoreOffsetOnDateTimeOffsetConversion = ignoreOffsetOnDateTimeOffsetConversion;
987+
}
988+
976989
/** Session Recovery Object */
977990
private transient IdleConnectionResiliency sessionRecovery = new IdleConnectionResiliency(this);
978991

@@ -2935,6 +2948,14 @@ else if (0 == requestedPacketSize)
29352948
}
29362949
delayLoadingLobs = isBooleanPropertyOn(sPropKey, sPropValue);
29372950

2951+
sPropKey = SQLServerDriverBooleanProperty.IGNORE_OFFSET_ON_DATE_TIME_OFFSET_CONVERSION.toString();
2952+
sPropValue = activeConnectionProperties.getProperty(sPropKey);
2953+
if (null == sPropValue) {
2954+
sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.IGNORE_OFFSET_ON_DATE_TIME_OFFSET_CONVERSION.getDefaultValue());
2955+
activeConnectionProperties.setProperty(sPropKey, sPropValue);
2956+
}
2957+
ignoreOffsetOnDateTimeOffsetConversion = isBooleanPropertyOn(sPropKey, sPropValue);
2958+
29382959
FailoverInfo fo = null;
29392960
String databaseNameProperty = SQLServerDriverStringProperty.DATABASE_NAME.toString();
29402961
String serverNameProperty = SQLServerDriverStringProperty.SERVER_NAME.toString();
@@ -7288,6 +7309,9 @@ public <T> T unwrap(Class<T> iface) throws SQLException {
72887309

72897310
/** original delayLoadingLobs */
72907311
private boolean originalDelayLoadingLobs;
7312+
7313+
/** original ignoreOffsetOnDateTimeOffsetConversion */
7314+
private boolean originalIgnoreOffsetOnDateTimeOffsetConversion;
72917315

72927316
/** Always Encrypted version */
72937317
private int aeVersion = TDS.COLUMNENCRYPTION_NOT_SUPPORTED;
@@ -7313,6 +7337,7 @@ void beginRequestInternal() throws SQLException {
73137337
openStatements = new LinkedList<>();
73147338
originalUseFmtOnly = useFmtOnly;
73157339
originalDelayLoadingLobs = delayLoadingLobs;
7340+
originalIgnoreOffsetOnDateTimeOffsetConversion = ignoreOffsetOnDateTimeOffsetConversion;
73167341
requestStarted = true;
73177342
}
73187343
} finally {
@@ -7371,6 +7396,9 @@ void endRequestInternal() throws SQLException {
73717396
if (delayLoadingLobs != originalDelayLoadingLobs) {
73727397
setDelayLoadingLobs(originalDelayLoadingLobs);
73737398
}
7399+
if (ignoreOffsetOnDateTimeOffsetConversion != originalIgnoreOffsetOnDateTimeOffsetConversion) {
7400+
setIgnoreOffsetOnDateTimeOffsetConversion(originalIgnoreOffsetOnDateTimeOffsetConversion);
7401+
}
73747402
sqlWarnings = originalSqlWarnings;
73757403
if (null != openStatements) {
73767404
while (!openStatements.isEmpty()) {

src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,16 @@ public void setDelayLoadingLobs(boolean delayLoadingLobs) {
632632
wrappedConnection.setDelayLoadingLobs(delayLoadingLobs);
633633
}
634634

635+
@Override
636+
public boolean getIgnoreOffsetOnDateTimeOffsetConversion() {
637+
return wrappedConnection.getIgnoreOffsetOnDateTimeOffsetConversion();
638+
}
639+
640+
@Override
641+
public void setIgnoreOffsetOnDateTimeOffsetConversion(boolean ignoreOffsetOnDateTimeOffsetConversion) {
642+
wrappedConnection.setIgnoreOffsetOnDateTimeOffsetConversion(ignoreOffsetOnDateTimeOffsetConversion);
643+
}
644+
635645
@Override
636646
public void setIPAddressPreference(String iPAddressPreference) {
637647
wrappedConnection.setIPAddressPreference(iPAddressPreference);

src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,7 @@ enum SQLServerDriverBooleanProperty {
694694
USE_FMT_ONLY("useFmtOnly", false),
695695
SEND_TEMPORAL_DATATYPES_AS_STRING_FOR_BULK_COPY("sendTemporalDataTypesAsStringForBulkCopy", true),
696696
DELAY_LOADING_LOBS("delayLoadingLobs", true),
697+
IGNORE_OFFSET_ON_DATE_TIME_OFFSET_CONVERSION("ignoreOffsetOnDateTimeOffsetConversion", false),
697698
USE_DEFAULT_JAAS_CONFIG("useDefaultJaasConfig", false),
698699
USE_DEFAULT_GSS_CREDENTIAL("useDefaultGSSCredential", false);
699700

src/main/java/com/microsoft/sqlserver/jdbc/dtv.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3543,13 +3543,13 @@ Object denormalizedValue(byte[] decryptedValue, JDBCType jdbcType, TypeInfo base
35433543
// cannot reuse method
35443544
int daysIntoCE = getDaysIntoCE(decryptedValue, baseSSType);
35453545

3546-
return DDC.convertTemporalToObject(jdbcType, baseSSType, cal, daysIntoCE, 0, 0);
3546+
return DDC.convertTemporalToObject(con, jdbcType, baseSSType, cal, daysIntoCE, 0, 0);
35473547

35483548
case TIME:
35493549
long localNanosSinceMidnight = readNanosSinceMidnightAE(decryptedValue, baseTypeInfo.getScale(),
35503550
baseSSType);
35513551

3552-
return DDC.convertTemporalToObject(jdbcType, SSType.TIME, cal, 0, localNanosSinceMidnight,
3552+
return DDC.convertTemporalToObject(con, jdbcType, SSType.TIME, cal, 0, localNanosSinceMidnight,
35533553
baseTypeInfo.getScale());
35543554

35553555
case DATETIME2:
@@ -3570,7 +3570,7 @@ Object denormalizedValue(byte[] decryptedValue, JDBCType jdbcType, TypeInfo base
35703570
int daysIntoCE2 = getDaysIntoCE(datePortion, baseSSType);
35713571

35723572
// Convert the DATETIME2 value to the desired Java type.
3573-
return DDC.convertTemporalToObject(jdbcType, SSType.DATETIME2, cal, daysIntoCE2,
3573+
return DDC.convertTemporalToObject(con, jdbcType, SSType.DATETIME2, cal, daysIntoCE2,
35743574
localNanosSinceMidnight2, baseTypeInfo.getScale());
35753575

35763576
case SMALLDATETIME:
@@ -3582,7 +3582,7 @@ Object denormalizedValue(byte[] decryptedValue, JDBCType jdbcType, TypeInfo base
35823582
// SQL smalldatetime has less precision. It stores 2 bytes
35833583
// for the days since SQL Base Date and 2 bytes for minutes
35843584
// after midnight.
3585-
return DDC.convertTemporalToObject(jdbcType, SSType.DATETIME, cal,
3585+
return DDC.convertTemporalToObject(con, jdbcType, SSType.DATETIME, cal,
35863586
Util.readUnsignedShort(decryptedValue, 0),
35873587
Util.readUnsignedShort(decryptedValue, 2) * 60L * 1000L, 0);
35883588

@@ -3597,7 +3597,7 @@ Object denormalizedValue(byte[] decryptedValue, JDBCType jdbcType, TypeInfo base
35973597
// SQL datetime is 4 bytes for days since SQL Base Date
35983598
// (January 1, 1900 00:00:00 GMT) and 4 bytes for
35993599
// the number of three hundredths (1/300) of a second since midnight.
3600-
return DDC.convertTemporalToObject(jdbcType, SSType.DATETIME, cal, Util.readInt(decryptedValue, 0),
3600+
return DDC.convertTemporalToObject(con, jdbcType, SSType.DATETIME, cal, Util.readInt(decryptedValue, 0),
36013601
ticksSinceMidnight, 0);
36023602

36033603
case DATETIMEOFFSET:
@@ -3616,7 +3616,7 @@ Object denormalizedValue(byte[] decryptedValue, JDBCType jdbcType, TypeInfo base
36163616

36173617
int localMinutesOffset = ByteBuffer.wrap(offsetPortion2).order(ByteOrder.LITTLE_ENDIAN).getShort();
36183618

3619-
return DDC.convertTemporalToObject(jdbcType, SSType.DATETIMEOFFSET,
3619+
return DDC.convertTemporalToObject(con, jdbcType, SSType.DATETIMEOFFSET,
36203620
new GregorianCalendar(new SimpleTimeZone(localMinutesOffset * 60 * 1000, ""), Locale.US),
36213621
daysIntoCE3, localNanosSinceMidnight3, baseTypeInfo.getScale());
36223622

0 commit comments

Comments
 (0)