-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutils.ts
More file actions
192 lines (165 loc) · 6.23 KB
/
Copy pathutils.ts
File metadata and controls
192 lines (165 loc) · 6.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
import type { TimeHistory, Domain, DateString, SessionDayStat, SessionStartStats } from '../types.js';
/**
* Extract domain from URL, removing www. prefix
*/
export function extractDomain(url: string): string | null {
try {
const parsedUrl = new URL(url);
let hostname = parsedUrl.hostname;
if (hostname.startsWith('www.')) {
hostname = hostname.substring(4);
}
return hostname;
} catch (error) {
console.error("Error parsing URL:", error);
return null;
}
}
/**
* Format time in seconds to HH:MM format
*/
export function formatTime(totalTime: number): string {
const hours = Math.floor(totalTime / 3600);
const minutes = Math.floor((totalTime % 3600) / 60);
const formattedHours = hours.toString().padStart(2, '0');
const formattedMinutes = minutes.toString().padStart(2, '0');
return `${formattedHours}:${formattedMinutes}`;
}
/**
* Format a DURATION (seconds) as unit-labelled "4h 36m" / "25m" / "0m".
* Used for popup display numbers (hero stats, session card) where the colon
* "HH:MM" format is ambiguous against the on-screen timer's "MM:SS" clock.
* No colons → no confusion with a ticking timer. Always shows minutes; rounds
* down (these are elapsed/total figures, not a countdown to a deadline).
*/
export function formatDuration(totalSeconds: number): string {
const s = Math.max(0, Math.floor(totalSeconds));
const hours = Math.floor(s / 3600);
const minutes = Math.floor((s % 3600) / 60);
return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
}
/**
* Like formatDuration but ALWAYS shows the hours part ("0h 3m"), so a column of
* durations keeps a consistent "Xh Ym" shape even under an hour. Used by the
* usage card where the two figures sit side by side.
*/
export function formatDurationHM(totalSeconds: number): string {
const s = Math.max(0, Math.floor(totalSeconds));
const hours = Math.floor(s / 3600);
const minutes = Math.floor((s % 3600) / 60);
return `${hours}h ${minutes}m`;
}
/**
* Format a DURATION (seconds) as a clock "M:SS" / "H:MM:SS". Used in the session
* card, where the value is a live session/cooldown countdown and seconds matter —
* unlike the hero stats (daily totals) which use the unit-labelled formatDuration.
* Minutes are unpadded when there's no hours part ("4:05"), padded after hours
* ("1:04:05"); seconds always padded.
*/
export function formatClock(totalSeconds: number): string {
const s = Math.max(0, Math.floor(totalSeconds));
const hours = Math.floor(s / 3600);
const minutes = Math.floor((s % 3600) / 60);
const seconds = s % 60;
const ss = seconds.toString().padStart(2, '0');
if (hours > 0) {
const mm = minutes.toString().padStart(2, '0');
return `${hours}:${mm}:${ss}`;
}
return `${minutes}:${ss}`;
}
/**
* Get current date as YYYY-MM-DD string in local timezone,
* adjusted for custom day reset time
*/
export function getLocalDateStr(resetHour: number = 0): string {
const now = new Date();
if (now.getHours() < resetHour) {
now.setDate(now.getDate() - 1);
}
const monthNum = now.getMonth() + 1;
const yyyy = now.getFullYear();
const mm = String(monthNum).padStart(2, "0");
const dd = String(now.getDate()).padStart(2, "0");
return `${yyyy}-${mm}-${dd}`;
}
/**
* Parse a YYYY-MM-DD date string safely in local time (avoids UTC midnight timezone issues).
*/
function parseDateLocal(dateStr: DateString): Date {
const [year, month, day] = dateStr.split('-').map(Number);
return new Date(year, month - 1, day, 12, 0, 0); // noon local time
}
/**
* Format date string for display (e.g., "2025-01-15" -> "Jan 15")
*/
export function formatDateForDisplay(dateString: string): string {
const date = parseDateLocal(dateString);
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
return `${months[date.getMonth()]} ${date.getDate()}`;
}
/**
* Day-of-week abbreviation (e.g. "2025-01-15" -> "Wed") for a YYYY-MM-DD string.
*/
export function getDayOfWeek(dateString: string): string {
const date = parseDateLocal(dateString);
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
return days[date.getDay()];
}
/**
* Format a date string with its weekday (e.g. "2025-01-15" -> "Jan 15 (Wed)").
*/
export function formatDateWithDayOfWeek(dateString: string): string {
return `${formatDateForDisplay(dateString)} (${getDayOfWeek(dateString)})`;
}
/**
* Debug logging, off in production. Flip DEBUG_ENABLED to true to get the full
* trace (tab switches, saves, ticks, session/cooldown lifecycle) back.
*/
const DEBUG_ENABLED = false;
export function log(...args: unknown[]): void {
if (DEBUG_ENABLED) {
console.log('[WebTime Debug]:', ...args);
}
}
/**
* Format time in seconds to a compact human-readable string (e.g. "1h 23m", "45m")
*/
export function formatTimeCompact(seconds: number): string {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
if (hours > 0 && minutes > 0) return `${hours}h ${minutes}m`;
if (hours > 0) return `${hours}h`;
return `${minutes}m`;
}
/**
* Compute 7-day stats for a domain, excluding today.
* Returns per-day breakdown, mean seconds over days with data, and count of days with data.
* averageSeconds = 0 and daysWithData = 0 if no history exists.
*
* Uses local-time date arithmetic to avoid UTC midnight timezone issues.
*/
export function compute7DayStats(
timeHistory: TimeHistory,
domain: Domain,
currentDateStr: DateString
): SessionStartStats {
const days: SessionDayStat[] = [];
const baseDate = parseDateLocal(currentDateStr);
for (let i = 1; i <= 7; i++) {
const date = new Date(baseDate);
date.setDate(date.getDate() - i);
const yyyy = date.getFullYear();
const mm = String(date.getMonth() + 1).padStart(2, '0');
const dd = String(date.getDate()).padStart(2, '0');
const dateStr: DateString = `${yyyy}-${mm}-${dd}`;
const seconds = timeHistory[dateStr]?.[domain] ?? 0;
days.unshift({ date: dateStr, seconds });
}
const daysWithDataArr = days.filter(d => d.seconds > 0);
const daysWithData = daysWithDataArr.length;
const averageSeconds = daysWithData > 0
? Math.round(daysWithDataArr.reduce((sum, d) => sum + d.seconds, 0) / daysWithData)
: 0;
return { days, averageSeconds, daysWithData };
}