Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix getObject() conversion of DATETIMEOFFSET to LocalDateTime #2204

Merged
merged 7 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading