kyd-shared-badge 0.3.18 → 0.3.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kyd-shared-badge",
3
- "version": "0.3.18",
3
+ "version": "0.3.20",
4
4
  "private": false,
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -299,9 +299,11 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
299
299
  <td className={'px-4 py-4 whitespace-normal text-sm'} style={{ color: 'var(--text-secondary)' }}>{br.category || '—'}</td>
300
300
  <td className={'px-4 py-4 whitespace-normal text-sm font-medium'} style={{ color: 'var(--text-main)' }}>{br.label || '—'}</td>
301
301
  <td className={'px-4 py-4 whitespace-normal text-sm'} style={{ color: 'var(--text-secondary)' }}>{(() => {
302
- const weight = Number(br.weight || 0);
303
- if (!Number.isFinite(weight)) return '';
304
- return weight.toFixed(3).replace(/\.000$/, '.0');
302
+ const weight = Number(br.weight);
303
+ if (!Number.isFinite(weight)) return 'Neutral';
304
+ if (weight > 0) return 'Positive';
305
+ if (weight < 0) return 'Negative';
306
+ return 'Neutral';
305
307
  })()}</td>
306
308
  </tr>
307
309
  );
@@ -0,0 +1,68 @@
1
+ export type DateLike = string | number | Date | null | undefined;
2
+
3
+ const hasTimezoneDesignator = (value: string): boolean => {
4
+ return /[zZ]$/.test(value) || /[+-]\d{2}:?\d{2}$/.test(value);
5
+ };
6
+
7
+ const truncateExcessFraction = (value: string): string => {
8
+ // Keep at most 3 fractional digits for milliseconds
9
+ return value.replace(/(\.\d{3})\d+/, '$1');
10
+ };
11
+
12
+ export const parseUtcDate = (input: DateLike): Date => {
13
+ if (input == null) return new Date(Number.NaN);
14
+ if (input instanceof Date) return new Date(input.getTime());
15
+ if (typeof input === 'number') {
16
+ const millis = input < 1e12 ? input * 1000 : input;
17
+ return new Date(millis);
18
+ }
19
+
20
+ const raw = String(input).trim();
21
+ if (!raw) return new Date(Number.NaN);
22
+
23
+ // Numeric strings (epoch seconds or millis)
24
+ if (/^\d{10}$/.test(raw)) return new Date(Number(raw) * 1000);
25
+ if (/^\d{13}$/.test(raw)) return new Date(Number(raw));
26
+
27
+ // Already has timezone; trust the browser parser
28
+ if (hasTimezoneDesignator(raw)) return new Date(raw);
29
+
30
+ // Normalize common forms: "YYYY-MM-DD HH:MM:SS[.fff...]" -> ISO-like
31
+ let norm = raw.replace(' ', 'T');
32
+ norm = truncateExcessFraction(norm);
33
+
34
+ // Date-only string -> treat as UTC midnight
35
+ if (/^\d{4}-\d{2}-\d{2}$/.test(norm)) {
36
+ return new Date(`${norm}T00:00:00Z`);
37
+ }
38
+
39
+ // If ISO-like without timezone, explicitly mark as UTC
40
+ if (/^\d{4}-\d{2}-\d{2}T/.test(norm) && !hasTimezoneDesignator(norm)) {
41
+ return new Date(`${norm}Z`);
42
+ }
43
+
44
+ // Fallback to native parsing
45
+ return new Date(norm);
46
+ };
47
+
48
+ export const formatLocalDate = (value: DateLike, options?: Intl.DateTimeFormatOptions): string => {
49
+ const d = value instanceof Date ? value : parseUtcDate(value);
50
+ if (Number.isNaN(d.getTime())) return 'N/A';
51
+ return d.toLocaleDateString(undefined, options);
52
+ };
53
+
54
+ export const formatLocalDateTime = (value: DateLike, options?: Intl.DateTimeFormatOptions): string => {
55
+ const d = value instanceof Date ? value : parseUtcDate(value);
56
+ if (Number.isNaN(d.getTime())) return 'N/A';
57
+ const base: Intl.DateTimeFormatOptions = {
58
+ year: 'numeric',
59
+ month: 'long',
60
+ day: 'numeric',
61
+ hour: 'numeric',
62
+ minute: '2-digit',
63
+ timeZoneName: 'short',
64
+ };
65
+ return d.toLocaleString(undefined, { ...base, ...(options || {}) });
66
+ };
67
+
68
+