Skip to content

Commit 73e616f

Browse files
committed
Timezone changes
1 parent d6c8825 commit 73e616f

25 files changed

+380
-152
lines changed

doc/src/release_notes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ node-oracledb `v6.0.0 <https://github.com/oracle/node-oracledb/compare/v5.5.0...
4343
#) Multiple calls to ``initOracleClient()`` with the same arguments no longer
4444
result in an error.
4545

46+
#) Oracle Database DATE and TIMESTAMP types are now returned as JavaScript date
47+
types in the application's timezone, and no longer fetched or bound as
48+
TIMESTAMP WITH LOCAL TIME ZONE. The connection session time zone no longer
49+
impacts these types. This behavior aligns with other Oracle Database tools
50+
and drivers.
51+
4652
#) Query column metadata now always returns unique column names regardless of
4753
the value of the ``outFormat`` setting. Previously they were only unique when
4854
``oracledb.OUT_FORMAT_OBJECT`` was used.

lib/connection.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,9 +386,9 @@ class Connection extends EventEmitter {
386386
// handle binding dates
387387
} else if (util.isDate(value)) {
388388
this._checkBindType(bindInfo, iterNum,
389-
types.DB_TYPE_TIMESTAMP_LTZ,
390-
types.DB_TYPE_TIMESTAMP_TZ,
391389
types.DB_TYPE_TIMESTAMP,
390+
types.DB_TYPE_TIMESTAMP_TZ,
391+
types.DB_TYPE_TIMESTAMP_LTZ,
392392
types.DB_TYPE_DATE);
393393
bindInfo.values[iterNum] = value;
394394

lib/oracledb.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -983,7 +983,7 @@ module.exports = {
983983
BUFFER: types.DB_TYPE_RAW,
984984
CLOB: types.DB_TYPE_CLOB,
985985
CURSOR: types.DB_TYPE_CURSOR,
986-
DATE: types.DB_TYPE_TIMESTAMP_LTZ,
986+
DATE: types.DB_TYPE_TIMESTAMP,
987987
NCLOB: types.DB_TYPE_NCLOB,
988988
NUMBER: types.DB_TYPE_NUMBER,
989989
STRING: types.DB_TYPE_VARCHAR,

lib/settings.js

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,56 @@ class Settings {
6161
this.fetchTypeHandler = undefined;
6262
}
6363

64+
//---------------------------------------------------------------------------
65+
// _getDateComponents()
66+
//
67+
// Returns the components of a date. DATE and TIMESTAMP data from the
68+
// database are returned as though they used the JavaScript time zone
69+
// setting. TIMESTAMP WITH TIME ZONE and TIMESTAMP WITH LOCAL TIME ZONE data
70+
// are returned in native JavaScript format (since they contain time zone
71+
// information).
72+
//---------------------------------------------------------------------------
73+
_getDateComponents(useLocal, date) {
74+
if (useLocal) {
75+
return [
76+
date.getFullYear(),
77+
date.getMonth() + 1,
78+
date.getDate(),
79+
date.getHours(),
80+
date.getMinutes(),
81+
date.getSeconds(),
82+
date.getMilliseconds() * 1000 * 1000
83+
];
84+
} else {
85+
return [
86+
date.getUTCFullYear(),
87+
date.getUTCMonth() + 1,
88+
date.getUTCDate(),
89+
date.getUTCHours(),
90+
date.getUTCMinutes(),
91+
date.getUTCSeconds(),
92+
date.getUTCMilliseconds() * 1000 * 1000
93+
];
94+
}
95+
}
96+
97+
//---------------------------------------------------------------------------
98+
// _makeDate()
99+
//
100+
// Returns a date from the given components. DATE and TIMESTAMP data from the
101+
// database are returned as though they used the JavaScript time zone
102+
// setting. TIMESTAMP WITH TIME ZONE and TIMESTAMP WITH LOCAL TIME ZONE data
103+
// are returned in native JavaScript format (since they contain time zone
104+
// information).
105+
//---------------------------------------------------------------------------
106+
_makeDate(useLocal, year, month, day, hour, minute, second, fseconds, offset) {
107+
if (useLocal) {
108+
return new Date(year, month - 1, day, hour, minute, second, fseconds);
109+
}
110+
return new Date(Date.UTC(year, month - 1, day, hour, minute, second,
111+
fseconds) - offset * 60000);
112+
}
113+
64114
//---------------------------------------------------------------------------
65115
// addToOptions()
66116
//
@@ -97,7 +147,7 @@ class Settings {
97147
map.set(types.DB_TYPE_BINARY_INTEGER, types.DB_TYPE_VARCHAR);
98148
map.set(types.DB_TYPE_NUMBER, types.DB_TYPE_VARCHAR);
99149
break;
100-
case types.DB_TYPE_TIMESTAMP_LTZ:
150+
case types.DB_TYPE_TIMESTAMP:
101151
map.set(types.DB_TYPE_DATE, types.DB_TYPE_VARCHAR);
102152
map.set(types.DB_TYPE_TIMESTAMP, types.DB_TYPE_VARCHAR);
103153
map.set(types.DB_TYPE_TIMESTAMP_TZ, types.DB_TYPE_VARCHAR);

lib/thin/protocol/buffer.js

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
const constants = require("./constants.js");
3030
const errors = require("../../errors.js");
31+
const settings = require("../../settings.js");
3132
const types = require("../../types.js");
3233

3334
/**
@@ -176,18 +177,14 @@ class BaseBuffer {
176177
// timestamps and the server returns the data in that format, too. The Date
177178
// type in Node.js doesn't support time zone information.
178179
//---------------------------------------------------------------------------
179-
parseOracleDate(buf, offset) {
180+
parseOracleDate(buf, useLocalTime = true) {
180181
let fseconds = 0;
181182
if (buf.length >= 11) {
182-
fseconds = Math.floor(buf.readUInt32BE(7) / 1000);
183+
fseconds = Math.floor(buf.readUInt32BE(7) / (1000 * 1000));
183184
}
184185
const year = (buf[0] - 100) * 100 + buf[1] - 100;
185-
let dateRet = new Date(Date.UTC(year, buf[2] - 1, buf[3], buf[4] - 1, buf[5] - 1,
186-
buf[6] - 1, fseconds / 1000, fseconds % 1000));
187-
if (offset) {
188-
dateRet = new Date(dateRet.getTime() - offset * 60000);
189-
}
190-
return dateRet;
186+
return settings._makeDate(useLocalTime, year, buf[2], buf[3], buf[4] - 1,
187+
buf[5] - 1, buf[6] - 1, fseconds, 0);
191188
}
192189

193190
//---------------------------------------------------------------------------
@@ -395,12 +392,12 @@ class BaseBuffer {
395392
// Reads an Oracle date from the buffer and returns a Date or a String,
396393
// depending on the desired type.
397394
//---------------------------------------------------------------------------
398-
readOracleDate(desiredType, offset) {
395+
readOracleDate(desiredType, useLocalTime) {
399396
const buf = this.readBytesWithLength();
400397
if (!buf) {
401398
return null;
402399
}
403-
const val = this.parseOracleDate(buf, offset);
400+
const val = this.parseOracleDate(buf, useLocalTime);
404401
if (desiredType === types.DB_TYPE_VARCHAR) {
405402
return val.toString();
406403
}
@@ -698,11 +695,14 @@ class BaseBuffer {
698695
//---------------------------------------------------------------------------
699696
// writeOracleDate()
700697
//
701-
// Writes the date to the buffer. The length specifies the amount of
702-
// information the server is expecting for the particular date type.
698+
// Writes the date to the buffer using the given Oracle type. Note that if a
699+
// timestamp with zero milliseconds is written, the type is automatically
700+
// changed to DB_TYPE_DATE (except for DB_TYPE_TIMESTAMP_TZ which requires
701+
// the full amount to be written).
703702
//---------------------------------------------------------------------------
704-
writeOracleDate(date, length, writeLength = true) {
703+
writeOracleDate(date, type, writeLength = true) {
705704
let fsec;
705+
let length = type._bufferSizeFactor;
706706
if (length > 7) {
707707
fsec = date.getUTCMilliseconds() * 1000 * 1000;
708708
if (fsec === 0 && length <= 11)
@@ -712,14 +712,25 @@ class BaseBuffer {
712712
this.writeUInt8(length);
713713
}
714714
const ptr = this.reserveBytes(length);
715-
const year = date.getUTCFullYear();
716-
ptr[0] = Math.floor(year / 100) + 100;
717-
ptr[1] = year % 100 + 100;
718-
ptr[2] = date.getUTCMonth() + 1;
719-
ptr[3] = date.getUTCDate();
720-
ptr[4] = date.getUTCHours() + 1;
721-
ptr[5] = date.getUTCMinutes() + 1;
722-
ptr[6] = date.getUTCSeconds() + 1;
715+
if (type === types.DB_TYPE_DATE || type == types.DB_TYPE_TIMESTAMP) {
716+
const year = date.getFullYear();
717+
ptr[0] = Math.floor(year / 100) + 100;
718+
ptr[1] = year % 100 + 100;
719+
ptr[2] = date.getMonth() + 1;
720+
ptr[3] = date.getDate();
721+
ptr[4] = date.getHours() + 1;
722+
ptr[5] = date.getMinutes() + 1;
723+
ptr[6] = date.getSeconds() + 1;
724+
} else {
725+
const year = date.getUTCFullYear();
726+
ptr[0] = Math.floor(year / 100) + 100;
727+
ptr[1] = year % 100 + 100;
728+
ptr[2] = date.getUTCMonth() + 1;
729+
ptr[3] = date.getUTCDate();
730+
ptr[4] = date.getUTCHours() + 1;
731+
ptr[5] = date.getUTCMinutes() + 1;
732+
ptr[6] = date.getUTCSeconds() + 1;
733+
}
723734
if (length > 7) {
724735
ptr.writeInt32BE(fsec, 7);
725736
if (length > 11) {

lib/thin/protocol/constants.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,6 @@ module.exports = {
429429
TNS_MSG_TYPE_ONEWAY_FN: 26,
430430

431431
// parameter keyword numbers,
432-
TNS_KEYWORD_AL8KW_TIMEZONE: 163,
433432
TNS_KEYWORD_NUM_CURRENT_SCHEMA: 168,
434433
TNS_KEYWORD_NUM_EDITION: 172,
435434

@@ -472,11 +471,6 @@ module.exports = {
472471
// session return constants
473472
TNS_SESSGET_SESSION_CHANGED: 4,
474473

475-
// timezone constants
476-
TNS_LDIREGIDFLAG: 120,
477-
TNS_LDIREGIDSET: 181,
478-
TNS_LDIMAXTIMEFIELD: 60,
479-
480474
// LOB operations
481475
TNS_LOB_OP_GET_LENGTH: 0x0001,
482476
TNS_LOB_OP_READ: 0x0002,

lib/thin/protocol/messages/auth.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,20 +143,19 @@ class AuthMessage extends Message {
143143
}
144144

145145
getAlterTimezoneStatement() {
146-
let tzHour, tzMinutes, timezoneMinutes, sign, tzRepr;
147-
let date = new Date();
148-
timezoneMinutes = date.getTimezoneOffset();
149-
tzHour = Math.trunc(timezoneMinutes / 60);
150-
tzMinutes = Math.abs((timezoneMinutes - tzHour * 60) % 60);
151-
this.conn.tzOffset = -tzHour * 60 + tzMinutes;
146+
let sign;
147+
const date = new Date();
148+
const timezoneMinutes = date.getTimezoneOffset();
149+
let tzHour = Math.trunc(timezoneMinutes / 60);
150+
const tzMinutes = Math.abs((timezoneMinutes - tzHour * 60) % 60);
152151
if (tzHour < 0) {
153152
sign = '+'; // getTimezoneOffset() = localtime - timeUTC
154153
tzHour = -tzHour;
155154
} else {
156155
sign = '-';
157156
}
158157
tzHour = tzHour.toLocaleString('en-US', {minimumIntegerDigits: 2});
159-
tzRepr = `${sign}${tzHour}:${tzMinutes}`;
158+
const tzRepr = `${sign}${tzHour}:${tzMinutes}`;
160159
return `ALTER SESSION SET TIME_ZONE ='${tzRepr}'\x00`;
161160
}
162161

lib/thin/protocol/messages/withData.js

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -387,13 +387,9 @@ class MessageWithData extends Message {
387387
oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP_LTZ ||
388388
oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP_TZ
389389
) {
390-
let offset;
391-
if (oraTypeNum === constants.TNS_DATA_TYPE_DATE ||
392-
oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP) {
393-
offset = this.connection.tzOffset;
394-
}
395-
colValue = buf.readOracleDate(outputType, offset);
396-
390+
const useLocalTime = (oraTypeNum === constants.TNS_DATA_TYPE_DATE ||
391+
oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP);
392+
colValue = buf.readOracleDate(outputType, useLocalTime);
397393
} else if (oraTypeNum === constants.TNS_DATA_TYPE_ROWID) {
398394
if (!this.inFetch) {
399395
colValue = buf.readStr(constants.TNS_CS_IMPLICIT);
@@ -464,7 +460,6 @@ class MessageWithData extends Message {
464460
processReturnParameter(buf) {
465461
let keywordNum = 0;
466462
let keyTextValue;
467-
let keyBinaryValue;
468463
let numParams = buf.readUB2(); // al8o4l (ignored)
469464

470465
for (let i = 0; i < numParams; i++) {
@@ -482,19 +477,9 @@ class MessageWithData extends Message {
482477
}
483478
numBytes = buf.readUB2(); // value
484479
if (numBytes > 0) {
485-
keyBinaryValue = buf.readBytesWithLength();
480+
buf.skipBytesChunked();
486481
}
487482
keywordNum = buf.readUB2(); // keyword num
488-
if (keywordNum === constants.TNS_KEYWORD_AL8KW_TIMEZONE) {
489-
let hour;
490-
if (keyBinaryValue[2] > constants.TNS_LDIREGIDFLAG) {
491-
hour = keyBinaryValue[4] - constants.TNS_LDIREGIDSET;
492-
} else {
493-
hour = keyBinaryValue[4] - constants.TNS_LDIMAXTIMEFIELD;
494-
}
495-
const minute = keyBinaryValue[5] - constants.TNS_LDIMAXTIMEFIELD;
496-
this.connection.tzOffset = hour * 60 + minute;
497-
}
498483
if (keywordNum === constants.TNS_KEYWORD_NUM_CURRENT_SCHEMA) {
499484
this.connection.currentSchema = keyTextValue;
500485
} else if (keywordNum === constants.TNS_KEYWORD_NUM_EDITION) {
@@ -741,7 +726,7 @@ class MessageWithData extends Message {
741726
oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP_TZ ||
742727
oraTypeNum === constants.TNS_DATA_TYPE_TIMESTAMP_LTZ
743728
) {
744-
buf.writeOracleDate(value, variable.type._bufferSizeFactor);
729+
buf.writeOracleDate(value, variable.type);
745730
} else if (oraTypeNum === constants.TNS_DATA_TYPE_BINARY_DOUBLE) {
746731
buf.writeBinaryDouble(value);
747732
} else if (oraTypeNum === constants.TNS_DATA_TYPE_BINARY_FLOAT) {

lib/thin/protocol/oson.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -520,10 +520,10 @@ class OsonTreeSegment extends GrowableBuffer {
520520
} else if (util.isDate(value)) {
521521
if (value.getUTCMilliseconds() === 0) {
522522
this.writeUInt8(constants.TNS_JSON_TYPE_TIMESTAMP7);
523-
this.writeOracleDate(value, 7, false);
523+
this.writeOracleDate(value, types.DB_TYPE_DATE, false);
524524
} else {
525525
this.writeUInt8(constants.TNS_JSON_TYPE_TIMESTAMP);
526-
this.writeOracleDate(value, 11, false);
526+
this.writeOracleDate(value, types.DB_TYPE_TIMESTAMP, false);
527527
}
528528

529529
// handle buffers

lib/types.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,8 @@ const DB_TYPE_CONVERSION_MAP = new Map([
248248
[DB_TYPE_TIMESTAMP_LTZ, DB_TYPE_TIMESTAMP_LTZ]
249249
])],
250250
[DB_TYPE_TIMESTAMP_LTZ, new Map([
251-
[DB_TYPE_VARCHAR, DB_TYPE_VARCHAR]
251+
[DB_TYPE_VARCHAR, DB_TYPE_VARCHAR],
252+
[DB_TYPE_TIMESTAMP_TZ, DB_TYPE_TIMESTAMP_TZ]
252253
])],
253254
[DB_TYPE_TIMESTAMP_TZ, new Map([
254255
[DB_TYPE_VARCHAR, DB_TYPE_VARCHAR],
@@ -270,7 +271,7 @@ const DB_TYPE_FETCH_TYPE_MAP = new Map([
270271
[DB_TYPE_CHAR, DB_TYPE_CHAR],
271272
[DB_TYPE_CLOB, DB_TYPE_CLOB],
272273
[DB_TYPE_CURSOR, DB_TYPE_CURSOR],
273-
[DB_TYPE_DATE, DB_TYPE_TIMESTAMP_LTZ],
274+
[DB_TYPE_DATE, DB_TYPE_DATE],
274275
[DB_TYPE_INTERVAL_DS, DB_TYPE_INTERVAL_DS],
275276
[DB_TYPE_INTERVAL_YM, DB_TYPE_INTERVAL_YM],
276277
[DB_TYPE_JSON, DB_TYPE_JSON],
@@ -284,9 +285,9 @@ const DB_TYPE_FETCH_TYPE_MAP = new Map([
284285
[DB_TYPE_OBJECT, DB_TYPE_OBJECT],
285286
[DB_TYPE_RAW, DB_TYPE_RAW],
286287
[DB_TYPE_ROWID, DB_TYPE_ROWID],
287-
[DB_TYPE_TIMESTAMP, DB_TYPE_TIMESTAMP_LTZ],
288-
[DB_TYPE_TIMESTAMP_LTZ, DB_TYPE_TIMESTAMP_LTZ],
289-
[DB_TYPE_TIMESTAMP_TZ, DB_TYPE_TIMESTAMP_LTZ],
288+
[DB_TYPE_TIMESTAMP, DB_TYPE_TIMESTAMP],
289+
[DB_TYPE_TIMESTAMP_LTZ, DB_TYPE_TIMESTAMP_TZ],
290+
[DB_TYPE_TIMESTAMP_TZ, DB_TYPE_TIMESTAMP_TZ],
290291
[DB_TYPE_UROWID, DB_TYPE_UROWID],
291292
[DB_TYPE_VARCHAR, DB_TYPE_VARCHAR]
292293
]);

0 commit comments

Comments
 (0)