Fix date functions for databases not in UTC timezone (#16027)

* Fix date functions for databases not in UTC timezone

* Fix passing raw date objects to DB

* Add unit test

* Use global timezone for mysql and maria

* Fix unit test timezone inconsistency as date object is in local timezone

* Date and datetime fields should be in local timezone

* Fix missing padStart in local string

Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>
This commit is contained in:
ian
2022-11-14 22:38:40 +08:00
committed by GitHub
parent 692b8c4807
commit 282d5ae54e
6 changed files with 112 additions and 50 deletions

View File

@@ -1,11 +1,16 @@
import { DateHelper } from '../types';
export class DateHelperSQLite extends DateHelper {
parse(date: string): string {
parse(date: string | Date): string {
if (!date) {
return date;
}
// Date generated from NOW()
if (date instanceof Date) {
return String(date.getTime());
}
// Return the time as string
if (date.length <= 8 && date.includes(':')) {
return date;

View File

@@ -2,7 +2,11 @@ import { DatabaseHelper } from '../types';
import { parseISO } from 'date-fns';
export abstract class DateHelper extends DatabaseHelper {
parse(date: string): string {
parse(date: string | Date): string {
// Date generated from NOW()
if (date instanceof Date) {
return date.toISOString();
}
return date;
}
readTimestampString(date: string): string {

View File

@@ -1,37 +1,44 @@
import { FnHelper, FnHelperOptions } from '../types';
import { Knex } from 'knex';
const parseLocaltime = (columnType?: string) => {
if (columnType === 'timestamp') {
return ` AT TIME ZONE 'UTC'`;
}
return '';
};
export class FnHelperMSSQL extends FnHelper {
year(table: string, column: string): Knex.Raw {
return this.knex.raw('DATEPART(year, ??.??)', [table, column]);
year(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`DATEPART(year, ??.??${parseLocaltime(options?.type)})`, [table, column]);
}
month(table: string, column: string): Knex.Raw {
return this.knex.raw('DATEPART(month, ??.??)', [table, column]);
month(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`DATEPART(month, ??.??${parseLocaltime(options?.type)})`, [table, column]);
}
week(table: string, column: string): Knex.Raw {
return this.knex.raw('DATEPART(week, ??.??)', [table, column]);
week(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`DATEPART(week, ??.??${parseLocaltime(options?.type)})`, [table, column]);
}
day(table: string, column: string): Knex.Raw {
return this.knex.raw('DATEPART(day, ??.??)', [table, column]);
day(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`DATEPART(day, ??.??${parseLocaltime(options?.type)})`, [table, column]);
}
weekday(table: string, column: string): Knex.Raw {
return this.knex.raw('DATEPART(weekday, ??.??)', [table, column]);
weekday(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`DATEPART(weekday, ??.??${parseLocaltime(options?.type)})`, [table, column]);
}
hour(table: string, column: string): Knex.Raw {
return this.knex.raw('DATEPART(hour, ??.??)', [table, column]);
hour(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`DATEPART(hour, ??.??${parseLocaltime(options?.type)})`, [table, column]);
}
minute(table: string, column: string): Knex.Raw {
return this.knex.raw('DATEPART(minute, ??.??)', [table, column]);
minute(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`DATEPART(minute, ??.??${parseLocaltime(options?.type)})`, [table, column]);
}
second(table: string, column: string): Knex.Raw {
return this.knex.raw('DATEPART(second, ??.??)', [table, column]);
second(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`DATEPART(second, ??.??${parseLocaltime(options?.type)})`, [table, column]);
}
count(table: string, column: string, options?: FnHelperOptions): Knex.Raw<any> {

View File

@@ -1,37 +1,44 @@
import { FnHelper, FnHelperOptions } from '../types';
import { Knex } from 'knex';
const parseLocaltime = (columnType?: string) => {
if (columnType === 'timestamp') {
return `CONVERT_TZ(??.??, @@GLOBAL.time_zone, 'UTC')`;
}
return '??.??';
};
export class FnHelperMySQL extends FnHelper {
year(table: string, column: string): Knex.Raw {
return this.knex.raw('YEAR(??.??)', [table, column]);
year(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`YEAR(${parseLocaltime(options?.type)})`, [table, column]);
}
month(table: string, column: string): Knex.Raw {
return this.knex.raw('MONTH(??.??)', [table, column]);
month(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`MONTH(${parseLocaltime(options?.type)})`, [table, column]);
}
week(table: string, column: string): Knex.Raw {
return this.knex.raw('WEEK(??.??)', [table, column]);
week(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`WEEK(${parseLocaltime(options?.type)})`, [table, column]);
}
day(table: string, column: string): Knex.Raw {
return this.knex.raw('DAYOFMONTH(??.??)', [table, column]);
day(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`DAYOFMONTH(${parseLocaltime(options?.type)})`, [table, column]);
}
weekday(table: string, column: string): Knex.Raw {
return this.knex.raw('DAYOFWEEK(??.??)', [table, column]);
weekday(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`DAYOFWEEK(${parseLocaltime(options?.type)})`, [table, column]);
}
hour(table: string, column: string): Knex.Raw {
return this.knex.raw('HOUR(??.??)', [table, column]);
hour(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`HOUR(${parseLocaltime(options?.type)})`, [table, column]);
}
minute(table: string, column: string): Knex.Raw {
return this.knex.raw('MINUTE(??.??)', [table, column]);
minute(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`MINUTE(${parseLocaltime(options?.type)})`, [table, column]);
}
second(table: string, column: string): Knex.Raw {
return this.knex.raw('SECOND(??.??)', [table, column]);
second(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`SECOND(${parseLocaltime(options?.type)})`, [table, column]);
}
count(table: string, column: string, options?: FnHelperOptions): Knex.Raw {

View File

@@ -1,37 +1,44 @@
import { FnHelper, FnHelperOptions } from '../types';
import { Knex } from 'knex';
const parseLocaltime = (columnType?: string) => {
if (columnType === 'timestamp') {
return ` AT TIME ZONE 'UTC'`;
}
return '';
};
export class FnHelperPostgres extends FnHelper {
year(table: string, column: string): Knex.Raw {
return this.knex.raw('EXTRACT(YEAR FROM ??.??)', [table, column]);
year(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`DATE_PART('year', ??.??${parseLocaltime(options?.type)})`, [table, column]);
}
month(table: string, column: string): Knex.Raw {
return this.knex.raw('EXTRACT(MONTH FROM ??.??)', [table, column]);
month(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`DATE_PART('month', ??.??${parseLocaltime(options?.type)})`, [table, column]);
}
week(table: string, column: string): Knex.Raw {
return this.knex.raw('EXTRACT(WEEK FROM ??.??)', [table, column]);
week(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`DATE_PART('week', ??.??${parseLocaltime(options?.type)})`, [table, column]);
}
day(table: string, column: string): Knex.Raw {
return this.knex.raw('EXTRACT(DAY FROM ??.??)', [table, column]);
day(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`DATE_PART('day', ??.??${parseLocaltime(options?.type)})`, [table, column]);
}
weekday(table: string, column: string): Knex.Raw {
return this.knex.raw('EXTRACT(DOW FROM ??.??)', [table, column]);
weekday(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`DATE_PART('dow', ??.??${parseLocaltime(options?.type)})`, [table, column]);
}
hour(table: string, column: string): Knex.Raw {
return this.knex.raw('EXTRACT(HOUR FROM ??.??)', [table, column]);
hour(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`DATE_PART('hour', ??.??${parseLocaltime(options?.type)})`, [table, column]);
}
minute(table: string, column: string): Knex.Raw {
return this.knex.raw('EXTRACT(MINUTE FROM ??.??)', [table, column]);
minute(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`DATE_PART('minute', ??.??${parseLocaltime(options?.type)})`, [table, column]);
}
second(table: string, column: string): Knex.Raw {
return this.knex.raw('EXTRACT(SECOND FROM ??.??)', [table, column]);
second(table: string, column: string, options: FnHelperOptions): Knex.Raw {
return this.knex.raw(`DATE_PART('second', ??.??${parseLocaltime(options?.type)})`, [table, column]);
}
count(table: string, column: string, options?: FnHelperOptions): Knex.Raw {

View File

@@ -214,7 +214,39 @@ describe('Integration Tests', () => {
},
]);
});
it('with date object values', async () => {
const result = await service.processDates(
[
{
[dateFieldId]: new Date(1666777777000),
[dateTimeFieldId]: new Date(1666666666000),
[timestampFieldId]: new Date(1666555444333),
},
],
'read'
);
expect(result).toMatchObject([
{
[dateFieldId]: toLocalISOString(new Date(1666777777000)).slice(0, 10),
[dateTimeFieldId]: toLocalISOString(new Date(1666666666000)),
[timestampFieldId]: new Date(1666555444333).toISOString(),
},
]);
});
});
});
});
});
function toLocalISOString(date: Date) {
const year = String(date.getFullYear());
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
}