Skip to content

Commit e847341

Browse files
authored
refactor: use date-fns/tz instead of a custom tz impl (#3685)
1 parent 92afbf3 commit e847341

File tree

3 files changed

+39
-114
lines changed

3 files changed

+39
-114
lines changed

apps/desktop/src/components/main/sidebar/timeline/item.tsx

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
TooltipContent,
99
TooltipTrigger,
1010
} from "@hypr/ui/components/ui/tooltip";
11-
import { cn, safeParseDate } from "@hypr/utils";
11+
import { cn, format, getYear, safeParseDate, TZDate } from "@hypr/utils";
1212

1313
import { useListener } from "../../../../contexts/listener";
1414
import { useIsSessionEnhancing } from "../../../../hooks/useEnhancedNotes";
@@ -423,47 +423,23 @@ function formatDisplayTime(
423423
precision: TimelinePrecision,
424424
timezone?: string,
425425
): string {
426-
const date = safeParseDate(timestamp);
427-
if (!date) {
426+
const parsed = safeParseDate(timestamp);
427+
if (!parsed) {
428428
return "";
429429
}
430430

431-
const timeOptions: Intl.DateTimeFormatOptions = {
432-
hour: "numeric",
433-
minute: "numeric",
434-
timeZone: timezone,
435-
};
436-
437-
const time = date.toLocaleTimeString([], timeOptions);
431+
const date = timezone ? new TZDate(parsed, timezone) : parsed;
432+
const time = format(date, "h:mm a");
438433

439434
if (precision === "time") {
440435
return time;
441436
}
442437

443-
const now = new Date();
444-
const yearInTz = timezone
445-
? parseInt(
446-
new Intl.DateTimeFormat("en-US", {
447-
year: "numeric",
448-
timeZone: timezone,
449-
}).format(date),
450-
)
451-
: date.getFullYear();
452-
const currentYearInTz = timezone
453-
? parseInt(
454-
new Intl.DateTimeFormat("en-US", {
455-
year: "numeric",
456-
timeZone: timezone,
457-
}).format(now),
458-
)
459-
: now.getFullYear();
460-
const sameYear = yearInTz === currentYearInTz;
461-
462-
const dateOptions: Intl.DateTimeFormatOptions = sameYear
463-
? { month: "short", day: "numeric", timeZone: timezone }
464-
: { month: "short", day: "numeric", year: "numeric", timeZone: timezone };
465-
466-
const dateStr = date.toLocaleDateString([], dateOptions);
438+
const now = timezone ? new TZDate(new Date(), timezone) : new Date();
439+
const sameYear = getYear(date) === getYear(now);
440+
const dateStr = sameYear
441+
? format(date, "MMM d")
442+
: format(date, "MMM d, yyyy");
467443

468444
return `${dateStr}, ${time}`;
469445
}

apps/desktop/src/utils/timeline.ts

Lines changed: 28 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,15 @@
1-
import { isPast, safeParseDate } from "@hypr/utils";
2-
3-
interface DateParts {
4-
year: number;
5-
month: number;
6-
day: number;
7-
}
8-
9-
function getDatePartsInTimezone(date: Date, timezone?: string): DateParts {
10-
const formatter = new Intl.DateTimeFormat("en-CA", {
11-
timeZone: timezone,
12-
year: "numeric",
13-
month: "2-digit",
14-
day: "2-digit",
15-
});
16-
const formatted = formatter.format(date);
17-
const [year, month, day] = formatted.split("-").map(Number);
18-
return { year, month, day };
19-
}
20-
21-
function datePartsToUtcMidnight(parts: DateParts): number {
22-
return Date.UTC(parts.year, parts.month - 1, parts.day);
23-
}
24-
25-
function differenceInCalendarDaysInTimezone(
26-
dateLeft: Date,
27-
dateRight: Date,
28-
timezone?: string,
29-
): number {
30-
const leftParts = getDatePartsInTimezone(dateLeft, timezone);
31-
const rightParts = getDatePartsInTimezone(dateRight, timezone);
32-
const leftUtc = datePartsToUtcMidnight(leftParts);
33-
const rightUtc = datePartsToUtcMidnight(rightParts);
34-
return Math.round((leftUtc - rightUtc) / (1000 * 60 * 60 * 24));
35-
}
36-
37-
function differenceInCalendarMonthsInTimezone(
38-
dateLeft: Date,
39-
dateRight: Date,
40-
timezone?: string,
41-
): number {
42-
const leftParts = getDatePartsInTimezone(dateLeft, timezone);
43-
const rightParts = getDatePartsInTimezone(dateRight, timezone);
44-
return (
45-
(leftParts.year - rightParts.year) * 12 +
46-
(leftParts.month - rightParts.month)
47-
);
48-
}
49-
50-
function getSortKeyForDateInTimezone(date: Date, timezone?: string): number {
51-
const parts = getDatePartsInTimezone(date, timezone);
52-
return datePartsToUtcMidnight(parts);
1+
import {
2+
differenceInCalendarDays,
3+
differenceInCalendarMonths,
4+
isPast,
5+
safeParseDate,
6+
startOfDay,
7+
startOfMonth,
8+
TZDate,
9+
} from "@hypr/utils";
10+
11+
function toTZ(date: Date, timezone?: string): Date {
12+
return timezone ? new TZDate(date, timezone) : date;
5313
}
5414

5515
// comes from QUERIES.timelineEvents
@@ -109,8 +69,10 @@ export function getBucketInfo(
10969
precision: TimelinePrecision;
11070
} {
11171
const now = new Date();
112-
const daysDiff = differenceInCalendarDaysInTimezone(date, now, timezone);
113-
const sortKey = getSortKeyForDateInTimezone(date, timezone);
72+
const tzDate = toTZ(date, timezone);
73+
const tzNow = toTZ(now, timezone);
74+
const daysDiff = differenceInCalendarDays(tzDate, tzNow);
75+
const sortKey = startOfDay(tzDate).getTime();
11476
const absDays = Math.abs(daysDiff);
11577

11678
if (daysDiff === 0) {
@@ -136,7 +98,7 @@ export function getBucketInfo(
13698
const weekRangeEnd = new Date(
13799
now.getTime() - weekRangeEndDay * 24 * 60 * 60 * 1000,
138100
);
139-
const weekSortKey = getSortKeyForDateInTimezone(weekRangeEnd, timezone);
101+
const weekSortKey = startOfDay(toTZ(weekRangeEnd, timezone)).getTime();
140102

141103
return {
142104
label: weeks === 1 ? "a week ago" : `${weeks} weeks ago`,
@@ -145,25 +107,17 @@ export function getBucketInfo(
145107
};
146108
}
147109

148-
let months = Math.abs(
149-
differenceInCalendarMonthsInTimezone(date, now, timezone),
150-
);
110+
let months = Math.abs(differenceInCalendarMonths(tzDate, tzNow));
151111
if (months === 0) {
152112
months = 1;
153113
}
154-
const targetParts = getDatePartsInTimezone(date, timezone);
155-
const monthStartKey = datePartsToUtcMidnight({
156-
year: targetParts.year,
157-
month: targetParts.month,
158-
day: 1,
159-
});
114+
const monthStartKey = startOfMonth(tzDate).getTime();
160115
const lastDayInMonthBucket = new Date(
161116
now.getTime() - 28 * 24 * 60 * 60 * 1000,
162117
);
163-
const lastDayKey = getSortKeyForDateInTimezone(
164-
lastDayInMonthBucket,
165-
timezone,
166-
);
118+
const lastDayKey = startOfDay(
119+
toTZ(lastDayInMonthBucket, timezone),
120+
).getTime();
167121
const monthSortKey = Math.min(monthStartKey, lastDayKey);
168122
return {
169123
label: months === 1 ? "a month ago" : `${months} months ago`,
@@ -182,7 +136,7 @@ export function getBucketInfo(
182136
const weekRangeStart = new Date(
183137
now.getTime() + weekRangeStartDay * 24 * 60 * 60 * 1000,
184138
);
185-
const weekSortKey = getSortKeyForDateInTimezone(weekRangeStart, timezone);
139+
const weekSortKey = startOfDay(toTZ(weekRangeStart, timezone)).getTime();
186140

187141
return {
188142
label: weeks === 1 ? "next week" : `in ${weeks} weeks`,
@@ -191,23 +145,17 @@ export function getBucketInfo(
191145
};
192146
}
193147

194-
let months = differenceInCalendarMonthsInTimezone(date, now, timezone);
148+
let months = differenceInCalendarMonths(tzDate, tzNow);
195149
if (months === 0) {
196150
months = 1;
197151
}
198-
const targetParts = getDatePartsInTimezone(date, timezone);
199-
const monthStartKey = datePartsToUtcMidnight({
200-
year: targetParts.year,
201-
month: targetParts.month,
202-
day: 1,
203-
});
152+
const monthStartKey = startOfMonth(tzDate).getTime();
204153
const firstDayInMonthBucket = new Date(
205154
now.getTime() + 28 * 24 * 60 * 60 * 1000,
206155
);
207-
const firstDayKey = getSortKeyForDateInTimezone(
208-
firstDayInMonthBucket,
209-
timezone,
210-
);
156+
const firstDayKey = startOfDay(
157+
toTZ(firstDayInMonthBucket, timezone),
158+
).getTime();
211159
const monthSortKey = Math.max(monthStartKey, firstDayKey);
212160
return {
213161
label: months === 1 ? "next month" : `in ${months} months`,

packages/utils/src/date.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { format as dateFnsFormat, isValid } from "date-fns";
99

1010
// Re-export ALL date-fns functions so users can import any date-fns function from @hypr/utils
1111
export * from "date-fns";
12+
export { TZDate } from "@date-fns/tz";
1213

1314
export function safeParseDate(value: unknown): Date | null {
1415
if (value === null || value === undefined) {

0 commit comments

Comments
 (0)