@umituz/react-native-subscription 2.27.115 → 2.27.116
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 +1 -1
- package/src/domains/credits/infrastructure/CreditsRepository.ts +16 -19
- package/src/domains/credits/utils/creditCalculations.ts +6 -11
- package/src/domains/wallet/infrastructure/repositories/TransactionRepository.ts +17 -41
- package/src/domains/wallet/infrastructure/services/ProductMetadataService.ts +2 -6
- package/src/shared/infrastructure/firestore/collectionUtils.ts +67 -0
- package/src/shared/infrastructure/firestore/index.ts +6 -0
- package/src/shared/infrastructure/firestore/resultUtils.ts +68 -0
- package/src/shared/infrastructure/index.ts +6 -0
- package/src/shared/presentation/hooks/index.ts +6 -0
- package/src/shared/presentation/hooks/useAsyncState.ts +72 -0
- package/src/shared/presentation/hooks/useServiceCall.ts +66 -0
- package/src/shared/types/CommonTypes.ts +6 -1
- package/src/shared/types/ReactTypes.ts +80 -0
- package/src/shared/utils/arrayUtils.core.ts +81 -0
- package/src/shared/utils/arrayUtils.query.ts +118 -0
- package/src/shared/utils/arrayUtils.transforms.ts +116 -0
- package/src/shared/utils/arrayUtils.ts +19 -0
- package/src/shared/utils/index.ts +14 -0
- package/src/shared/utils/numberUtils.aggregate.ts +35 -0
- package/src/shared/utils/numberUtils.core.ts +73 -0
- package/src/shared/utils/numberUtils.format.ts +42 -0
- package/src/shared/utils/numberUtils.math.ts +48 -0
- package/src/shared/utils/numberUtils.ts +9 -0
- package/src/shared/utils/stringUtils.case.ts +64 -0
- package/src/shared/utils/stringUtils.check.ts +65 -0
- package/src/shared/utils/stringUtils.format.ts +84 -0
- package/src/shared/utils/stringUtils.generate.ts +47 -0
- package/src/shared/utils/stringUtils.modify.ts +67 -0
- package/src/shared/utils/stringUtils.ts +10 -0
- package/src/shared/utils/validators.ts +187 -0
- package/src/utils/dateUtils.compare.ts +65 -0
- package/src/utils/dateUtils.core.ts +67 -0
- package/src/utils/dateUtils.format.ts +138 -0
- package/src/utils/dateUtils.math.ts +112 -0
- package/src/utils/dateUtils.ts +6 -28
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String Utilities - Formatting
|
|
3
|
+
* String formatting and display functions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Truncate string to specified length and add ellipsis
|
|
8
|
+
*/
|
|
9
|
+
export function truncate(str: string, maxLength: number, suffix: string = "..."): string {
|
|
10
|
+
if (!str || str.length <= maxLength) return str;
|
|
11
|
+
return str.slice(0, maxLength - suffix.length) + suffix;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Format bytes as human readable string
|
|
16
|
+
*/
|
|
17
|
+
export function formatBytes(bytes: number, decimals: number = 2): string {
|
|
18
|
+
if (bytes === 0) return "0 Bytes";
|
|
19
|
+
|
|
20
|
+
const k = 1024;
|
|
21
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
22
|
+
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
|
|
23
|
+
|
|
24
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
25
|
+
|
|
26
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Format a list of items as a comma-separated string with "and" for the last item
|
|
31
|
+
*/
|
|
32
|
+
export function formatList(items: string[], conjunction: string = "and"): string {
|
|
33
|
+
if (items.length === 0) return "";
|
|
34
|
+
if (items.length === 1) return items[0] ?? "";
|
|
35
|
+
if (items.length === 2) return items.join(` ${conjunction} `);
|
|
36
|
+
|
|
37
|
+
const lastItem = items[items.length - 1];
|
|
38
|
+
return `${items.slice(0, -1).join(", ")} ${conjunction} ${lastItem ?? ""}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Pluralize a word based on count
|
|
43
|
+
*/
|
|
44
|
+
export function pluralize(count: number, singular: string, plural?: string): string {
|
|
45
|
+
if (count === 1) return singular;
|
|
46
|
+
return plural || singular + "s";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Format number as ordinal (1st, 2nd, 3rd, etc.)
|
|
51
|
+
*/
|
|
52
|
+
export function formatOrdinal(num: number): string {
|
|
53
|
+
const suffixes = ["th", "st", "nd", "rd"];
|
|
54
|
+
const value = num % 100;
|
|
55
|
+
const suffix = suffixes[(value - 20) % 10] || suffixes[value] || suffixes[0];
|
|
56
|
+
return `${num}${suffix}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Mask sensitive data (e.g., credit card, email)
|
|
61
|
+
*/
|
|
62
|
+
export function maskString(str: string, visibleChars: number = 4, maskChar: string = "*"): string {
|
|
63
|
+
if (!str) return "";
|
|
64
|
+
if (str.length <= visibleChars * 2) return maskChar.repeat(str.length);
|
|
65
|
+
|
|
66
|
+
const start = str.substring(0, visibleChars);
|
|
67
|
+
const end = str.substring(str.length - visibleChars);
|
|
68
|
+
const middleLength = str.length - visibleChars * 2;
|
|
69
|
+
|
|
70
|
+
return `${start}${maskChar.repeat(middleLength)}${end}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Extract initials from a name
|
|
75
|
+
*/
|
|
76
|
+
export function getInitials(name: string, maxInitials: number = 2): string {
|
|
77
|
+
if (!name) return "";
|
|
78
|
+
return name
|
|
79
|
+
.split(" ")
|
|
80
|
+
.filter((word) => word.length > 0)
|
|
81
|
+
.map((word) => word.charAt(0).toUpperCase())
|
|
82
|
+
.slice(0, maxInitials)
|
|
83
|
+
.join("");
|
|
84
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String Utilities - Generate Functions
|
|
3
|
+
* String generation and encoding functions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate a random string of specified length
|
|
8
|
+
*/
|
|
9
|
+
export function randomString(
|
|
10
|
+
length: number,
|
|
11
|
+
charset: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
|
12
|
+
): string {
|
|
13
|
+
let result = "";
|
|
14
|
+
for (let i = 0; i < length; i++) {
|
|
15
|
+
result += charset.charAt(Math.floor(Math.random() * charset.length));
|
|
16
|
+
}
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Generate a random ID
|
|
22
|
+
*/
|
|
23
|
+
export function randomId(prefix: string = ""): string {
|
|
24
|
+
const timestamp = Date.now().toString(36);
|
|
25
|
+
const random = Math.random().toString(36).substring(2, 9);
|
|
26
|
+
return prefix ? `${prefix}_${timestamp}${random}` : `${timestamp}${random}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Convert a string to base64
|
|
31
|
+
*/
|
|
32
|
+
export function toBase64(str: string): string {
|
|
33
|
+
if (typeof btoa !== "undefined") {
|
|
34
|
+
return btoa(str);
|
|
35
|
+
}
|
|
36
|
+
return Buffer.from(str).toString("base64");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Decode a base64 string
|
|
41
|
+
*/
|
|
42
|
+
export function fromBase64(base64: string): string {
|
|
43
|
+
if (typeof atob !== "undefined") {
|
|
44
|
+
return atob(base64);
|
|
45
|
+
}
|
|
46
|
+
return Buffer.from(base64, "base64").toString();
|
|
47
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String Utilities - Modify Operations
|
|
3
|
+
* String manipulation and transformation functions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Remove all whitespace from string
|
|
8
|
+
*/
|
|
9
|
+
export function removeWhitespace(str: string): string {
|
|
10
|
+
return str.replace(/\s+/g, "");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Normalize whitespace (replace multiple spaces with single space)
|
|
15
|
+
*/
|
|
16
|
+
export function normalizeWhitespace(str: string): string {
|
|
17
|
+
return str.trim().replace(/\s+/g, " ");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Count words in a string
|
|
22
|
+
*/
|
|
23
|
+
export function countWords(str: string): number {
|
|
24
|
+
if (!str.trim()) return 0;
|
|
25
|
+
return str.trim().split(/\s+/).length;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Count characters in a string (excluding whitespace)
|
|
30
|
+
*/
|
|
31
|
+
export function countChars(str: string, excludeWhitespace: boolean = true): number {
|
|
32
|
+
return excludeWhitespace ? removeWhitespace(str).length : str.length;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Replace all occurrences of a substring
|
|
37
|
+
*/
|
|
38
|
+
export function replaceAll(str: string, search: string, replacement: string): string {
|
|
39
|
+
return str.split(search).join(replacement);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Escape special regex characters in a string
|
|
44
|
+
*/
|
|
45
|
+
export function escapeRegex(str: string): string {
|
|
46
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Strip HTML tags from a string
|
|
51
|
+
*/
|
|
52
|
+
export function stripHtml(html: string): string {
|
|
53
|
+
return html.replace(/<[^>]*>/g, "");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Sanitize string input (trim and remove extra whitespace)
|
|
58
|
+
*/
|
|
59
|
+
export function sanitizeStringValue(value: unknown): string | null {
|
|
60
|
+
if (value === null || value === undefined) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
if (typeof value === "string") {
|
|
64
|
+
return value.trim().replace(/\s+/g, " ");
|
|
65
|
+
}
|
|
66
|
+
return String(value).trim().replace(/\s+/g, " ");
|
|
67
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String Utilities
|
|
3
|
+
* Re-exports all string utility modules
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export * from "./stringUtils.case";
|
|
7
|
+
export * from "./stringUtils.check";
|
|
8
|
+
export * from "./stringUtils.format";
|
|
9
|
+
export * from "./stringUtils.generate";
|
|
10
|
+
export * from "./stringUtils.modify";
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Validation Utilities
|
|
3
|
+
* Common validation functions and type guards
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Check if value is a non-empty string
|
|
8
|
+
*/
|
|
9
|
+
export function isNonEmptyString(value: unknown): value is string {
|
|
10
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Check if value is a valid number (not NaN or Infinity)
|
|
15
|
+
*/
|
|
16
|
+
export function isValidNumber(value: unknown): value is number {
|
|
17
|
+
return typeof value === "number" && !isNaN(value) && isFinite(value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check if value is a positive number
|
|
22
|
+
*/
|
|
23
|
+
export function isPositiveNumber(value: unknown): value is number {
|
|
24
|
+
return isValidNumber(value) && value > 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if value is a non-negative number
|
|
29
|
+
*/
|
|
30
|
+
export function isNonNegativeNumber(value: unknown): value is number {
|
|
31
|
+
return isValidNumber(value) && value >= 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if value is a valid integer
|
|
36
|
+
*/
|
|
37
|
+
export function isInteger(value: unknown): value is number {
|
|
38
|
+
return isValidNumber(value) && Number.isInteger(value);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check if value is a valid date
|
|
43
|
+
*/
|
|
44
|
+
export function isValidDate(value: unknown): boolean {
|
|
45
|
+
if (value instanceof Date) {
|
|
46
|
+
return !isNaN(value.getTime());
|
|
47
|
+
}
|
|
48
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
49
|
+
const d = new Date(value);
|
|
50
|
+
return !isNaN(d.getTime());
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if value is a valid email (basic validation)
|
|
57
|
+
*/
|
|
58
|
+
export function isValidEmail(value: unknown): value is string {
|
|
59
|
+
if (!isNonEmptyString(value)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
63
|
+
return emailRegex.test(value);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check if value is a valid URL (basic validation)
|
|
68
|
+
*/
|
|
69
|
+
export function isValidUrl(value: unknown): value is string {
|
|
70
|
+
if (!isNonEmptyString(value)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
new URL(value);
|
|
75
|
+
return true;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if value is within a numeric range (type guard version)
|
|
83
|
+
*/
|
|
84
|
+
export function isValueInRange(
|
|
85
|
+
value: unknown,
|
|
86
|
+
min: number,
|
|
87
|
+
max: number,
|
|
88
|
+
inclusive: boolean = true
|
|
89
|
+
): value is number {
|
|
90
|
+
if (!isValidNumber(value)) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
if (inclusive) {
|
|
94
|
+
return value >= min && value <= max;
|
|
95
|
+
}
|
|
96
|
+
return value > min && value < max;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Check if array has at least one element
|
|
101
|
+
*/
|
|
102
|
+
export function isNonEmptyArray<T>(value: unknown): value is [T, ...T[]] {
|
|
103
|
+
return Array.isArray(value) && value.length > 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check if value is a plain object (not null, not array)
|
|
108
|
+
*/
|
|
109
|
+
export function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
110
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check if value is defined (not null or undefined)
|
|
115
|
+
*/
|
|
116
|
+
export function isDefined<T>(value: T | null | undefined): value is T {
|
|
117
|
+
return value !== null && value !== undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Validate that a number is within credit limits
|
|
122
|
+
*/
|
|
123
|
+
export function isValidCreditAmount(value: unknown): value is number {
|
|
124
|
+
return isInteger(value) && isNonNegativeNumber(value);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Validate that a price is valid (positive number with max 2 decimal places)
|
|
129
|
+
*/
|
|
130
|
+
export function isValidPrice(value: unknown): value is number {
|
|
131
|
+
if (!isPositiveNumber(value)) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
// Check for max 2 decimal places
|
|
135
|
+
const decimalPlaces = (value.toString().split(".")[1] || "").length;
|
|
136
|
+
return decimalPlaces <= 2;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Check if a string is a valid product identifier
|
|
141
|
+
*/
|
|
142
|
+
export function isValidProductId(value: unknown): value is string {
|
|
143
|
+
return isNonEmptyString(value) && /^[a-zA-Z0-9._-]+$/.test(value);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check if value is a valid user ID
|
|
148
|
+
*/
|
|
149
|
+
export function isValidUserId(value: unknown): value is string {
|
|
150
|
+
return isNonEmptyString(value) && value.length > 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Sanitize string input (trim and remove extra whitespace)
|
|
155
|
+
*/
|
|
156
|
+
export function sanitizeString(value: unknown): string | null {
|
|
157
|
+
if (value === null || value === undefined) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
if (typeof value === "string") {
|
|
161
|
+
return value.trim().replace(/\s+/g, " ");
|
|
162
|
+
}
|
|
163
|
+
return String(value).trim().replace(/\s+/g, " ");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Validate and sanitize a number input
|
|
168
|
+
*/
|
|
169
|
+
export function sanitizeNumber(
|
|
170
|
+
value: unknown,
|
|
171
|
+
defaultValue: number = 0
|
|
172
|
+
): number {
|
|
173
|
+
if (isValidNumber(value)) {
|
|
174
|
+
return value;
|
|
175
|
+
}
|
|
176
|
+
return defaultValue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Check if a value is within an allowed set of values
|
|
181
|
+
*/
|
|
182
|
+
export function isOneOf<T>(
|
|
183
|
+
value: unknown,
|
|
184
|
+
allowedValues: readonly T[]
|
|
185
|
+
): value is T {
|
|
186
|
+
return allowedValues.includes(value as T);
|
|
187
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Date Utilities - Comparison Operations
|
|
3
|
+
* Date comparison and calculation functions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { isValidDate, isPast, currentDate } from "./dateUtils.core";
|
|
7
|
+
import { addDays } from "./dateUtils.math";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Calculate difference in days between two dates
|
|
11
|
+
*/
|
|
12
|
+
export function daysBetween(date1: Date | string | number, date2: Date | string | number): number {
|
|
13
|
+
const d1 = new Date(date1);
|
|
14
|
+
const d2 = new Date(date2);
|
|
15
|
+
const diffTime = d2.getTime() - d1.getTime();
|
|
16
|
+
return Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Calculate days remaining until a date
|
|
21
|
+
* Returns null if date is in the past or invalid
|
|
22
|
+
*/
|
|
23
|
+
export function daysUntil(date: Date | string | number): number | null {
|
|
24
|
+
const target = new Date(date);
|
|
25
|
+
if (!isValidDate(target) || isPast(target)) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return daysBetween(currentDate(), target);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if two dates are the same day
|
|
33
|
+
*/
|
|
34
|
+
export function isSameDay(date1: Date | string | number, date2: Date | string | number): boolean {
|
|
35
|
+
const d1 = new Date(date1);
|
|
36
|
+
const d2 = new Date(date2);
|
|
37
|
+
return (
|
|
38
|
+
d1.getFullYear() === d2.getFullYear() &&
|
|
39
|
+
d1.getMonth() === d2.getMonth() &&
|
|
40
|
+
d1.getDate() === d2.getDate()
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if a date is today
|
|
46
|
+
*/
|
|
47
|
+
export function isToday(date: Date | string | number): boolean {
|
|
48
|
+
return isSameDay(date, currentDate());
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if a date is yesterday
|
|
53
|
+
*/
|
|
54
|
+
export function isYesterday(date: Date | string | number): boolean {
|
|
55
|
+
const yesterday = addDays(currentDate(), -1);
|
|
56
|
+
return isSameDay(date, yesterday);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check if a date is tomorrow
|
|
61
|
+
*/
|
|
62
|
+
export function isTomorrow(date: Date | string | number): boolean {
|
|
63
|
+
const tomorrow = addDays(currentDate(), 1);
|
|
64
|
+
return isSameDay(date, tomorrow);
|
|
65
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Date Utilities - Core Operations
|
|
3
|
+
* Basic date manipulation and validation functions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type DateLike = Date | string | number;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Checks if a date is in the past
|
|
10
|
+
*/
|
|
11
|
+
export function isPast(date: DateLike): boolean {
|
|
12
|
+
const d = new Date(date);
|
|
13
|
+
return d.getTime() < Date.now();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Checks if a date is in the future
|
|
18
|
+
*/
|
|
19
|
+
export function isFuture(date: DateLike): boolean {
|
|
20
|
+
const d = new Date(date);
|
|
21
|
+
return d.getTime() > Date.now();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Checks if a date is valid
|
|
26
|
+
*/
|
|
27
|
+
export function isValidDate(date: DateLike): boolean {
|
|
28
|
+
const d = date instanceof Date ? date : new Date(date);
|
|
29
|
+
return !isNaN(d.getTime());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Converts various timestamp formats to a safe Date object
|
|
34
|
+
*/
|
|
35
|
+
export function toSafeDate(ts: unknown): Date | null {
|
|
36
|
+
if (!ts) return null;
|
|
37
|
+
if (typeof ts === "object" && ts !== null && "toDate" in ts && typeof ts.toDate === "function") {
|
|
38
|
+
return (ts as { toDate: () => Date }).toDate();
|
|
39
|
+
}
|
|
40
|
+
if (ts instanceof Date) return ts;
|
|
41
|
+
if (typeof ts === "string" || typeof ts === "number") {
|
|
42
|
+
const d = new Date(ts);
|
|
43
|
+
return isNaN(d.getTime()) ? null : d;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Formats a date to ISO string safely
|
|
50
|
+
*/
|
|
51
|
+
export function formatISO(date: Date | null): string | null {
|
|
52
|
+
return date ? date.toISOString() : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get current timestamp in milliseconds
|
|
57
|
+
*/
|
|
58
|
+
export function now(): number {
|
|
59
|
+
return Date.now();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get current date
|
|
64
|
+
*/
|
|
65
|
+
export function currentDate(): Date {
|
|
66
|
+
return new Date();
|
|
67
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Date Utilities - Formatting
|
|
3
|
+
* Date display and formatting functions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { DateLike } from "./dateUtils.core";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Format date to locale string
|
|
10
|
+
*/
|
|
11
|
+
export function formatLocale(
|
|
12
|
+
date: DateLike,
|
|
13
|
+
options?: Intl.DateTimeFormatOptions,
|
|
14
|
+
locale: string = "en-US"
|
|
15
|
+
): string {
|
|
16
|
+
const d = new Date(date);
|
|
17
|
+
if (isNaN(d.getTime())) {
|
|
18
|
+
return "";
|
|
19
|
+
}
|
|
20
|
+
return d.toLocaleDateString(locale, options);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Format date to relative time (e.g., "2 days ago", "in 3 hours")
|
|
25
|
+
*/
|
|
26
|
+
export function formatRelative(date: DateLike, now: Date = new Date()): string {
|
|
27
|
+
const target = new Date(date);
|
|
28
|
+
if (isNaN(target.getTime())) {
|
|
29
|
+
return "";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const diffMs = target.getTime() - now.getTime();
|
|
33
|
+
const diffSecs = Math.floor(diffMs / 1000);
|
|
34
|
+
const diffMins = Math.floor(diffSecs / 60);
|
|
35
|
+
const diffHours = Math.floor(diffMins / 60);
|
|
36
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
37
|
+
|
|
38
|
+
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
|
39
|
+
|
|
40
|
+
if (Math.abs(diffDays) > 0) {
|
|
41
|
+
return rtf.format(diffDays, "day");
|
|
42
|
+
}
|
|
43
|
+
if (Math.abs(diffHours) > 0) {
|
|
44
|
+
return rtf.format(diffHours, "hour");
|
|
45
|
+
}
|
|
46
|
+
if (Math.abs(diffMins) > 0) {
|
|
47
|
+
return rtf.format(diffMins, "minute");
|
|
48
|
+
}
|
|
49
|
+
return rtf.format(diffSecs, "second");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Format date to short date string (e.g., "1/1/2024")
|
|
54
|
+
*/
|
|
55
|
+
export function formatShort(date: DateLike, locale: string = "en-US"): string {
|
|
56
|
+
return formatLocale(date, { month: "numeric", day: "numeric", year: "numeric" }, locale);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Format date to medium date string (e.g., "Jan 1, 2024")
|
|
61
|
+
*/
|
|
62
|
+
export function formatMedium(date: DateLike, locale: string = "en-US"): string {
|
|
63
|
+
return formatLocale(date, { month: "short", day: "numeric", year: "numeric" }, locale);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Format date to long date string (e.g., "January 1, 2024")
|
|
68
|
+
*/
|
|
69
|
+
export function formatLong(date: DateLike, locale: string = "en-US"): string {
|
|
70
|
+
return formatLocale(date, { month: "long", day: "numeric", year: "numeric" }, locale);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Format date to time string
|
|
75
|
+
*/
|
|
76
|
+
export function formatTime(date: DateLike, locale: string = "en-US"): string {
|
|
77
|
+
return formatLocale(date, { hour: "numeric", minute: "numeric" }, locale);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Format date to date and time string
|
|
82
|
+
*/
|
|
83
|
+
export function formatDateTime(date: DateLike, locale: string = "en-US"): string {
|
|
84
|
+
return formatLocale(
|
|
85
|
+
date,
|
|
86
|
+
{ month: "short", day: "numeric", year: "numeric", hour: "numeric", minute: "numeric" },
|
|
87
|
+
locale
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Convert milliseconds to human-readable duration
|
|
93
|
+
*/
|
|
94
|
+
export function formatDuration(ms: number): string {
|
|
95
|
+
const seconds = Math.floor(ms / 1000);
|
|
96
|
+
const minutes = Math.floor(seconds / 60);
|
|
97
|
+
const hours = Math.floor(minutes / 60);
|
|
98
|
+
const days = Math.floor(hours / 24);
|
|
99
|
+
|
|
100
|
+
if (days > 0) {
|
|
101
|
+
return `${days}d ${hours % 24}h`;
|
|
102
|
+
}
|
|
103
|
+
if (hours > 0) {
|
|
104
|
+
return `${hours}h ${minutes % 60}m`;
|
|
105
|
+
}
|
|
106
|
+
if (minutes > 0) {
|
|
107
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
108
|
+
}
|
|
109
|
+
return `${seconds}s`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Format date to weekday name (e.g., "Monday")
|
|
114
|
+
*/
|
|
115
|
+
export function formatWeekday(date: DateLike, locale: string = "en-US"): string {
|
|
116
|
+
return formatLocale(date, { weekday: "long" }, locale);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Format date to short weekday name (e.g., "Mon")
|
|
121
|
+
*/
|
|
122
|
+
export function formatWeekdayShort(date: DateLike, locale: string = "en-US"): string {
|
|
123
|
+
return formatLocale(date, { weekday: "short" }, locale);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Format date to month name (e.g., "January")
|
|
128
|
+
*/
|
|
129
|
+
export function formatMonth(date: DateLike, locale: string = "en-US"): string {
|
|
130
|
+
return formatLocale(date, { month: "long" }, locale);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Format date to short month name (e.g., "Jan")
|
|
135
|
+
*/
|
|
136
|
+
export function formatMonthShort(date: DateLike, locale: string = "en-US"): string {
|
|
137
|
+
return formatLocale(date, { month: "short" }, locale);
|
|
138
|
+
}
|