mirror of
https://github.com/directus/directus.git
synced 2026-01-23 11:47:59 -05:00
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:
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user