diff --git a/client/src/components/Budget/BudgetPanel.tsx b/client/src/components/Budget/BudgetPanel.tsx
index 0f447bb8..a0cbd3d4 100644
--- a/client/src/components/Budget/BudgetPanel.tsx
+++ b/client/src/components/Budget/BudgetPanel.tsx
@@ -489,7 +489,7 @@ export default function BudgetPanel({ tripId, tripMembers = [] }: BudgetPanelPro
const d = currencyDecimals(currency)
const fmtPrice = (v: number | null | undefined) => v != null ? v.toFixed(d) : ''
- const fmtDate = (iso: string) => { if (!iso) return ''; const d = new Date(iso + 'T00:00:00'); return d.toLocaleDateString(locale, { day: '2-digit', month: '2-digit', year: 'numeric' }) }
+ const fmtDate = (iso: string) => { if (!iso) return ''; const d = new Date(iso + 'T00:00:00Z'); return d.toLocaleDateString(locale, { day: '2-digit', month: '2-digit', year: 'numeric', timeZone: 'UTC' }) }
const header = ['Category', 'Name', 'Date', 'Total (' + currency + ')', 'Persons', 'Days', 'Per Person', 'Per Day', 'Per Person/Day', 'Note']
const rows = [header.join(sep)]
diff --git a/client/src/components/Collab/WhatsNextWidget.tsx b/client/src/components/Collab/WhatsNextWidget.tsx
index 6ad678df..c5fd11a2 100644
--- a/client/src/components/Collab/WhatsNextWidget.tsx
+++ b/client/src/components/Collab/WhatsNextWidget.tsx
@@ -23,7 +23,7 @@ function formatDayLabel(date, t, locale) {
if (d.toDateString() === now.toDateString()) return t('collab.whatsNext.today') || 'Today'
if (d.toDateString() === tomorrow.toDateString()) return t('collab.whatsNext.tomorrow') || 'Tomorrow'
- return d.toLocaleDateString(locale || undefined, { weekday: 'short', day: 'numeric', month: 'short' })
+ return new Date(date + 'T00:00:00Z').toLocaleDateString(locale || undefined, { weekday: 'short', day: 'numeric', month: 'short', timeZone: 'UTC' })
}
interface TripMember {
diff --git a/client/src/components/PDF/TripPDF.tsx b/client/src/components/PDF/TripPDF.tsx
index c83cc4b7..2a4c03a0 100644
--- a/client/src/components/PDF/TripPDF.tsx
+++ b/client/src/components/PDF/TripPDF.tsx
@@ -61,15 +61,15 @@ function categoryIconSvg(iconName, color = '#6366f1', size = 24) {
function shortDate(d, locale) {
if (!d) return ''
- return new Date(d + 'T00:00:00').toLocaleDateString(locale, { weekday: 'short', day: 'numeric', month: 'short' })
+ return new Date(d + 'T00:00:00Z').toLocaleDateString(locale, { weekday: 'short', day: 'numeric', month: 'short', timeZone: 'UTC' })
}
function longDateRange(days, locale) {
const dd = [...days].filter(d => d.date).sort((a, b) => a.day_number - b.day_number)
if (!dd.length) return null
- const f = new Date(dd[0].date + 'T00:00:00')
- const l = new Date(dd[dd.length - 1].date + 'T00:00:00')
- return `${f.toLocaleDateString(locale, { day: 'numeric', month: 'long' })} – ${l.toLocaleDateString(locale, { day: 'numeric', month: 'long', year: 'numeric' })}`
+ const f = new Date(dd[0].date + 'T00:00:00Z')
+ const l = new Date(dd[dd.length - 1].date + 'T00:00:00Z')
+ return `${f.toLocaleDateString(locale, { day: 'numeric', month: 'long', timeZone: 'UTC' })} – ${l.toLocaleDateString(locale, { day: 'numeric', month: 'long', year: 'numeric', timeZone: 'UTC' })}`
}
function dayCost(assignments, dayId, locale) {
diff --git a/client/src/components/Photos/PhotoGallery.tsx b/client/src/components/Photos/PhotoGallery.tsx
index fe64a19b..4e7a90da 100644
--- a/client/src/components/Photos/PhotoGallery.tsx
+++ b/client/src/components/Photos/PhotoGallery.tsx
@@ -213,5 +213,5 @@ function PhotoThumbnail({ photo, days, places, onClick }: PhotoThumbnailProps) {
function formatDate(dateStr, locale) {
if (!dateStr) return ''
- return new Date(dateStr + 'T00:00:00').toLocaleDateString(locale, { day: 'numeric', month: 'short' })
+ return new Date(dateStr + 'T00:00:00Z').toLocaleDateString(locale, { day: 'numeric', month: 'short', timeZone: 'UTC' })
}
diff --git a/client/src/components/Planner/DayDetailPanel.tsx b/client/src/components/Planner/DayDetailPanel.tsx
index 22cc76ef..49cd6ff8 100644
--- a/client/src/components/Planner/DayDetailPanel.tsx
+++ b/client/src/components/Planner/DayDetailPanel.tsx
@@ -154,9 +154,9 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
if (!day) return null
- const formattedDate = day.date ? new Date(day.date + 'T00:00:00').toLocaleDateString(
+ const formattedDate = day.date ? new Date(day.date + 'T00:00:00Z').toLocaleDateString(
getLocaleForLanguage(language),
- { weekday: 'long', day: 'numeric', month: 'long' }
+ { weekday: 'long', day: 'numeric', month: 'long', timeZone: 'UTC' }
) : null
const placesWithCoords = places.filter(p => p.lat && p.lng)
@@ -445,7 +445,7 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
onChange={v => setHotelDayRange(prev => ({ start: v, end: Math.max(v, prev.end) }))}
options={days.map((d, i) => ({
value: d.id,
- label: `${d.title || t('planner.dayN', { n: i + 1 })}${d.date ? ` — ${new Date(d.date + 'T00:00:00').toLocaleDateString(locale, { day: 'numeric', month: 'short' })}` : ''}`,
+ label: `${d.title || t('planner.dayN', { n: i + 1 })}${d.date ? ` — ${new Date(d.date + 'T00:00:00Z').toLocaleDateString(locale, { day: 'numeric', month: 'short', timeZone: 'UTC' })}` : ''}`,
}))}
size="sm"
/>
@@ -457,7 +457,7 @@ export default function DayDetailPanel({ day, days, places, categories = [], tri
onChange={v => setHotelDayRange(prev => ({ start: Math.min(prev.start, v), end: v }))}
options={days.map((d, i) => ({
value: d.id,
- label: `${d.title || t('planner.dayN', { n: i + 1 })}${d.date ? ` — ${new Date(d.date + 'T00:00:00').toLocaleDateString(locale, { day: 'numeric', month: 'short' })}` : ''}`,
+ label: `${d.title || t('planner.dayN', { n: i + 1 })}${d.date ? ` — ${new Date(d.date + 'T00:00:00Z').toLocaleDateString(locale, { day: 'numeric', month: 'short', timeZone: 'UTC' })}` : ''}`,
}))}
size="sm"
/>
diff --git a/client/src/components/Planner/DayPlanSidebar.tsx b/client/src/components/Planner/DayPlanSidebar.tsx
index b9850f37..b83270e2 100644
--- a/client/src/components/Planner/DayPlanSidebar.tsx
+++ b/client/src/components/Planner/DayPlanSidebar.tsx
@@ -743,7 +743,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar({
{trip?.title}
{(trip?.start_date || trip?.end_date) && (
- {[trip.start_date, trip.end_date].filter(Boolean).map(d => new Date(d + 'T00:00:00').toLocaleDateString(locale, { day: 'numeric', month: 'short' })).join(' – ')}
+ {[trip.start_date, trip.end_date].filter(Boolean).map(d => new Date(d + 'T00:00:00Z').toLocaleDateString(locale, { day: 'numeric', month: 'short', timeZone: 'UTC' })).join(' – ')}
{days.length > 0 && ` · ${days.length} ${t('dayplan.days')}`}
)}
@@ -1671,7 +1671,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar({
{res.reservation_time?.includes('T')
? new Date(res.reservation_time).toLocaleString(locale, { weekday: 'short', day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit', hour12: timeFormat === '12h' })
: res.reservation_time
- ? new Date(res.reservation_time + 'T00:00:00').toLocaleDateString(locale, { weekday: 'short', day: 'numeric', month: 'short' })
+ ? new Date(res.reservation_time + 'T00:00:00Z').toLocaleDateString(locale, { weekday: 'short', day: 'numeric', month: 'short', timeZone: 'UTC' })
: ''
}
{res.reservation_end_time?.includes('T') && ` – ${new Date(res.reservation_end_time).toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit', hour12: timeFormat === '12h' })}`}
diff --git a/client/src/components/Planner/PlaceInspector.tsx b/client/src/components/Planner/PlaceInspector.tsx
index f6c00a57..13deb63a 100644
--- a/client/src/components/Planner/PlaceInspector.tsx
+++ b/client/src/components/Planner/PlaceInspector.tsx
@@ -373,7 +373,7 @@ export default function PlaceInspector({
{res.reservation_time && (
{t('reservations.date')}
-
{new Date(res.reservation_time).toLocaleDateString(locale, { weekday: 'short', day: 'numeric', month: 'short' })}
+
{new Date((res.reservation_time.includes('T') ? res.reservation_time.split('T')[0] : res.reservation_time) + 'T00:00:00Z').toLocaleDateString(locale, { weekday: 'short', day: 'numeric', month: 'short', timeZone: 'UTC' })}
)}
{res.reservation_time?.includes('T') && (
diff --git a/client/src/components/Planner/PlacesSidebar.tsx b/client/src/components/Planner/PlacesSidebar.tsx
index 91e50a5b..ce8c6c51 100644
--- a/client/src/components/Planner/PlacesSidebar.tsx
+++ b/client/src/components/Planner/PlacesSidebar.tsx
@@ -424,7 +424,7 @@ const PlacesSidebar = React.memo(function PlacesSidebar({
{i + 1}
{day.title || t('dayplan.dayN', { n: i + 1 })}
- {day.date &&
{new Date(day.date + 'T00:00:00').toLocaleDateString()}
}
+ {day.date &&
{new Date(day.date + 'T00:00:00Z').toLocaleDateString(undefined, { timeZone: 'UTC' })}
}
{(assignments[String(day.id)] || []).some(a => a.place?.id === dayPickerPlace.id) && }
diff --git a/client/src/components/Planner/ReservationModal.tsx b/client/src/components/Planner/ReservationModal.tsx
index dbc3ab36..8a376d77 100644
--- a/client/src/components/Planner/ReservationModal.tsx
+++ b/client/src/components/Planner/ReservationModal.tsx
@@ -572,6 +572,6 @@ export function ReservationModal({ isOpen, onClose, onSave, reservation, days, p
function formatDate(dateStr, locale) {
if (!dateStr) return ''
- const d = new Date(dateStr + 'T00:00:00')
- return d.toLocaleDateString(locale || undefined, { day: 'numeric', month: 'short' })
+ const d = new Date(dateStr + 'T00:00:00Z')
+ return d.toLocaleDateString(locale || undefined, { day: 'numeric', month: 'short', timeZone: 'UTC' })
}
diff --git a/client/src/components/Planner/ReservationsPanel.tsx b/client/src/components/Planner/ReservationsPanel.tsx
index b6f9c551..4d37fab8 100644
--- a/client/src/components/Planner/ReservationsPanel.tsx
+++ b/client/src/components/Planner/ReservationsPanel.tsx
@@ -84,8 +84,8 @@ function ReservationCard({ r, tripId, onEdit, onDelete, files = [], onNavigateTo
}
const fmtDate = (str) => {
- const d = new Date(str)
- return d.toLocaleDateString(locale, { weekday: 'short', day: 'numeric', month: 'short' })
+ const dateOnly = str.includes('T') ? str.split('T')[0] : str
+ return new Date(dateOnly + 'T00:00:00Z').toLocaleDateString(locale, { weekday: 'short', day: 'numeric', month: 'short', timeZone: 'UTC' })
}
const fmtTime = (str) => {
const d = new Date(str)
diff --git a/client/src/components/Trips/TripFormModal.tsx b/client/src/components/Trips/TripFormModal.tsx
index 4e834b91..aff40120 100644
--- a/client/src/components/Trips/TripFormModal.tsx
+++ b/client/src/components/Trips/TripFormModal.tsx
@@ -197,10 +197,10 @@ export default function TripFormModal({ isOpen, onClose, onSave, trip, onCoverUp
if (!prev.end_date || prev.end_date < value) {
next.end_date = value
} else if (prev.start_date) {
- const oldStart = new Date(prev.start_date + 'T00:00:00')
- const oldEnd = new Date(prev.end_date + 'T00:00:00')
+ const oldStart = new Date(prev.start_date + 'T00:00:00Z')
+ const oldEnd = new Date(prev.end_date + 'T00:00:00Z')
const duration = Math.round((oldEnd - oldStart) / 86400000)
- const newEnd = new Date(value + 'T00:00:00')
+ const newEnd = new Date(value + 'T00:00:00Z')
newEnd.setDate(newEnd.getDate() + duration)
next.end_date = newEnd.toISOString().split('T')[0]
}
diff --git a/client/src/components/Vacay/holidays.ts b/client/src/components/Vacay/holidays.ts
index fe5903b0..61f92a9a 100644
--- a/client/src/components/Vacay/holidays.ts
+++ b/client/src/components/Vacay/holidays.ts
@@ -104,18 +104,18 @@ export function getHolidays(year: number, bundesland: string = 'NW'): Record(null)
const dropRef = useRef(null)
- const parsed = value ? new Date(value + 'T00:00:00') : null
- const [viewYear, setViewYear] = useState(parsed?.getFullYear() || new Date().getFullYear())
- const [viewMonth, setViewMonth] = useState(parsed?.getMonth() ?? new Date().getMonth())
+ const parsed = value ? new Date(value + 'T00:00:00Z') : null
+ const [viewYear, setViewYear] = useState(parsed?.getUTCFullYear() || new Date().getFullYear())
+ const [viewMonth, setViewMonth] = useState(parsed?.getUTCMonth() ?? new Date().getMonth())
useEffect(() => {
const handler = (e: MouseEvent) => {
@@ -36,7 +36,7 @@ export function CustomDatePicker({ value, onChange, placeholder, style = {}, com
}, [open])
useEffect(() => {
- if (open && parsed) { setViewYear(parsed.getFullYear()); setViewMonth(parsed.getMonth()) }
+ if (open && parsed) { setViewYear(parsed.getUTCFullYear()); setViewMonth(parsed.getUTCMonth()) }
}, [open])
const prevMonth = () => { if (viewMonth === 0) { setViewMonth(11); setViewYear(y => y - 1) } else setViewMonth(m => m - 1) }
@@ -47,7 +47,7 @@ export function CustomDatePicker({ value, onChange, placeholder, style = {}, com
const startDay = (getWeekday(viewYear, viewMonth, 1) + 6) % 7
const weekdays = Array.from({ length: 7 }, (_, i) => new Date(2024, 0, i + 1).toLocaleDateString(locale, { weekday: 'narrow' }))
- const displayValue = parsed ? parsed.toLocaleDateString(locale, compact ? { day: '2-digit', month: '2-digit', year: '2-digit' } : { day: 'numeric', month: 'short', year: 'numeric' }) : null
+ const displayValue = parsed ? parsed.toLocaleDateString(locale, compact ? { day: '2-digit', month: '2-digit', year: '2-digit', timeZone: 'UTC' } : { day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC' }) : null
const selectDay = (day: number) => {
const y = String(viewYear)
@@ -57,7 +57,7 @@ export function CustomDatePicker({ value, onChange, placeholder, style = {}, com
setOpen(false)
}
- const selectedDay = parsed && parsed.getFullYear() === viewYear && parsed.getMonth() === viewMonth ? parsed.getDate() : null
+ const selectedDay = parsed && parsed.getUTCFullYear() === viewYear && parsed.getUTCMonth() === viewMonth ? parsed.getUTCDate() : null
const today = new Date()
const isToday = (d: number) => today.getFullYear() === viewYear && today.getMonth() === viewMonth && today.getDate() === d
diff --git a/client/src/pages/DashboardPage.tsx b/client/src/pages/DashboardPage.tsx
index 16f36d06..a3863791 100644
--- a/client/src/pages/DashboardPage.tsx
+++ b/client/src/pages/DashboardPage.tsx
@@ -59,12 +59,12 @@ function getTripStatus(trip: DashboardTrip): string | null {
function formatDate(dateStr: string | null | undefined, locale: string = 'en-US'): string | null {
if (!dateStr) return null
- return new Date(dateStr + 'T00:00:00').toLocaleDateString(locale, { day: 'numeric', month: 'short', year: 'numeric' })
+ return new Date(dateStr + 'T00:00:00Z').toLocaleDateString(locale, { day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC' })
}
function formatDateShort(dateStr: string | null | undefined, locale: string = 'en-US'): string | null {
if (!dateStr) return null
- return new Date(dateStr + 'T00:00:00').toLocaleDateString(locale, { day: 'numeric', month: 'short' })
+ return new Date(dateStr + 'T00:00:00Z').toLocaleDateString(locale, { day: 'numeric', month: 'short', timeZone: 'UTC' })
}
function sortTrips(trips: DashboardTrip[]): DashboardTrip[] {
diff --git a/client/src/pages/SharedTripPage.tsx b/client/src/pages/SharedTripPage.tsx
index d8fb54a3..09fa8669 100644
--- a/client/src/pages/SharedTripPage.tsx
+++ b/client/src/pages/SharedTripPage.tsx
@@ -106,7 +106,7 @@ export default function SharedTripPage() {
{(trip.start_date || trip.end_date) && (
- {[trip.start_date, trip.end_date].filter(Boolean).map((d: string) => new Date(d + 'T00:00:00').toLocaleDateString(locale, { day: 'numeric', month: 'short', year: 'numeric' })).join(' — ')}
+ {[trip.start_date, trip.end_date].filter(Boolean).map((d: string) => new Date(d + 'T00:00:00Z').toLocaleDateString(locale, { day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC' })).join(' — ')}
{days?.length > 0 &&
·}
{days?.length > 0 &&
{days.length} {t('shared.days')}}
@@ -199,7 +199,7 @@ export default function SharedTripPage() {
{di + 1}
{day.title || `Day ${day.day_number}`}
- {day.date &&
{new Date(day.date + 'T00:00:00').toLocaleDateString(locale, { weekday: 'short', day: 'numeric', month: 'short' })}
}
+ {day.date &&
{new Date(day.date + 'T00:00:00Z').toLocaleDateString(locale, { weekday: 'short', day: 'numeric', month: 'short', timeZone: 'UTC' })}
}
{dayAccs.map((acc: any) => (
@@ -274,7 +274,7 @@ export default function SharedTripPage() {
const meta = typeof r.metadata === 'string' ? JSON.parse(r.metadata || '{}') : (r.metadata || {})
const TIcon = TRANSPORT_ICONS[r.type] || Ticket
const time = r.reservation_time?.includes('T') ? r.reservation_time.split('T')[1]?.substring(0, 5) : ''
- const date = r.reservation_time ? new Date(r.reservation_time.includes('T') ? r.reservation_time : r.reservation_time + 'T00:00:00').toLocaleDateString(locale, { day: 'numeric', month: 'short' }) : ''
+ const date = r.reservation_time ? new Date((r.reservation_time.includes('T') ? r.reservation_time.split('T')[0] : r.reservation_time) + 'T00:00:00Z').toLocaleDateString(locale, { day: 'numeric', month: 'short', timeZone: 'UTC' }) : ''
return (
diff --git a/client/src/utils/formatters.ts b/client/src/utils/formatters.ts
index 7e7ebc04..980f85ac 100644
--- a/client/src/utils/formatters.ts
+++ b/client/src/utils/formatters.ts
@@ -10,9 +10,9 @@ export function formatDate(dateStr: string | null | undefined, locale: string, t
if (!dateStr) return null
const opts: Intl.DateTimeFormatOptions = {
weekday: 'short', day: 'numeric', month: 'short',
+ timeZone: timeZone || 'UTC',
}
- if (timeZone) opts.timeZone = timeZone
- return new Date(dateStr + 'T00:00:00').toLocaleDateString(locale, opts)
+ return new Date(dateStr + 'T00:00:00Z').toLocaleDateString(locale, opts)
}
export function formatTime(timeStr: string | null | undefined, locale: string, timeFormat: string): string {