Skip to content

Commit

Permalink
Fix getObject() conversion of DATETIMEOFFSET to LocalDateTime (#2204)
Browse files Browse the repository at this point in the history
  • Loading branch information
rdicroce committed Oct 31, 2023
1 parent 69ad140 commit 974e4a1
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 24 deletions.
14 changes: 12 additions & 2 deletions src/main/java/com/microsoft/sqlserver/jdbc/DDC.java
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,9 @@ private static String fractionalSecondsString(long subSecondNanos, int scale) {
*
* java.sql.Date java.sql.Time java.sql.Timestamp java.lang.String
*
* @param connection
* the JDBC connection from which value was read
*
* @param jdbcType
* the JDBC type indicating the desired conversion
*
Expand Down Expand Up @@ -890,7 +893,8 @@ private static String fractionalSecondsString(long subSecondNanos, int scale) {
*
* @return a Java object of the desired type.
*/
static final Object convertTemporalToObject(JDBCType jdbcType, SSType ssType, Calendar timeZoneCalendar,
static final Object convertTemporalToObject(
SQLServerConnection connection, JDBCType jdbcType, SSType ssType, Calendar timeZoneCalendar,
int daysSinceBaseDate, long ticksSinceMidnight, int fractionalSecondsScale) throws SQLServerException {

// In cases where a Calendar object (and therefore Timezone) is not passed to the method,
Expand Down Expand Up @@ -1127,7 +1131,13 @@ static final Object convertTemporalToObject(JDBCType jdbcType, SSType ssType, Ca
java.sql.Timestamp ts2 = new java.sql.Timestamp(cal.getTimeInMillis());
ts2.setNanos(subSecondNanos);
if (jdbcType == JDBCType.LOCALDATETIME) {
return ts2.toLocalDateTime();
if (connection.getIgnoreOffsetOnDateTimeOffsetConversion()) {
return LocalDateTime.of(
cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH),
cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND), subSecondNanos);
} else {
return ts2.toLocalDateTime();
}
}
return ts2;

Expand Down
10 changes: 5 additions & 5 deletions src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -7229,7 +7229,7 @@ final Object readDateTime(int valueLength, Calendar appTimeZoneCalendar, JDBCTyp
}

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

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

// Convert the TIME value to the desired Java type.
return DDC.convertTemporalToObject(jdbcType, SSType.TIME, appTimeZoneCalendar, 0, localNanosSinceMidnight,
return DDC.convertTemporalToObject(con, jdbcType, SSType.TIME, appTimeZoneCalendar, 0, localNanosSinceMidnight,
typeInfo.getScale());
}

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

// Convert the DATETIME2 value to the desired Java type.
return DDC.convertTemporalToObject(jdbcType, SSType.DATETIME2, appTimeZoneCalendar, localDaysIntoCE,
return DDC.convertTemporalToObject(con, jdbcType, SSType.DATETIME2, appTimeZoneCalendar, localDaysIntoCE,
localNanosSinceMidnight, typeInfo.getScale());
}

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

// Convert the DATETIMEOFFSET value to the desired Java type.
return DDC.convertTemporalToObject(jdbcType, SSType.DATETIMEOFFSET,
return DDC.convertTemporalToObject(con, jdbcType, SSType.DATETIMEOFFSET,
new GregorianCalendar(new SimpleTimeZone(localMinutesOffset * 60 * 1000, ""), Locale.US), utcDaysIntoCE,
utcNanosSinceMidnight, typeInfo.getScale());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,21 @@ CallableStatement prepareCall(String sql, int nType, int nConcur, int nHold,
*/
void setDelayLoadingLobs(boolean delayLoadingLobs);

/**
* Returns the current flag value for ignoreOffsetOnDateTimeOffsetConversion.
*
* @return 'ignoreOffsetOnDateTimeOffsetConversion' property value.
*/
boolean getIgnoreOffsetOnDateTimeOffsetConversion();

/**
* Specifies the flag to ignore offset when converting DATETIMEOFFSET to LocalDateTime.
*
* @param ignoreOffsetOnDateTimeOffsetConversion
* boolean value for 'ignoreOffsetOnDateTimeOffsetConversion'.
*/
void setIgnoreOffsetOnDateTimeOffsetConversion(boolean ignoreOffsetOnDateTimeOffsetConversion);

/**
* Sets the name of the preferred type of IP Address.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,19 @@ public void setDelayLoadingLobs(boolean b) {
delayLoadingLobs = b;
}

/** Boolean that indicates whether datetime types are converted to java.time objects using java.time rules */
private boolean ignoreOffsetOnDateTimeOffsetConversion = SQLServerDriverBooleanProperty.IGNORE_OFFSET_ON_DATE_TIME_OFFSET_CONVERSION.getDefaultValue();

@Override
public boolean getIgnoreOffsetOnDateTimeOffsetConversion() {
return ignoreOffsetOnDateTimeOffsetConversion;
}

@Override
public void setIgnoreOffsetOnDateTimeOffsetConversion(boolean ignoreOffsetOnDateTimeOffsetConversion) {
this.ignoreOffsetOnDateTimeOffsetConversion = ignoreOffsetOnDateTimeOffsetConversion;
}

/** Session Recovery Object */
private transient IdleConnectionResiliency sessionRecovery = new IdleConnectionResiliency(this);

Expand Down Expand Up @@ -2935,6 +2948,14 @@ else if (0 == requestedPacketSize)
}
delayLoadingLobs = isBooleanPropertyOn(sPropKey, sPropValue);

sPropKey = SQLServerDriverBooleanProperty.IGNORE_OFFSET_ON_DATE_TIME_OFFSET_CONVERSION.toString();
sPropValue = activeConnectionProperties.getProperty(sPropKey);
if (null == sPropValue) {
sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.IGNORE_OFFSET_ON_DATE_TIME_OFFSET_CONVERSION.getDefaultValue());
activeConnectionProperties.setProperty(sPropKey, sPropValue);
}
ignoreOffsetOnDateTimeOffsetConversion = isBooleanPropertyOn(sPropKey, sPropValue);

FailoverInfo fo = null;
String databaseNameProperty = SQLServerDriverStringProperty.DATABASE_NAME.toString();
String serverNameProperty = SQLServerDriverStringProperty.SERVER_NAME.toString();
Expand Down Expand Up @@ -7288,6 +7309,9 @@ public <T> T unwrap(Class<T> iface) throws SQLException {

/** original delayLoadingLobs */
private boolean originalDelayLoadingLobs;

/** original ignoreOffsetOnDateTimeOffsetConversion */
private boolean originalIgnoreOffsetOnDateTimeOffsetConversion;

/** Always Encrypted version */
private int aeVersion = TDS.COLUMNENCRYPTION_NOT_SUPPORTED;
Expand All @@ -7313,6 +7337,7 @@ void beginRequestInternal() throws SQLException {
openStatements = new LinkedList<>();
originalUseFmtOnly = useFmtOnly;
originalDelayLoadingLobs = delayLoadingLobs;
originalIgnoreOffsetOnDateTimeOffsetConversion = ignoreOffsetOnDateTimeOffsetConversion;
requestStarted = true;
}
} finally {
Expand Down Expand Up @@ -7371,6 +7396,9 @@ void endRequestInternal() throws SQLException {
if (delayLoadingLobs != originalDelayLoadingLobs) {
setDelayLoadingLobs(originalDelayLoadingLobs);
}
if (ignoreOffsetOnDateTimeOffsetConversion != originalIgnoreOffsetOnDateTimeOffsetConversion) {
setIgnoreOffsetOnDateTimeOffsetConversion(originalIgnoreOffsetOnDateTimeOffsetConversion);
}
sqlWarnings = originalSqlWarnings;
if (null != openStatements) {
while (!openStatements.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,16 @@ public void setDelayLoadingLobs(boolean delayLoadingLobs) {
wrappedConnection.setDelayLoadingLobs(delayLoadingLobs);
}

@Override
public boolean getIgnoreOffsetOnDateTimeOffsetConversion() {
return wrappedConnection.getIgnoreOffsetOnDateTimeOffsetConversion();
}

@Override
public void setIgnoreOffsetOnDateTimeOffsetConversion(boolean ignoreOffsetOnDateTimeOffsetConversion) {
wrappedConnection.setIgnoreOffsetOnDateTimeOffsetConversion(ignoreOffsetOnDateTimeOffsetConversion);
}

@Override
public void setIPAddressPreference(String iPAddressPreference) {
wrappedConnection.setIPAddressPreference(iPAddressPreference);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,7 @@ enum SQLServerDriverBooleanProperty {
USE_FMT_ONLY("useFmtOnly", false),
SEND_TEMPORAL_DATATYPES_AS_STRING_FOR_BULK_COPY("sendTemporalDataTypesAsStringForBulkCopy", true),
DELAY_LOADING_LOBS("delayLoadingLobs", true),
IGNORE_OFFSET_ON_DATE_TIME_OFFSET_CONVERSION("ignoreOffsetOnDateTimeOffsetConversion", false),
USE_DEFAULT_JAAS_CONFIG("useDefaultJaasConfig", false),
USE_DEFAULT_GSS_CREDENTIAL("useDefaultGSSCredential", false);

Expand Down
12 changes: 6 additions & 6 deletions src/main/java/com/microsoft/sqlserver/jdbc/dtv.java
Original file line number Diff line number Diff line change
Expand Up @@ -3543,13 +3543,13 @@ Object denormalizedValue(byte[] decryptedValue, JDBCType jdbcType, TypeInfo base
// cannot reuse method
int daysIntoCE = getDaysIntoCE(decryptedValue, baseSSType);

return DDC.convertTemporalToObject(jdbcType, baseSSType, cal, daysIntoCE, 0, 0);
return DDC.convertTemporalToObject(con, jdbcType, baseSSType, cal, daysIntoCE, 0, 0);

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

return DDC.convertTemporalToObject(jdbcType, SSType.TIME, cal, 0, localNanosSinceMidnight,
return DDC.convertTemporalToObject(con, jdbcType, SSType.TIME, cal, 0, localNanosSinceMidnight,
baseTypeInfo.getScale());

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

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

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

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

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

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

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

Expand Down
Loading

0 comments on commit 974e4a1

Please sign in to comment.