Validate date time fields when adding/editing photos

This commit is contained in:
Sam Becker 2025-01-22 18:05:33 -06:00
parent ad11ce32b0
commit d6e5aa012e
4 changed files with 74 additions and 25 deletions

View File

@ -1,6 +1,9 @@
/* eslint-disable max-len */
import {
convertTimestampToNaivePostgresString,
convertTimestampWithOffsetToPostgresString,
validatePostgresDateString,
validateNaivePostgresDateString,
} from '../src/utility/date';
describe('Date utility', () => {
@ -29,7 +32,6 @@ describe('Date utility', () => {
expect(convertTimestampToNaivePostgresString(timestamp))
.toBe('2023-12-02 16:38:36');
});
});
it('Malformed date string', () => {
const timestamp = '2024/01a/01 Z';
expect(convertTimestampWithOffsetToPostgresString(timestamp))
@ -45,3 +47,19 @@ describe('Date utility', () => {
));
});
});
describe('validates date strings', () => {
it('Correct', () => {
expect(validatePostgresDateString('2025-01-03T21:00:44.000Z')).toBe(true);
expect(validateNaivePostgresDateString('2025-01-03 16:00:44')).toBe(true);
});
it('Incorrect', () => {
expect(validatePostgresDateString('2024-01-01')).toBe(false);
expect(validatePostgresDateString('2025-01-03 16:00:44')).toBe(false);
expect(validateNaivePostgresDateString('2024-01-01')).toBe(false);
expect(validatePostgresDateString('2025-01-03T21:00:44.000')).toBe(false);
expect(validateNaivePostgresDateString('2025-01-03T16:00:44')).toBe(false);
expect(validatePostgresDateString('2025-01-03T21:00:44.000ZZ')).toBe(false);
expect(validateNaivePostgresDateString('2025-01-03 16:00:44Z')).toBe(false);
});
});
});

View File

@ -58,7 +58,7 @@ export default function FieldSetWithStatus({
{!hideLabel && label &&
<label
className={clsx(
'flex gap-2 items-center select-none',
'flex flex-wrap gap-x-2 items-center select-none',
type === 'checkbox' && 'order-2 pt-[3px]',
)}
htmlFor={id}

View File

@ -5,6 +5,8 @@ import {
convertTimestampWithOffsetToPostgresString,
generateLocalNaivePostgresString,
generateLocalPostgresString,
validationMessageNaivePostgresDateString,
validationMessagePostgresDateString,
} from '@/utility/date';
import {
convertApertureValueToFNumber,
@ -116,8 +118,14 @@ const FORM_METADATA = (
locationName: { label: 'location name', hide: true },
latitude: { label: 'latitude' },
longitude: { label: 'longitude' },
takenAt: { label: 'taken at' },
takenAtNaive: { label: 'taken at (naive)' },
takenAt: {
label: 'taken at',
validate: validationMessagePostgresDateString,
},
takenAtNaive: {
label: 'taken at (naive)',
validate: validationMessageNaivePostgresDateString,
},
priorityOrder: { label: 'priority order' },
favorite: { label: 'favorite', type: 'checkbox', excludeFromInsert: true },
hidden: { label: 'hidden', type: 'checkbox' },

View File

@ -16,6 +16,9 @@ const DATE_STRING_FORMAT_LONG_PLACEHOLDER = '00 000 0000 00:0000';
const DATE_STRING_FORMAT_POSTGRES = 'yyyy-MM-dd HH:mm:ss';
export const VALIDATION_EXAMPLE_POSTGRES = '2025-01-03T21:00:44.000Z';
export const VALIDATION_EXAMPLE_POSTGRES_NAIVE = '2025-01-03 16:00:44';
type AmbiguousTimestamp = number | string;
type Length = 'tiny' | 'short' | 'medium' | 'long';
@ -103,3 +106,23 @@ export const generateLocalPostgresString = () =>
export const generateLocalNaivePostgresString = () =>
format(new Date(), DATE_STRING_FORMAT_POSTGRES);
// Form validation to prevent Postgres runtime errors
// POSTGRES: 2025-01-03T21:00:44.000Z
export const validatePostgresDateString = (date = ''): boolean =>
/^(\d{4}-\d{2}-\d{2})T\d{2}:\d{2}:\d{2}(.[\d]+)*Z$/.test(date);
export const validationMessagePostgresDateString = (date = '') =>
validatePostgresDateString(date)
? undefined
: `Invalid format (${VALIDATION_EXAMPLE_POSTGRES})`;
// NAIVE: 2025-01-03 16:00:44
export const validateNaivePostgresDateString = (date = ''): boolean =>
/^(\d{4}-\d{2}-\d{2}) \d{2}:\d{2}:\d{2}$/.test(date);
export const validationMessageNaivePostgresDateString = (date = '') =>
validateNaivePostgresDateString(date)
? undefined
: `Invalid format (${VALIDATION_EXAMPLE_POSTGRES_NAIVE})`;