From 671c1d1e047abdfce2724689a32509120818ab88 Mon Sep 17 00:00:00 2001 From: Michael Sun Date: Fri, 1 Dec 2023 15:24:12 -0800 Subject: [PATCH 1/5] chore: add range error validation for date types --- src/data-type.ts | 2 +- src/data-types/date.ts | 22 +++++- src/data-types/datetime.ts | 22 +++++- src/data-types/datetime2.ts | 22 +++++- src/data-types/datetimeoffset.ts | 22 +++++- src/data-types/smalldatetime.ts | 20 +++++- test/unit/data-type.js | 115 +++++++++++++++++++++++++++++++ 7 files changed, 214 insertions(+), 11 deletions(-) diff --git a/src/data-type.ts b/src/data-type.ts index 77d81eed9..ae0e4447b 100644 --- a/src/data-type.ts +++ b/src/data-type.ts @@ -80,7 +80,7 @@ export interface DataType { generateTypeInfo(parameter: ParameterData, options: InternalConnectionOptions): Buffer; generateParameterLength(parameter: ParameterData, options: InternalConnectionOptions): Buffer; generateParameterData(parameter: ParameterData, options: InternalConnectionOptions): Generator; - validate(value: any, collation: Collation | undefined): any; // TODO: Refactor 'any' and replace with more specific type. + validate(value: any, collation: Collation | undefined, options?: InternalConnectionOptions): any; // TODO: Refactor 'any' and replace with more specific type. hasTableName?: boolean; diff --git a/src/data-types/date.ts b/src/data-types/date.ts index 8e8e1b760..b5433541b 100644 --- a/src/data-types/date.ts +++ b/src/data-types/date.ts @@ -1,5 +1,8 @@ import { type DataType } from '../data-type'; import { ChronoUnit, LocalDate } from '@js-joda/core'; +import { type InternalConnectionOptions } from '../connection'; + +import { Collation } from '../collation'; // globalDate is to be used for JavaScript's global 'Date' object to avoid name clashing with the 'Date' constant below const globalDate = global.Date; @@ -7,6 +10,9 @@ const EPOCH_DATE = LocalDate.ofYearDay(1, 1); const NULL_LENGTH = Buffer.from([0x00]); const DATA_LENGTH = Buffer.from([0x03]); +const MIN_DATE = new globalDate('January 1, 0001'); +const MAX_DATE = new globalDate('December 31, 9999'); + const Date: DataType = { id: 0x28, type: 'DATEN', @@ -35,7 +41,7 @@ const Date: DataType = { const value = parameter.value as any; // Temporary solution. Remove 'any' later. - let date; + let date: LocalDate; if (options.useUTC) { date = LocalDate.of(value.getUTCFullYear(), value.getUTCMonth() + 1, value.getUTCDate()); } else { @@ -49,7 +55,7 @@ const Date: DataType = { }, // TODO: value is technically of type 'unknown'. - validate: function(value): null | Date { + validate: function(value: any, collation: Collation | undefined, options: InternalConnectionOptions): null | Date { if (value == null) { return null; } @@ -58,6 +64,18 @@ const Date: DataType = { value = new globalDate(globalDate.parse(value)); } + value = value as Date; + + // TODO: check date range: January 1, 0001, through December 31, 9999 + // : time range: 00:00:00 through 23:59:59.997 + if (options && options.useUTC) { + value = new globalDate(value.toUTCString()); + } + + if (value < MIN_DATE || value > MAX_DATE) { + throw new TypeError('Out of range.'); + } + if (isNaN(value)) { throw new TypeError('Invalid date.'); } diff --git a/src/data-types/datetime.ts b/src/data-types/datetime.ts index 02f62ef15..3058f3a63 100644 --- a/src/data-types/datetime.ts +++ b/src/data-types/datetime.ts @@ -1,11 +1,17 @@ import { type DataType } from '../data-type'; import DateTimeN from './datetimen'; import { ChronoUnit, LocalDate } from '@js-joda/core'; +import { type InternalConnectionOptions } from '../connection'; + +import { Collation } from '../collation'; const EPOCH_DATE = LocalDate.ofYearDay(1900, 1); const NULL_LENGTH = Buffer.from([0x00]); const DATA_LENGTH = Buffer.from([0x08]); +const MIN_DATE = new Date('January 1, 1753'); +const MAX_DATE = new Date('December 31, 9999'); + const DateTime: DataType = { id: 0x3D, type: 'DATETIME', @@ -34,7 +40,7 @@ const DateTime: DataType = { const value = parameter.value as any; // Temporary solution. Remove 'any' later. - let date; + let date: LocalDate; if (options.useUTC) { date = LocalDate.of(value.getUTCFullYear(), value.getUTCMonth() + 1, value.getUTCDate()); } else { @@ -72,7 +78,7 @@ const DateTime: DataType = { }, // TODO: type 'any' needs to be revisited. - validate: function(value): null | number { + validate: function(value: any, collation: Collation | undefined, options: InternalConnectionOptions): null | number { if (value == null) { return null; } @@ -81,6 +87,18 @@ const DateTime: DataType = { value = new Date(Date.parse(value)); } + value = value as Date; + + // TODO: check date range: January 1, 1753, through December 31, 9999 + // : time range: 00:00:00 through 23:59:59.997 + if (options && options.useUTC) { + value = new Date(value.toUTCString()); + } + + if (value < MIN_DATE || value > MAX_DATE) { + throw new TypeError('Out of range.'); + } + if (isNaN(value)) { throw new TypeError('Invalid date.'); } diff --git a/src/data-types/datetime2.ts b/src/data-types/datetime2.ts index a31151107..ab7dfa821 100644 --- a/src/data-types/datetime2.ts +++ b/src/data-types/datetime2.ts @@ -1,10 +1,16 @@ import { type DataType } from '../data-type'; import { ChronoUnit, LocalDate } from '@js-joda/core'; import WritableTrackingBuffer from '../tracking-buffer/writable-tracking-buffer'; +import { type InternalConnectionOptions } from '../connection'; + +import { Collation } from '../collation'; const EPOCH_DATE = LocalDate.ofYearDay(1, 1); const NULL_LENGTH = Buffer.from([0x00]); +const MIN_DATE = new Date('January 1, 0001'); +const MAX_DATE = new Date('December 31, 9999'); + const DateTime2: DataType & { resolveScale: NonNullable } = { id: 0x2A, type: 'DATETIME2N', @@ -64,7 +70,7 @@ const DateTime2: DataType & { resolveScale: NonNullable MAX_DATE) { + throw new TypeError('Out of range.'); + } + if (isNaN(value)) { throw new TypeError('Invalid date.'); } diff --git a/src/data-types/datetimeoffset.ts b/src/data-types/datetimeoffset.ts index b936fcc18..aa7fa888a 100644 --- a/src/data-types/datetimeoffset.ts +++ b/src/data-types/datetimeoffset.ts @@ -1,10 +1,16 @@ import { type DataType } from '../data-type'; import { ChronoUnit, LocalDate } from '@js-joda/core'; import WritableTrackingBuffer from '../tracking-buffer/writable-tracking-buffer'; +import { type InternalConnectionOptions } from '../connection'; + +import { Collation } from '../collation'; const EPOCH_DATE = LocalDate.ofYearDay(1, 1); const NULL_LENGTH = Buffer.from([0x00]); +const MIN_DATE = new Date('January 1, 0001'); +const MAX_DATE = new Date('December 31, 9999'); + const DateTimeOffset: DataType & { resolveScale: NonNullable } = { id: 0x2B, type: 'DATETIMEOFFSETN', @@ -62,7 +68,7 @@ const DateTimeOffset: DataType & { resolveScale: NonNullable MAX_DATE) { + throw new TypeError('Out of range.'); + } + if (isNaN(value)) { throw new TypeError('Invalid date.'); } diff --git a/src/data-types/smalldatetime.ts b/src/data-types/smalldatetime.ts index b17d0d834..86db10d03 100644 --- a/src/data-types/smalldatetime.ts +++ b/src/data-types/smalldatetime.ts @@ -1,9 +1,15 @@ import { type DataType } from '../data-type'; import DateTimeN from './datetimen'; +import { type InternalConnectionOptions } from '../connection'; + +import { Collation } from '../collation'; const EPOCH_DATE = new Date(1900, 0, 1); const UTC_EPOCH_DATE = new Date(Date.UTC(1900, 0, 1)); +const MIN_DATE = new Date(1900, 1, 1); +const MAX_DATE = new Date(2079, 5, 6, 23, 59, 59, 0); + const DATA_LENGTH = Buffer.from([0x04]); const NULL_LENGTH = Buffer.from([0x00]); @@ -35,7 +41,7 @@ const SmallDateTime: DataType = { const buffer = Buffer.alloc(4); - let days, dstDiff, minutes; + let days: number, dstDiff: number, minutes: number; if (options.useUTC) { days = Math.floor((parameter.value.getTime() - UTC_EPOCH_DATE.getTime()) / (1000 * 60 * 60 * 24)); minutes = (parameter.value.getUTCHours() * 60) + parameter.value.getUTCMinutes(); @@ -51,7 +57,7 @@ const SmallDateTime: DataType = { yield buffer; }, - validate: function(value): null | Date { + validate: function(value, collation: Collation | undefined, options: InternalConnectionOptions): null | Date { if (value == null) { return null; } @@ -60,6 +66,16 @@ const SmallDateTime: DataType = { value = new Date(Date.parse(value)); } + value = value as Date; + + if (options && options.useUTC) { + value = new Date(value.toUTCString()); + } + + if (value < MIN_DATE || value > MAX_DATE) { + throw new TypeError('Out of range.'); + } + if (isNaN(value)) { throw new TypeError('Invalid date.'); } diff --git a/test/unit/data-type.js b/test/unit/data-type.js index 6f51391bd..0186f9e32 100644 --- a/test/unit/data-type.js +++ b/test/unit/data-type.js @@ -211,6 +211,18 @@ describe('Date', function() { assert.deepEqual(result, expected); }); }); + + describe('.validate', function() { + it('returns a TypeError for dates that are out of range', function() { + assert.throws(() => { + TYPES.Date.validate(new Date('Dec 31 2000')); + }, TypeError, 'Out of range.'); + + assert.throws(() => { + TYPES.Date.validate(new Date('Jan 1, 10000')); + }, TypeError, 'Out of range.'); + }); + }); }); describe('DateTime', function() { @@ -246,6 +258,18 @@ describe('DateTime', function() { assert.deepEqual(result, expected); }); }); + + describe('.validate', function() { + it('returns a TypeError for dates that are out of range', function() { + assert.throws(() => { + TYPES.DateTime.validate(new Date('Dec 1, 1752')); + }, TypeError, 'Out of range.'); + + assert.throws(() => { + TYPES.DateTime.validate('Jan 1, 10000'); + }, TypeError, 'Out of range.'); + }); + }); }); describe('DateTime2', function() { @@ -293,6 +317,17 @@ describe('DateTime2', function() { assert.deepEqual(buffer, expected); }); }); + describe('.validate', function() { + it('returns a TypeError for dates that are out of range', function() { + assert.throws(() => { + TYPES.DateTime2.validate(new Date('Dec 31, 2000')); + }, TypeError, 'Out of range.'); + + assert.throws(() => { + TYPES.DateTime2.validate(new Date('Jan 1, 10000')); + }, TypeError, 'Out of range.'); + }); + }); }); describe('DateTimeOffset', function() { @@ -346,6 +381,18 @@ describe('DateTimeOffset', function() { assert.deepEqual(buffer, expected); }); }); + + describe('.validate', function() { + it('returns a TypeError for dates that are out of range', function() { + assert.throws(() => { + TYPES.DateTimeOffset.validate(new Date('Dec 31, 2000')); + }, TypeError, 'Out of range.'); + + assert.throws(() => { + TYPES.DateTimeOffset.validate(new Date('Jan 1, 10000')); + }, TypeError, 'Out of range.'); + }); + }); }); describe('Decimal', function() { @@ -449,6 +496,34 @@ describe('Decimal', function() { assert.deepEqual(result4, expected4); }); }); + + describe('.validate', function() { + it('returns a TypeError for decimals if the passed in value is unacceptable', function() { + assert.throws(() => { + TYPES.Decimal.validate('ABC'); + }, TypeError, 'Invalid number.'); + assert.throws(() => { + TYPES.Decimal.validate('e123'); + }, TypeError, 'Invalid number.'); + }); + + it('returns a the "Infinity" literal the decimals is outside the double-precision 64-bit IEEE 754-2019 format range', function() { + assert.equal(TYPES.Decimal.validate(1.7976931348623159e+308), Infinity); + assert.equal(TYPES.Decimal.validate(-1.7976931348623159e+308), -Infinity); + assert.equal(TYPES.Decimal.validate('Infinity'), Infinity); + assert.equal(TYPES.Decimal.validate('-Infinity'), -Infinity); + }); + + it('Corect pasing the decimals with special cases', function() { + assert.equal(TYPES.Decimal.validate('123.3.3'), 123.3); + assert.equal(TYPES.Decimal.validate('1-23'), 1); + assert.equal(TYPES.Decimal.validate('1+23'), 1); + assert.equal(TYPES.Decimal.validate('1e23e4'), 1e23); + assert.equal(TYPES.Decimal.validate(' 123'), 123); + assert.equal(TYPES.Decimal.validate('1-e5'), 1); + assert.equal(TYPES.Decimal.validate('1e2e3'), 100); + }); + }); }); describe('Float', function() { @@ -492,6 +567,34 @@ describe('Float', function() { assert.deepEqual(result, expected); }); }); + + describe('.validate', function() { + it('returns a TypeError for decimals if the passed in value is unacceptable', function() { + assert.throws(() => { + TYPES.Float.validate('ABC'); + }, TypeError, 'Invalid number.'); + assert.throws(() => { + TYPES.Float.validate('e123'); + }, TypeError, 'Invalid number.'); + }); + + it('returns a the "Infinity" literal the decimals is outside the double-precision 64-bit IEEE 754-2019 format range', function() { + assert.equal(TYPES.Float.validate(1.7976931348623159e+308), Infinity); + assert.equal(TYPES.Float.validate(-1.7976931348623159e+308), -Infinity); + assert.equal(TYPES.Float.validate('Infinity'), Infinity); + assert.equal(TYPES.Float.validate('-Infinity'), -Infinity); + }); + + it('Corect pasing the decimals with special cases', function() { + assert.equal(TYPES.Float.validate('123.3.3'), 123.3); + assert.equal(TYPES.Float.validate('1-23'), 1); + assert.equal(TYPES.Float.validate('1+23'), 1); + assert.equal(TYPES.Float.validate('1e23e4'), 1e23); + assert.equal(TYPES.Float.validate(' 123'), 123); + assert.equal(TYPES.Float.validate('1-e5'), 1); + assert.equal(TYPES.Float.validate('1e2e3'), 100); + }); + }); }); describe('Image', function() { @@ -919,6 +1022,18 @@ describe('SmallDateTime', function() { assert.deepEqual(result, expected); }); }); + + describe('.validate', function() { + it('returns a TypeError for dates that are out of range', function() { + assert.throws(() => { + TYPES.SmallDateTime.validate(new Date('Dec 31, 1889')); + }, TypeError, 'Out of range.'); + + assert.throws(() => { + TYPES.SmallDateTime.validate(new Date('June 7, 2079')); + }, TypeError, 'Out of range.'); + }); + }); }); describe('SmallInt', function() { From 75118e03ce118f78fe6a0f31cd08c031ea8bcb2a Mon Sep 17 00:00:00 2001 From: mShan0 <96149598+mShan0@users.noreply.github.com> Date: Tue, 5 Dec 2023 14:21:07 -0800 Subject: [PATCH 2/5] add validation tests for numbers --- test/unit/data-type.js | 72 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/test/unit/data-type.js b/test/unit/data-type.js index 0186f9e32..679da5fd7 100644 --- a/test/unit/data-type.js +++ b/test/unit/data-type.js @@ -680,6 +680,24 @@ describe('Int', function() { assert.deepEqual(result, expected); }); }); + + describe.only('.validate', function() { + it('throws Invalid number error for NaN input', function() { + assert.throws(() => { + TYPES.Int.validate('string'); + }, TypeError, 'Invalid number.'); + }); + + it('throws Out of Range error for numbers out of range', function() { + assert.throws(() => { + TYPES.Int.validate(-2147483648 - 1); + }, TypeError, 'Value must be between -2147483648 and 2147483647, inclusive.'); + + assert.throws(() => { + TYPES.Int.validate(2147483647 + 1); + }, TypeError, 'Value must be between -2147483648 and 2147483647, inclusive.'); + }); + }); }); describe('Money', function() { @@ -1077,6 +1095,24 @@ describe('SmallInt', function() { assert.deepEqual(result, expected); }); }); + + describe('.validate', function() { + it('throws Invalid number error for NaN input', function() { + assert.throws(() => { + TYPES.SmallInt.validate('string'); + }, TypeError, 'Invalid number.'); + }); + + it('throws Out of Range error for numbers out of range', function() { + assert.throws(() => { + TYPES.SmallInt.validate(-32768 - 1); + }, TypeError, 'Value must be between -32768 and 32767, inclusive.'); + + assert.throws(() => { + TYPES.SmallInt.validate(32767 + 1); + }, TypeError, 'Value must be between -32768 and 32767, inclusive.'); + }); + }); }); describe('SmallMoney', function() { @@ -1109,6 +1145,24 @@ describe('SmallMoney', function() { const buffer = Buffer.concat([...type.generateParameterData(parameterValue, { useUTC: false })]); assert.deepEqual(buffer, expected); }); + + describe('.validate', function() { + it('throws Invalid number error for NaN input', function() { + assert.throws(() => { + TYPES.SmallMoney.validate('string'); + }, TypeError, 'Invalid number.'); + }); + + it('throws Out of Range error for numbers out of range', function() { + assert.throws(() => { + TYPES.SmallMoney.validate(-214748.3648 - 0.0001); + }, TypeError, 'Value must be between -214748.3648 and 214748.3647.'); + + assert.throws(() => { + TYPES.SmallMoney.validate(214748.3647 + 0.0001); + }, TypeError, 'Value must be between -214748.3648 and 214748.3647.'); + }); + }); }); describe('.generateTypeInfo', function() { @@ -1257,6 +1311,24 @@ describe('TinyInt', function() { assert.deepEqual(result, expected); }); }); + + describe('.validate', function() { + it('throws Invalid number error for NaN input', function() { + assert.throws(() => { + TYPES.TinyInt.validate('string'); + }, TypeError, 'Invalid number.'); + }); + + it('throws Out of Range error for numbers out of range', function() { + assert.throws(() => { + TYPES.TinyInt.validate(-1); + }, TypeError, 'Value must be between 0 and 255, inclusive.'); + + assert.throws(() => { + TYPES.TinyInt.validate(256); + }, TypeError, 'Value must be between 0 and 255, inclusive.'); + }); + }); }); describe('TVP', function() { From 6effc24588a623e59f312a80c8d38080ca685dd8 Mon Sep 17 00:00:00 2001 From: mShan0 <96149598+mShan0@users.noreply.github.com> Date: Tue, 5 Dec 2023 14:22:02 -0800 Subject: [PATCH 3/5] remove only --- test/unit/data-type.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/data-type.js b/test/unit/data-type.js index 679da5fd7..e42fd2b14 100644 --- a/test/unit/data-type.js +++ b/test/unit/data-type.js @@ -681,7 +681,7 @@ describe('Int', function() { }); }); - describe.only('.validate', function() { + describe('.validate', function() { it('throws Invalid number error for NaN input', function() { assert.throws(() => { TYPES.Int.validate('string'); From 30744594e175451c31c8590307d9cb06bb84b208 Mon Sep 17 00:00:00 2001 From: mShan0 <96149598+mShan0@users.noreply.github.com> Date: Tue, 5 Dec 2023 14:54:58 -0800 Subject: [PATCH 4/5] add money validation --- src/data-types/money.ts | 8 ++++++++ test/unit/data-type.js | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/data-types/money.ts b/src/data-types/money.ts index 7a614606e..5c1271484 100644 --- a/src/data-types/money.ts +++ b/src/data-types/money.ts @@ -49,6 +49,14 @@ const Money: DataType = { if (isNaN(value)) { throw new TypeError('Invalid number.'); } + // money: -922337203685477.5808 to 922337203685477.5807 + // in javascript -922337203685477.5808 === -922337203685477.6 + // 922337203685477.5807 === 922337203685477.6 + // javascript number doesn't have enough precision. + if (value < -922337203685477.6 || value > 922337203685477.6) { + throw new TypeError('Value must be between -922337203685477.5808 and 922337203685477.5807, inclusive.'); + } + return value; } }; diff --git a/test/unit/data-type.js b/test/unit/data-type.js index e42fd2b14..362c2ae15 100644 --- a/test/unit/data-type.js +++ b/test/unit/data-type.js @@ -741,6 +741,25 @@ describe('Money', function() { assert.deepEqual(result, expected); }); }); + + describe.only('.validate', function() { + it('throws Invalid number error for NaN input', function() { + assert.throws(() => { + TYPES.TinyInt.validate('string'); + }, TypeError, 'Invalid number.'); + }); + + it('throws Out of Range error for numbers out of range', function() { + assert.throws(() => { + + TYPES.Money.validate(-922337203685477.5808 - 0.1); + }, TypeError, 'Value must be between -922337203685477.5808 and 922337203685477.5807, inclusive.'); + + assert.throws(() => { + TYPES.Money.validate(922337203685477.5807 + 0.1); + }, TypeError, 'Value must be between -922337203685477.5808 and 922337203685477.5807, inclusive.'); + }); + }); }); describe('NChar', function() { From 772340e89d4fcc6c780378e5b04016f5718b22c1 Mon Sep 17 00:00:00 2001 From: Michael Sun <47126816+MichaelSun90@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:43:18 -0800 Subject: [PATCH 5/5] chore: remove un-needed types --- src/data-types/date.ts | 5 +---- src/data-types/datetime.ts | 5 +---- src/data-types/datetime2.ts | 5 +---- src/data-types/datetimeoffset.ts | 5 +---- src/data-types/smalldatetime.ts | 5 +---- 5 files changed, 5 insertions(+), 20 deletions(-) diff --git a/src/data-types/date.ts b/src/data-types/date.ts index b5433541b..9bec9d5ef 100644 --- a/src/data-types/date.ts +++ b/src/data-types/date.ts @@ -1,8 +1,5 @@ import { type DataType } from '../data-type'; import { ChronoUnit, LocalDate } from '@js-joda/core'; -import { type InternalConnectionOptions } from '../connection'; - -import { Collation } from '../collation'; // globalDate is to be used for JavaScript's global 'Date' object to avoid name clashing with the 'Date' constant below const globalDate = global.Date; @@ -55,7 +52,7 @@ const Date: DataType = { }, // TODO: value is technically of type 'unknown'. - validate: function(value: any, collation: Collation | undefined, options: InternalConnectionOptions): null | Date { + validate: function(value, collation, options): null | Date { if (value == null) { return null; } diff --git a/src/data-types/datetime.ts b/src/data-types/datetime.ts index 3058f3a63..6c253c756 100644 --- a/src/data-types/datetime.ts +++ b/src/data-types/datetime.ts @@ -1,9 +1,6 @@ import { type DataType } from '../data-type'; import DateTimeN from './datetimen'; import { ChronoUnit, LocalDate } from '@js-joda/core'; -import { type InternalConnectionOptions } from '../connection'; - -import { Collation } from '../collation'; const EPOCH_DATE = LocalDate.ofYearDay(1900, 1); const NULL_LENGTH = Buffer.from([0x00]); @@ -78,7 +75,7 @@ const DateTime: DataType = { }, // TODO: type 'any' needs to be revisited. - validate: function(value: any, collation: Collation | undefined, options: InternalConnectionOptions): null | number { + validate: function(value: any, collation, options): null | number { if (value == null) { return null; } diff --git a/src/data-types/datetime2.ts b/src/data-types/datetime2.ts index ab7dfa821..5299c3b9d 100644 --- a/src/data-types/datetime2.ts +++ b/src/data-types/datetime2.ts @@ -1,9 +1,6 @@ import { type DataType } from '../data-type'; import { ChronoUnit, LocalDate } from '@js-joda/core'; import WritableTrackingBuffer from '../tracking-buffer/writable-tracking-buffer'; -import { type InternalConnectionOptions } from '../connection'; - -import { Collation } from '../collation'; const EPOCH_DATE = LocalDate.ofYearDay(1, 1); const NULL_LENGTH = Buffer.from([0x00]); @@ -108,7 +105,7 @@ const DateTime2: DataType & { resolveScale: NonNullable