@fluixi/utils 1.0.0-alpha.53
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/LICENSE +21 -0
- package/README.md +73 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +1 -0
- package/dist/lib/array/advanced.cjs +1 -0
- package/dist/lib/array/advanced.d.ts +1116 -0
- package/dist/lib/array/advanced.d.ts.map +1 -0
- package/dist/lib/array/advanced.js +2429 -0
- package/dist/lib/array/advanced.mjs +1 -0
- package/dist/lib/array/array.cjs +1 -0
- package/dist/lib/array/array.d.ts +6 -0
- package/dist/lib/array/array.d.ts.map +1 -0
- package/dist/lib/array/array.js +31 -0
- package/dist/lib/array/array.mjs +1 -0
- package/dist/lib/array/index.cjs +1 -0
- package/dist/lib/array/index.d.ts +4 -0
- package/dist/lib/array/index.d.ts.map +1 -0
- package/dist/lib/array/index.js +3 -0
- package/dist/lib/array/index.mjs +1 -0
- package/dist/lib/array/utils.cjs +1 -0
- package/dist/lib/array/utils.d.ts +100 -0
- package/dist/lib/array/utils.d.ts.map +1 -0
- package/dist/lib/array/utils.js +165 -0
- package/dist/lib/array/utils.mjs +1 -0
- package/dist/lib/color/color.cjs +1 -0
- package/dist/lib/color/color.d.ts +366 -0
- package/dist/lib/color/color.d.ts.map +1 -0
- package/dist/lib/color/color.js +722 -0
- package/dist/lib/color/color.mjs +1 -0
- package/dist/lib/color/constants.cjs +1 -0
- package/dist/lib/color/constants.d.ts +361 -0
- package/dist/lib/color/constants.d.ts.map +1 -0
- package/dist/lib/color/constants.js +358 -0
- package/dist/lib/color/constants.mjs +1 -0
- package/dist/lib/color/conversions.cjs +1 -0
- package/dist/lib/color/conversions.d.ts +166 -0
- package/dist/lib/color/conversions.d.ts.map +1 -0
- package/dist/lib/color/conversions.js +766 -0
- package/dist/lib/color/conversions.mjs +1 -0
- package/dist/lib/color/helpers.cjs +1 -0
- package/dist/lib/color/helpers.d.ts +75 -0
- package/dist/lib/color/helpers.d.ts.map +1 -0
- package/dist/lib/color/helpers.js +419 -0
- package/dist/lib/color/helpers.mjs +1 -0
- package/dist/lib/color/index.cjs +1 -0
- package/dist/lib/color/index.d.ts +12 -0
- package/dist/lib/color/index.d.ts.map +1 -0
- package/dist/lib/color/index.js +15 -0
- package/dist/lib/color/index.mjs +1 -0
- package/dist/lib/color/types.cjs +1 -0
- package/dist/lib/color/types.d.ts +179 -0
- package/dist/lib/color/types.d.ts.map +1 -0
- package/dist/lib/color/types.js +1 -0
- package/dist/lib/color/types.mjs +0 -0
- package/dist/lib/color/utils.cjs +1 -0
- package/dist/lib/color/utils.d.ts +199 -0
- package/dist/lib/color/utils.d.ts.map +1 -0
- package/dist/lib/color/utils.js +858 -0
- package/dist/lib/color/utils.mjs +1 -0
- package/dist/lib/compare/comparators.cjs +1 -0
- package/dist/lib/compare/comparators.d.ts +65 -0
- package/dist/lib/compare/comparators.d.ts.map +1 -0
- package/dist/lib/compare/comparators.js +210 -0
- package/dist/lib/compare/comparators.mjs +1 -0
- package/dist/lib/compare/index.cjs +1 -0
- package/dist/lib/compare/index.d.ts +2 -0
- package/dist/lib/compare/index.d.ts.map +1 -0
- package/dist/lib/compare/index.js +1 -0
- package/dist/lib/compare/index.mjs +1 -0
- package/dist/lib/crypto/hash.cjs +1 -0
- package/dist/lib/crypto/hash.d.ts +6 -0
- package/dist/lib/crypto/hash.d.ts.map +1 -0
- package/dist/lib/crypto/hash.js +53 -0
- package/dist/lib/crypto/hash.mjs +1 -0
- package/dist/lib/crypto/index.cjs +1 -0
- package/dist/lib/crypto/index.d.ts +2 -0
- package/dist/lib/crypto/index.d.ts.map +1 -0
- package/dist/lib/crypto/index.js +1 -0
- package/dist/lib/crypto/index.mjs +1 -0
- package/dist/lib/dom/events.cjs +1 -0
- package/dist/lib/dom/events.d.ts +159 -0
- package/dist/lib/dom/events.d.ts.map +1 -0
- package/dist/lib/dom/events.js +50 -0
- package/dist/lib/dom/events.mjs +1 -0
- package/dist/lib/dom/index.cjs +1 -0
- package/dist/lib/dom/index.d.ts +3 -0
- package/dist/lib/dom/index.d.ts.map +1 -0
- package/dist/lib/dom/index.js +2 -0
- package/dist/lib/dom/index.mjs +1 -0
- package/dist/lib/dom/utils.cjs +1 -0
- package/dist/lib/dom/utils.d.ts +6 -0
- package/dist/lib/dom/utils.d.ts.map +1 -0
- package/dist/lib/dom/utils.js +37 -0
- package/dist/lib/dom/utils.mjs +1 -0
- package/dist/lib/functions/functions.cjs +1 -0
- package/dist/lib/functions/functions.d.ts +329 -0
- package/dist/lib/functions/functions.d.ts.map +1 -0
- package/dist/lib/functions/functions.js +585 -0
- package/dist/lib/functions/functions.mjs +1 -0
- package/dist/lib/functions/index.cjs +1 -0
- package/dist/lib/functions/index.d.ts +3 -0
- package/dist/lib/functions/index.d.ts.map +1 -0
- package/dist/lib/functions/index.js +1 -0
- package/dist/lib/functions/index.mjs +1 -0
- package/dist/lib/functions/types.cjs +1 -0
- package/dist/lib/functions/types.d.ts +2 -0
- package/dist/lib/functions/types.d.ts.map +1 -0
- package/dist/lib/functions/types.js +1 -0
- package/dist/lib/functions/types.mjs +0 -0
- package/dist/lib/helpers.cjs +1 -0
- package/dist/lib/helpers.d.ts +13 -0
- package/dist/lib/helpers.d.ts.map +1 -0
- package/dist/lib/helpers.js +66 -0
- package/dist/lib/helpers.mjs +1 -0
- package/dist/lib/index.cjs +1 -0
- package/dist/lib/index.d.ts +6 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +8 -0
- package/dist/lib/index.mjs +1 -0
- package/dist/lib/maths/constants.cjs +1 -0
- package/dist/lib/maths/constants.d.ts +84 -0
- package/dist/lib/maths/constants.d.ts.map +1 -0
- package/dist/lib/maths/constants.js +79 -0
- package/dist/lib/maths/constants.mjs +1 -0
- package/dist/lib/maths/fn/advanced.cjs +1 -0
- package/dist/lib/maths/fn/advanced.d.ts +149 -0
- package/dist/lib/maths/fn/advanced.d.ts.map +1 -0
- package/dist/lib/maths/fn/advanced.js +264 -0
- package/dist/lib/maths/fn/advanced.mjs +1 -0
- package/dist/lib/maths/fn/basic.cjs +1 -0
- package/dist/lib/maths/fn/basic.d.ts +182 -0
- package/dist/lib/maths/fn/basic.d.ts.map +1 -0
- package/dist/lib/maths/fn/basic.js +343 -0
- package/dist/lib/maths/fn/basic.mjs +1 -0
- package/dist/lib/maths/fn/complex.cjs +1 -0
- package/dist/lib/maths/fn/complex.d.ts +153 -0
- package/dist/lib/maths/fn/complex.d.ts.map +1 -0
- package/dist/lib/maths/fn/complex.js +383 -0
- package/dist/lib/maths/fn/complex.mjs +1 -0
- package/dist/lib/maths/fn/index.cjs +1 -0
- package/dist/lib/maths/fn/index.d.ts +10 -0
- package/dist/lib/maths/fn/index.d.ts.map +1 -0
- package/dist/lib/maths/fn/index.js +9 -0
- package/dist/lib/maths/fn/index.mjs +1 -0
- package/dist/lib/maths/fn/trigo.cjs +1 -0
- package/dist/lib/maths/fn/trigo.d.ts +147 -0
- package/dist/lib/maths/fn/trigo.d.ts.map +1 -0
- package/dist/lib/maths/fn/trigo.js +227 -0
- package/dist/lib/maths/fn/trigo.mjs +1 -0
- package/dist/lib/maths/fn/vector.cjs +1 -0
- package/dist/lib/maths/fn/vector.d.ts +512 -0
- package/dist/lib/maths/fn/vector.d.ts.map +1 -0
- package/dist/lib/maths/fn/vector.js +1136 -0
- package/dist/lib/maths/fn/vector.mjs +1 -0
- package/dist/lib/maths/formatter.cjs +1 -0
- package/dist/lib/maths/formatter.d.ts +35 -0
- package/dist/lib/maths/formatter.d.ts.map +1 -0
- package/dist/lib/maths/formatter.js +56 -0
- package/dist/lib/maths/formatter.mjs +1 -0
- package/dist/lib/maths/guards.cjs +1 -0
- package/dist/lib/maths/guards.d.ts +166 -0
- package/dist/lib/maths/guards.d.ts.map +1 -0
- package/dist/lib/maths/guards.js +315 -0
- package/dist/lib/maths/guards.mjs +1 -0
- package/dist/lib/maths/index.cjs +1 -0
- package/dist/lib/maths/index.d.ts +24 -0
- package/dist/lib/maths/index.d.ts.map +1 -0
- package/dist/lib/maths/index.js +29 -0
- package/dist/lib/maths/index.mjs +1 -0
- package/dist/lib/maths/parser.cjs +1 -0
- package/dist/lib/maths/parser.d.ts +20 -0
- package/dist/lib/maths/parser.d.ts.map +1 -0
- package/dist/lib/maths/parser.js +78 -0
- package/dist/lib/maths/parser.mjs +1 -0
- package/dist/lib/maths/random.cjs +1 -0
- package/dist/lib/maths/random.d.ts +27 -0
- package/dist/lib/maths/random.d.ts.map +1 -0
- package/dist/lib/maths/random.js +49 -0
- package/dist/lib/maths/random.mjs +1 -0
- package/dist/lib/maths/types/advanced.cjs +1 -0
- package/dist/lib/maths/types/advanced.d.ts +357 -0
- package/dist/lib/maths/types/advanced.d.ts.map +1 -0
- package/dist/lib/maths/types/advanced.js +5 -0
- package/dist/lib/maths/types/advanced.mjs +0 -0
- package/dist/lib/maths/types/basic.cjs +1 -0
- package/dist/lib/maths/types/basic.d.ts +95 -0
- package/dist/lib/maths/types/basic.d.ts.map +1 -0
- package/dist/lib/maths/types/basic.js +5 -0
- package/dist/lib/maths/types/basic.mjs +0 -0
- package/dist/lib/maths/types/index.cjs +1 -0
- package/dist/lib/maths/types/index.d.ts +4 -0
- package/dist/lib/maths/types/index.d.ts.map +1 -0
- package/dist/lib/maths/types/index.js +3 -0
- package/dist/lib/maths/types/index.mjs +0 -0
- package/dist/lib/maths/types/negative.cjs +1 -0
- package/dist/lib/maths/types/negative.d.ts +51 -0
- package/dist/lib/maths/types/negative.d.ts.map +1 -0
- package/dist/lib/maths/types/negative.js +5 -0
- package/dist/lib/maths/types/negative.mjs +0 -0
- package/dist/lib/maths/types/operations.cjs +1 -0
- package/dist/lib/maths/types/operations.d.ts +188 -0
- package/dist/lib/maths/types/operations.d.ts.map +1 -0
- package/dist/lib/maths/types/operations.js +6 -0
- package/dist/lib/maths/types/operations.mjs +0 -0
- package/dist/lib/object/clone.cjs +1 -0
- package/dist/lib/object/clone.d.ts +46 -0
- package/dist/lib/object/clone.d.ts.map +1 -0
- package/dist/lib/object/clone.js +104 -0
- package/dist/lib/object/clone.mjs +1 -0
- package/dist/lib/object/diff.cjs +1 -0
- package/dist/lib/object/diff.d.ts +203 -0
- package/dist/lib/object/diff.d.ts.map +1 -0
- package/dist/lib/object/diff.js +805 -0
- package/dist/lib/object/diff.mjs +1 -0
- package/dist/lib/object/getter-setter.cjs +1 -0
- package/dist/lib/object/getter-setter.d.ts +361 -0
- package/dist/lib/object/getter-setter.d.ts.map +1 -0
- package/dist/lib/object/getter-setter.js +1771 -0
- package/dist/lib/object/getter-setter.mjs +1 -0
- package/dist/lib/object/index.cjs +6 -0
- package/dist/lib/object/index.d.ts +13 -0
- package/dist/lib/object/index.d.ts.map +1 -0
- package/dist/lib/object/index.js +12 -0
- package/dist/lib/object/index.mjs +6 -0
- package/dist/lib/object/merge.cjs +1 -0
- package/dist/lib/object/merge.d.ts +93 -0
- package/dist/lib/object/merge.d.ts.map +1 -0
- package/dist/lib/object/merge.js +474 -0
- package/dist/lib/object/merge.mjs +1 -0
- package/dist/lib/object/object.cjs +1 -0
- package/dist/lib/object/object.d.ts +128 -0
- package/dist/lib/object/object.d.ts.map +1 -0
- package/dist/lib/object/object.js +432 -0
- package/dist/lib/object/object.mjs +1 -0
- package/dist/lib/object/omit.cjs +1 -0
- package/dist/lib/object/omit.d.ts +351 -0
- package/dist/lib/object/omit.d.ts.map +1 -0
- package/dist/lib/object/omit.js +656 -0
- package/dist/lib/object/omit.mjs +1 -0
- package/dist/lib/object/reconcile.cjs +1 -0
- package/dist/lib/object/reconcile.d.ts +7 -0
- package/dist/lib/object/reconcile.d.ts.map +1 -0
- package/dist/lib/object/reconcile.js +122 -0
- package/dist/lib/object/reconcile.mjs +1 -0
- package/dist/lib/object/selector.cjs +1 -0
- package/dist/lib/object/selector.d.ts +50 -0
- package/dist/lib/object/selector.d.ts.map +1 -0
- package/dist/lib/object/selector.js +371 -0
- package/dist/lib/object/selector.mjs +1 -0
- package/dist/lib/object/sort.cjs +1 -0
- package/dist/lib/object/sort.d.ts +149 -0
- package/dist/lib/object/sort.d.ts.map +1 -0
- package/dist/lib/object/sort.js +298 -0
- package/dist/lib/object/sort.mjs +1 -0
- package/dist/lib/object/transform.cjs +1 -0
- package/dist/lib/object/transform.d.ts +620 -0
- package/dist/lib/object/transform.d.ts.map +1 -0
- package/dist/lib/object/transform.js +1058 -0
- package/dist/lib/object/transform.mjs +1 -0
- package/dist/lib/object/types.cjs +1 -0
- package/dist/lib/object/types.d.ts +250 -0
- package/dist/lib/object/types.d.ts.map +1 -0
- package/dist/lib/object/types.js +1 -0
- package/dist/lib/object/types.mjs +0 -0
- package/dist/lib/object/utils.cjs +6 -0
- package/dist/lib/object/utils.d.ts +165 -0
- package/dist/lib/object/utils.d.ts.map +1 -0
- package/dist/lib/object/utils.js +444 -0
- package/dist/lib/object/utils.mjs +6 -0
- package/dist/lib/primitive/boolean/boolean.cjs +1 -0
- package/dist/lib/primitive/boolean/boolean.d.ts +276 -0
- package/dist/lib/primitive/boolean/boolean.d.ts.map +1 -0
- package/dist/lib/primitive/boolean/boolean.js +615 -0
- package/dist/lib/primitive/boolean/boolean.mjs +1 -0
- package/dist/lib/primitive/boolean/index.cjs +1 -0
- package/dist/lib/primitive/boolean/index.d.ts +3 -0
- package/dist/lib/primitive/boolean/index.d.ts.map +1 -0
- package/dist/lib/primitive/boolean/index.js +2 -0
- package/dist/lib/primitive/boolean/index.mjs +1 -0
- package/dist/lib/primitive/boolean/types.cjs +1 -0
- package/dist/lib/primitive/boolean/types.d.ts +14 -0
- package/dist/lib/primitive/boolean/types.d.ts.map +1 -0
- package/dist/lib/primitive/boolean/types.js +1 -0
- package/dist/lib/primitive/boolean/types.mjs +0 -0
- package/dist/lib/primitive/date/date.cjs +1 -0
- package/dist/lib/primitive/date/date.d.ts +641 -0
- package/dist/lib/primitive/date/date.d.ts.map +1 -0
- package/dist/lib/primitive/date/date.js +1998 -0
- package/dist/lib/primitive/date/date.mjs +1 -0
- package/dist/lib/primitive/date/index.cjs +1 -0
- package/dist/lib/primitive/date/index.d.ts +3 -0
- package/dist/lib/primitive/date/index.d.ts.map +1 -0
- package/dist/lib/primitive/date/index.js +2 -0
- package/dist/lib/primitive/date/index.mjs +1 -0
- package/dist/lib/primitive/date/types.cjs +1 -0
- package/dist/lib/primitive/date/types.d.ts +205 -0
- package/dist/lib/primitive/date/types.d.ts.map +1 -0
- package/dist/lib/primitive/date/types.js +7 -0
- package/dist/lib/primitive/date/types.mjs +0 -0
- package/dist/lib/primitive/index.cjs +5 -0
- package/dist/lib/primitive/index.d.ts +5 -0
- package/dist/lib/primitive/index.d.ts.map +1 -0
- package/dist/lib/primitive/index.js +4 -0
- package/dist/lib/primitive/index.mjs +5 -0
- package/dist/lib/primitive/string/index.cjs +5 -0
- package/dist/lib/primitive/string/index.d.ts +3 -0
- package/dist/lib/primitive/string/index.d.ts.map +1 -0
- package/dist/lib/primitive/string/index.js +2 -0
- package/dist/lib/primitive/string/index.mjs +5 -0
- package/dist/lib/primitive/string/parser-2.cjs +8 -0
- package/dist/lib/primitive/string/parser-2.d.ts +17 -0
- package/dist/lib/primitive/string/parser-2.d.ts.map +1 -0
- package/dist/lib/primitive/string/parser-2.js +656 -0
- package/dist/lib/primitive/string/parser-2.mjs +8 -0
- package/dist/lib/primitive/string/parser.cjs +3 -0
- package/dist/lib/primitive/string/parser.d.ts +9 -0
- package/dist/lib/primitive/string/parser.d.ts.map +1 -0
- package/dist/lib/primitive/string/parser.js +775 -0
- package/dist/lib/primitive/string/parser.mjs +3 -0
- package/dist/lib/primitive/string/string.cjs +5 -0
- package/dist/lib/primitive/string/string.d.ts +535 -0
- package/dist/lib/primitive/string/string.d.ts.map +1 -0
- package/dist/lib/primitive/string/string.js +1301 -0
- package/dist/lib/primitive/string/string.mjs +5 -0
- package/dist/lib/primitive/string/types.cjs +1 -0
- package/dist/lib/primitive/string/types.d.ts +318 -0
- package/dist/lib/primitive/string/types.d.ts.map +1 -0
- package/dist/lib/primitive/string/types.js +5 -0
- package/dist/lib/primitive/string/types.mjs +0 -0
- package/dist/lib/primitive/types.cjs +1 -0
- package/dist/lib/primitive/types.d.ts +27 -0
- package/dist/lib/primitive/types.d.ts.map +1 -0
- package/dist/lib/primitive/types.js +1 -0
- package/dist/lib/primitive/types.mjs +0 -0
- package/dist/lib/url/index.cjs +1 -0
- package/dist/lib/url/index.d.ts +3 -0
- package/dist/lib/url/index.d.ts.map +1 -0
- package/dist/lib/url/index.js +2 -0
- package/dist/lib/url/index.mjs +1 -0
- package/dist/lib/url/parser.cjs +1 -0
- package/dist/lib/url/parser.d.ts +216 -0
- package/dist/lib/url/parser.d.ts.map +1 -0
- package/dist/lib/url/parser.js +488 -0
- package/dist/lib/url/parser.mjs +1 -0
- package/dist/lib/url/types.cjs +1 -0
- package/dist/lib/url/types.d.ts +18 -0
- package/dist/lib/url/types.d.ts.map +1 -0
- package/dist/lib/url/types.js +1 -0
- package/dist/lib/url/types.mjs +0 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/package.json +150 -0
|
@@ -0,0 +1,1998 @@
|
|
|
1
|
+
// Constants
|
|
2
|
+
const MILLISECONDS_IN_SECOND = 1000;
|
|
3
|
+
const SECONDS_IN_MINUTE = 60;
|
|
4
|
+
const MINUTES_IN_HOUR = 60;
|
|
5
|
+
const HOURS_IN_DAY = 24;
|
|
6
|
+
const DAYS_IN_WEEK = 7;
|
|
7
|
+
const MONTHS_IN_YEAR = 12;
|
|
8
|
+
const QUARTERS_IN_YEAR = 4;
|
|
9
|
+
const WEEKDAY_NAMES = [
|
|
10
|
+
'Sunday',
|
|
11
|
+
'Monday',
|
|
12
|
+
'Tuesday',
|
|
13
|
+
'Wednesday',
|
|
14
|
+
'Thursday',
|
|
15
|
+
'Friday',
|
|
16
|
+
'Saturday',
|
|
17
|
+
];
|
|
18
|
+
const WEEKDAY_NAMES_SHORT = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
19
|
+
const MONTH_NAMES = [
|
|
20
|
+
'January',
|
|
21
|
+
'February',
|
|
22
|
+
'March',
|
|
23
|
+
'April',
|
|
24
|
+
'May',
|
|
25
|
+
'June',
|
|
26
|
+
'July',
|
|
27
|
+
'August',
|
|
28
|
+
'September',
|
|
29
|
+
'October',
|
|
30
|
+
'November',
|
|
31
|
+
'December',
|
|
32
|
+
];
|
|
33
|
+
const MONTH_NAMES_SHORT = [
|
|
34
|
+
'Jan',
|
|
35
|
+
'Feb',
|
|
36
|
+
'Mar',
|
|
37
|
+
'Apr',
|
|
38
|
+
'May',
|
|
39
|
+
'Jun',
|
|
40
|
+
'Jul',
|
|
41
|
+
'Aug',
|
|
42
|
+
'Sep',
|
|
43
|
+
'Oct',
|
|
44
|
+
'Nov',
|
|
45
|
+
'Dec',
|
|
46
|
+
];
|
|
47
|
+
// Default configuration
|
|
48
|
+
const DEFAULT_CONFIG = {
|
|
49
|
+
defaultTimezone: 'UTC',
|
|
50
|
+
defaultLocale: 'en-US',
|
|
51
|
+
weekStartsOn: 0, // Sunday
|
|
52
|
+
businessDays: {
|
|
53
|
+
businessDays: [1, 2, 3, 4, 5], // Monday to Friday
|
|
54
|
+
holidays: [],
|
|
55
|
+
holidayCheck: () => false,
|
|
56
|
+
workStartHour: 9,
|
|
57
|
+
workEndHour: 17,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
let config = { ...DEFAULT_CONFIG };
|
|
61
|
+
/**
|
|
62
|
+
* Configure date utility settings globally
|
|
63
|
+
* @function configureDateUtils
|
|
64
|
+
* @param {Partial<DateConfig>} newConfig - New configuration options
|
|
65
|
+
* @returns {void}
|
|
66
|
+
* @example
|
|
67
|
+
* // Configure with French locale and week starting on Monday
|
|
68
|
+
* configureDateUtils({
|
|
69
|
+
* defaultLocale: 'fr-FR',
|
|
70
|
+
* weekStartsOn: 1,
|
|
71
|
+
* });
|
|
72
|
+
*/
|
|
73
|
+
export function configureDateUtils(newConfig) {
|
|
74
|
+
config = {
|
|
75
|
+
...config,
|
|
76
|
+
...newConfig,
|
|
77
|
+
businessDays: {
|
|
78
|
+
...config.businessDays,
|
|
79
|
+
...(newConfig.businessDays || {}),
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Reset configuration to defaults
|
|
85
|
+
* @function resetDateConfig
|
|
86
|
+
* @returns {void}
|
|
87
|
+
* @example
|
|
88
|
+
* resetDateConfig(); // Reset all settings to defaults
|
|
89
|
+
*/
|
|
90
|
+
export function resetDateConfig() {
|
|
91
|
+
config = { ...DEFAULT_CONFIG };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Parse a date from various input types with flexible formatting options
|
|
95
|
+
* @function parseDate
|
|
96
|
+
* @param {Date | string | number} input - Date to parse (Date object, string, or timestamp)
|
|
97
|
+
* @param {ParseOptions} [options={}] - Parsing options
|
|
98
|
+
* @returns {Date} Parsed Date object
|
|
99
|
+
* @throws {Error} If date cannot be parsed and strict mode is enabled
|
|
100
|
+
* @example
|
|
101
|
+
* // Parse ISO date
|
|
102
|
+
* parseDate('2024-01-15T10:30:00Z');
|
|
103
|
+
*
|
|
104
|
+
* // Parse US date
|
|
105
|
+
* parseDate('01/15/2024', { format: 'US' });
|
|
106
|
+
*
|
|
107
|
+
* // Parse with custom format
|
|
108
|
+
* parseDate('2024-15-01', { format: 'CUSTOM', customFormat: 'YYYY-DD-MM' });
|
|
109
|
+
*/
|
|
110
|
+
export function parseDate(input, options = {}) {
|
|
111
|
+
const { format = 'ISO', customFormat = '', timezone = config.defaultTimezone, strict = true, locale = config.defaultLocale, } = options;
|
|
112
|
+
// If already a valid Date, return it
|
|
113
|
+
if (input instanceof Date && isDate(input)) {
|
|
114
|
+
return input;
|
|
115
|
+
}
|
|
116
|
+
let parsedDate = null;
|
|
117
|
+
// Handle number input (timestamp)
|
|
118
|
+
if (typeof input === 'number') {
|
|
119
|
+
parsedDate = new Date(input);
|
|
120
|
+
if (!isDate(parsedDate)) {
|
|
121
|
+
throw new Error(`Invalid timestamp: ${input}`);
|
|
122
|
+
}
|
|
123
|
+
return applyTimezone(parsedDate, timezone);
|
|
124
|
+
}
|
|
125
|
+
// Handle string input
|
|
126
|
+
if (typeof input !== 'string') {
|
|
127
|
+
throw new Error(`Invalid input type: ${typeof input}`);
|
|
128
|
+
}
|
|
129
|
+
const trimmedInput = input.trim();
|
|
130
|
+
if (!trimmedInput) {
|
|
131
|
+
throw new Error('Empty date string');
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
switch (format) {
|
|
135
|
+
case 'ISO':
|
|
136
|
+
case 'ISO_DATE':
|
|
137
|
+
case 'ISO_TIME':
|
|
138
|
+
parsedDate = parseISODate(trimmedInput, format);
|
|
139
|
+
break;
|
|
140
|
+
case 'US':
|
|
141
|
+
parsedDate = parseUSDate(trimmedInput);
|
|
142
|
+
break;
|
|
143
|
+
case 'EU':
|
|
144
|
+
parsedDate = parseEUDate(trimmedInput);
|
|
145
|
+
break;
|
|
146
|
+
case 'CUSTOM':
|
|
147
|
+
parsedDate = parseCustomDate(trimmedInput, customFormat);
|
|
148
|
+
break;
|
|
149
|
+
case 'RFC_2822':
|
|
150
|
+
parsedDate = parseRFC2822Date(trimmedInput);
|
|
151
|
+
break;
|
|
152
|
+
case 'TIMESTAMP':
|
|
153
|
+
parsedDate = parseTimestamp(trimmedInput);
|
|
154
|
+
break;
|
|
155
|
+
default:
|
|
156
|
+
// Try locale-aware parsing as fallback
|
|
157
|
+
parsedDate = parseLocaleDate(trimmedInput, locale, strict);
|
|
158
|
+
}
|
|
159
|
+
if (!parsedDate || !isDate(parsedDate)) {
|
|
160
|
+
if (strict) {
|
|
161
|
+
throw new Error(`Parsing resulted in invalid date from input: "${trimmedInput}"`);
|
|
162
|
+
}
|
|
163
|
+
// In non-strict mode, try generic parsing
|
|
164
|
+
parsedDate = new Date(trimmedInput);
|
|
165
|
+
if (!isDate(parsedDate)) {
|
|
166
|
+
throw new Error(`Invalid date format: ${trimmedInput}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Apply timezone if specified
|
|
170
|
+
if (timezone && timezone !== 'UTC') {
|
|
171
|
+
parsedDate = convertTimezone(parsedDate, timezone);
|
|
172
|
+
}
|
|
173
|
+
if (!parsedDate) {
|
|
174
|
+
throw new Error(`Invalid date format: ${trimmedInput}`);
|
|
175
|
+
}
|
|
176
|
+
return parsedDate;
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
if (strict) {
|
|
180
|
+
throw new Error(`Failed to parse date "${trimmedInput}" with format ${format}: ${error instanceof Error ? error.message : String(error)}`);
|
|
181
|
+
}
|
|
182
|
+
// Fallback to Date constructor in non-strict mode
|
|
183
|
+
const fallbackDate = new Date(trimmedInput);
|
|
184
|
+
if (isDate(fallbackDate)) {
|
|
185
|
+
const t = applyTimezone(fallbackDate, timezone);
|
|
186
|
+
if (!t) {
|
|
187
|
+
throw new Error(`Invalid date format: ${trimmedInput}`);
|
|
188
|
+
}
|
|
189
|
+
return t;
|
|
190
|
+
}
|
|
191
|
+
throw new Error(`Invalid date format: ${trimmedInput}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// ============================================================================
|
|
195
|
+
// Fixed Internal Helper Functions
|
|
196
|
+
// ============================================================================
|
|
197
|
+
/**
|
|
198
|
+
* Parse ISO date string
|
|
199
|
+
* @private
|
|
200
|
+
* @param {string} input - ISO date string
|
|
201
|
+
* @param {DateFormat} format - ISO format type
|
|
202
|
+
* @returns {Date} Parsed date
|
|
203
|
+
*/
|
|
204
|
+
function parseISODate(input, format) {
|
|
205
|
+
try {
|
|
206
|
+
if (format === 'ISO_DATE') {
|
|
207
|
+
// ISO date without time: YYYY-MM-DD
|
|
208
|
+
// Ensure we have a complete date
|
|
209
|
+
const parts = input.split('-').map((part) => parseInt(part, 10));
|
|
210
|
+
if (parts.length < 3 || parts.some(isNaN)) {
|
|
211
|
+
throw new Error('Invalid ISO date format');
|
|
212
|
+
}
|
|
213
|
+
// Create date at midnight UTC
|
|
214
|
+
const [year, month, day] = parts;
|
|
215
|
+
return new Date(Date.UTC(year, month - 1, day));
|
|
216
|
+
}
|
|
217
|
+
if (format === 'ISO_TIME') {
|
|
218
|
+
// ISO time only: HH:MM:SS.mmm
|
|
219
|
+
const today = new Date();
|
|
220
|
+
const datePart = today.toISOString().split('T')[0];
|
|
221
|
+
return new Date(`${datePart}T${input}Z`);
|
|
222
|
+
}
|
|
223
|
+
// Full ISO format
|
|
224
|
+
const date = new Date(input);
|
|
225
|
+
if (!isDate(date)) {
|
|
226
|
+
throw new Error('Invalid ISO date');
|
|
227
|
+
}
|
|
228
|
+
return date;
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
throw new Error(`Invalid ISO ${format} format: ${input}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Parse US date string (MM/DD/YYYY or MM/DD/YY)
|
|
236
|
+
* @private
|
|
237
|
+
* @param {string} input - US date string
|
|
238
|
+
* @returns {Date} Parsed date
|
|
239
|
+
*/
|
|
240
|
+
function parseUSDate(input) {
|
|
241
|
+
try {
|
|
242
|
+
// Handle various US date formats: MM/DD/YYYY, MM/DD/YY, M/D/YYYY, etc.
|
|
243
|
+
const parts = input.split('/');
|
|
244
|
+
if (parts.length !== 3) {
|
|
245
|
+
throw new Error('Invalid US date format - expected MM/DD/YYYY');
|
|
246
|
+
}
|
|
247
|
+
let month = parseInt(parts[0], 10) - 1; // Convert to 0-based
|
|
248
|
+
let day = parseInt(parts[1], 10);
|
|
249
|
+
let year = parseInt(parts[2], 10);
|
|
250
|
+
if (isNaN(month) || isNaN(day) || isNaN(year)) {
|
|
251
|
+
throw new Error('Invalid US date format - non-numeric parts');
|
|
252
|
+
}
|
|
253
|
+
// Handle 2-digit years
|
|
254
|
+
if (year < 100) {
|
|
255
|
+
const currentYear = new Date().getFullYear();
|
|
256
|
+
const century = Math.floor(currentYear / 100) * 100;
|
|
257
|
+
year += year < 50 ? century : century - 100;
|
|
258
|
+
}
|
|
259
|
+
// Validate date parts
|
|
260
|
+
if (month < 0 || month > 11) {
|
|
261
|
+
throw new Error(`Month must be between 1 and 12, got ${month + 1}`);
|
|
262
|
+
}
|
|
263
|
+
const date = new Date(year, month, day);
|
|
264
|
+
if (!isDate(date)) {
|
|
265
|
+
throw new Error('Invalid date');
|
|
266
|
+
}
|
|
267
|
+
// Check if parsed date matches input (handles invalid dates like February 30)
|
|
268
|
+
if (date.getFullYear() !== year ||
|
|
269
|
+
date.getMonth() !== month ||
|
|
270
|
+
date.getDate() !== day) {
|
|
271
|
+
throw new Error('Invalid date');
|
|
272
|
+
}
|
|
273
|
+
return date;
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
throw new Error(`Invalid US date format: ${input} - ${error instanceof Error ? error.message : String(error)}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Parse EU date string (DD/MM/YYYY or DD/MM/YY)
|
|
281
|
+
* @private
|
|
282
|
+
* @param {string} input - EU date string
|
|
283
|
+
* @returns {Date} Parsed date
|
|
284
|
+
*/
|
|
285
|
+
function parseEUDate(input) {
|
|
286
|
+
try {
|
|
287
|
+
// Handle various EU date formats: DD/MM/YYYY, DD/MM/YY, D/M/YYYY, etc.
|
|
288
|
+
const parts = input.split('/');
|
|
289
|
+
if (parts.length !== 3) {
|
|
290
|
+
throw new Error('Invalid EU date format - expected DD/MM/YYYY');
|
|
291
|
+
}
|
|
292
|
+
let day = parseInt(parts[0], 10);
|
|
293
|
+
let month = parseInt(parts[1], 10) - 1; // Convert to 0-based
|
|
294
|
+
let year = parseInt(parts[2], 10);
|
|
295
|
+
if (isNaN(day) || isNaN(month) || isNaN(year)) {
|
|
296
|
+
throw new Error('Invalid EU date format - non-numeric parts');
|
|
297
|
+
}
|
|
298
|
+
// Handle 2-digit years
|
|
299
|
+
if (year < 100) {
|
|
300
|
+
const currentYear = new Date().getFullYear();
|
|
301
|
+
const century = Math.floor(currentYear / 100) * 100;
|
|
302
|
+
year += year < 50 ? century : century - 100;
|
|
303
|
+
}
|
|
304
|
+
// Validate date parts
|
|
305
|
+
if (month < 0 || month > 11) {
|
|
306
|
+
throw new Error(`Month must be between 1 and 12, got ${month + 1}`);
|
|
307
|
+
}
|
|
308
|
+
const date = new Date(year, month, day);
|
|
309
|
+
if (!isDate(date)) {
|
|
310
|
+
throw new Error('Invalid date');
|
|
311
|
+
}
|
|
312
|
+
// Check if parsed date matches input
|
|
313
|
+
if (date.getFullYear() !== year ||
|
|
314
|
+
date.getMonth() !== month ||
|
|
315
|
+
date.getDate() !== day) {
|
|
316
|
+
throw new Error('Invalid date');
|
|
317
|
+
}
|
|
318
|
+
return date;
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
throw new Error(`Invalid EU date format: ${input} - ${error instanceof Error ? error.message : String(error)}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Parse date with custom format string
|
|
326
|
+
* @private
|
|
327
|
+
* @param {string} input - Date string
|
|
328
|
+
* @param {string} format - Custom format string
|
|
329
|
+
* @returns {Date} Parsed date
|
|
330
|
+
*/
|
|
331
|
+
function parseCustomDate(input, format) {
|
|
332
|
+
try {
|
|
333
|
+
if (!format) {
|
|
334
|
+
throw new Error('Custom format string is required');
|
|
335
|
+
}
|
|
336
|
+
// Simple custom format parsing
|
|
337
|
+
// Supported tokens: YYYY, YY, MM, M, DD, D, HH, H, hh, h, mm, m, ss, s
|
|
338
|
+
const formatPattern = format
|
|
339
|
+
.replace(/YYYY/g, '(\\d{4})')
|
|
340
|
+
.replace(/YY/g, '(\\d{2})')
|
|
341
|
+
.replace(/MM/g, '(\\d{2})')
|
|
342
|
+
.replace(/M/g, '(\\d{1,2})')
|
|
343
|
+
.replace(/DD/g, '(\\d{2})')
|
|
344
|
+
.replace(/D/g, '(\\d{1,2})')
|
|
345
|
+
.replace(/HH/g, '(\\d{2})')
|
|
346
|
+
.replace(/H/g, '(\\d{1,2})')
|
|
347
|
+
.replace(/hh/g, '(\\d{2})')
|
|
348
|
+
.replace(/h/g, '(\\d{1,2})')
|
|
349
|
+
.replace(/mm/g, '(\\d{2})')
|
|
350
|
+
.replace(/m/g, '(\\d{1,2})')
|
|
351
|
+
.replace(/ss/g, '(\\d{2})')
|
|
352
|
+
.replace(/s/g, '(\\d{1,2})');
|
|
353
|
+
const regex = new RegExp(`^${formatPattern}$`);
|
|
354
|
+
const match = input.match(regex);
|
|
355
|
+
if (!match) {
|
|
356
|
+
throw new Error(`Input does not match custom format: ${format}`);
|
|
357
|
+
}
|
|
358
|
+
// Extract values based on format
|
|
359
|
+
let year = 0, month = 0, day = 1, hour = 0, minute = 0, second = 0;
|
|
360
|
+
let matchIndex = 1;
|
|
361
|
+
const formatTokens = format.match(/YYYY|YY|MM|M|DD|D|HH|H|hh|h|mm|m|ss|s/g) || [];
|
|
362
|
+
for (const token of formatTokens) {
|
|
363
|
+
const value = parseInt(match[matchIndex], 10);
|
|
364
|
+
if (isNaN(value)) {
|
|
365
|
+
throw new Error(`Invalid numeric value for token ${token}`);
|
|
366
|
+
}
|
|
367
|
+
switch (token) {
|
|
368
|
+
case 'YYYY':
|
|
369
|
+
year = value;
|
|
370
|
+
break;
|
|
371
|
+
case 'YY':
|
|
372
|
+
year = value;
|
|
373
|
+
// Handle 2-digit years
|
|
374
|
+
if (year < 100) {
|
|
375
|
+
const currentYear = new Date().getFullYear();
|
|
376
|
+
const century = Math.floor(currentYear / 100) * 100;
|
|
377
|
+
year += year < 50 ? century : century - 100;
|
|
378
|
+
}
|
|
379
|
+
break;
|
|
380
|
+
case 'MM':
|
|
381
|
+
case 'M':
|
|
382
|
+
month = value - 1; // Convert to 0-based
|
|
383
|
+
break;
|
|
384
|
+
case 'DD':
|
|
385
|
+
case 'D':
|
|
386
|
+
day = value;
|
|
387
|
+
break;
|
|
388
|
+
case 'HH':
|
|
389
|
+
case 'H':
|
|
390
|
+
hour = value;
|
|
391
|
+
break;
|
|
392
|
+
case 'hh':
|
|
393
|
+
case 'h':
|
|
394
|
+
hour = value;
|
|
395
|
+
// Handle 12-hour format (needs AM/PM indicator - not implemented)
|
|
396
|
+
break;
|
|
397
|
+
case 'mm':
|
|
398
|
+
case 'm':
|
|
399
|
+
minute = value;
|
|
400
|
+
break;
|
|
401
|
+
case 'ss':
|
|
402
|
+
case 's':
|
|
403
|
+
second = value;
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
matchIndex++;
|
|
407
|
+
}
|
|
408
|
+
// Validate date parts
|
|
409
|
+
if (year < 1000 || year > 9999) {
|
|
410
|
+
throw new Error(`Year must be between 1000 and 9999: ${year}`);
|
|
411
|
+
}
|
|
412
|
+
if (month < 0 || month > 11) {
|
|
413
|
+
throw new Error(`Month must be between 1 and 12: ${month + 1}`);
|
|
414
|
+
}
|
|
415
|
+
const date = new Date(year, month, day, hour, minute, second);
|
|
416
|
+
if (!isDate(date)) {
|
|
417
|
+
throw new Error('Invalid date');
|
|
418
|
+
}
|
|
419
|
+
// Check if parsed date matches input
|
|
420
|
+
if (date.getFullYear() !== year ||
|
|
421
|
+
date.getMonth() !== month ||
|
|
422
|
+
date.getDate() !== day) {
|
|
423
|
+
throw new Error('Invalid date');
|
|
424
|
+
}
|
|
425
|
+
return date;
|
|
426
|
+
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
throw new Error(`Failed to parse with custom format "${format}": ${error instanceof Error ? error.message : String(error)}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Parse RFC 2822 date string
|
|
433
|
+
* @private
|
|
434
|
+
* @param {string} input - RFC 2822 date string
|
|
435
|
+
* @returns {Date} Parsed date
|
|
436
|
+
*/
|
|
437
|
+
function parseRFC2822Date(input) {
|
|
438
|
+
try {
|
|
439
|
+
const date = new Date(input);
|
|
440
|
+
if (!isDate(date)) {
|
|
441
|
+
throw new Error('Invalid RFC 2822 date');
|
|
442
|
+
}
|
|
443
|
+
return date;
|
|
444
|
+
}
|
|
445
|
+
catch {
|
|
446
|
+
throw new Error(`Invalid RFC 2822 date format: ${input}`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Parse timestamp string (YYYYMMDDHHMMSS or YYYYMMDD)
|
|
451
|
+
* @private
|
|
452
|
+
* @param {string} input - Timestamp string
|
|
453
|
+
* @returns {Date} Parsed date
|
|
454
|
+
*/
|
|
455
|
+
function parseTimestamp(input) {
|
|
456
|
+
try {
|
|
457
|
+
// Handle YYYYMMDDHHMMSS or YYYYMMDD
|
|
458
|
+
if (!/^\d{8}(\d{6})?$/.test(input)) {
|
|
459
|
+
throw new Error('Invalid timestamp format - expected YYYYMMDD or YYYYMMDDHHMMSS');
|
|
460
|
+
}
|
|
461
|
+
const year = parseInt(input.substring(0, 4), 10);
|
|
462
|
+
const month = parseInt(input.substring(4, 6), 10) - 1;
|
|
463
|
+
const day = parseInt(input.substring(6, 8), 10);
|
|
464
|
+
let hour = 0, minute = 0, second = 0;
|
|
465
|
+
if (input.length === 14) {
|
|
466
|
+
hour = parseInt(input.substring(8, 10), 10);
|
|
467
|
+
minute = parseInt(input.substring(10, 12), 10);
|
|
468
|
+
second = parseInt(input.substring(12, 14), 10);
|
|
469
|
+
}
|
|
470
|
+
// Validate date parts
|
|
471
|
+
if (month < 0 || month > 11) {
|
|
472
|
+
throw new Error(`Month must be between 1 and 12: ${month + 1}`);
|
|
473
|
+
}
|
|
474
|
+
const date = new Date(year, month, day, hour, minute, second);
|
|
475
|
+
if (!isDate(date)) {
|
|
476
|
+
throw new Error('Invalid timestamp');
|
|
477
|
+
}
|
|
478
|
+
return date;
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
throw new Error(`Invalid timestamp format: ${input} - ${error instanceof Error ? error.message : String(error)}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Parse date using locale-specific formatting
|
|
486
|
+
* @private
|
|
487
|
+
* @param {string} input - Date string
|
|
488
|
+
* @param {string} locale - Locale to use for parsing
|
|
489
|
+
* @param {boolean} strict - Whether to throw errors
|
|
490
|
+
* @returns {Date} Parsed date
|
|
491
|
+
*/
|
|
492
|
+
function parseLocaleDate(input, locale, strict) {
|
|
493
|
+
try {
|
|
494
|
+
// Try to parse with Date constructor first (handles many common formats)
|
|
495
|
+
const date = new Date(input);
|
|
496
|
+
if (isDate(date)) {
|
|
497
|
+
return date;
|
|
498
|
+
}
|
|
499
|
+
// Try using Intl.DateTimeFormat for locale-aware parsing
|
|
500
|
+
const formatter = new Intl.DateTimeFormat(locale, {
|
|
501
|
+
year: 'numeric',
|
|
502
|
+
month: 'numeric',
|
|
503
|
+
day: 'numeric',
|
|
504
|
+
hour: 'numeric',
|
|
505
|
+
minute: 'numeric',
|
|
506
|
+
second: 'numeric',
|
|
507
|
+
});
|
|
508
|
+
// This is a simplification - in practice, you'd need a proper parser
|
|
509
|
+
// For now, fall back to Date constructor
|
|
510
|
+
const fallback = new Date(input);
|
|
511
|
+
if (isDate(fallback)) {
|
|
512
|
+
return fallback;
|
|
513
|
+
}
|
|
514
|
+
if (strict) {
|
|
515
|
+
throw new Error(`Could not parse date with locale ${locale}`);
|
|
516
|
+
}
|
|
517
|
+
return new Date(NaN); // Return invalid date
|
|
518
|
+
}
|
|
519
|
+
catch (error) {
|
|
520
|
+
if (strict) {
|
|
521
|
+
throw new Error(`Failed to parse locale date: ${error instanceof Error ? error.message : String(error)}`);
|
|
522
|
+
}
|
|
523
|
+
return new Date(NaN);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Apply timezone to a date
|
|
528
|
+
* @private
|
|
529
|
+
* @param {Date} date - Date to adjust
|
|
530
|
+
* @param {string} timezone - Target timezone
|
|
531
|
+
* @returns {Date} Date with timezone applied
|
|
532
|
+
*/
|
|
533
|
+
function applyTimezone(date, timezone) {
|
|
534
|
+
if (!timezone || timezone === 'UTC') {
|
|
535
|
+
return date;
|
|
536
|
+
}
|
|
537
|
+
// For now, return the date as-is
|
|
538
|
+
// In a production environment, you'd use a library like date-fns-tz or moment-timezone
|
|
539
|
+
return date;
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Validate if a value is a valid Date object
|
|
543
|
+
* @function isDate
|
|
544
|
+
* @param {unknown} date - Value to validate
|
|
545
|
+
* @returns {date is Date} True if value is a valid Date object
|
|
546
|
+
* @example
|
|
547
|
+
* isDate(new Date()); // true
|
|
548
|
+
* isDate('invalid'); // false
|
|
549
|
+
* isDate(new Date('invalid')); // false
|
|
550
|
+
*/
|
|
551
|
+
export function isDate(date) {
|
|
552
|
+
// More robust validation that doesn't rely solely on instanceof
|
|
553
|
+
if (!date || typeof date !== 'object') {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
// Check if it has Date-like properties
|
|
557
|
+
const d = date;
|
|
558
|
+
if (typeof d.getTime !== 'function' || typeof d.getFullYear !== 'function') {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
// Check if the internal time value is valid
|
|
562
|
+
const time = d.getTime();
|
|
563
|
+
return !isNaN(time) && typeof time === 'number' && isFinite(time);
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Format a date to string with various formatting options
|
|
567
|
+
* @function formatDate
|
|
568
|
+
* @param {Date | string | number} date - Date to format
|
|
569
|
+
* @param {FormatOptions} [options={}] - Formatting options
|
|
570
|
+
* @returns {string} Formatted date string
|
|
571
|
+
* @example
|
|
572
|
+
* // Format as ISO
|
|
573
|
+
* formatDate(new Date(), { format: 'ISO' });
|
|
574
|
+
*
|
|
575
|
+
* // Format as US date
|
|
576
|
+
* formatDate('2024-01-15', { format: 'US' });
|
|
577
|
+
*
|
|
578
|
+
* // Format with custom format
|
|
579
|
+
* formatDate(new Date(), { format: 'CUSTOM', customFormat: 'YYYY-MM-DD HH:mm:ss' });
|
|
580
|
+
*/
|
|
581
|
+
export function formatDate(date, options = {}) {
|
|
582
|
+
const parsedDate = parseDate(date);
|
|
583
|
+
const { format = 'ISO', customFormat = '', timezone = config.defaultTimezone, locale = config.defaultLocale, includeTime = true, includeSeconds = false, includeMilliseconds = false, } = options;
|
|
584
|
+
// Convert to target timezone if needed
|
|
585
|
+
let targetDate = parsedDate;
|
|
586
|
+
if (timezone && timezone !== 'UTC') {
|
|
587
|
+
targetDate = convertTimezone(parsedDate, timezone);
|
|
588
|
+
}
|
|
589
|
+
switch (format) {
|
|
590
|
+
case 'ISO':
|
|
591
|
+
return targetDate.toISOString();
|
|
592
|
+
case 'ISO_DATE':
|
|
593
|
+
return targetDate.toISOString().split('T')[0];
|
|
594
|
+
case 'ISO_TIME': {
|
|
595
|
+
const timePart = targetDate.toISOString().split('T')[1];
|
|
596
|
+
return includeMilliseconds ? timePart : timePart.split('.')[0];
|
|
597
|
+
}
|
|
598
|
+
case 'US':
|
|
599
|
+
return formatUSDate(targetDate);
|
|
600
|
+
case 'EU':
|
|
601
|
+
return formatEUDate(targetDate);
|
|
602
|
+
case 'FULL':
|
|
603
|
+
return formatFullDate(targetDate, locale);
|
|
604
|
+
case 'LONG':
|
|
605
|
+
return formatLongDate(targetDate, locale);
|
|
606
|
+
case 'MEDIUM':
|
|
607
|
+
return formatMediumDate(targetDate, locale);
|
|
608
|
+
case 'SHORT':
|
|
609
|
+
return formatShortDate(targetDate, locale);
|
|
610
|
+
case 'TIME':
|
|
611
|
+
return formatTime(targetDate, locale, includeSeconds);
|
|
612
|
+
case 'TIME_24':
|
|
613
|
+
return formatTime24(targetDate, includeSeconds);
|
|
614
|
+
case 'DATETIME':
|
|
615
|
+
return `${formatMediumDate(targetDate, locale)}, ${formatTime(targetDate, locale, includeSeconds)}`;
|
|
616
|
+
case 'TIMESTAMP':
|
|
617
|
+
return formatTimestamp(targetDate);
|
|
618
|
+
case 'RFC_2822':
|
|
619
|
+
return formatRFC2822(targetDate);
|
|
620
|
+
case 'CUSTOM':
|
|
621
|
+
return formatCustomDate(targetDate, customFormat);
|
|
622
|
+
default:
|
|
623
|
+
return targetDate.toISOString();
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Get current date and time
|
|
628
|
+
* @function now
|
|
629
|
+
* @returns {Date} Current date and time
|
|
630
|
+
* @example
|
|
631
|
+
* const current = now(); // Returns current Date object
|
|
632
|
+
*/
|
|
633
|
+
export function now() {
|
|
634
|
+
return new Date();
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Get today's date at midnight (00:00:00.000)
|
|
638
|
+
* @function today
|
|
639
|
+
* @returns {Date} Today's date at midnight
|
|
640
|
+
* @example
|
|
641
|
+
* const todayDate = today(); // Returns today at midnight
|
|
642
|
+
*/
|
|
643
|
+
export function today() {
|
|
644
|
+
return startOfDay(new Date());
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Get tomorrow's date at midnight
|
|
648
|
+
* @function tomorrow
|
|
649
|
+
* @returns {Date} Tomorrow's date at midnight
|
|
650
|
+
* @example
|
|
651
|
+
* const tomorrowDate = tomorrow(); // Returns tomorrow at midnight
|
|
652
|
+
*/
|
|
653
|
+
export function tomorrow() {
|
|
654
|
+
return addDays(today(), 1);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Get yesterday's date at midnight
|
|
658
|
+
* @function yesterday
|
|
659
|
+
* @returns {Date} Yesterday's date at midnight
|
|
660
|
+
* @example
|
|
661
|
+
* const yesterdayDate = yesterday(); // Returns yesterday at midnight
|
|
662
|
+
*/
|
|
663
|
+
export function yesterday() {
|
|
664
|
+
return subtractDays(today(), 1);
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Create a date from individual components
|
|
668
|
+
* @function createDate
|
|
669
|
+
* @param {number} year - Year (e.g., 2024)
|
|
670
|
+
* @param {Month} month - Month (0-11, where 0 = January)
|
|
671
|
+
* @param {number} day - Day of month (1-31)
|
|
672
|
+
* @param {number} [hour=0] - Hour (0-23)
|
|
673
|
+
* @param {number} [minute=0] - Minute (0-59)
|
|
674
|
+
* @param {number} [second=0] - Second (0-59)
|
|
675
|
+
* @param {number} [millisecond=0] - Millisecond (0-999)
|
|
676
|
+
* @returns {Date} Created Date object
|
|
677
|
+
* @throws {Error} If any component is out of valid range
|
|
678
|
+
* @example
|
|
679
|
+
* // Create date for January 15, 2024, 10:30:45
|
|
680
|
+
* createDate(2024, 0, 15, 10, 30, 45);
|
|
681
|
+
*/
|
|
682
|
+
export function createDate(year, month, day, hour = 0, minute = 0, second = 0, millisecond = 0) {
|
|
683
|
+
validateDateParts(year, month, day, hour, minute, second, millisecond);
|
|
684
|
+
return new Date(year, month, day, hour, minute, second, millisecond);
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Add time to a date
|
|
688
|
+
* @function add
|
|
689
|
+
* @param {Date | string | number} date - Base date
|
|
690
|
+
* @param {number} amount - Amount to add (can be negative to subtract)
|
|
691
|
+
* @param {DateUnit} [unit='day'] - Unit of time to add
|
|
692
|
+
* @returns {Date} New date after addition
|
|
693
|
+
* @example
|
|
694
|
+
* // Add 5 days
|
|
695
|
+
* add(new Date(), 5, 'day');
|
|
696
|
+
*
|
|
697
|
+
* // Add 2 months
|
|
698
|
+
* add('2024-01-15', 2, 'month');
|
|
699
|
+
*
|
|
700
|
+
* // Subtract 3 hours
|
|
701
|
+
* add(new Date(), -3, 'hour');
|
|
702
|
+
*/
|
|
703
|
+
export function add(date, amount, unit = 'day') {
|
|
704
|
+
const parsedDate = parseDate(date);
|
|
705
|
+
const result = new Date(parsedDate);
|
|
706
|
+
switch (unit) {
|
|
707
|
+
case 'millisecond':
|
|
708
|
+
result.setMilliseconds(result.getMilliseconds() + amount);
|
|
709
|
+
break;
|
|
710
|
+
case 'second':
|
|
711
|
+
result.setSeconds(result.getSeconds() + amount);
|
|
712
|
+
break;
|
|
713
|
+
case 'minute':
|
|
714
|
+
result.setMinutes(result.getMinutes() + amount);
|
|
715
|
+
break;
|
|
716
|
+
case 'hour':
|
|
717
|
+
result.setHours(result.getHours() + amount);
|
|
718
|
+
break;
|
|
719
|
+
case 'day':
|
|
720
|
+
result.setDate(result.getDate() + amount);
|
|
721
|
+
break;
|
|
722
|
+
case 'week':
|
|
723
|
+
result.setDate(result.getDate() + amount * DAYS_IN_WEEK);
|
|
724
|
+
break;
|
|
725
|
+
case 'month':
|
|
726
|
+
result.setMonth(result.getMonth() + amount);
|
|
727
|
+
break;
|
|
728
|
+
case 'quarter':
|
|
729
|
+
result.setMonth(result.getMonth() + amount * 3);
|
|
730
|
+
break;
|
|
731
|
+
case 'year':
|
|
732
|
+
result.setFullYear(result.getFullYear() + amount);
|
|
733
|
+
break;
|
|
734
|
+
default:
|
|
735
|
+
throw new Error(`Unsupported date unit: ${unit}`);
|
|
736
|
+
}
|
|
737
|
+
return result;
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Subtract time from a date
|
|
741
|
+
* @function subtract
|
|
742
|
+
* @param {Date | string | number} date - Base date
|
|
743
|
+
* @param {number} amount - Amount to subtract (can be negative to add)
|
|
744
|
+
* @param {DateUnit} [unit='day'] - Unit of time to subtract
|
|
745
|
+
* @returns {Date} New date after subtraction
|
|
746
|
+
* @example
|
|
747
|
+
* // Subtract 5 days
|
|
748
|
+
* subtract(new Date(), 5, 'day');
|
|
749
|
+
*
|
|
750
|
+
* // Subtract 2 months
|
|
751
|
+
* subtract('2024-01-15', 2, 'month');
|
|
752
|
+
*/
|
|
753
|
+
export function subtract(date, amount, unit = 'day') {
|
|
754
|
+
return add(date, -amount, unit);
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Add days to a date
|
|
758
|
+
* @function addDays
|
|
759
|
+
* @param {Date | string | number} date - Base date
|
|
760
|
+
* @param {number} days - Number of days to add (can be negative)
|
|
761
|
+
* @returns {Date} New date after adding days
|
|
762
|
+
* @example
|
|
763
|
+
* // Add 5 days
|
|
764
|
+
* addDays(new Date(), 5);
|
|
765
|
+
*
|
|
766
|
+
* // Subtract 3 days
|
|
767
|
+
* addDays('2024-01-15', -3);
|
|
768
|
+
*/
|
|
769
|
+
export function addDays(date, days) {
|
|
770
|
+
return add(date, days, 'day');
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Subtract days from a date
|
|
774
|
+
* @function subtractDays
|
|
775
|
+
* @param {Date | string | number} date - Base date
|
|
776
|
+
* @param {number} days - Number of days to subtract
|
|
777
|
+
* @returns {Date} New date after subtracting days
|
|
778
|
+
* @example
|
|
779
|
+
* // Subtract 5 days
|
|
780
|
+
* subtractDays(new Date(), 5);
|
|
781
|
+
*/
|
|
782
|
+
export function subtractDays(date, days) {
|
|
783
|
+
return subtract(date, days, 'day');
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Add months to a date
|
|
787
|
+
* @function addMonths
|
|
788
|
+
* @param {Date | string | number} date - Base date
|
|
789
|
+
* @param {number} months - Number of months to add (can be negative)
|
|
790
|
+
* @returns {Date} New date after adding months
|
|
791
|
+
* @example
|
|
792
|
+
* // Add 2 months
|
|
793
|
+
* addMonths(new Date(), 2);
|
|
794
|
+
*
|
|
795
|
+
* // Subtract 3 months
|
|
796
|
+
* addMonths('2024-01-15', -3);
|
|
797
|
+
*/
|
|
798
|
+
export function addMonths(date, months) {
|
|
799
|
+
return add(date, months, 'month');
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Subtract months from a date
|
|
803
|
+
* @function subtractMonths
|
|
804
|
+
* @param {Date | string | number} date - Base date
|
|
805
|
+
* @param {number} months - Number of months to subtract
|
|
806
|
+
* @returns {Date} New date after subtracting months
|
|
807
|
+
* @example
|
|
808
|
+
* // Subtract 2 months
|
|
809
|
+
* subtractMonths(new Date(), 2);
|
|
810
|
+
*/
|
|
811
|
+
export function subtractMonths(date, months) {
|
|
812
|
+
return subtract(date, months, 'month');
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Add years to a date
|
|
816
|
+
* @function addYears
|
|
817
|
+
* @param {Date | string | number} date - Base date
|
|
818
|
+
* @param {number} years - Number of years to add (can be negative)
|
|
819
|
+
* @returns {Date} New date after adding years
|
|
820
|
+
* @example
|
|
821
|
+
* // Add 1 year
|
|
822
|
+
* addYears(new Date(), 1);
|
|
823
|
+
*
|
|
824
|
+
* // Subtract 5 years
|
|
825
|
+
* addYears('2024-01-15', -5);
|
|
826
|
+
*/
|
|
827
|
+
export function addYears(date, years) {
|
|
828
|
+
return add(date, years, 'year');
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Subtract years from a date
|
|
832
|
+
* @function subtractYears
|
|
833
|
+
* @param {Date | string | number} date - Base date
|
|
834
|
+
* @param {number} years - Number of years to subtract
|
|
835
|
+
* @returns {Date} New date after subtracting years
|
|
836
|
+
* @example
|
|
837
|
+
* // Subtract 10 years
|
|
838
|
+
* subtractYears(new Date(), 10);
|
|
839
|
+
*/
|
|
840
|
+
export function subtractYears(date, years) {
|
|
841
|
+
return subtract(date, years, 'year');
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Get the start of a time unit
|
|
845
|
+
* @function startOf
|
|
846
|
+
* @param {Date | string | number} date - Base date
|
|
847
|
+
* @param {DateUnit} unit - Time unit to get start of
|
|
848
|
+
* @returns {Date} Date representing the start of the specified unit
|
|
849
|
+
* @example
|
|
850
|
+
* // Start of month
|
|
851
|
+
* startOf(new Date(), 'month');
|
|
852
|
+
*
|
|
853
|
+
* // Start of year
|
|
854
|
+
* startOf('2024-06-15', 'year');
|
|
855
|
+
*
|
|
856
|
+
* // Start of week (configurable via weekStartsOn)
|
|
857
|
+
* startOf(new Date(), 'week');
|
|
858
|
+
*/
|
|
859
|
+
export function startOf(date, unit) {
|
|
860
|
+
const parsedDate = parseDate(date);
|
|
861
|
+
const result = new Date(parsedDate);
|
|
862
|
+
switch (unit) {
|
|
863
|
+
case 'year':
|
|
864
|
+
result.setMonth(0, 1);
|
|
865
|
+
result.setHours(0, 0, 0, 0);
|
|
866
|
+
break;
|
|
867
|
+
case 'month':
|
|
868
|
+
result.setDate(1);
|
|
869
|
+
result.setHours(0, 0, 0, 0);
|
|
870
|
+
break;
|
|
871
|
+
case 'day':
|
|
872
|
+
result.setHours(0, 0, 0, 0);
|
|
873
|
+
break;
|
|
874
|
+
case 'hour':
|
|
875
|
+
result.setMinutes(0, 0, 0);
|
|
876
|
+
break;
|
|
877
|
+
case 'minute':
|
|
878
|
+
result.setSeconds(0, 0);
|
|
879
|
+
break;
|
|
880
|
+
case 'second':
|
|
881
|
+
result.setMilliseconds(0);
|
|
882
|
+
break;
|
|
883
|
+
case 'week': {
|
|
884
|
+
const dayOfWeek = result.getDay();
|
|
885
|
+
const diff = (dayOfWeek - config.weekStartsOn + DAYS_IN_WEEK) % DAYS_IN_WEEK;
|
|
886
|
+
result.setDate(result.getDate() - diff);
|
|
887
|
+
result.setHours(0, 0, 0, 0);
|
|
888
|
+
break;
|
|
889
|
+
}
|
|
890
|
+
case 'quarter': {
|
|
891
|
+
const quarter = Math.floor(result.getMonth() / 3);
|
|
892
|
+
result.setMonth(quarter * 3, 1);
|
|
893
|
+
result.setHours(0, 0, 0, 0);
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
896
|
+
default:
|
|
897
|
+
throw new Error(`Unsupported unit for startOf: ${unit}`);
|
|
898
|
+
}
|
|
899
|
+
return result;
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Get the end of a time unit
|
|
903
|
+
* @function endOf
|
|
904
|
+
* @param {Date | string | number} date - Base date
|
|
905
|
+
* @param {DateUnit} unit - Time unit to get end of
|
|
906
|
+
* @returns {Date} Date representing the end of the specified unit
|
|
907
|
+
* @example
|
|
908
|
+
* // End of month
|
|
909
|
+
* endOf(new Date(), 'month');
|
|
910
|
+
*
|
|
911
|
+
* // End of day (23:59:59.999)
|
|
912
|
+
* endOf('2024-01-15', 'day');
|
|
913
|
+
*/
|
|
914
|
+
export function endOf(date, unit) {
|
|
915
|
+
const start = startOf(date, unit);
|
|
916
|
+
const result = new Date(start);
|
|
917
|
+
switch (unit) {
|
|
918
|
+
case 'year':
|
|
919
|
+
result.setFullYear(result.getFullYear() + 1);
|
|
920
|
+
break;
|
|
921
|
+
case 'quarter':
|
|
922
|
+
result.setMonth(result.getMonth() + 3);
|
|
923
|
+
break;
|
|
924
|
+
case 'month':
|
|
925
|
+
result.setMonth(result.getMonth() + 1);
|
|
926
|
+
break;
|
|
927
|
+
case 'week':
|
|
928
|
+
result.setDate(result.getDate() + DAYS_IN_WEEK);
|
|
929
|
+
break;
|
|
930
|
+
case 'day':
|
|
931
|
+
result.setDate(result.getDate() + 1);
|
|
932
|
+
break;
|
|
933
|
+
case 'hour':
|
|
934
|
+
result.setHours(result.getHours() + 1);
|
|
935
|
+
break;
|
|
936
|
+
case 'minute':
|
|
937
|
+
result.setMinutes(result.getMinutes() + 1);
|
|
938
|
+
break;
|
|
939
|
+
case 'second':
|
|
940
|
+
result.setSeconds(result.getSeconds() + 1);
|
|
941
|
+
break;
|
|
942
|
+
default:
|
|
943
|
+
throw new Error(`Unsupported unit for endOf: ${unit}`);
|
|
944
|
+
}
|
|
945
|
+
result.setMilliseconds(result.getMilliseconds() - 1);
|
|
946
|
+
return result;
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Get start of day (midnight)
|
|
950
|
+
* @function startOfDay
|
|
951
|
+
* @param {Date | string | number} date - Base date
|
|
952
|
+
* @returns {Date} Date at 00:00:00.000
|
|
953
|
+
* @example
|
|
954
|
+
* startOfDay(new Date()); // Returns today at midnight
|
|
955
|
+
*/
|
|
956
|
+
export function startOfDay(date) {
|
|
957
|
+
return startOf(date, 'day');
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Get end of day (last millisecond)
|
|
961
|
+
* @function endOfDay
|
|
962
|
+
* @param {Date | string | number} date - Base date
|
|
963
|
+
* @returns {Date} Date at 23:59:59.999
|
|
964
|
+
* @example
|
|
965
|
+
* endOfDay(new Date()); // Returns today at 23:59:59.999
|
|
966
|
+
*/
|
|
967
|
+
export function endOfDay(date) {
|
|
968
|
+
return endOf(date, 'day');
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Get start of month
|
|
972
|
+
* @function startOfMonth
|
|
973
|
+
* @param {Date | string | number} date - Base date
|
|
974
|
+
* @returns {Date} First day of month at 00:00:00.000
|
|
975
|
+
* @example
|
|
976
|
+
* startOfMonth(new Date()); // Returns first day of current month
|
|
977
|
+
*/
|
|
978
|
+
export function startOfMonth(date) {
|
|
979
|
+
return startOf(date, 'month');
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Get end of month
|
|
983
|
+
* @function endOfMonth
|
|
984
|
+
* @param {Date | string | number} date - Base date
|
|
985
|
+
* @returns {Date} Last day of month at 23:59:59.999
|
|
986
|
+
* @example
|
|
987
|
+
* endOfMonth(new Date()); // Returns last day of current month at 23:59:59.999
|
|
988
|
+
*/
|
|
989
|
+
export function endOfMonth(date) {
|
|
990
|
+
return endOf(date, 'month');
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Get start of year
|
|
994
|
+
* @function startOfYear
|
|
995
|
+
* @param {Date | string | number} date - Base date
|
|
996
|
+
* @returns {Date} January 1st at 00:00:00.000
|
|
997
|
+
* @example
|
|
998
|
+
* startOfYear(new Date()); // Returns January 1st of current year
|
|
999
|
+
*/
|
|
1000
|
+
export function startOfYear(date) {
|
|
1001
|
+
return startOf(date, 'year');
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Get end of year
|
|
1005
|
+
* @function endOfYear
|
|
1006
|
+
* @param {Date | string | number} date - Base date
|
|
1007
|
+
* @returns {Date} December 31st at 23:59:59.999
|
|
1008
|
+
* @example
|
|
1009
|
+
* endOfYear(new Date()); // Returns December 31st of current year at 23:59:59.999
|
|
1010
|
+
*/
|
|
1011
|
+
export function endOfYear(date) {
|
|
1012
|
+
return endOf(date, 'year');
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Get start of week
|
|
1016
|
+
* @function startOfWeek
|
|
1017
|
+
* @param {Date | string | number} date - Base date
|
|
1018
|
+
* @returns {Date} Start of week (configurable via weekStartsOn) at 00:00:00.000
|
|
1019
|
+
* @example
|
|
1020
|
+
* // With weekStartsOn = 0 (Sunday)
|
|
1021
|
+
* startOfWeek(new Date('2024-01-15')); // Returns Sunday, January 14, 2024
|
|
1022
|
+
*/
|
|
1023
|
+
export function startOfWeek(date) {
|
|
1024
|
+
return startOf(date, 'week');
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Get end of week
|
|
1028
|
+
* @function endOfWeek
|
|
1029
|
+
* @param {Date | string | number} date - Base date
|
|
1030
|
+
* @returns {Date} End of week (configurable via weekStartsOn) at 23:59:59.999
|
|
1031
|
+
* @example
|
|
1032
|
+
* // With weekStartsOn = 0 (Sunday)
|
|
1033
|
+
* endOfWeek(new Date('2024-01-15')); // Returns Saturday, January 20, 2024
|
|
1034
|
+
*/
|
|
1035
|
+
export function endOfWeek(date) {
|
|
1036
|
+
return endOf(date, 'week');
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Check if a date is between two other dates
|
|
1040
|
+
* @function isBetween
|
|
1041
|
+
* @param {Date | string | number} date - Date to check
|
|
1042
|
+
* @param {Date | string | number} start - Start of range
|
|
1043
|
+
* @param {Date | string | number} end - End of range
|
|
1044
|
+
* @param {boolean} [inclusive=true] - Whether to include boundaries
|
|
1045
|
+
* @returns {boolean} True if date is between start and end
|
|
1046
|
+
* @example
|
|
1047
|
+
* // Check if date is in January 2024
|
|
1048
|
+
* isBetween('2024-01-15', '2024-01-01', '2024-01-31');
|
|
1049
|
+
*
|
|
1050
|
+
* // Exclusive check
|
|
1051
|
+
* isBetween('2024-01-01', '2024-01-01', '2024-01-31', false); // false
|
|
1052
|
+
*/
|
|
1053
|
+
export function isBetween(date, start, end, inclusive = true) {
|
|
1054
|
+
const parsedDate = parseDate(date);
|
|
1055
|
+
const parsedStart = parseDate(start);
|
|
1056
|
+
const parsedEnd = parseDate(end);
|
|
1057
|
+
if (inclusive) {
|
|
1058
|
+
return parsedDate >= parsedStart && parsedDate <= parsedEnd;
|
|
1059
|
+
}
|
|
1060
|
+
return parsedDate > parsedStart && parsedDate < parsedEnd;
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Check if a date is before another date
|
|
1064
|
+
* @function isBefore
|
|
1065
|
+
* @param {Date | string | number} date - Date to check
|
|
1066
|
+
* @param {Date | string | number} compareTo - Date to compare against
|
|
1067
|
+
* @returns {boolean} True if date is before compareTo
|
|
1068
|
+
* @example
|
|
1069
|
+
* isBefore('2024-01-15', '2024-01-20'); // true
|
|
1070
|
+
* isBefore('2024-01-20', '2024-01-15'); // false
|
|
1071
|
+
*/
|
|
1072
|
+
export function isBefore(date, compareTo) {
|
|
1073
|
+
return parseDate(date) < parseDate(compareTo);
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Check if a date is after another date
|
|
1077
|
+
* @function isAfter
|
|
1078
|
+
* @param {Date | string | number} date - Date to check
|
|
1079
|
+
* @param {Date | string | number} compareTo - Date to compare against
|
|
1080
|
+
* @returns {boolean} True if date is after compareTo
|
|
1081
|
+
* @example
|
|
1082
|
+
* isAfter('2024-01-20', '2024-01-15'); // true
|
|
1083
|
+
* isAfter('2024-01-15', '2024-01-20'); // false
|
|
1084
|
+
*/
|
|
1085
|
+
export function isAfter(date, compareTo) {
|
|
1086
|
+
return parseDate(date) > parseDate(compareTo);
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Check if two dates are the same (optionally by specific unit)
|
|
1090
|
+
* @function isSame
|
|
1091
|
+
* @param {Date | string | number} date1 - First date
|
|
1092
|
+
* @param {Date | string | number} date2 - Second date
|
|
1093
|
+
* @param {DateUnit} [unit] - Unit to compare (if not provided, compares exact timestamps)
|
|
1094
|
+
* @returns {boolean} True if dates are the same
|
|
1095
|
+
* @example
|
|
1096
|
+
* // Exact timestamp comparison
|
|
1097
|
+
* isSame(new Date('2024-01-15T10:30:00'), new Date('2024-01-15T10:30:00')); // true
|
|
1098
|
+
*
|
|
1099
|
+
* // Same day comparison
|
|
1100
|
+
* isSame('2024-01-15T10:30:00', '2024-01-15T15:45:00', 'day'); // true
|
|
1101
|
+
*
|
|
1102
|
+
* // Same month comparison
|
|
1103
|
+
* isSame('2024-01-15', '2024-01-31', 'month'); // true
|
|
1104
|
+
*/
|
|
1105
|
+
export function isSame(date1, date2, unit) {
|
|
1106
|
+
const d1 = parseDate(date1);
|
|
1107
|
+
const d2 = parseDate(date2);
|
|
1108
|
+
if (!unit) {
|
|
1109
|
+
return d1.getTime() === d2.getTime();
|
|
1110
|
+
}
|
|
1111
|
+
const start1 = startOf(d1, unit);
|
|
1112
|
+
const start2 = startOf(d2, unit);
|
|
1113
|
+
return start1.getTime() === start2.getTime();
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Check if a date is today
|
|
1117
|
+
* @function isToday
|
|
1118
|
+
* @param {Date | string | number} date - Date to check
|
|
1119
|
+
* @returns {boolean} True if date is today
|
|
1120
|
+
* @example
|
|
1121
|
+
* isToday(new Date()); // true
|
|
1122
|
+
* isToday('2024-01-15'); // depends on current date
|
|
1123
|
+
*/
|
|
1124
|
+
export function isToday(date) {
|
|
1125
|
+
return isSame(date, today(), 'day');
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Check if a date is in the past
|
|
1129
|
+
* @function isPast
|
|
1130
|
+
* @param {Date | string | number} date - Date to check
|
|
1131
|
+
* @returns {boolean} True if date is before now
|
|
1132
|
+
* @example
|
|
1133
|
+
* isPast('2020-01-01'); // true
|
|
1134
|
+
* isPast('2030-01-01'); // false
|
|
1135
|
+
*/
|
|
1136
|
+
export function isPast(date) {
|
|
1137
|
+
return parseDate(date) < now();
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Check if a date is in the future
|
|
1141
|
+
* @function isFuture
|
|
1142
|
+
* @param {Date | string | number} date - Date to check
|
|
1143
|
+
* @returns {boolean} True if date is after now
|
|
1144
|
+
* @example
|
|
1145
|
+
* isFuture('2030-01-01'); // true
|
|
1146
|
+
* isFuture('2020-01-01'); // false
|
|
1147
|
+
*/
|
|
1148
|
+
export function isFuture(date) {
|
|
1149
|
+
return parseDate(date) > now();
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Calculate the difference between two dates in multiple units
|
|
1153
|
+
* @function difference
|
|
1154
|
+
* @param {Date | string | number} date1 - First date
|
|
1155
|
+
* @param {Date | string | number} date2 - Second date
|
|
1156
|
+
* @returns {DateDifference} Object containing differences in various units
|
|
1157
|
+
* @example
|
|
1158
|
+
* const diff = difference('2024-01-10', '2024-01-15');
|
|
1159
|
+
* console.log(diff.days); // 5
|
|
1160
|
+
* console.log(diff.totalHours); // 120
|
|
1161
|
+
*/
|
|
1162
|
+
export function difference(date1, date2) {
|
|
1163
|
+
const d1 = parseDate(date1);
|
|
1164
|
+
const d2 = parseDate(date2);
|
|
1165
|
+
const diffMs = Math.abs(d2.getTime() - d1.getTime());
|
|
1166
|
+
const totalSeconds = Math.floor(diffMs / MILLISECONDS_IN_SECOND);
|
|
1167
|
+
const totalMinutes = Math.floor(totalSeconds / SECONDS_IN_MINUTE);
|
|
1168
|
+
const totalHours = Math.floor(totalMinutes / MINUTES_IN_HOUR);
|
|
1169
|
+
const totalDays = Math.floor(totalHours / HOURS_IN_DAY);
|
|
1170
|
+
return {
|
|
1171
|
+
years: Math.floor(totalDays / 365),
|
|
1172
|
+
months: Math.floor(totalDays / 30),
|
|
1173
|
+
weeks: Math.floor(totalDays / DAYS_IN_WEEK),
|
|
1174
|
+
days: totalDays,
|
|
1175
|
+
hours: totalHours,
|
|
1176
|
+
minutes: totalMinutes,
|
|
1177
|
+
seconds: totalSeconds,
|
|
1178
|
+
milliseconds: diffMs,
|
|
1179
|
+
totalDays,
|
|
1180
|
+
totalHours,
|
|
1181
|
+
totalMinutes,
|
|
1182
|
+
totalSeconds,
|
|
1183
|
+
totalMilliseconds: diffMs,
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Get human-readable description of date difference
|
|
1188
|
+
* @function humanizeDifference
|
|
1189
|
+
* @param {Date | string | number} date1 - First date
|
|
1190
|
+
* @param {Date | string | number} [date2=now()] - Second date (defaults to now)
|
|
1191
|
+
* @param {boolean} [detailed=false] - Whether to use detailed descriptions
|
|
1192
|
+
* @returns {string} Human-readable difference
|
|
1193
|
+
* @example
|
|
1194
|
+
* // Returns something like "2 days ago" or "in 3 hours"
|
|
1195
|
+
* humanizeDifference('2024-01-13');
|
|
1196
|
+
*
|
|
1197
|
+
* // Detailed format
|
|
1198
|
+
* humanizeDifference('2024-01-13', new Date(), true); // "2 days ago"
|
|
1199
|
+
*/
|
|
1200
|
+
export function humanizeDifference(date1, date2 = now(), detailed = false) {
|
|
1201
|
+
const diff = difference(date1, date2);
|
|
1202
|
+
const isPast = parseDate(date1) < parseDate(date2);
|
|
1203
|
+
const prefix = isPast ? 'ago' : 'in';
|
|
1204
|
+
if (diff.years > 0) {
|
|
1205
|
+
return detailed
|
|
1206
|
+
? `${diff.years} year${diff.years === 1 ? '' : 's'} ${prefix}`
|
|
1207
|
+
: `${diff.years}y ${prefix}`;
|
|
1208
|
+
}
|
|
1209
|
+
if (diff.months > 0) {
|
|
1210
|
+
return detailed
|
|
1211
|
+
? `${diff.months} month${diff.months === 1 ? '' : 's'} ${prefix}`
|
|
1212
|
+
: `${diff.months}mo ${prefix}`;
|
|
1213
|
+
}
|
|
1214
|
+
if (diff.weeks > 0) {
|
|
1215
|
+
return detailed
|
|
1216
|
+
? `${diff.weeks} week${diff.weeks === 1 ? '' : 's'} ${prefix}`
|
|
1217
|
+
: `${diff.weeks}w ${prefix}`;
|
|
1218
|
+
}
|
|
1219
|
+
if (diff.days > 0) {
|
|
1220
|
+
return detailed
|
|
1221
|
+
? `${diff.days} day${diff.days === 1 ? '' : 's'} ${prefix}`
|
|
1222
|
+
: `${diff.days}d ${prefix}`;
|
|
1223
|
+
}
|
|
1224
|
+
if (diff.hours > 0) {
|
|
1225
|
+
return detailed
|
|
1226
|
+
? `${diff.hours} hour${diff.hours === 1 ? '' : 's'} ${prefix}`
|
|
1227
|
+
: `${diff.hours}h ${prefix}`;
|
|
1228
|
+
}
|
|
1229
|
+
if (diff.minutes > 0) {
|
|
1230
|
+
return detailed
|
|
1231
|
+
? `${diff.minutes} minute${diff.minutes === 1 ? '' : 's'} ${prefix}`
|
|
1232
|
+
: `${diff.minutes}m ${prefix}`;
|
|
1233
|
+
}
|
|
1234
|
+
return detailed
|
|
1235
|
+
? `${diff.seconds} second${diff.seconds === 1 ? '' : 's'} ${prefix}`
|
|
1236
|
+
: `${diff.seconds}s ${prefix}`;
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Check if a date falls on a weekend
|
|
1240
|
+
* @function isWeekend
|
|
1241
|
+
* @param {Date | string | number} date - Date to check
|
|
1242
|
+
* @returns {boolean} True if date is Saturday or Sunday
|
|
1243
|
+
* @example
|
|
1244
|
+
* isWeekend('2024-01-15'); // false (Monday)
|
|
1245
|
+
* isWeekend('2024-01-20'); // true (Saturday)
|
|
1246
|
+
*/
|
|
1247
|
+
export function isWeekend(date) {
|
|
1248
|
+
const parsedDate = parseDate(date);
|
|
1249
|
+
const day = parsedDate.getDay();
|
|
1250
|
+
return day === 0 || day === 6; // Sunday or Saturday
|
|
1251
|
+
}
|
|
1252
|
+
/**
|
|
1253
|
+
* Check if a date falls on a weekday
|
|
1254
|
+
* @function isWeekday
|
|
1255
|
+
* @param {Date | string | number} date - Date to check
|
|
1256
|
+
* @returns {boolean} True if date is Monday through Friday
|
|
1257
|
+
* @example
|
|
1258
|
+
* isWeekday('2024-01-15'); // true (Monday)
|
|
1259
|
+
* isWeekday('2024-01-20'); // false (Saturday)
|
|
1260
|
+
*/
|
|
1261
|
+
export function isWeekday(date) {
|
|
1262
|
+
return !isWeekend(date);
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Check if a date is a business day (considering weekends and holidays)
|
|
1266
|
+
* @function isBusinessDay
|
|
1267
|
+
* @param {Date | string | number} date - Date to check
|
|
1268
|
+
* @returns {boolean} True if date is a business day
|
|
1269
|
+
* @example
|
|
1270
|
+
* // Assuming Monday-Friday are business days
|
|
1271
|
+
* isBusinessDay('2024-01-15'); // true (Monday)
|
|
1272
|
+
* isBusinessDay('2024-01-20'); // false (Saturday)
|
|
1273
|
+
*/
|
|
1274
|
+
export function isBusinessDay(date) {
|
|
1275
|
+
const parsedDate = parseDate(date);
|
|
1276
|
+
// Check if it's a weekend
|
|
1277
|
+
if (isWeekend(parsedDate)) {
|
|
1278
|
+
return false;
|
|
1279
|
+
}
|
|
1280
|
+
// Check if it's a configured business day
|
|
1281
|
+
if (!config.businessDays.businessDays.includes(parsedDate.getDay())) {
|
|
1282
|
+
return false;
|
|
1283
|
+
}
|
|
1284
|
+
// Check if it's a holiday
|
|
1285
|
+
const isHoliday = config.businessDays.holidays?.some((holiday) => isSame(holiday, parsedDate, 'day')) || config.businessDays.holidayCheck?.(parsedDate);
|
|
1286
|
+
return !isHoliday;
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* Add business days to a date (skips weekends and holidays)
|
|
1290
|
+
* @function addBusinessDays
|
|
1291
|
+
* @param {Date | string | number} date - Base date
|
|
1292
|
+
* @param {number} days - Number of business days to add (can be negative)
|
|
1293
|
+
* @returns {Date} New date after adding business days
|
|
1294
|
+
* @example
|
|
1295
|
+
* // Add 3 business days to Friday
|
|
1296
|
+
* addBusinessDays('2024-01-12', 3); // Returns Wednesday, January 17
|
|
1297
|
+
*/
|
|
1298
|
+
export function addBusinessDays(date, days) {
|
|
1299
|
+
let result = parseDate(date);
|
|
1300
|
+
let daysAdded = 0;
|
|
1301
|
+
const direction = days > 0 ? 1 : -1;
|
|
1302
|
+
while (daysAdded < Math.abs(days)) {
|
|
1303
|
+
result = addDays(result, direction);
|
|
1304
|
+
if (isBusinessDay(result)) {
|
|
1305
|
+
daysAdded++;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
return result;
|
|
1309
|
+
}
|
|
1310
|
+
/**
|
|
1311
|
+
* Calculate number of days between two dates
|
|
1312
|
+
* @function daysBetween
|
|
1313
|
+
* @param {Date | string | number} start - Start date
|
|
1314
|
+
* @param {Date | string | number} end - End date
|
|
1315
|
+
* @param {boolean} [inclusive=false] - Whether to include both start and end dates
|
|
1316
|
+
* @returns {number} Number of days between dates
|
|
1317
|
+
* @example
|
|
1318
|
+
* daysBetween('2024-01-10', '2024-01-15'); // 5
|
|
1319
|
+
* daysBetween('2024-01-10', '2024-01-15', true); // 6
|
|
1320
|
+
*/
|
|
1321
|
+
export function daysBetween(start, end, inclusive = false) {
|
|
1322
|
+
const startDate = startOfDay(parseDate(start));
|
|
1323
|
+
const endDate = startOfDay(parseDate(end));
|
|
1324
|
+
const diffMs = Math.abs(endDate.getTime() - startDate.getTime());
|
|
1325
|
+
const days = Math.floor(diffMs /
|
|
1326
|
+
(MILLISECONDS_IN_SECOND *
|
|
1327
|
+
SECONDS_IN_MINUTE *
|
|
1328
|
+
MINUTES_IN_HOUR *
|
|
1329
|
+
HOURS_IN_DAY));
|
|
1330
|
+
return inclusive ? days + 1 : days;
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Calculate number of months between two dates
|
|
1334
|
+
* @function monthsBetween
|
|
1335
|
+
* @param {Date | string | number} start - Start date
|
|
1336
|
+
* @param {Date | string | number} end - End date
|
|
1337
|
+
* @returns {number} Number of months between dates
|
|
1338
|
+
* @example
|
|
1339
|
+
* monthsBetween('2024-01-15', '2024-03-15'); // 2
|
|
1340
|
+
* monthsBetween('2024-01-31', '2024-02-28'); // 1
|
|
1341
|
+
*/
|
|
1342
|
+
export function monthsBetween(start, end) {
|
|
1343
|
+
const startDate = parseDate(start);
|
|
1344
|
+
const endDate = parseDate(end);
|
|
1345
|
+
const yearsDiff = endDate.getFullYear() - startDate.getFullYear();
|
|
1346
|
+
const monthsDiff = endDate.getMonth() - startDate.getMonth();
|
|
1347
|
+
return yearsDiff * 12 + monthsDiff;
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Calculate number of years between two dates
|
|
1351
|
+
* @function yearsBetween
|
|
1352
|
+
* @param {Date | string | number} start - Start date
|
|
1353
|
+
* @param {Date | string | number} end - End date
|
|
1354
|
+
* @returns {number} Number of years between dates
|
|
1355
|
+
* @example
|
|
1356
|
+
* yearsBetween('2020-06-15', '2024-01-15'); // 4
|
|
1357
|
+
*/
|
|
1358
|
+
export function yearsBetween(start, end) {
|
|
1359
|
+
const startDate = parseDate(start);
|
|
1360
|
+
const endDate = parseDate(end);
|
|
1361
|
+
return endDate.getFullYear() - startDate.getFullYear();
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Calculate age from birth date
|
|
1365
|
+
* @function getAge
|
|
1366
|
+
* @param {Date | string | number} birthDate - Birth date
|
|
1367
|
+
* @param {Date | string | number} [asOfDate=now()] - Date to calculate age as of
|
|
1368
|
+
* @returns {number} Age in years
|
|
1369
|
+
* @example
|
|
1370
|
+
* getAge('1990-06-15', '2024-01-15'); // 33
|
|
1371
|
+
* getAge('2000-02-29', '2024-02-28'); // 23
|
|
1372
|
+
* getAge('2000-02-29', '2024-02-29'); // 24
|
|
1373
|
+
*/
|
|
1374
|
+
export function getAge(birthDate, asOfDate = now()) {
|
|
1375
|
+
const birth = parseDate(birthDate);
|
|
1376
|
+
const asOf = parseDate(asOfDate);
|
|
1377
|
+
let age = asOf.getFullYear() - birth.getFullYear();
|
|
1378
|
+
const monthDiff = asOf.getMonth() - birth.getMonth();
|
|
1379
|
+
if (monthDiff < 0 || (monthDiff === 0 && asOf.getDate() < birth.getDate())) {
|
|
1380
|
+
age--;
|
|
1381
|
+
}
|
|
1382
|
+
return Math.max(0, age);
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Get ISO week number of year
|
|
1386
|
+
* @function getWeekNumber
|
|
1387
|
+
* @param {Date | string | number} date - Date to check
|
|
1388
|
+
* @returns {number} Week number (1-53)
|
|
1389
|
+
* @example
|
|
1390
|
+
* getWeekNumber('2024-01-01'); // 1
|
|
1391
|
+
* getWeekNumber('2024-01-15'); // 3
|
|
1392
|
+
*/
|
|
1393
|
+
export function getWeekNumber(date) {
|
|
1394
|
+
const parsedDate = parseDate(date);
|
|
1395
|
+
const firstDayOfYear = new Date(parsedDate.getFullYear(), 0, 1);
|
|
1396
|
+
const pastDaysOfYear = (parsedDate.getTime() - firstDayOfYear.getTime()) /
|
|
1397
|
+
(MILLISECONDS_IN_SECOND *
|
|
1398
|
+
SECONDS_IN_MINUTE *
|
|
1399
|
+
MINUTES_IN_HOUR *
|
|
1400
|
+
HOURS_IN_DAY);
|
|
1401
|
+
return Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / DAYS_IN_WEEK);
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* Get quarter of year
|
|
1405
|
+
* @function getQuarter
|
|
1406
|
+
* @param {Date | string | number} date - Date to check
|
|
1407
|
+
* @returns {number} Quarter number (1-4)
|
|
1408
|
+
* @example
|
|
1409
|
+
* getQuarter('2024-01-15'); // 1
|
|
1410
|
+
* getQuarter('2024-04-15'); // 2
|
|
1411
|
+
* getQuarter('2024-07-15'); // 3
|
|
1412
|
+
* getQuarter('2024-10-15'); // 4
|
|
1413
|
+
*/
|
|
1414
|
+
export function getQuarter(date) {
|
|
1415
|
+
const month = parseDate(date).getMonth();
|
|
1416
|
+
return Math.floor(month / 3) + 1;
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* Get day name
|
|
1420
|
+
* @function getDayName
|
|
1421
|
+
* @param {Date | string | number} date - Date to check
|
|
1422
|
+
* @param {'full' | 'short'} [format='full'] - Format of day name
|
|
1423
|
+
* @returns {string} Day name
|
|
1424
|
+
* @example
|
|
1425
|
+
* getDayName('2024-01-15'); // "Monday"
|
|
1426
|
+
* getDayName('2024-01-15', 'short'); // "Mon"
|
|
1427
|
+
*/
|
|
1428
|
+
export function getDayName(date, format = 'full') {
|
|
1429
|
+
const parsedDate = parseDate(date);
|
|
1430
|
+
const dayIndex = parsedDate.getDay();
|
|
1431
|
+
return format === 'full'
|
|
1432
|
+
? WEEKDAY_NAMES[dayIndex]
|
|
1433
|
+
: WEEKDAY_NAMES_SHORT[dayIndex];
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Get month name
|
|
1437
|
+
* @function getMonthName
|
|
1438
|
+
* @param {Date | string | number} date - Date to check
|
|
1439
|
+
* @param {'full' | 'short'} [format='full'] - Format of month name
|
|
1440
|
+
* @returns {string} Month name
|
|
1441
|
+
* @example
|
|
1442
|
+
* getMonthName('2024-01-15'); // "January"
|
|
1443
|
+
* getMonthName('2024-01-15', 'short'); // "Jan"
|
|
1444
|
+
*/
|
|
1445
|
+
export function getMonthName(date, format = 'full') {
|
|
1446
|
+
const parsedDate = parseDate(date);
|
|
1447
|
+
const monthIndex = parsedDate.getMonth();
|
|
1448
|
+
return format === 'full'
|
|
1449
|
+
? MONTH_NAMES[monthIndex]
|
|
1450
|
+
: MONTH_NAMES_SHORT[monthIndex];
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Get number of days in month
|
|
1454
|
+
* @function getDaysInMonth
|
|
1455
|
+
* @param {Date | string | number} date - Date to check
|
|
1456
|
+
* @returns {number} Number of days in month
|
|
1457
|
+
* @example
|
|
1458
|
+
* getDaysInMonth('2024-01-15'); // 31
|
|
1459
|
+
* getDaysInMonth('2024-02-15'); // 29 (leap year)
|
|
1460
|
+
* getDaysInMonth('2023-02-15'); // 28
|
|
1461
|
+
*/
|
|
1462
|
+
export function getDaysInMonth(date) {
|
|
1463
|
+
const parsedDate = parseDate(date);
|
|
1464
|
+
return new Date(parsedDate.getFullYear(), parsedDate.getMonth() + 1, 0).getDate();
|
|
1465
|
+
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Check if year is a leap year
|
|
1468
|
+
* @function isLeapYear
|
|
1469
|
+
* @param {number} year - Year to check
|
|
1470
|
+
* @returns {boolean} True if year is a leap year
|
|
1471
|
+
* @example
|
|
1472
|
+
* isLeapYear(2024); // true
|
|
1473
|
+
* isLeapYear(2023); // false
|
|
1474
|
+
* isLeapYear(2000); // true (century divisible by 400)
|
|
1475
|
+
* isLeapYear(1900); // false (century not divisible by 400)
|
|
1476
|
+
*/
|
|
1477
|
+
export function isLeapYear(year) {
|
|
1478
|
+
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Validate a date with constraints
|
|
1482
|
+
* @function validateDate
|
|
1483
|
+
* @param {Date | string | number} date - Date to validate
|
|
1484
|
+
* @param {ValidationOptions} [options={}] - Validation options
|
|
1485
|
+
* @returns {ValidationResult} Validation result with errors if any
|
|
1486
|
+
* @example
|
|
1487
|
+
* // Simple validation
|
|
1488
|
+
* validateDate('2024-01-15');
|
|
1489
|
+
*
|
|
1490
|
+
* // With constraints
|
|
1491
|
+
* validateDate('2024-01-15', {
|
|
1492
|
+
* min: '2024-01-01',
|
|
1493
|
+
* max: '2024-12-31',
|
|
1494
|
+
* allowPast: false,
|
|
1495
|
+
* });
|
|
1496
|
+
*/
|
|
1497
|
+
export function validateDate(date, options = {}) {
|
|
1498
|
+
const errors = [];
|
|
1499
|
+
try {
|
|
1500
|
+
const parsedDate = parseDate(date);
|
|
1501
|
+
if (options.required && !parsedDate) {
|
|
1502
|
+
errors.push('Date is required');
|
|
1503
|
+
}
|
|
1504
|
+
if (options.min || options.minDate) {
|
|
1505
|
+
const minDate = parseDate(options.min || options.minDate);
|
|
1506
|
+
if (parsedDate < minDate) {
|
|
1507
|
+
errors.push(`Date must be on or after ${formatDate(minDate, {
|
|
1508
|
+
format: 'MEDIUM',
|
|
1509
|
+
})}`);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
if (options.max || options.maxDate) {
|
|
1513
|
+
const maxDate = parseDate(options.max || options.maxDate);
|
|
1514
|
+
if (parsedDate > maxDate) {
|
|
1515
|
+
errors.push(`Date must be on or before ${formatDate(maxDate, {
|
|
1516
|
+
format: 'MEDIUM',
|
|
1517
|
+
})}`);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
if (options.allowPast === false && isPast(parsedDate)) {
|
|
1521
|
+
errors.push('Date must be in the future');
|
|
1522
|
+
}
|
|
1523
|
+
if (options.allowFuture === false && isFuture(parsedDate)) {
|
|
1524
|
+
errors.push('Date must be in the past');
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
catch (error) {
|
|
1528
|
+
errors.push(error instanceof Error ? error.message : 'Invalid date');
|
|
1529
|
+
}
|
|
1530
|
+
return {
|
|
1531
|
+
isValid: errors.length === 0,
|
|
1532
|
+
errors,
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
/**
|
|
1536
|
+
* Get timezone information for a date
|
|
1537
|
+
* @function getTimezoneInfo
|
|
1538
|
+
* @param {Date | string | number} date - Date to get timezone info for
|
|
1539
|
+
* @returns {TimezoneInfo} Timezone information
|
|
1540
|
+
* @example
|
|
1541
|
+
* const info = getTimezoneInfo(new Date());
|
|
1542
|
+
* console.log(info.name); // "America/New_York"
|
|
1543
|
+
* console.log(info.offsetString); // "UTC-05:00"
|
|
1544
|
+
*/
|
|
1545
|
+
export function getTimezoneInfo(date) {
|
|
1546
|
+
const parsedDate = parseDate(date);
|
|
1547
|
+
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1548
|
+
const offset = -parsedDate.getTimezoneOffset();
|
|
1549
|
+
const hours = Math.floor(Math.abs(offset) / 60);
|
|
1550
|
+
const minutes = Math.abs(offset) % 60;
|
|
1551
|
+
const sign = offset >= 0 ? '+' : '-';
|
|
1552
|
+
return {
|
|
1553
|
+
abbreviation: getTimezoneAbbreviation(parsedDate),
|
|
1554
|
+
name: timezone,
|
|
1555
|
+
offset,
|
|
1556
|
+
offsetString: `UTC${sign}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`,
|
|
1557
|
+
isDST: isDST(parsedDate),
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Convert date to another timezone
|
|
1562
|
+
* @function convertTimezone
|
|
1563
|
+
* @param {Date | string | number} date - Date to convert
|
|
1564
|
+
* @param {string} targetTimezone - Target timezone (e.g., 'America/New_York', 'UTC')
|
|
1565
|
+
* @returns {Date} Date converted to target timezone
|
|
1566
|
+
* @example
|
|
1567
|
+
* // Convert to UTC
|
|
1568
|
+
* convertTimezone(new Date(), 'UTC');
|
|
1569
|
+
*
|
|
1570
|
+
* // Convert to New York time
|
|
1571
|
+
* convertTimezone('2024-01-15T10:30:00Z', 'America/New_York');
|
|
1572
|
+
*/
|
|
1573
|
+
export function convertTimezone(date, targetTimezone) {
|
|
1574
|
+
const parsedDate = parseDate(date);
|
|
1575
|
+
if (targetTimezone === 'UTC') {
|
|
1576
|
+
return new Date(parsedDate.getTime() + parsedDate.getTimezoneOffset() * 60000);
|
|
1577
|
+
}
|
|
1578
|
+
// For other timezones, use Intl.DateTimeFormat
|
|
1579
|
+
const formatter = new Intl.DateTimeFormat('en-US', {
|
|
1580
|
+
timeZone: targetTimezone,
|
|
1581
|
+
year: 'numeric',
|
|
1582
|
+
month: 'numeric',
|
|
1583
|
+
day: 'numeric',
|
|
1584
|
+
hour: 'numeric',
|
|
1585
|
+
minute: 'numeric',
|
|
1586
|
+
second: 'numeric',
|
|
1587
|
+
});
|
|
1588
|
+
const parts = formatter.formatToParts(parsedDate);
|
|
1589
|
+
const getPart = (type) => parts.find((p) => p.type === type)?.value;
|
|
1590
|
+
return createDate(parseInt(getPart('year')), (parseInt(getPart('month')) - 1), parseInt(getPart('day')), parseInt(getPart('hour')), parseInt(getPart('minute')), parseInt(getPart('second')));
|
|
1591
|
+
}
|
|
1592
|
+
// ============================================================================
|
|
1593
|
+
// Internal Helper Functions
|
|
1594
|
+
// ============================================================================
|
|
1595
|
+
// /**
|
|
1596
|
+
// * Parse ISO date string
|
|
1597
|
+
// * @private
|
|
1598
|
+
// * @param {string} input - ISO date string
|
|
1599
|
+
// * @param {DateFormat} format - ISO format type
|
|
1600
|
+
// * @returns {Date} Parsed date
|
|
1601
|
+
// */
|
|
1602
|
+
// function parseISODate(input: string, format: DateFormat): Date {
|
|
1603
|
+
// if (format === 'ISO_DATE') {
|
|
1604
|
+
// // Ensure we have a complete date
|
|
1605
|
+
// const parts = input.split('-');
|
|
1606
|
+
// if (parts.length < 3) {
|
|
1607
|
+
// throw new Error('Invalid ISO date format');
|
|
1608
|
+
// }
|
|
1609
|
+
// return new Date(`${input}T00:00:00.000Z`);
|
|
1610
|
+
// }
|
|
1611
|
+
// if (format === 'ISO_TIME') {
|
|
1612
|
+
// // Parse time only (use today's date)
|
|
1613
|
+
// const today = new Date().toISOString().split('T')[0];
|
|
1614
|
+
// return new Date(`${today}T${input}Z`);
|
|
1615
|
+
// }
|
|
1616
|
+
// // Full ISO format
|
|
1617
|
+
// return new Date(input);
|
|
1618
|
+
// }
|
|
1619
|
+
// /**
|
|
1620
|
+
// * Parse US date string (MM/DD/YYYY or MM/DD/YY)
|
|
1621
|
+
// * @private
|
|
1622
|
+
// * @param {string} input - US date string
|
|
1623
|
+
// * @returns {Date} Parsed date
|
|
1624
|
+
// */
|
|
1625
|
+
// function parseUSDate(input: string): Date {
|
|
1626
|
+
// // MM/DD/YYYY or MM/DD/YY
|
|
1627
|
+
// const parts = input.split('/');
|
|
1628
|
+
// if (parts.length !== 3) {
|
|
1629
|
+
// throw new Error('Invalid US date format');
|
|
1630
|
+
// }
|
|
1631
|
+
// let month = parseInt(parts[0]) - 1;
|
|
1632
|
+
// let day = parseInt(parts[1]);
|
|
1633
|
+
// let year = parseInt(parts[2]);
|
|
1634
|
+
// // Handle 2-digit years
|
|
1635
|
+
// if (year < 100) {
|
|
1636
|
+
// year += year < 50 ? 2000 : 1900;
|
|
1637
|
+
// }
|
|
1638
|
+
// validateDateParts(year, month as Month, day);
|
|
1639
|
+
// return new Date(year, month, day);
|
|
1640
|
+
// }
|
|
1641
|
+
// /**
|
|
1642
|
+
// * Parse EU date string (DD/MM/YYYY or DD/MM/YY)
|
|
1643
|
+
// * @private
|
|
1644
|
+
// * @param {string} input - EU date string
|
|
1645
|
+
// * @returns {Date} Parsed date
|
|
1646
|
+
// */
|
|
1647
|
+
// function parseEUDate(input: string): Date {
|
|
1648
|
+
// // DD/MM/YYYY or DD/MM/YY
|
|
1649
|
+
// const parts = input.split('/');
|
|
1650
|
+
// if (parts.length !== 3) {
|
|
1651
|
+
// throw new Error('Invalid EU date format');
|
|
1652
|
+
// }
|
|
1653
|
+
// let day = parseInt(parts[0]);
|
|
1654
|
+
// let month = parseInt(parts[1]) - 1;
|
|
1655
|
+
// let year = parseInt(parts[2]);
|
|
1656
|
+
// // Handle 2-digit years
|
|
1657
|
+
// if (year < 100) {
|
|
1658
|
+
// year += year < 50 ? 2000 : 1900;
|
|
1659
|
+
// }
|
|
1660
|
+
// validateDateParts(year, month as Month, day);
|
|
1661
|
+
// return new Date(year, month, day);
|
|
1662
|
+
// }
|
|
1663
|
+
// /**
|
|
1664
|
+
// * Parse date with custom format string
|
|
1665
|
+
// * @private
|
|
1666
|
+
// * @param {string} input - Date string
|
|
1667
|
+
// * @param {string} format - Custom format string
|
|
1668
|
+
// * @returns {Date} Parsed date
|
|
1669
|
+
// */
|
|
1670
|
+
// function parseCustomDate(input: string, format: string): Date {
|
|
1671
|
+
// // Simple custom format parsing
|
|
1672
|
+
// const formatChars = format.split('');
|
|
1673
|
+
// const inputChars = input.split('');
|
|
1674
|
+
// let year = 0,
|
|
1675
|
+
// month = 0,
|
|
1676
|
+
// day = 0,
|
|
1677
|
+
// hour = 0,
|
|
1678
|
+
// minute = 0,
|
|
1679
|
+
// second = 0;
|
|
1680
|
+
// for (let i = 0; i < formatChars.length; i++) {
|
|
1681
|
+
// const formatChar = formatChars[i];
|
|
1682
|
+
// const inputChar = inputChars[i];
|
|
1683
|
+
// switch (formatChar) {
|
|
1684
|
+
// case 'Y':
|
|
1685
|
+
// year = year * 10 + parseInt(inputChar);
|
|
1686
|
+
// break;
|
|
1687
|
+
// case 'M':
|
|
1688
|
+
// month = month * 10 + parseInt(inputChar);
|
|
1689
|
+
// break;
|
|
1690
|
+
// case 'D':
|
|
1691
|
+
// day = day * 10 + parseInt(inputChar);
|
|
1692
|
+
// break;
|
|
1693
|
+
// case 'h':
|
|
1694
|
+
// hour = hour * 10 + parseInt(inputChar);
|
|
1695
|
+
// break;
|
|
1696
|
+
// case 'm':
|
|
1697
|
+
// minute = minute * 10 + parseInt(inputChar);
|
|
1698
|
+
// break;
|
|
1699
|
+
// case 's':
|
|
1700
|
+
// second = second * 10 + parseInt(inputChar);
|
|
1701
|
+
// break;
|
|
1702
|
+
// default:
|
|
1703
|
+
// // Skip separator characters
|
|
1704
|
+
// break;
|
|
1705
|
+
// }
|
|
1706
|
+
// }
|
|
1707
|
+
// // Adjust month (format is 1-based, Date is 0-based)
|
|
1708
|
+
// month = Math.max(1, month) - 1;
|
|
1709
|
+
// validateDateParts(year, month as Month, day, hour, minute, second);
|
|
1710
|
+
// return new Date(year, month, day, hour, minute, second);
|
|
1711
|
+
// }
|
|
1712
|
+
// /**
|
|
1713
|
+
// * Parse RFC 2822 date string
|
|
1714
|
+
// * @private
|
|
1715
|
+
// * @param {string} input - RFC 2822 date string
|
|
1716
|
+
// * @returns {Date} Parsed date
|
|
1717
|
+
// */
|
|
1718
|
+
// function parseRFC2822Date(input: string): Date {
|
|
1719
|
+
// return new Date(input);
|
|
1720
|
+
// }
|
|
1721
|
+
// /**
|
|
1722
|
+
// * Parse timestamp string (YYYYMMDDHHMMSS)
|
|
1723
|
+
// * @private
|
|
1724
|
+
// * @param {string} input - Timestamp string
|
|
1725
|
+
// * @returns {Date} Parsed date
|
|
1726
|
+
// */
|
|
1727
|
+
// function parseTimestamp(input: string): Date {
|
|
1728
|
+
// // YYYYMMDDHHMMSS format
|
|
1729
|
+
// if (input.length !== 14) {
|
|
1730
|
+
// throw new Error('Invalid timestamp format');
|
|
1731
|
+
// }
|
|
1732
|
+
// const year = parseInt(input.substring(0, 4));
|
|
1733
|
+
// const month = parseInt(input.substring(4, 6)) - 1;
|
|
1734
|
+
// const day = parseInt(input.substring(6, 8));
|
|
1735
|
+
// const hour = parseInt(input.substring(8, 10));
|
|
1736
|
+
// const minute = parseInt(input.substring(10, 12));
|
|
1737
|
+
// const second = parseInt(input.substring(12, 14));
|
|
1738
|
+
// validateDateParts(year, month as Month, day, hour, minute, second);
|
|
1739
|
+
// return new Date(year, month, day, hour, minute, second);
|
|
1740
|
+
// }
|
|
1741
|
+
// /**
|
|
1742
|
+
// * Parse date using locale-specific formatting
|
|
1743
|
+
// * @private
|
|
1744
|
+
// * @param {string} input - Date string
|
|
1745
|
+
// * @param {string} locale - Locale to use for parsing
|
|
1746
|
+
// * @returns {Date} Parsed date
|
|
1747
|
+
// */
|
|
1748
|
+
// function parseLocaleDate(input: string, locale: string): Date {
|
|
1749
|
+
// // Try various common formats
|
|
1750
|
+
// const formats = [
|
|
1751
|
+
// new Intl.DateTimeFormat(locale, { dateStyle: 'short' }),
|
|
1752
|
+
// new Intl.DateTimeFormat(locale, { dateStyle: 'medium' }),
|
|
1753
|
+
// new Intl.DateTimeFormat(locale, { dateStyle: 'long' }),
|
|
1754
|
+
// ];
|
|
1755
|
+
// for (const formatter of formats) {
|
|
1756
|
+
// try {
|
|
1757
|
+
// // Use Date's built-in parsing for now
|
|
1758
|
+
// const date = new Date(input);
|
|
1759
|
+
// if (isDate(date)) {
|
|
1760
|
+
// return date;
|
|
1761
|
+
// }
|
|
1762
|
+
// } catch {
|
|
1763
|
+
// // Continue to next format
|
|
1764
|
+
// }
|
|
1765
|
+
// }
|
|
1766
|
+
// throw new Error('Could not parse date with locale');
|
|
1767
|
+
// }
|
|
1768
|
+
/**
|
|
1769
|
+
* Format date as US format (MM/DD/YYYY)
|
|
1770
|
+
* @private
|
|
1771
|
+
* @param {Date} date - Date to format
|
|
1772
|
+
* @returns {string} Formatted date
|
|
1773
|
+
*/
|
|
1774
|
+
function formatUSDate(date) {
|
|
1775
|
+
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
1776
|
+
const day = date.getDate().toString().padStart(2, '0');
|
|
1777
|
+
const year = date.getFullYear();
|
|
1778
|
+
return `${month}/${day}/${year}`;
|
|
1779
|
+
}
|
|
1780
|
+
/**
|
|
1781
|
+
* Format date as EU format (DD/MM/YYYY)
|
|
1782
|
+
* @private
|
|
1783
|
+
* @param {Date} date - Date to format
|
|
1784
|
+
* @returns {string} Formatted date
|
|
1785
|
+
*/
|
|
1786
|
+
function formatEUDate(date) {
|
|
1787
|
+
const day = date.getDate().toString().padStart(2, '0');
|
|
1788
|
+
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
1789
|
+
const year = date.getFullYear();
|
|
1790
|
+
return `${day}/${month}/${year}`;
|
|
1791
|
+
}
|
|
1792
|
+
/**
|
|
1793
|
+
* Format date as full textual format
|
|
1794
|
+
* @private
|
|
1795
|
+
* @param {Date} date - Date to format
|
|
1796
|
+
* @param {string} locale - Locale for formatting
|
|
1797
|
+
* @returns {string} Formatted date
|
|
1798
|
+
*/
|
|
1799
|
+
function formatFullDate(date, locale) {
|
|
1800
|
+
return new Intl.DateTimeFormat(locale, {
|
|
1801
|
+
weekday: 'long',
|
|
1802
|
+
year: 'numeric',
|
|
1803
|
+
month: 'long',
|
|
1804
|
+
day: 'numeric',
|
|
1805
|
+
}).format(date);
|
|
1806
|
+
}
|
|
1807
|
+
/**
|
|
1808
|
+
* Format date as long format
|
|
1809
|
+
* @private
|
|
1810
|
+
* @param {Date} date - Date to format
|
|
1811
|
+
* @param {string} locale - Locale for formatting
|
|
1812
|
+
* @returns {string} Formatted date
|
|
1813
|
+
*/
|
|
1814
|
+
function formatLongDate(date, locale) {
|
|
1815
|
+
return new Intl.DateTimeFormat(locale, {
|
|
1816
|
+
year: 'numeric',
|
|
1817
|
+
month: 'long',
|
|
1818
|
+
day: 'numeric',
|
|
1819
|
+
}).format(date);
|
|
1820
|
+
}
|
|
1821
|
+
/**
|
|
1822
|
+
* Format date as medium format
|
|
1823
|
+
* @private
|
|
1824
|
+
* @param {Date} date - Date to format
|
|
1825
|
+
* @param {string} locale - Locale for formatting
|
|
1826
|
+
* @returns {string} Formatted date
|
|
1827
|
+
*/
|
|
1828
|
+
function formatMediumDate(date, locale) {
|
|
1829
|
+
return new Intl.DateTimeFormat(locale, {
|
|
1830
|
+
year: 'numeric',
|
|
1831
|
+
month: 'short',
|
|
1832
|
+
day: 'numeric',
|
|
1833
|
+
}).format(date);
|
|
1834
|
+
}
|
|
1835
|
+
/**
|
|
1836
|
+
* Format date as short format
|
|
1837
|
+
* @private
|
|
1838
|
+
* @param {Date} date - Date to format
|
|
1839
|
+
* @param {string} locale - Locale for formatting
|
|
1840
|
+
* @returns {string} Formatted date
|
|
1841
|
+
*/
|
|
1842
|
+
function formatShortDate(date, locale) {
|
|
1843
|
+
return new Intl.DateTimeFormat(locale, {
|
|
1844
|
+
year: '2-digit',
|
|
1845
|
+
month: 'numeric',
|
|
1846
|
+
day: 'numeric',
|
|
1847
|
+
}).format(date);
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* Format time
|
|
1851
|
+
* @private
|
|
1852
|
+
* @param {Date} date - Date to format
|
|
1853
|
+
* @param {string} locale - Locale for formatting
|
|
1854
|
+
* @param {boolean} includeSeconds - Whether to include seconds
|
|
1855
|
+
* @returns {string} Formatted time
|
|
1856
|
+
*/
|
|
1857
|
+
function formatTime(date, locale, includeSeconds) {
|
|
1858
|
+
return new Intl.DateTimeFormat(locale, {
|
|
1859
|
+
hour: 'numeric',
|
|
1860
|
+
minute: '2-digit',
|
|
1861
|
+
second: includeSeconds ? '2-digit' : undefined,
|
|
1862
|
+
hour12: true,
|
|
1863
|
+
}).format(date);
|
|
1864
|
+
}
|
|
1865
|
+
/**
|
|
1866
|
+
* Format time in 24-hour format
|
|
1867
|
+
* @private
|
|
1868
|
+
* @param {Date} date - Date to format
|
|
1869
|
+
* @param {boolean} includeSeconds - Whether to include seconds
|
|
1870
|
+
* @returns {string} Formatted time
|
|
1871
|
+
*/
|
|
1872
|
+
function formatTime24(date, includeSeconds) {
|
|
1873
|
+
const hours = date.getHours().toString().padStart(2, '0');
|
|
1874
|
+
const minutes = date.getMinutes().toString().padStart(2, '0');
|
|
1875
|
+
const seconds = includeSeconds
|
|
1876
|
+
? `:${date.getSeconds().toString().padStart(2, '0')}`
|
|
1877
|
+
: '';
|
|
1878
|
+
return `${hours}:${minutes}${seconds}`;
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1881
|
+
* Format date as timestamp (YYYYMMDDHHMMSS)
|
|
1882
|
+
* @private
|
|
1883
|
+
* @param {Date} date - Date to format
|
|
1884
|
+
* @returns {string} Formatted timestamp
|
|
1885
|
+
*/
|
|
1886
|
+
function formatTimestamp(date) {
|
|
1887
|
+
const year = date.getFullYear();
|
|
1888
|
+
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
1889
|
+
const day = date.getDate().toString().padStart(2, '0');
|
|
1890
|
+
const hours = date.getHours().toString().padStart(2, '0');
|
|
1891
|
+
const minutes = date.getMinutes().toString().padStart(2, '0');
|
|
1892
|
+
const seconds = date.getSeconds().toString().padStart(2, '0');
|
|
1893
|
+
return `${year}${month}${day}${hours}${minutes}${seconds}`;
|
|
1894
|
+
}
|
|
1895
|
+
/**
|
|
1896
|
+
* Format date as RFC 2822 string
|
|
1897
|
+
* @private
|
|
1898
|
+
* @param {Date} date - Date to format
|
|
1899
|
+
* @returns {string} RFC 2822 formatted date
|
|
1900
|
+
*/
|
|
1901
|
+
function formatRFC2822(date) {
|
|
1902
|
+
return date.toUTCString();
|
|
1903
|
+
}
|
|
1904
|
+
/**
|
|
1905
|
+
* Format date with custom format string
|
|
1906
|
+
* @private
|
|
1907
|
+
* @param {Date} date - Date to format
|
|
1908
|
+
* @param {string} format - Custom format string
|
|
1909
|
+
* @returns {string} Formatted date
|
|
1910
|
+
*/
|
|
1911
|
+
function formatCustomDate(date, format) {
|
|
1912
|
+
const tokens = {
|
|
1913
|
+
YYYY: date.getFullYear().toString(),
|
|
1914
|
+
YY: date.getFullYear().toString().slice(-2),
|
|
1915
|
+
MMMM: MONTH_NAMES[date.getMonth()],
|
|
1916
|
+
MMM: MONTH_NAMES_SHORT[date.getMonth()],
|
|
1917
|
+
MM: (date.getMonth() + 1).toString().padStart(2, '0'),
|
|
1918
|
+
M: (date.getMonth() + 1).toString(),
|
|
1919
|
+
DDDD: WEEKDAY_NAMES[date.getDay()],
|
|
1920
|
+
DDD: WEEKDAY_NAMES_SHORT[date.getDay()],
|
|
1921
|
+
DD: date.getDate().toString().padStart(2, '0'),
|
|
1922
|
+
D: date.getDate().toString(),
|
|
1923
|
+
HH: date.getHours().toString().padStart(2, '0'),
|
|
1924
|
+
H: date.getHours().toString(),
|
|
1925
|
+
hh: (date.getHours() % 12 || 12).toString().padStart(2, '0'),
|
|
1926
|
+
h: (date.getHours() % 12 || 12).toString(),
|
|
1927
|
+
mm: date.getMinutes().toString().padStart(2, '0'),
|
|
1928
|
+
m: date.getMinutes().toString(),
|
|
1929
|
+
ss: date.getSeconds().toString().padStart(2, '0'),
|
|
1930
|
+
s: date.getSeconds().toString(),
|
|
1931
|
+
SSS: date.getMilliseconds().toString().padStart(3, '0'),
|
|
1932
|
+
A: date.getHours() < 12 ? 'AM' : 'PM',
|
|
1933
|
+
a: date.getHours() < 12 ? 'am' : 'pm',
|
|
1934
|
+
};
|
|
1935
|
+
return format.replace(/YYYY|YY|MMMM|MMM|MM|M|DDDD|DDD|DD|D|HH|H|hh|h|mm|m|ss|s|SSS|A|a/g, (match) => tokens[match] || match);
|
|
1936
|
+
}
|
|
1937
|
+
/**
|
|
1938
|
+
* Validate date component values
|
|
1939
|
+
* @private
|
|
1940
|
+
* @param {number} year - Year
|
|
1941
|
+
* @param {Month} month - Month (0-11)
|
|
1942
|
+
* @param {number} day - Day of month
|
|
1943
|
+
* @param {number} [hour=0] - Hour (0-23)
|
|
1944
|
+
* @param {number} [minute=0] - Minute (0-59)
|
|
1945
|
+
* @param {number} [second=0] - Second (0-59)
|
|
1946
|
+
* @param {number} [millisecond=0] - Millisecond (0-999)
|
|
1947
|
+
* @throws {Error} If any component is out of valid range
|
|
1948
|
+
*/
|
|
1949
|
+
function validateDateParts(year, month, day, hour = 0, minute = 0, second = 0, millisecond = 0) {
|
|
1950
|
+
if (year < 1000 || year > 9999) {
|
|
1951
|
+
throw new Error(`Year must be between 1000 and 9999: ${year}`);
|
|
1952
|
+
}
|
|
1953
|
+
if (month < 0 || month > 11) {
|
|
1954
|
+
throw new Error(`Month must be between 0 and 11: ${month}`);
|
|
1955
|
+
}
|
|
1956
|
+
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
1957
|
+
if (day < 1 || day > daysInMonth) {
|
|
1958
|
+
throw new Error(`Day must be between 1 and ${daysInMonth} for month ${month}: ${day}`);
|
|
1959
|
+
}
|
|
1960
|
+
if (hour < 0 || hour > 23) {
|
|
1961
|
+
throw new Error(`Hour must be between 0 and 23: ${hour}`);
|
|
1962
|
+
}
|
|
1963
|
+
if (minute < 0 || minute > 59) {
|
|
1964
|
+
throw new Error(`Minute must be between 0 and 59: ${minute}`);
|
|
1965
|
+
}
|
|
1966
|
+
if (second < 0 || second > 59) {
|
|
1967
|
+
throw new Error(`Second must be between 0 and 59: ${second}`);
|
|
1968
|
+
}
|
|
1969
|
+
if (millisecond < 0 || millisecond > 999) {
|
|
1970
|
+
throw new Error(`Millisecond must be between 0 and 999: ${millisecond}`);
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
/**
|
|
1974
|
+
* Get timezone abbreviation
|
|
1975
|
+
* @private
|
|
1976
|
+
* @param {Date} date - Date to get timezone for
|
|
1977
|
+
* @returns {string} Timezone abbreviation
|
|
1978
|
+
*/
|
|
1979
|
+
function getTimezoneAbbreviation(date) {
|
|
1980
|
+
const formatter = new Intl.DateTimeFormat('en-US', {
|
|
1981
|
+
timeZoneName: 'short',
|
|
1982
|
+
});
|
|
1983
|
+
const parts = formatter.formatToParts(date);
|
|
1984
|
+
const timezonePart = parts.find((part) => part.type === 'timeZoneName');
|
|
1985
|
+
return timezonePart ? timezonePart.value : 'UTC';
|
|
1986
|
+
}
|
|
1987
|
+
/**
|
|
1988
|
+
* Check if Daylight Saving Time is active for a date
|
|
1989
|
+
* @private
|
|
1990
|
+
* @param {Date} date - Date to check
|
|
1991
|
+
* @returns {boolean} True if DST is active
|
|
1992
|
+
*/
|
|
1993
|
+
function isDST(date) {
|
|
1994
|
+
const jan = new Date(date.getFullYear(), 0, 1);
|
|
1995
|
+
const jul = new Date(date.getFullYear(), 6, 1);
|
|
1996
|
+
const stdTimezoneOffset = Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
|
|
1997
|
+
return date.getTimezoneOffset() < stdTimezoneOffset;
|
|
1998
|
+
}
|