forked from Creditra/Creditra-Frontend
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdates.ts
More file actions
189 lines (164 loc) · 5.56 KB
/
Copy pathdates.ts
File metadata and controls
189 lines (164 loc) · 5.56 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
/**
* Small, dependency-free date helpers.
*
* We deliberately avoid pulling in moment / dayjs / luxon — the few
* formatting needs across the app can be served by `Intl.*` plus the
* helpers here.
*/
const DEFAULT_LOCALE = 'en-US';
/**
* Return a human-friendly "time ago" string (e.g. `"3 minutes ago"`,
* `"2 days ago"`). Uses the browser's `Intl.RelativeTimeFormat` where
* available.
*/
export function timeAgo(
iso: string | Date,
now: Date = new Date(),
locale: string = DEFAULT_LOCALE,
): string {
const then = iso instanceof Date ? iso : new Date(iso);
const diffSeconds = Math.round((then.getTime() - now.getTime()) / 1000);
const absSeconds = Math.abs(diffSeconds);
const units: Array<[Intl.RelativeTimeFormatUnit, number]> = [
['year', 60 * 60 * 24 * 365],
['month', 60 * 60 * 24 * 30],
['day', 60 * 60 * 24],
['hour', 60 * 60],
['minute', 60],
['second', 1],
];
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
for (const [unit, secondsPerUnit] of units) {
if (absSeconds >= secondsPerUnit || unit === 'second') {
const value = Math.round(diffSeconds / secondsPerUnit);
return rtf.format(value, unit);
}
}
return rtf.format(0, 'second');
}
/**
* Return `true` when the given ISO timestamp falls on the same calendar
* day as `reference` (defaults to today).
*/
export function isSameDay(iso: string | Date, reference: Date = new Date()): boolean {
const date = iso instanceof Date ? iso : new Date(iso);
return (
date.getFullYear() === reference.getFullYear() &&
date.getMonth() === reference.getMonth() &&
date.getDate() === reference.getDate()
);
}
// ─── Countdown helpers ────────────────────────────────────────────────────────
/**
* Format a future ISO timestamp as a compact countdown string for inline
* chips (e.g. "2h 14m", "3d", "45m"). Omits zero-valued units so the
* label stays short. Falls back to "now" when the target is in the past.
*/
export function formatCountdown(
targetIso: string | Date,
now: Date = new Date(),
): string {
const target = targetIso instanceof Date ? targetIso : new Date(targetIso);
const diffMs = target.getTime() - now.getTime();
if (diffMs <= 0) return 'now';
const totalSeconds = Math.ceil(diffMs / 1000);
const days = Math.floor(totalSeconds / 86400);
const hours = Math.floor((totalSeconds % 86400) / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const parts: string[] = [];
if (days > 0) parts.push(`${days}d`);
if (hours > 0) parts.push(`${hours}h`);
if (minutes > 0 || parts.length === 0) parts.push(`${minutes}m`);
return parts.join(' ');
}
/**
* Return a full-sentence accessible label for screen readers describing
* when the next interest accrual will occur, e.g.
* "Next interest in 2 hours 14 minutes". Falls back to
* "Interest is accruing now" for past dates.
*/
export function getCountdownAriaLabel(
targetIso: string | Date,
now: Date = new Date(),
locale: string = DEFAULT_LOCALE,
): string {
const target = targetIso instanceof Date ? targetIso : new Date(targetIso);
const diffMs = target.getTime() - now.getTime();
if (diffMs <= 0) {
return 'Interest is accruing now';
}
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
const totalSeconds = Math.ceil(diffMs / 1000);
const units: Array<[Intl.RelativeTimeFormatUnit, number]> = [
['day', 86400],
['hour', 3600],
['minute', 60],
];
for (const [unit, secondsPerUnit] of units) {
const value = Math.floor(totalSeconds / secondsPerUnit);
if (value > 0 || unit === 'minute') {
const formatted = rtf.format(value, unit);
const cleaned = formatted.replace(/^in\s+/, '');
return `Next interest in ${cleaned}`;
}
}
return `Next interest in ${rtf.format(0, 'second')}`;
}
/**
* Return a locale-aware relative label for a past timestamp.
*
* Examples:
* – just now (< 10 s ago)
* – 3 minutes ago
* – 2 hours ago
* – yesterday
* – Jun 12 (same year, > 1 day)
* – Jun 12, 2024 (different year)
*
* Uses `Intl.RelativeTimeFormat` for the short-range cases and
* `Intl.DateTimeFormat` for anything older than 24 h, so the string
* is always locale-correct without a third-party library.
*/
export function formatRelative(
ts: Date | string | number,
now: Date = new Date(),
locale: string = DEFAULT_LOCALE,
): string {
const then = ts instanceof Date ? ts : new Date(ts);
const diffMs = now.getTime() - then.getTime();
const diffSec = Math.round(diffMs / 1000);
if (diffSec < 10) return 'just now';
if (diffSec < 60) {
return new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }).format(
-diffSec,
'second',
);
}
const diffMin = Math.round(diffSec / 60);
if (diffMin < 60) {
return new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }).format(
-diffMin,
'minute',
);
}
const diffHr = Math.round(diffMin / 60);
if (diffHr < 24) {
return new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }).format(
-diffHr,
'hour',
);
}
if (diffHr < 48) {
return new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }).format(
-1,
'day',
);
}
// Older than 48 h → absolute date
const sameYear = then.getFullYear() === now.getFullYear();
return new Intl.DateTimeFormat(locale, {
month: 'short',
day: 'numeric',
...(sameYear ? {} : { year: 'numeric' }),
}).format(then);
}