From 9e4ef6faa525bcb02341f7c6378240fc75fb5954 Mon Sep 17 00:00:00 2001 From: Tanishq Rupaal Date: Sat, 1 Feb 2025 15:06:28 -0500 Subject: [PATCH] fix timezones --- internal/api/handlers.go | 3 ++ internal/web/templates/index.html | 57 ++++++++++++++++-------- internal/web/templates/table.html | 74 ++++++++++++++++++++----------- 3 files changed, 88 insertions(+), 46 deletions(-) diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 613746a..5350413 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -53,6 +53,9 @@ func (h *Handler) AddExpense(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusBadRequest, ErrorResponse{Error: "Invalid request body"}) return } + if !req.Date.IsZero() { + req.Date = req.Date.UTC() + } expense := &models.Expense{ Name: req.Name, Category: req.Category, diff --git a/internal/web/templates/index.html b/internal/web/templates/index.html index 873b5c5..ebf92b8 100644 --- a/internal/web/templates/index.html +++ b/internal/web/templates/index.html @@ -253,25 +253,42 @@

ExpenseOwl

Chart.defaults.color = '#b3b3b3'; Chart.defaults.borderColor = '#606060'; Chart.defaults.font.family = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'; - // Format month for display + + // Helpers function formatMonth(date) { return date.toLocaleDateString('en-US', { year: 'numeric', - month: 'long' + month: 'long', + timeZone: getUserTimeZone() }); } + function getISODateWithLocalTime(dateInput) { + const [year, month, day] = dateInput.split('-').map(Number); + const now = new Date(); + const hours = now.getHours(); + const minutes = now.getMinutes(); + const seconds = now.getSeconds(); + const localDateTime = new Date(year, month - 1, day, hours, minutes, seconds); + return localDateTime.toISOString(); + } + function getUserTimeZone() { + return Intl.DateTimeFormat().resolvedOptions().timeZone; + } + // Update month display and navigation buttons function updateMonthDisplay() { document.getElementById('currentMonth').textContent = formatMonth(currentDate); - // Disable next month button if current month is reached const now = new Date(); const isCurrentMonth = currentDate.getMonth() === now.getMonth() && currentDate.getFullYear() === now.getFullYear(); document.getElementById('nextMonth').disabled = isCurrentMonth; } // Get start and end of month function getMonthBounds(date) { - const start = new Date(date.getFullYear(), date.getMonth(), 1); - const end = new Date(date.getFullYear(), date.getMonth() + 1, 0, 23, 59, 59); + const localDate = new Date(date); + const startLocal = new Date(localDate.getFullYear(), localDate.getMonth(), 1); + const endLocal = new Date(localDate.getFullYear(), localDate.getMonth() + 1, 0, 23, 59, 59, 999); + const start = new Date(startLocal.toISOString()); + const end = new Date(endLocal.toISOString()); return { start, end }; } // Filter expenses for current month @@ -280,7 +297,7 @@

ExpenseOwl

return expenses.filter(exp => { const expDate = new Date(exp.date); return expDate >= start && expDate <= end; - }); + }).sort((a, b) => new Date(b.date) - new Date(a.date)); } function showNoDataMessage() { if (pieChart) { @@ -304,32 +321,34 @@

ExpenseOwl

// Event Listeners document.getElementById('prevMonth').addEventListener('click', () => { - currentDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1); + currentDate = new Date( + currentDate.getFullYear(), + currentDate.getMonth() - 1, + 1, + currentDate.getHours(), + currentDate.getMinutes() + ); updateMonthDisplay(); updateChartAndLegend(); }); document.getElementById('nextMonth').addEventListener('click', () => { - currentDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1); + currentDate = new Date( + currentDate.getFullYear(), + currentDate.getMonth() + 1, + 1, + currentDate.getHours(), + currentDate.getMinutes() + ); updateMonthDisplay(); updateChartAndLegend(); }); document.getElementById('expenseForm').addEventListener('submit', async (e) => { e.preventDefault(); - const selectedDate = new Date(document.getElementById('date').value); - const now = new Date(); - const expenseDate = new Date( - selectedDate.getFullYear(), - selectedDate.getMonth(), - selectedDate.getDate(), - now.getHours(), - now.getMinutes(), - now.getSeconds() - ); const formData = { name: document.getElementById('name').value, category: document.getElementById('category').value, amount: parseFloat(document.getElementById('amount').value), - date: expenseDate.toISOString() + date: getISODateWithLocalTime(document.getElementById('date').value) }; try { const response = await fetch('/expense', { diff --git a/internal/web/templates/table.html b/internal/web/templates/table.html index 57f7cec..9ede98f 100644 --- a/internal/web/templates/table.html +++ b/internal/web/templates/table.html @@ -81,15 +81,21 @@

Delete Expense

function formatMonth(date) { return date.toLocaleDateString('en-US', { year: 'numeric', - month: 'long' + month: 'long', + timeZone: getUserTimeZone() }); } - function formatDate(dateString) { - return new Date(dateString).toLocaleDateString('en-US', { - month: 'short', - day: 'numeric', - year: 'numeric' - }); + function getISODateWithLocalTime(dateInput) { + const [year, month, day] = dateInput.split('-').map(Number); + const now = new Date(); + const hours = now.getHours(); + const minutes = now.getMinutes(); + const seconds = now.getSeconds(); + const localDateTime = new Date(year, month - 1, day, hours, minutes, seconds); + return localDateTime.toISOString(); + } + function getUserTimeZone() { + return Intl.DateTimeFormat().resolvedOptions().timeZone; } function formatCurrency(amount) { return new Intl.NumberFormat('en-US', { @@ -98,17 +104,30 @@

Delete Expense

minimumFractionDigits: 2 }).format(amount); } + function formatDateFromUTC(utcDateString) { + const date = new Date(utcDateString); + return date.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + timeZoneName: 'short' + }); + } + function updateMonthDisplay() { document.getElementById('currentMonth').textContent = formatMonth(currentDate); - const now = new Date(); - const isCurrentMonth = currentDate.getMonth() === now.getMonth() - && currentDate.getFullYear() === now.getFullYear(); + const isCurrentMonth = currentDate.getMonth() === now.getMonth() && currentDate.getFullYear() === now.getFullYear(); document.getElementById('nextMonth').disabled = isCurrentMonth; } function getMonthBounds(date) { - const start = new Date(date.getFullYear(), date.getMonth(), 1); - const end = new Date(date.getFullYear(), date.getMonth() + 1, 0, 23, 59, 59); + const localDate = new Date(date); + const startLocal = new Date(localDate.getFullYear(), localDate.getMonth(), 1); + const endLocal = new Date(localDate.getFullYear(), localDate.getMonth() + 1, 0, 23, 59, 59, 999); + const start = new Date(startLocal.toISOString()); + const end = new Date(endLocal.toISOString()); return { start, end }; } function getMonthExpenses(expenses) { @@ -140,7 +159,7 @@

Delete Expense

${expense.name} ${expense.category} ${formatCurrency(expense.amount)} - ${formatDate(expense.date)} + ${formatDateFromUTC(expense.date)}