Prevent date offset for expenses in local timezones (#433)

This commit is contained in:
Derek
2025-11-03 13:46:24 -05:00
committed by GitHub
parent 8875b98980
commit b2f9498142
2 changed files with 30 additions and 2 deletions

View File

@@ -5,7 +5,7 @@ import { DocumentsCount } from '@/app/groups/[groupId]/expenses/documents-count'
import { Button } from '@/components/ui/button'
import { getGroupExpenses } from '@/lib/api'
import { Currency } from '@/lib/currency'
import { cn, formatCurrency, formatDate } from '@/lib/utils'
import { cn, formatCurrency, formatDateOnly } from '@/lib/utils'
import { ChevronRight } from 'lucide-react'
import { useLocale, useTranslations } from 'next-intl'
import Link from 'next/link'
@@ -99,7 +99,7 @@ export function ExpenseCard({
<DocumentsCount count={expense._count.documents} />
</div>
<div className="text-xs text-muted-foreground">
{formatDate(expense.expenseDate, locale, { dateStyle: 'medium' })}
{formatDateOnly(expense.expenseDate, locale, { dateStyle: 'medium' })}
</div>
</div>
<Button

View File

@@ -24,6 +24,34 @@ export function formatDate(
})
}
/**
* Formats a date-only field (without time) for display.
* Extracts UTC date components to avoid timezone shifts that can cause off-by-one day errors.
* Use this for dates stored as DATE type in the database (e.g., expenseDate).
*
* @param date - The date to format (typically from a database DATE field, e.g., 2025-10-17T00:00:00.000Z)
* @param locale - The locale string (e.g., 'en-US', 'fr-FR')
* @param options - Formatting options (dateStyle, timeStyle)
* @returns Formatted date string in the specified locale
*/
export function formatDateOnly(
date: Date,
locale: string,
options: { dateStyle?: DateTimeStyle; timeStyle?: DateTimeStyle } = {},
) {
// Extract UTC date components to avoid timezone shifts
const year = date.getUTCFullYear()
const month = date.getUTCMonth()
const day = date.getUTCDate()
// Create a new date in the user's local timezone with these components
const localDate = new Date(year, month, day)
return localDate.toLocaleString(locale, {
...options,
})
}
export function formatCategoryForAIPrompt(category: Category) {
return `"${category.grouping}/${category.name}" (ID: ${category.id})`
}