diff --git a/api/src/database/helpers/date/dialects/sqlite.ts b/api/src/database/helpers/date/dialects/sqlite.ts index 1bca80c327..7323b4c2f8 100644 --- a/api/src/database/helpers/date/dialects/sqlite.ts +++ b/api/src/database/helpers/date/dialects/sqlite.ts @@ -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; diff --git a/api/src/database/helpers/date/types.ts b/api/src/database/helpers/date/types.ts index 06f0608ec7..2edf38a8e9 100644 --- a/api/src/database/helpers/date/types.ts +++ b/api/src/database/helpers/date/types.ts @@ -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 { diff --git a/api/src/database/helpers/fn/dialects/mssql.ts b/api/src/database/helpers/fn/dialects/mssql.ts index f30f326b09..c505a0d1ae 100644 --- a/api/src/database/helpers/fn/dialects/mssql.ts +++ b/api/src/database/helpers/fn/dialects/mssql.ts @@ -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 { diff --git a/api/src/database/helpers/fn/dialects/mysql.ts b/api/src/database/helpers/fn/dialects/mysql.ts index 3e0f0adaa3..6e3e819e7d 100644 --- a/api/src/database/helpers/fn/dialects/mysql.ts +++ b/api/src/database/helpers/fn/dialects/mysql.ts @@ -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 { diff --git a/api/src/database/helpers/fn/dialects/postgres.ts b/api/src/database/helpers/fn/dialects/postgres.ts index 69e8e72424..07bdb6c9df 100644 --- a/api/src/database/helpers/fn/dialects/postgres.ts +++ b/api/src/database/helpers/fn/dialects/postgres.ts @@ -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 { diff --git a/api/src/services/payload.test.ts b/api/src/services/payload.test.ts index c8e65db674..8b1e1127b8 100644 --- a/api/src/services/payload.test.ts +++ b/api/src/services/payload.test.ts @@ -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}`; +}