Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 2 additions & 1 deletion frontend/src/components/assessment/ActivityHistoryModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from '@/components/ui/select';
import { activityService } from '@/services/activityService';
import type { ActivityHistoryRead, ActivityRead } from '@/types/utils';
import { parseServerDate } from '@/utils/dateFormatter';

const props = defineProps<{
open: boolean;
Expand Down Expand Up @@ -98,7 +99,7 @@ watch(selectedVersionId, (newId) => {

const formatDate = (dateString: string) => {
try {
const d = new Date(dateString);
const d = parseServerDate(dateString);
return (
d.toLocaleDateString() +
' ' +
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/ui/DateTimePicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
SelectValue,
} from '@/components/ui/select';
import { usePreferencesStore } from '@/stores/preferences';
import { formatDateTime, formatDateTimeEditable, parseDateTimeInput } from '@/utils/dateFormatter';
import { formatDateTime, formatDateTimeEditable, parseDateTimeInput, parseServerDate } from '@/utils/dateFormatter';

const props = defineProps<{
modelValue: string | null | undefined;
Expand All @@ -42,7 +42,7 @@ const timeValue = ref<string>('00:00'); // Always HH:mm 24h format for internal

// Helper: Parse UTC string to components in target timezone
function parseToTimezone(utcIsoString: string, timezone?: string) {
const date = new Date(utcIsoString);
const date = parseServerDate(utcIsoString);
if (isNaN(date.getTime())) return null;

const opts: Intl.DateTimeFormatOptions = {
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/composables/useActivityEvaluation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Ref } from 'vue';
import { computed, watch } from 'vue';
import type { ActivityEvaluationUpdate, ActivityRead } from '@/types/utils';
import { parseServerDate } from '@/utils/dateFormatter';

export type EvalResult = 'PASS' | 'FAIL' | 'N/A';

Expand All @@ -9,8 +10,8 @@ export function formatTimeDiff(
toTime: string | Date | null | undefined,
): string {
if (!fromTime || !toTime) return '';
const from = new Date(fromTime).getTime();
const to = new Date(toTime).getTime();
const from = parseServerDate(fromTime).getTime();
const to = parseServerDate(toTime).getTime();
if (Number.isNaN(from) || Number.isNaN(to)) return '';
const diffMs = to - from;
if (diffMs < 0) return 'N/A (negative)';
Expand Down
14 changes: 12 additions & 2 deletions frontend/src/utils/dateFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ export interface FormatOptions {
timeFormat?: TimeFormat;
}

// Server timestamps without an explicit offset (e.g. SQLite-backed responses)
// must be treated as UTC. JS's Date constructor would otherwise parse them as
// local time. Postgres responses include a "Z" suffix and are passed through.
const HAS_TZ_SUFFIX = /[zZ]|[+-]\d{2}:?\d{2}$/;

export function parseServerDate(value: string | Date): Date {
if (value instanceof Date) return value;
return new Date(HAS_TZ_SUFFIX.test(value) ? value : `${value}Z`);
}

export function formatDateTime(
dateString: string | null | undefined,
timezone?: string,
Expand All @@ -15,7 +25,7 @@ export function formatDateTime(
): string {
if (!dateString) return '-';

const date = new Date(dateString);
const date = parseServerDate(dateString);
if (Number.isNaN(date.getTime())) return 'Invalid Date';

const hour12 = timeFormat === 'browser' ? undefined : timeFormat === '12h';
Expand Down Expand Up @@ -150,7 +160,7 @@ export function formatDateTimeEditable(
timeFormat: TimeFormat = 'browser',
): string {
if (!dateString) return '';
const date = new Date(dateString);
const date = parseServerDate(dateString);
if (Number.isNaN(date.getTime())) return '';

const hour12 = timeFormat === 'browser' ? undefined : timeFormat === '12h';
Expand Down
Loading