fix: use Ecuador timezone (GMT-5) for all dates
This commit is contained in:
@@ -1,6 +1,12 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { formatDisplayDateFull } from '@/lib/date';
|
||||||
|
|
||||||
|
const gold = '#E8A849';
|
||||||
|
const navy = '#010D1E';
|
||||||
|
const cream = '#f5f0e8';
|
||||||
|
const warm = '#a6683c';
|
||||||
|
|
||||||
interface SocialEvent {
|
interface SocialEvent {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -23,11 +29,6 @@ interface User {
|
|||||||
comments_disabled?: boolean;
|
comments_disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const gold = '#E8A849';
|
|
||||||
const navy = '#010D1E';
|
|
||||||
const cream = '#f5f0e8';
|
|
||||||
const warm = '#a6683c';
|
|
||||||
|
|
||||||
export default function SocialEventsPage() {
|
export default function SocialEventsPage() {
|
||||||
const [events, setEvents] = useState<SocialEvent[]>([]);
|
const [events, setEvents] = useState<SocialEvent[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -167,7 +168,7 @@ export default function SocialEventsPage() {
|
|||||||
<h2 style={{ margin: '0 0 0.5rem', color: navy, fontSize: '24px' }}>{event.name}</h2>
|
<h2 style={{ margin: '0 0 0.5rem', color: navy, fontSize: '24px' }}>{event.name}</h2>
|
||||||
{event.date && (
|
{event.date && (
|
||||||
<p style={{ margin: '0 0 0.5rem', color: warm, fontSize: '15px', fontWeight: 600 }}>
|
<p style={{ margin: '0 0 0.5rem', color: warm, fontSize: '15px', fontWeight: 600 }}>
|
||||||
{new Date(event.date).toLocaleDateString(undefined, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}
|
{formatDisplayDateFull(event.date)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<p style={{ color: '#555', lineHeight: 1.5, flex: 1 }}>{event.description}</p>
|
<p style={{ color: '#555', lineHeight: 1.5, flex: 1 }}>{event.description}</p>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { getEcuadorToday, getEcuadorDateFromToday, daysUntilEcuador, getMonthDayEcuador } from '@/lib/date';
|
||||||
|
|
||||||
interface CalendarEvent {
|
interface CalendarEvent {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -50,34 +51,6 @@ const typeEmoji: Record<CalendarEvent['type'], string> = {
|
|||||||
event: '📌',
|
event: '📌',
|
||||||
};
|
};
|
||||||
|
|
||||||
function parseDateLocal(dateStr: string): Date {
|
|
||||||
// Parse date as local time to avoid timezone shifts
|
|
||||||
// Input format: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS
|
|
||||||
const parts = dateStr.split('T')[0].split('-');
|
|
||||||
const year = parseInt(parts[0], 10);
|
|
||||||
const month = parseInt(parts[1], 10) - 1; // JS months are 0-indexed
|
|
||||||
const day = parseInt(parts[2], 10);
|
|
||||||
return new Date(year, month, day);
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatMonthDay(dateStr: string) {
|
|
||||||
const d = parseDateLocal(dateStr);
|
|
||||||
return { month: d.toLocaleString('en-US', { month: 'short' }), day: d.getDate() };
|
|
||||||
}
|
|
||||||
|
|
||||||
function daysUntil(dateStr: string) {
|
|
||||||
const today = new Date();
|
|
||||||
today.setHours(0, 0, 0, 0);
|
|
||||||
const d = parseDateLocal(dateStr);
|
|
||||||
d.setHours(0, 0, 0, 0);
|
|
||||||
const diff = Math.ceil((d.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
|
|
||||||
if (isNaN(diff)) return 'Unknown';
|
|
||||||
if (diff === 0) return 'Today';
|
|
||||||
if (diff === 1) return 'Tomorrow';
|
|
||||||
if (diff < 0) return 'Past';
|
|
||||||
return `in ${diff} days`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CalendarStrip() {
|
export default function CalendarStrip() {
|
||||||
const [events, setEvents] = useState<CalendarEvent[]>([]);
|
const [events, setEvents] = useState<CalendarEvent[]>([]);
|
||||||
const [weather, setWeather] = useState<WeatherData | null>(null);
|
const [weather, setWeather] = useState<WeatherData | null>(null);
|
||||||
@@ -101,7 +74,7 @@ export default function CalendarStrip() {
|
|||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
id: 9997,
|
id: 9997,
|
||||||
date: new Date().toISOString().split('T')[0],
|
date: getEcuadorToday(),
|
||||||
title: `${getWeatherIcon(weather.today?.weathercode)} ${weather.current?.celsius ?? '--'}°C`,
|
title: `${getWeatherIcon(weather.today?.weathercode)} ${weather.current?.celsius ?? '--'}°C`,
|
||||||
type: 'weather' as const,
|
type: 'weather' as const,
|
||||||
description: `Today: ${weather.today?.maxC ?? '--'}° / ${weather.today?.minC ?? '--'}°C · ${weather.today?.summary ?? ''}`,
|
description: `Today: ${weather.today?.maxC ?? '--'}° / ${weather.today?.minC ?? '--'}°C · ${weather.today?.summary ?? ''}`,
|
||||||
@@ -110,7 +83,7 @@ export default function CalendarStrip() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 9998,
|
id: 9998,
|
||||||
date: new Date(Date.now() + 86400000).toISOString().split('T')[0],
|
date: getEcuadorDateFromToday(1),
|
||||||
title: `${getWeatherIcon(weather.tomorrow?.weathercode)} ${weather.tomorrow?.maxC ?? '--'}° / ${weather.tomorrow?.minC ?? '--'}°C`,
|
title: `${getWeatherIcon(weather.tomorrow?.weathercode)} ${weather.tomorrow?.maxC ?? '--'}° / ${weather.tomorrow?.minC ?? '--'}°C`,
|
||||||
type: 'weather' as const,
|
type: 'weather' as const,
|
||||||
description: `Tomorrow: ${weather.tomorrow?.summary ?? ''}`,
|
description: `Tomorrow: ${weather.tomorrow?.summary ?? ''}`,
|
||||||
@@ -119,7 +92,7 @@ export default function CalendarStrip() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 9999,
|
id: 9999,
|
||||||
date: new Date(Date.now() + 172800000).toISOString().split('T')[0],
|
date: getEcuadorDateFromToday(2),
|
||||||
title: `${getWeatherIcon(weather.dayAfter?.weathercode)} ${weather.dayAfter?.maxC ?? '--'}° / ${weather.dayAfter?.minC ?? '--'}°C`,
|
title: `${getWeatherIcon(weather.dayAfter?.weathercode)} ${weather.dayAfter?.maxC ?? '--'}° / ${weather.dayAfter?.minC ?? '--'}°C`,
|
||||||
type: 'weather' as const,
|
type: 'weather' as const,
|
||||||
description: `Day After: ${weather.dayAfter?.summary ?? ''}`,
|
description: `Day After: ${weather.dayAfter?.summary ?? ''}`,
|
||||||
@@ -184,7 +157,7 @@ export default function CalendarStrip() {
|
|||||||
ref={trackRef}
|
ref={trackRef}
|
||||||
>
|
>
|
||||||
{[...cards, ...cards].map((e, i) => {
|
{[...cards, ...cards].map((e, i) => {
|
||||||
const { month, day } = formatMonthDay(e.date);
|
const { month, day } = getMonthDayEcuador(e.date);
|
||||||
const accent = e.color || gold;
|
const accent = e.color || gold;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -239,7 +212,7 @@ export default function CalendarStrip() {
|
|||||||
{e.description || ''}
|
{e.description || ''}
|
||||||
</p>
|
</p>
|
||||||
<div style={{ marginTop: '0.8rem', fontSize: '12px', color: 'rgba(245,240,232,0.5)' }}>
|
<div style={{ marginTop: '0.8rem', fontSize: '12px', color: 'rgba(245,240,232,0.5)' }}>
|
||||||
{e.isWeather ? 'Otavalo now' : daysUntil(e.date)}
|
{e.isWeather ? 'Otavalo now' : daysUntilEcuador(e.date)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
+125
@@ -0,0 +1,125 @@
|
|||||||
|
// Date utilities for La Huasca - Ecuador is GMT-5 (no DST)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current date/time in Ecuador timezone (GMT-5)
|
||||||
|
*/
|
||||||
|
export function getEcuadorDate(): Date {
|
||||||
|
const now = new Date();
|
||||||
|
// Ecuador is UTC-5, so subtract 5 hours from UTC
|
||||||
|
const ecuadorOffset = -5 * 60; // minutes
|
||||||
|
const utcOffset = now.getTimezoneOffset(); // local offset from UTC
|
||||||
|
const ecuadorTime = new Date(now.getTime() + (utcOffset + ecuadorOffset) * 60000);
|
||||||
|
return ecuadorTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get today's date in Ecuador (YYYY-MM-DD format)
|
||||||
|
*/
|
||||||
|
export function getEcuadorToday(): string {
|
||||||
|
return formatDateEcuador(getEcuadorDate());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a date as YYYY-MM-DD in Ecuador timezone
|
||||||
|
*/
|
||||||
|
export function formatDateEcuador(date: Date): string {
|
||||||
|
const ecuadorOffset = -5 * 60; // minutes
|
||||||
|
const utc = date.getTime() + date.getTimezoneOffset() * 60000;
|
||||||
|
const ecuadorTime = new Date(utc + ecuadorOffset * 60000);
|
||||||
|
|
||||||
|
const year = ecuadorTime.getFullYear();
|
||||||
|
const month = String(ecuadorTime.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(ecuadorTime.getDate()).padStart(2, '0');
|
||||||
|
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a date N days from today in Ecuador timezone
|
||||||
|
*/
|
||||||
|
export function getEcuadorDateFromToday(days: number): string {
|
||||||
|
const today = getEcuadorDate();
|
||||||
|
today.setDate(today.getDate() + days);
|
||||||
|
return formatDateEcuador(today);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a date string (YYYY-MM-DD or ISO) for display in Ecuador timezone
|
||||||
|
*/
|
||||||
|
export function formatDisplayDate(dateStr: string, options?: Intl.DateTimeFormatOptions): string {
|
||||||
|
const date = parseDateAsEcuador(dateStr);
|
||||||
|
const defaultOptions: Intl.DateTimeFormatOptions = {
|
||||||
|
weekday: 'short',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
timeZone: 'America/Guayaquil',
|
||||||
|
};
|
||||||
|
return date.toLocaleDateString('en-US', options || defaultOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a date with full weekday name
|
||||||
|
*/
|
||||||
|
export function formatDisplayDateFull(dateStr: string): string {
|
||||||
|
return formatDisplayDate(dateStr, {
|
||||||
|
weekday: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
timeZone: 'America/Guayaquil',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a datetime string for display
|
||||||
|
*/
|
||||||
|
export function formatDisplayDateTime(dateStr: string): string {
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
return date.toLocaleString('en-US', {
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
timeZone: 'America/Guayaquil',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a date string (YYYY-MM-DD) as Ecuador date, avoiding timezone shifts
|
||||||
|
*/
|
||||||
|
export function parseDateAsEcuador(dateStr: string): Date {
|
||||||
|
// Parse as local components to avoid timezone shifts
|
||||||
|
const parts = dateStr.split('T')[0].split('-');
|
||||||
|
const year = parseInt(parts[0], 10);
|
||||||
|
const month = parseInt(parts[1], 10) - 1; // JS months are 0-indexed
|
||||||
|
const day = parseInt(parts[2], 10);
|
||||||
|
return new Date(year, month, day);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate days until a date (in Ecuador timezone)
|
||||||
|
*/
|
||||||
|
export function daysUntilEcuador(dateStr: string): string {
|
||||||
|
const today = getEcuadorDate();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
const target = parseDateAsEcuador(dateStr);
|
||||||
|
target.setHours(0, 0, 0, 0);
|
||||||
|
const diff = Math.ceil((target.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
if (isNaN(diff)) return 'Unknown';
|
||||||
|
if (diff === 0) return 'Today';
|
||||||
|
if (diff === 1) return 'Tomorrow';
|
||||||
|
if (diff < 0) return 'Past';
|
||||||
|
return `in ${diff} days`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get month and day for display (e.g., "Jun 29")
|
||||||
|
*/
|
||||||
|
export function getMonthDayEcuador(dateStr: string): { month: string; day: number } {
|
||||||
|
const date = parseDateAsEcuador(dateStr);
|
||||||
|
return {
|
||||||
|
month: date.toLocaleString('en-US', { month: 'short' }),
|
||||||
|
day: date.getDate(),
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user