Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions ghost/core/core/server/lib/db-date.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
import moment from 'moment';
import * as errors from '@tryghost/errors';
import {z} from 'zod';

export const DATABASE_DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';

export type DatabaseDate = Date | string | number;

// Raw Knex queries need this UTC datetime format for consistent MySQL and SQLite behavior.
export const toDatabaseDate = (date: Date | string): string => moment.utc(date).format(DATABASE_DATE_FORMAT);

export const fromDatabaseDate = (date: DatabaseDate): Date => {
if (date instanceof Date) {
return new Date(date);
}

if (typeof date === 'string') {
return moment.utc(date, DATABASE_DATE_FORMAT).toDate();
}

// Defense-in-depth for legacy SQLite rows stored as epoch milliseconds.
if (typeof date === 'number') {
return moment.utc(date).toDate();
}

const exhaustive: never = date;
throw new errors.InternalServerError({message: `Unexpected type for database date: ${exhaustive}`});
};

// A zod codec for datetime columns: MySQL returns a Date, SQLite a string/number;
// normalise to a Date on read and pass a Date through on write.
export const DbDate = z.codec(z.union([z.date(), z.string(), z.number()]), z.date(), {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import type {
EditAutomationData,
Page
} from './automations-repository';
import {fromDatabaseDate, toDatabaseDate, type DatabaseDate} from './database-date';
import {fromDatabaseDate, toDatabaseDate, type DatabaseDate} from '../../lib/db-date';
import {getStaleLockCutoff} from './stale-lock-cutoff';
import type {ExclusifyUnion, ReadonlyDeep} from 'type-fest';

Expand Down
26 changes: 0 additions & 26 deletions ghost/core/core/server/services/automations/database-date.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from 'node:assert/strict';
import {spawn} from 'node:child_process';
import {once} from 'node:events';
import {fromDatabaseDate, toDatabaseDate} from '../../../../../core/server/services/automations/database-date';
import {fromDatabaseDate, toDatabaseDate} from '../../../../core/server/lib/db-date';

describe('database date utilities', function () {
const timezones = [
Expand All @@ -19,7 +19,7 @@ describe('database date utilities', function () {
const runInOtherTimezones = async (toRun: string) => {
// JSON.stringify does a good job wrapping strings in quotes and escaping.
const s = JSON.stringify;
const modulePath = require.resolve('../../../../../core/server/services/automations/database-date');
const modulePath = require.resolve('../../../../core/server/lib/db-date');

await Promise.all(timezones.map(async ({tz, expectedNaive}) => {
const source = `
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {NON_EMPTY_EMAIL_LEXICAL} from '../../../../utils/automations-fixtures';
import ghostConfig from '../../../../../core/shared/config';
import {createDatabaseAutomationsRepository} from '../../../../../core/server/services/automations/database-automations-repository';
import type {AutomatedEmailEvents, AutomationAction, AutomationsRepository, AutomationStepToRun} from '../../../../../core/server/services/automations/automations-repository';
import {DATABASE_DATE_FORMAT, fromDatabaseDate, toDatabaseDate} from '../../../../../core/server/services/automations/database-date';
import {DATABASE_DATE_FORMAT, fromDatabaseDate, toDatabaseDate} from '../../../../../core/server/lib/db-date';

const HOUR_MS = 60 * 60 * 1000;
const FAKE_WAIT_HOURS_MULTIPLIER = 2500;
Expand Down
Loading