goodchuck-utils 1.1.0 → 1.3.0
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/dist/date/index.d.ts +64 -0
- package/dist/date/index.d.ts.map +1 -0
- package/dist/date/index.js +92 -0
- package/dist/date/index.test.d.ts +2 -0
- package/dist/date/index.test.d.ts.map +1 -0
- package/dist/date/index.test.js +166 -0
- package/dist/form/__tests__/formatter.test.d.ts +2 -0
- package/dist/form/__tests__/formatter.test.d.ts.map +1 -0
- package/dist/form/__tests__/formatter.test.js +74 -0
- package/dist/form/__tests__/helpers.test.d.ts +2 -0
- package/dist/form/__tests__/helpers.test.d.ts.map +1 -0
- package/dist/form/__tests__/helpers.test.js +42 -0
- package/dist/form/__tests__/validation.test.d.ts +2 -0
- package/dist/form/__tests__/validation.test.d.ts.map +1 -0
- package/dist/form/__tests__/validation.test.js +67 -0
- package/dist/form/formatter.d.ts +34 -0
- package/dist/form/formatter.d.ts.map +1 -0
- package/dist/form/formatter.js +76 -0
- package/dist/form/helpers.d.ts +16 -0
- package/dist/form/helpers.d.ts.map +1 -0
- package/dist/form/helpers.js +34 -0
- package/dist/form/index.d.ts +9 -0
- package/dist/form/index.d.ts.map +1 -0
- package/dist/form/index.js +11 -0
- package/dist/form/validation.d.ts +33 -0
- package/dist/form/validation.d.ts.map +1 -0
- package/dist/form/validation.js +56 -0
- package/dist/hooks/index.d.ts +11 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +12 -0
- package/dist/hooks/useClickOutside.d.ts +49 -0
- package/dist/hooks/useClickOutside.d.ts.map +1 -0
- package/dist/hooks/useClickOutside.js +94 -0
- package/dist/hooks/useLocalStorage.d.ts +19 -0
- package/dist/hooks/useLocalStorage.d.ts.map +1 -0
- package/dist/hooks/useLocalStorage.js +91 -0
- package/dist/hooks/useMediaQuery.d.ts +56 -0
- package/dist/hooks/useMediaQuery.d.ts.map +1 -0
- package/dist/hooks/useMediaQuery.js +104 -0
- package/dist/hooks/useSessionStorage.d.ts +19 -0
- package/dist/hooks/useSessionStorage.d.ts.map +1 -0
- package/dist/hooks/useSessionStorage.js +85 -0
- package/dist/index.d.ts +4 -5
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -18
- package/dist/string/__tests__/case.test.d.ts +2 -0
- package/dist/string/__tests__/case.test.d.ts.map +1 -0
- package/dist/string/__tests__/case.test.js +61 -0
- package/dist/string/__tests__/manipulation.test.d.ts +2 -0
- package/dist/string/__tests__/manipulation.test.d.ts.map +1 -0
- package/dist/string/__tests__/manipulation.test.js +109 -0
- package/dist/string/__tests__/validation.test.d.ts +2 -0
- package/dist/string/__tests__/validation.test.d.ts.map +1 -0
- package/dist/string/__tests__/validation.test.js +101 -0
- package/dist/string/case.d.ts +42 -0
- package/dist/string/case.d.ts.map +1 -0
- package/dist/string/case.js +71 -0
- package/dist/string/index.d.ts +9 -0
- package/dist/string/index.d.ts.map +1 -0
- package/dist/string/index.js +11 -0
- package/dist/string/manipulation.d.ts +61 -0
- package/dist/string/manipulation.d.ts.map +1 -0
- package/dist/string/manipulation.js +106 -0
- package/dist/string/validation.d.ts +79 -0
- package/dist/string/validation.d.ts.map +1 -0
- package/dist/string/validation.js +115 -0
- package/package.json +27 -3
- package/src/date/index.test.ts +206 -0
- package/src/date/index.ts +123 -0
- package/src/form/__tests__/formatter.test.ts +97 -0
- package/src/form/__tests__/helpers.test.ts +53 -0
- package/src/form/__tests__/validation.test.ts +84 -0
- package/src/form/formatter.ts +85 -0
- package/src/form/helpers.ts +44 -0
- package/src/form/index.ts +14 -0
- package/src/form/validation.ts +72 -0
- package/src/hooks/index.ts +14 -0
- package/src/hooks/useClickOutside.ts +114 -0
- package/src/hooks/useLocalStorage.ts +112 -0
- package/src/hooks/useMediaQuery.ts +116 -0
- package/src/hooks/useSessionStorage.ts +106 -0
- package/src/index.ts +14 -13
- package/src/string/__tests__/case.test.ts +78 -0
- package/src/string/__tests__/manipulation.test.ts +142 -0
- package/src/string/__tests__/validation.test.ts +128 -0
- package/src/string/case.ts +76 -0
- package/src/string/index.ts +14 -0
- package/src/string/manipulation.ts +124 -0
- package/src/string/validation.ts +126 -0
- package/tsconfig.json +15 -11
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String Manipulation Utilities
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* 문자열을 지정된 길이로 자르고 말줄임표 추가
|
|
6
|
+
* @example truncate('Hello World', 5) // 'Hello...'
|
|
7
|
+
*/
|
|
8
|
+
export function truncate(str, length, suffix = '...') {
|
|
9
|
+
if (str.length <= length)
|
|
10
|
+
return str;
|
|
11
|
+
return str.slice(0, length) + suffix;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 문자열 양쪽 공백 제거 및 연속된 공백을 하나로
|
|
15
|
+
* @example normalizeWhitespace(' hello world ') // 'hello world'
|
|
16
|
+
*/
|
|
17
|
+
export function normalizeWhitespace(str) {
|
|
18
|
+
return str.trim().replace(/\s+/g, ' ');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* URL 친화적인 슬러그 생성
|
|
22
|
+
* @example slugify('Hello World!') // 'hello-world'
|
|
23
|
+
* @example slugify('Test123 World') // 'test123-world'
|
|
24
|
+
*/
|
|
25
|
+
export function slugify(str) {
|
|
26
|
+
return str
|
|
27
|
+
.toLowerCase()
|
|
28
|
+
.trim()
|
|
29
|
+
.replace(/[^\w\s-]/g, '')
|
|
30
|
+
.replace(/[\s_]+/g, '-')
|
|
31
|
+
.replace(/-+/g, '-')
|
|
32
|
+
.replace(/^-+|-+$/g, '');
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 랜덤 문자열 생성
|
|
36
|
+
* @param length - 생성할 문자열 길이
|
|
37
|
+
* @param charset - 사용할 문자 집합 ('alphanumeric', 'alpha', 'numeric', 'hex')
|
|
38
|
+
*/
|
|
39
|
+
export function randomString(length, charset = 'alphanumeric') {
|
|
40
|
+
const charsets = {
|
|
41
|
+
alphanumeric: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
|
|
42
|
+
alpha: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
|
|
43
|
+
numeric: '0123456789',
|
|
44
|
+
hex: '0123456789abcdef',
|
|
45
|
+
};
|
|
46
|
+
const chars = charsets[charset];
|
|
47
|
+
let result = '';
|
|
48
|
+
for (let i = 0; i < length; i++) {
|
|
49
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 문자열 반복
|
|
55
|
+
* @example repeat('ab', 3) // 'ababab'
|
|
56
|
+
*/
|
|
57
|
+
export function repeat(str, count) {
|
|
58
|
+
return str.repeat(count);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 문자열 뒤집기
|
|
62
|
+
* @example reverse('hello') // 'olleh'
|
|
63
|
+
*/
|
|
64
|
+
export function reverse(str) {
|
|
65
|
+
return str.split('').reverse().join('');
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 문자열에서 HTML 태그 제거
|
|
69
|
+
* @example stripHtml('<p>Hello <strong>World</strong></p>') // 'Hello World'
|
|
70
|
+
*/
|
|
71
|
+
export function stripHtml(str) {
|
|
72
|
+
return str.replace(/<[^>]*>/g, '');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 문자열을 특정 길이로 패딩
|
|
76
|
+
* @example padStart('5', 3, '0') // '005'
|
|
77
|
+
*/
|
|
78
|
+
export function padStart(str, length, fillString = ' ') {
|
|
79
|
+
return str.padStart(length, fillString);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 문자열을 특정 길이로 패딩 (오른쪽)
|
|
83
|
+
* @example padEnd('5', 3, '0') // '500'
|
|
84
|
+
*/
|
|
85
|
+
export function padEnd(str, length, fillString = ' ') {
|
|
86
|
+
return str.padEnd(length, fillString);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 템플릿 문자열 치환
|
|
90
|
+
* @example template('Hello {name}!', { name: 'World' }) // 'Hello World!'
|
|
91
|
+
*/
|
|
92
|
+
export function template(str, values) {
|
|
93
|
+
return str.replace(/\{(\w+)\}/g, (match, key) => {
|
|
94
|
+
return values[key] !== undefined ? String(values[key]) : match;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 마스킹 (특정 위치의 문자를 *로 변경)
|
|
99
|
+
* @example mask('1234567890', 3, 7) // '123****890'
|
|
100
|
+
*/
|
|
101
|
+
export function mask(str, start, end, maskChar = '*') {
|
|
102
|
+
const endPos = end ?? str.length;
|
|
103
|
+
return (str.slice(0, start) +
|
|
104
|
+
maskChar.repeat(endPos - start) +
|
|
105
|
+
str.slice(endPos));
|
|
106
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String Validation Utilities
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* 빈 문자열 또는 공백만 있는지 확인
|
|
6
|
+
* @example isEmpty('') // true
|
|
7
|
+
* @example isEmpty(' ') // true
|
|
8
|
+
* @example isEmpty('hello') // false
|
|
9
|
+
*/
|
|
10
|
+
export declare function isEmpty(str: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* 문자열이 특정 문자로 시작하는지 확인
|
|
13
|
+
* @example startsWith('hello', 'he') // true
|
|
14
|
+
*/
|
|
15
|
+
export declare function startsWith(str: string, searchString: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* 문자열이 특정 문자로 끝나는지 확인
|
|
18
|
+
* @example endsWith('hello', 'lo') // true
|
|
19
|
+
*/
|
|
20
|
+
export declare function endsWith(str: string, searchString: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* 문자열에 특정 문자열이 포함되어 있는지 확인
|
|
23
|
+
* @example contains('hello world', 'world') // true
|
|
24
|
+
*/
|
|
25
|
+
export declare function contains(str: string, searchString: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* 알파벳만 포함하는지 확인
|
|
28
|
+
* @example isAlpha('hello') // true
|
|
29
|
+
* @example isAlpha('hello123') // false
|
|
30
|
+
*/
|
|
31
|
+
export declare function isAlpha(str: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* 숫자만 포함하는지 확인
|
|
34
|
+
* @example isNumeric('123') // true
|
|
35
|
+
* @example isNumeric('123.45') // false
|
|
36
|
+
*/
|
|
37
|
+
export declare function isNumeric(str: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* 알파벳과 숫자만 포함하는지 확인
|
|
40
|
+
* @example isAlphanumeric('hello123') // true
|
|
41
|
+
* @example isAlphanumeric('hello_123') // false
|
|
42
|
+
*/
|
|
43
|
+
export declare function isAlphanumeric(str: string): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* 소문자만 포함하는지 확인
|
|
46
|
+
* @example isLowerCase('hello') // true
|
|
47
|
+
* @example isLowerCase('Hello') // false
|
|
48
|
+
*/
|
|
49
|
+
export declare function isLowerCase(str: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* 대문자만 포함하는지 확인
|
|
52
|
+
* @example isUpperCase('HELLO') // true
|
|
53
|
+
* @example isUpperCase('Hello') // false
|
|
54
|
+
*/
|
|
55
|
+
export declare function isUpperCase(str: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* JSON 형식인지 확인
|
|
58
|
+
* @example isJSON('{"name":"John"}') // true
|
|
59
|
+
* @example isJSON('not json') // false
|
|
60
|
+
*/
|
|
61
|
+
export declare function isJSON(str: string): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* 16진수 색상 코드인지 확인
|
|
64
|
+
* @example isHexColor('#fff') // true
|
|
65
|
+
* @example isHexColor('#ffffff') // true
|
|
66
|
+
* @example isHexColor('fff') // false
|
|
67
|
+
*/
|
|
68
|
+
export declare function isHexColor(str: string): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* UUID 형식인지 확인
|
|
71
|
+
* @example isUUID('123e4567-e89b-12d3-a456-426614174000') // true
|
|
72
|
+
*/
|
|
73
|
+
export declare function isUUID(str: string): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Base64 인코딩된 문자열인지 확인
|
|
76
|
+
* @example isBase64('SGVsbG8gV29ybGQ=') // true
|
|
77
|
+
*/
|
|
78
|
+
export declare function isBase64(str: string): boolean;
|
|
79
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/string/validation.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE5C;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAErE;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAEnE;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAEnE;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE5C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAO3C;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE3C;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAM7C"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String Validation Utilities
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* 빈 문자열 또는 공백만 있는지 확인
|
|
6
|
+
* @example isEmpty('') // true
|
|
7
|
+
* @example isEmpty(' ') // true
|
|
8
|
+
* @example isEmpty('hello') // false
|
|
9
|
+
*/
|
|
10
|
+
export function isEmpty(str) {
|
|
11
|
+
return !str || str.trim().length === 0;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 문자열이 특정 문자로 시작하는지 확인
|
|
15
|
+
* @example startsWith('hello', 'he') // true
|
|
16
|
+
*/
|
|
17
|
+
export function startsWith(str, searchString) {
|
|
18
|
+
return str.startsWith(searchString);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 문자열이 특정 문자로 끝나는지 확인
|
|
22
|
+
* @example endsWith('hello', 'lo') // true
|
|
23
|
+
*/
|
|
24
|
+
export function endsWith(str, searchString) {
|
|
25
|
+
return str.endsWith(searchString);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 문자열에 특정 문자열이 포함되어 있는지 확인
|
|
29
|
+
* @example contains('hello world', 'world') // true
|
|
30
|
+
*/
|
|
31
|
+
export function contains(str, searchString) {
|
|
32
|
+
return str.includes(searchString);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 알파벳만 포함하는지 확인
|
|
36
|
+
* @example isAlpha('hello') // true
|
|
37
|
+
* @example isAlpha('hello123') // false
|
|
38
|
+
*/
|
|
39
|
+
export function isAlpha(str) {
|
|
40
|
+
return /^[a-zA-Z]+$/.test(str);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 숫자만 포함하는지 확인
|
|
44
|
+
* @example isNumeric('123') // true
|
|
45
|
+
* @example isNumeric('123.45') // false
|
|
46
|
+
*/
|
|
47
|
+
export function isNumeric(str) {
|
|
48
|
+
return /^[0-9]+$/.test(str);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 알파벳과 숫자만 포함하는지 확인
|
|
52
|
+
* @example isAlphanumeric('hello123') // true
|
|
53
|
+
* @example isAlphanumeric('hello_123') // false
|
|
54
|
+
*/
|
|
55
|
+
export function isAlphanumeric(str) {
|
|
56
|
+
return /^[a-zA-Z0-9]+$/.test(str);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 소문자만 포함하는지 확인
|
|
60
|
+
* @example isLowerCase('hello') // true
|
|
61
|
+
* @example isLowerCase('Hello') // false
|
|
62
|
+
*/
|
|
63
|
+
export function isLowerCase(str) {
|
|
64
|
+
return str === str.toLowerCase() && str !== str.toUpperCase();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 대문자만 포함하는지 확인
|
|
68
|
+
* @example isUpperCase('HELLO') // true
|
|
69
|
+
* @example isUpperCase('Hello') // false
|
|
70
|
+
*/
|
|
71
|
+
export function isUpperCase(str) {
|
|
72
|
+
return str === str.toUpperCase() && str !== str.toLowerCase();
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* JSON 형식인지 확인
|
|
76
|
+
* @example isJSON('{"name":"John"}') // true
|
|
77
|
+
* @example isJSON('not json') // false
|
|
78
|
+
*/
|
|
79
|
+
export function isJSON(str) {
|
|
80
|
+
try {
|
|
81
|
+
JSON.parse(str);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 16진수 색상 코드인지 확인
|
|
90
|
+
* @example isHexColor('#fff') // true
|
|
91
|
+
* @example isHexColor('#ffffff') // true
|
|
92
|
+
* @example isHexColor('fff') // false
|
|
93
|
+
*/
|
|
94
|
+
export function isHexColor(str) {
|
|
95
|
+
return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(str);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* UUID 형식인지 확인
|
|
99
|
+
* @example isUUID('123e4567-e89b-12d3-a456-426614174000') // true
|
|
100
|
+
*/
|
|
101
|
+
export function isUUID(str) {
|
|
102
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Base64 인코딩된 문자열인지 확인
|
|
106
|
+
* @example isBase64('SGVsbG8gV29ybGQ=') // true
|
|
107
|
+
*/
|
|
108
|
+
export function isBase64(str) {
|
|
109
|
+
try {
|
|
110
|
+
return btoa(atob(str)) === str;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "goodchuck-utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"prepare": "npm run build:tsc",
|
|
8
|
+
"prepublishOnly": "npm run test:run && npm run build:tsc",
|
|
8
9
|
"build": "npm run build",
|
|
9
|
-
"build:tsc": "tsc"
|
|
10
|
+
"build:tsc": "tsc",
|
|
11
|
+
"test": "vitest",
|
|
12
|
+
"test:ui": "vitest --ui",
|
|
13
|
+
"test:run": "vitest run",
|
|
14
|
+
"test:coverage": "vitest run --coverage"
|
|
10
15
|
},
|
|
11
16
|
"exports": {
|
|
12
17
|
".": {
|
|
13
18
|
"types": "./dist/index.d.ts",
|
|
14
19
|
"import": "./dist/index.js",
|
|
15
20
|
"require": "./src/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./hooks": {
|
|
23
|
+
"types": "./dist/hooks/index.d.ts",
|
|
24
|
+
"import": "./dist/hooks/index.js",
|
|
25
|
+
"require": "./src/hooks/index.js"
|
|
16
26
|
}
|
|
17
27
|
},
|
|
18
28
|
"keywords": [],
|
|
@@ -21,9 +31,23 @@
|
|
|
21
31
|
"description": "",
|
|
22
32
|
"dependencies": {
|
|
23
33
|
"@types/node": "^25.0.6",
|
|
34
|
+
"dayjs": "^1.11.19",
|
|
24
35
|
"typescript": "^5.9.3"
|
|
25
36
|
},
|
|
26
37
|
"include": [
|
|
27
38
|
"src/**/*.ts"
|
|
28
|
-
]
|
|
39
|
+
],
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/react": "^19.2.8",
|
|
42
|
+
"@vitest/ui": "^4.0.16",
|
|
43
|
+
"vitest": "^4.0.16"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"react": ">=16.8.0"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"react": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
52
|
+
}
|
|
29
53
|
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
formatDate,
|
|
4
|
+
now,
|
|
5
|
+
fromNow,
|
|
6
|
+
diffDate,
|
|
7
|
+
addDate,
|
|
8
|
+
subtractDate,
|
|
9
|
+
isBefore,
|
|
10
|
+
isAfter,
|
|
11
|
+
isSame,
|
|
12
|
+
isValidDate,
|
|
13
|
+
toTimezone,
|
|
14
|
+
} from './index';
|
|
15
|
+
|
|
16
|
+
describe('Date Utils', () => {
|
|
17
|
+
describe('formatDate', () => {
|
|
18
|
+
it('should format date with default format', () => {
|
|
19
|
+
const date = new Date('2024-01-15 10:30:45');
|
|
20
|
+
const result = formatDate(date);
|
|
21
|
+
expect(result).toBe('2024-01-15 10:30:45');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should format date with custom format', () => {
|
|
25
|
+
const date = new Date('2024-01-15');
|
|
26
|
+
const result = formatDate(date, 'YYYY년 MM월 DD일');
|
|
27
|
+
expect(result).toBe('2024년 01월 15일');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should format string date', () => {
|
|
31
|
+
const result = formatDate('2024-01-15', 'YYYY-MM-DD');
|
|
32
|
+
expect(result).toBe('2024-01-15');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should format timestamp', () => {
|
|
36
|
+
const timestamp = new Date('2024-01-15').getTime();
|
|
37
|
+
const result = formatDate(timestamp, 'YYYY-MM-DD');
|
|
38
|
+
expect(result).toBe('2024-01-15');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('now', () => {
|
|
43
|
+
it('should return formatted current date when format is provided', () => {
|
|
44
|
+
const result = now('YYYY-MM-DD');
|
|
45
|
+
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should return Date object when no format is provided', () => {
|
|
49
|
+
const result = now();
|
|
50
|
+
expect(result).toBeInstanceOf(Date);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('fromNow', () => {
|
|
55
|
+
it('should return relative time in Korean', () => {
|
|
56
|
+
const yesterday = new Date();
|
|
57
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
58
|
+
const result = fromNow(yesterday, 'ko');
|
|
59
|
+
expect(result).toContain('전');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should return relative time in English', () => {
|
|
63
|
+
const yesterday = new Date();
|
|
64
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
65
|
+
const result = fromNow(yesterday, 'en');
|
|
66
|
+
expect(result).toContain('ago');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('diffDate', () => {
|
|
71
|
+
it('should calculate difference in days', () => {
|
|
72
|
+
const date1 = new Date('2024-01-20');
|
|
73
|
+
const date2 = new Date('2024-01-15');
|
|
74
|
+
const result = diffDate(date1, date2, 'day');
|
|
75
|
+
expect(result).toBe(5);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should calculate difference in hours', () => {
|
|
79
|
+
const date1 = new Date('2024-01-15 15:00:00');
|
|
80
|
+
const date2 = new Date('2024-01-15 12:00:00');
|
|
81
|
+
const result = diffDate(date1, date2, 'hour');
|
|
82
|
+
expect(result).toBe(3);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should handle negative differences', () => {
|
|
86
|
+
const date1 = new Date('2024-01-15');
|
|
87
|
+
const date2 = new Date('2024-01-20');
|
|
88
|
+
const result = diffDate(date1, date2, 'day');
|
|
89
|
+
expect(result).toBe(-5);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('addDate', () => {
|
|
94
|
+
it('should add days to date', () => {
|
|
95
|
+
const date = new Date('2024-01-15');
|
|
96
|
+
const result = addDate(date, 5, 'day');
|
|
97
|
+
expect(formatDate(result, 'YYYY-MM-DD')).toBe('2024-01-20');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should add hours to date', () => {
|
|
101
|
+
const date = new Date('2024-01-15 10:00:00');
|
|
102
|
+
const result = addDate(date, 3, 'hour');
|
|
103
|
+
expect(formatDate(result, 'YYYY-MM-DD HH:mm:ss')).toBe('2024-01-15 13:00:00');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should add months to date', () => {
|
|
107
|
+
const date = new Date('2024-01-15');
|
|
108
|
+
const result = addDate(date, 2, 'month');
|
|
109
|
+
expect(formatDate(result, 'YYYY-MM-DD')).toBe('2024-03-15');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('subtractDate', () => {
|
|
114
|
+
it('should subtract days from date', () => {
|
|
115
|
+
const date = new Date('2024-01-20');
|
|
116
|
+
const result = subtractDate(date, 5, 'day');
|
|
117
|
+
expect(formatDate(result, 'YYYY-MM-DD')).toBe('2024-01-15');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should subtract hours from date', () => {
|
|
121
|
+
const date = new Date('2024-01-15 13:00:00');
|
|
122
|
+
const result = subtractDate(date, 3, 'hour');
|
|
123
|
+
expect(formatDate(result, 'YYYY-MM-DD HH:mm:ss')).toBe('2024-01-15 10:00:00');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('isBefore', () => {
|
|
128
|
+
it('should return true if first date is before second', () => {
|
|
129
|
+
const date1 = new Date('2024-01-15');
|
|
130
|
+
const date2 = new Date('2024-01-20');
|
|
131
|
+
expect(isBefore(date1, date2)).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should return false if first date is after second', () => {
|
|
135
|
+
const date1 = new Date('2024-01-20');
|
|
136
|
+
const date2 = new Date('2024-01-15');
|
|
137
|
+
expect(isBefore(date1, date2)).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('isAfter', () => {
|
|
142
|
+
it('should return true if first date is after second', () => {
|
|
143
|
+
const date1 = new Date('2024-01-20');
|
|
144
|
+
const date2 = new Date('2024-01-15');
|
|
145
|
+
expect(isAfter(date1, date2)).toBe(true);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should return false if first date is before second', () => {
|
|
149
|
+
const date1 = new Date('2024-01-15');
|
|
150
|
+
const date2 = new Date('2024-01-20');
|
|
151
|
+
expect(isAfter(date1, date2)).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('isSame', () => {
|
|
156
|
+
it('should return true for exact same dates', () => {
|
|
157
|
+
const date1 = new Date('2024-01-15 10:30:45');
|
|
158
|
+
const date2 = new Date('2024-01-15 10:30:45');
|
|
159
|
+
expect(isSame(date1, date2)).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should return true for same day (ignoring time)', () => {
|
|
163
|
+
const date1 = new Date('2024-01-15 10:00:00');
|
|
164
|
+
const date2 = new Date('2024-01-15 15:00:00');
|
|
165
|
+
expect(isSame(date1, date2, 'day')).toBe(true);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should return false for different days', () => {
|
|
169
|
+
const date1 = new Date('2024-01-15');
|
|
170
|
+
const date2 = new Date('2024-01-16');
|
|
171
|
+
expect(isSame(date1, date2, 'day')).toBe(false);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('isValidDate', () => {
|
|
176
|
+
it('should return true for valid dates', () => {
|
|
177
|
+
expect(isValidDate(new Date('2024-01-15'))).toBe(true);
|
|
178
|
+
expect(isValidDate('2024-01-15')).toBe(true);
|
|
179
|
+
expect(isValidDate(1705276800000)).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should return false for invalid dates', () => {
|
|
183
|
+
expect(isValidDate('invalid-date')).toBe(false);
|
|
184
|
+
expect(isValidDate(NaN)).toBe(false);
|
|
185
|
+
expect(isValidDate(null)).toBe(false);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('toTimezone', () => {
|
|
190
|
+
it('should convert to specific timezone', () => {
|
|
191
|
+
const date = new Date('2024-01-15T00:00:00Z');
|
|
192
|
+
const result = toTimezone(date, 'Asia/Seoul');
|
|
193
|
+
expect(result).toBeInstanceOf(Date);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should handle different timezones', () => {
|
|
197
|
+
const date = new Date('2024-01-15T12:00:00Z');
|
|
198
|
+
const nyTime = toTimezone(date, 'America/New_York');
|
|
199
|
+
const seoulTime = toTimezone(date, 'Asia/Seoul');
|
|
200
|
+
|
|
201
|
+
expect(nyTime).toBeInstanceOf(Date);
|
|
202
|
+
expect(seoulTime).toBeInstanceOf(Date);
|
|
203
|
+
expect(nyTime.getTime()).toBe(seoulTime.getTime());
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import dayjs from 'dayjs';
|
|
2
|
+
import utc from 'dayjs/plugin/utc';
|
|
3
|
+
import timezone from 'dayjs/plugin/timezone';
|
|
4
|
+
import relativeTime from 'dayjs/plugin/relativeTime';
|
|
5
|
+
import 'dayjs/locale/ko';
|
|
6
|
+
|
|
7
|
+
dayjs.extend(utc);
|
|
8
|
+
dayjs.extend(timezone);
|
|
9
|
+
dayjs.extend(relativeTime);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 날짜를 지정된 포맷으로 변환
|
|
13
|
+
* @param date - 변환할 날짜 (Date, string, number)
|
|
14
|
+
* @param format - 포맷 문자열 (기본값: 'YYYY-MM-DD HH:mm:ss')
|
|
15
|
+
*/
|
|
16
|
+
export function formatDate(
|
|
17
|
+
date: Date | string | number = new Date(),
|
|
18
|
+
format: string = 'YYYY-MM-DD HH:mm:ss'
|
|
19
|
+
): string {
|
|
20
|
+
return dayjs(date).format(format);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 현재 날짜/시간 반환
|
|
25
|
+
* @param format - 포맷 문자열 (선택)
|
|
26
|
+
*/
|
|
27
|
+
export function now(format?: string): string | Date {
|
|
28
|
+
const current = dayjs();
|
|
29
|
+
return format ? current.format(format) : current.toDate();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 상대 시간 반환 (예: "3시간 전")
|
|
34
|
+
* @param date - 기준 날짜
|
|
35
|
+
* @param locale - 로케일 (기본값: 'ko')
|
|
36
|
+
*/
|
|
37
|
+
export function fromNow(date: Date | string | number, locale: string = 'ko'): string {
|
|
38
|
+
return dayjs(date).locale(locale).fromNow();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 두 날짜 사이의 차이 계산
|
|
43
|
+
* @param date1 - 첫 번째 날짜
|
|
44
|
+
* @param date2 - 두 번째 날짜
|
|
45
|
+
* @param unit - 단위 ('day', 'hour', 'minute' 등)
|
|
46
|
+
*/
|
|
47
|
+
export function diffDate(
|
|
48
|
+
date1: Date | string | number,
|
|
49
|
+
date2: Date | string | number,
|
|
50
|
+
unit: dayjs.OpUnitType = 'day'
|
|
51
|
+
): number {
|
|
52
|
+
return dayjs(date1).diff(dayjs(date2), unit);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 날짜에 시간 더하기
|
|
57
|
+
* @param date - 기준 날짜
|
|
58
|
+
* @param value - 더할 값
|
|
59
|
+
* @param unit - 단위 ('day', 'hour', 'minute' 등)
|
|
60
|
+
*/
|
|
61
|
+
export function addDate(
|
|
62
|
+
date: Date | string | number,
|
|
63
|
+
value: number,
|
|
64
|
+
unit: dayjs.ManipulateType = 'day'
|
|
65
|
+
): Date {
|
|
66
|
+
return dayjs(date).add(value, unit).toDate();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 날짜에 시간 빼기
|
|
71
|
+
* @param date - 기준 날짜
|
|
72
|
+
* @param value - 뺄 값
|
|
73
|
+
* @param unit - 단위 ('day', 'hour', 'minute' 등)
|
|
74
|
+
*/
|
|
75
|
+
export function subtractDate(
|
|
76
|
+
date: Date | string | number,
|
|
77
|
+
value: number,
|
|
78
|
+
unit: dayjs.ManipulateType = 'day'
|
|
79
|
+
): Date {
|
|
80
|
+
return dayjs(date).subtract(value, unit).toDate();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 날짜가 특정 날짜보다 이전인지 확인
|
|
85
|
+
*/
|
|
86
|
+
export function isBefore(date1: Date | string | number, date2: Date | string | number): boolean {
|
|
87
|
+
return dayjs(date1).isBefore(dayjs(date2));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 날짜가 특정 날짜보다 이후인지 확인
|
|
92
|
+
*/
|
|
93
|
+
export function isAfter(date1: Date | string | number, date2: Date | string | number): boolean {
|
|
94
|
+
return dayjs(date1).isAfter(dayjs(date2));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 두 날짜가 같은지 확인
|
|
99
|
+
* @param unit - 비교 단위 (기본값: 'millisecond')
|
|
100
|
+
*/
|
|
101
|
+
export function isSame(
|
|
102
|
+
date1: Date | string | number,
|
|
103
|
+
date2: Date | string | number,
|
|
104
|
+
unit: dayjs.OpUnitType = 'millisecond'
|
|
105
|
+
): boolean {
|
|
106
|
+
return dayjs(date1).isSame(dayjs(date2), unit);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 날짜가 유효한지 확인
|
|
111
|
+
*/
|
|
112
|
+
export function isValidDate(date: any): boolean {
|
|
113
|
+
return dayjs(date).isValid();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 타임존 변환
|
|
118
|
+
* @param date - 변환할 날짜
|
|
119
|
+
* @param tz - 타임존 (예: 'Asia/Seoul')
|
|
120
|
+
*/
|
|
121
|
+
export function toTimezone(date: Date | string | number, tz: string): Date {
|
|
122
|
+
return dayjs(date).tz(tz).toDate();
|
|
123
|
+
}
|