-
-
Notifications
You must be signed in to change notification settings - Fork 5
Refactor/date fns #1069
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Refactor/date fns #1069
Changes from all commits
1507cf8
4205b56
e92ae2b
2ae3c26
29cba82
8c666be
dabf422
b698123
2e026c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,6 @@ | ||
| 'use client' | ||
|
|
||
| import * as React from 'react' | ||
| import { addDays, format } from 'date-fns' | ||
| import { CalendarIcon } from 'lucide-react' | ||
| import { type DateRange } from '@daypicker/react' | ||
|
|
||
|
|
@@ -13,11 +12,19 @@ import { | |
| PopoverContent, | ||
| PopoverTrigger, | ||
| } from '@/components/ui/popover' | ||
| import { useTranslation } from 'react-i18next' | ||
|
|
||
| export function DatePickerWithRange() { | ||
| const { i18n } = useTranslation() | ||
| const [date, setDate] = React.useState<DateRange | undefined>({ | ||
| from: new Date(new Date().getFullYear(), 0, 20), | ||
| to: addDays(new Date(new Date().getFullYear(), 0, 20), 20), | ||
| to: new Date(new Date().getFullYear(), 1, 10), | ||
| }) | ||
|
|
||
| const dateTimeFormat = new Intl.DateTimeFormat(i18n.language, { | ||
| year: 'numeric', | ||
| month: 'long', | ||
| day: 'numeric', | ||
| }) | ||
|
Comment on lines
+24
to
28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
files=(
"app/components/ui/range-picker.tsx"
"app/components/map/layers/mobile/mobile-overview-layer.tsx"
"app/components/mydevices/dt/columns.tsx"
"app/routes/admin.devices._index.tsx"
"app/routes/admin.users._index.tsx"
"app/routes/device.\$deviceId.edit.logs.tsx"
"app/routes/device.\$deviceId.edit.transfer.tsx"
"app/routes/profile.\$username.tsx"
)
for file in "${files[@]}"; do
echo "===== $file ====="
wc -l "$file"
rg -n -C 4 'Intl\.DateTimeFormat|toLocale(Date|String)|dateTimeFormat|useTranslation|createdAt|updatedAt|startTime|endTime|transferExpiresAt' "$file" || true
done
echo "===== SSR/client boundaries and timezone configuration ====="
rg -n -S 'clientOnly|ClientOnly|Hydrat|timeZone|timezone|TZ=|process\.env\.TZ|defaultTimeZone|DateTimeFormat' app package.json vite.config.* remix.config.* i18n* 2>/dev/null || true
echo "===== route/component usage ====="
rg -n -S 'RangePicker|MobileOverviewLayer|admin\.devices|admin\.users|edit\.logs|edit\.transfer|profile\.' app | head -300Repository: openSenseMap/frontend Length of output: 18711 🏁 Script executed: #!/bin/bash
set -eu
echo "===== relevant source context ====="
sed -n '1,75p' app/components/ui/range-picker.tsx
sed -n '105,145p;430,480p' app/components/map/layers/mobile/mobile-overview-layer.tsx
sed -n '1,110p' app/components/mydevices/dt/columns.tsx
sed -n '1,75p' app/routes/admin.devices._index.tsx
sed -n '1,65p' app/routes/admin.users._index.tsx
sed -n '85,110p;160,182p' 'app/routes/device.$deviceId.edit.logs.tsx'
sed -n '135,160p;286,304p' 'app/routes/device.$deviceId.edit.transfer.tsx'
sed -n '120,170p;190,215p' 'app/routes/profile.$username.tsx'
echo "===== framework and hydration setup ====="
sed -n '1,90p' app/entry.server.tsx 2>/dev/null || true
sed -n '1,80p' app/entry.client.tsx
sed -n '1,55p' app/components/client-only.tsx
rg -n -S 'renderTo|hydrate|SSR|ssr|ClientOnly|DatePickerWithRange|MobileOverviewLayer|getColumns|DataTable' app --glob '*.{ts,tsx}' | head -250
echo "===== date/time configuration ====="
rg -n -S 'timeZone|timezone|TZ=|process\.env\.TZ|resolvedLanguage|supportedLngs|fallbackLng|i18n' . --glob '!node_modules' --glob '!build' --glob '!dist' | head -300
echo "===== standalone Intl default-time-zone probe ====="
TZ=UTC node - <<'JS'
const d = new Date('2024-01-15T23:30:00Z')
console.log('UTC:', new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'short' }).format(d))
JS
TZ=America/Los_Angeles node - <<'JS'
const d = new Date('2024-01-15T23:30:00Z')
console.log('Los_Angeles:', new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'short' }).format(d))
JS
TZ=UTC node - <<'JS'
const d = new Date('2024-01-15T23:30:00Z')
console.log('explicit UTC:', new Intl.DateTimeFormat('en-US', {
dateStyle: 'medium', timeStyle: 'short', timeZone: 'UTC'
}).format(d))
JS
TZ=America/Los_Angeles node - <<'JS'
const d = new Date('2024-01-15T23:30:00Z')
console.log('explicit UTC:', new Intl.DateTimeFormat('en-US', {
dateStyle: 'medium', timeStyle: 'short', timeZone: 'UTC'
}).format(d))
JSRepository: openSenseMap/frontend Length of output: 42073 🏁 Script executed: #!/bin/bash
set -eu
echo "===== DataTable render path ====="
sed -n '1,180p' app/components/mydevices/dt/data-table.tsx
sed -n '1,145p' 'app/routes/explore.$deviceId.tsx'
echo "===== range-picker and mobile state/output conditions ====="
rg -n -C 5 'DatePickerWithRange|popupInfo|setPopupInfo|MobileOverviewLayer|ClientOnly|useHydrated' app --glob '*.{ts,tsx}'
echo "===== date-only versus instant formatting probe ====="
node - <<'JS'
const instant = new Date('2024-01-15T23:30:00Z')
const dateOnly = new Date(2024, 0, 20)
for (const tz of ['UTC', 'America/Los_Angeles']) {
process.env.TZ = tz
console.log(tz, {
instantDate: instant.toLocaleDateString('en-US'),
instantDateTime: instant.toLocaleString('en-US'),
dateOnlyDate: dateOnly.toLocaleDateString('en-US'),
dateOnlyDateTime: dateOnly.toLocaleString('en-US'),
})
}
JSRepository: openSenseMap/frontend Length of output: 27119 Use a deterministic time zone for SSR-rendered timestamps. These formatters omit Apply this to:
The range picker uses local date-only values. The mobile popup is populated by client-side map events, so neither confirms this hydration issue. 📍 Affects 8 files
Source: Linters/SAST tools |
||
|
|
||
| return ( | ||
|
|
@@ -34,11 +41,11 @@ export function DatePickerWithRange() { | |
| {date?.from ? ( | ||
| date.to ? ( | ||
| <> | ||
| {format(date.from, 'LLL dd, y')} -{' '} | ||
| {format(date.to, 'LLL dd, y')} | ||
| {dateTimeFormat.format(date.from)} -{' '} | ||
| {dateTimeFormat.format(date.to)} | ||
| </> | ||
| ) : ( | ||
| format(date.from, 'LLL dd, y') | ||
| dateTimeFormat.format(date.from) | ||
| ) | ||
| ) : ( | ||
| <span>Pick a date</span> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| const ONE_MINUTE_IN_S = 60 | ||
| const ONE_HOUR_IN_S = 60 * ONE_MINUTE_IN_S | ||
| const ONE_DAY_IN_S = 24 * ONE_HOUR_IN_S | ||
| const ONE_WEEK_IN_S = 7 * ONE_DAY_IN_S | ||
| const ONE_MONTH_IN_S = 4 * ONE_WEEK_IN_S | ||
| const ONE_QUARTER_IN_S = 3 * ONE_MONTH_IN_S | ||
| const ONE_YEAR_IN_S = 12 * ONE_MONTH_IN_S | ||
|
Comment on lines
+1
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- candidate files ---'
fd -t f -i 'date' . | head -80
printf '%s\n' '--- date symbols and references ---'
rg -n --glob '!node_modules' 'ONE_(MONTH|YEAR|QUARTER|WEEK|DAY|HOUR|MINUTE)_IN_S|app/lib/date|format.*date|relative' app tests . 2>/dev/null | head -240
printf '%s\n' '--- date.ts outline ---'
if command -v ast-grep >/dev/null 2>&1; then
ast-grep outline app/lib/date.ts
else
wc -l app/lib/date.ts
fi
printf '%s\n' '--- app/lib/date.ts ---'
cat -n app/lib/date.tsRepository: openSenseMap/frontend Length of output: 30854 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- exact call sites ---'
rg -n --glob '!node_modules' 'dateDiffToNowInWords' .
printf '%s\n' '--- test/config files ---'
git ls-files | rg '(^|/)(package\.json|.*date.*spec|.*date.*test|vitest|jest|README|CHANGELOG|CONTRIBUTING)' | head -160
printf '%s\n' '--- app/lib/date.ts history summary ---'
git diff --stat -- app/lib/date.ts
git diff -- app/lib/date.ts | head -220
printf '%s\n' '--- behavioral boundary calculation ---'
node - <<'JS'
const MINUTE = 60;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;
const WEEK = 7 * DAY;
const MONTH = 4 * WEEK;
const QUARTER = 3 * MONTH;
const YEAR = 12 * MONTH;
function bucket(ageDays) {
const diff = Math.round(ageDays * DAY);
const abs = Math.abs(diff);
if (abs < MINUTE) return ['second', -diff];
if (abs < HOUR) return ['minute', -Math.round(diff / MINUTE)];
if (abs < DAY) return ['hour', -Math.round(diff / HOUR)];
if (abs < WEEK) return ['day', -Math.round(diff / DAY)];
if (abs < MONTH) return ['week', -Math.round(diff / WEEK)];
if (abs < QUARTER) return ['month', -Math.round(diff / MONTH)];
if (abs < YEAR) return ['quarter', -Math.round(diff / QUARTER)];
return ['year', -Math.round(diff / YEAR)];
}
for (const days of [27, 27.99, 28, 29, 56, 83, 84, 335, 336, 350, 365, 366]) {
console.log(`${days} days -> ${bucket(days)[1]} ${bucket(days)[0]}`);
}
for (const days of [350, 365, 366]) {
const d = new Date(Date.UTC(2025, 0, 1));
d.setUTCDate(d.getUTCDate() - days);
const elapsed = (Date.UTC(2025, 0, 1) - d.getTime()) / 86400000;
console.log(`calendar probe: ${days} requested -> ${d.toISOString()} -> ${elapsed} elapsed days`);
}
JSRepository: openSenseMap/frontend Length of output: 1248 Use calendar-aware differences for month and year output.
|
||
|
|
||
| export const dateDiffToNowInWords = (locale: string, date: Date) => { | ||
| const r = new Intl.RelativeTimeFormat(locale) | ||
| const now = new Date() | ||
| const diffInSeconds = Math.round((now.getTime() - date.getTime()) / 1000) | ||
| const absDiffInSeconds = Math.abs(diffInSeconds) | ||
|
|
||
| if (absDiffInSeconds < ONE_MINUTE_IN_S) | ||
| return r.format(-diffInSeconds, 'second') | ||
| if (absDiffInSeconds < ONE_HOUR_IN_S) | ||
| return r.format(-Math.round(diffInSeconds / ONE_MINUTE_IN_S), 'minute') | ||
| if (absDiffInSeconds < ONE_DAY_IN_S) | ||
| return r.format(-Math.round(diffInSeconds / ONE_HOUR_IN_S), 'hour') | ||
| if (absDiffInSeconds < ONE_WEEK_IN_S) | ||
| return r.format(-Math.round(diffInSeconds / ONE_DAY_IN_S), 'day') | ||
| if (absDiffInSeconds < ONE_MONTH_IN_S) | ||
| return r.format(-Math.round(diffInSeconds / ONE_WEEK_IN_S), 'week') | ||
| if (absDiffInSeconds < ONE_QUARTER_IN_S) | ||
| return r.format(-Math.round(diffInSeconds / ONE_MONTH_IN_S), 'month') | ||
| if (absDiffInSeconds < ONE_YEAR_IN_S) | ||
| return r.format(-Math.round(diffInSeconds / ONE_QUARTER_IN_S), 'quarter') | ||
| return r.format(-Math.round(diffInSeconds / ONE_YEAR_IN_S), 'year') | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: openSenseMap/frontend
Length of output: 4674
🏁 Script executed:
Repository: openSenseMap/frontend
Length of output: 5460
Preserve the original 20-day range offset.
addDays(January 20, 20)returns February 9. Settoto February 9 to preserve the previous default range.