ts-time-utils 0.0.1
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/README.md +343 -0
- package/dist/age.d.ts +49 -0
- package/dist/age.d.ts.map +1 -0
- package/dist/age.js +106 -0
- package/dist/calculate.d.ts +49 -0
- package/dist/calculate.d.ts.map +1 -0
- package/dist/calculate.js +179 -0
- package/dist/calendar.d.ts +82 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +154 -0
- package/dist/constants.d.ts +35 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +17 -0
- package/dist/esm/age.d.ts +49 -0
- package/dist/esm/age.d.ts.map +1 -0
- package/dist/esm/age.js +106 -0
- package/dist/esm/calculate.d.ts +49 -0
- package/dist/esm/calculate.d.ts.map +1 -0
- package/dist/esm/calculate.js +179 -0
- package/dist/esm/calendar.d.ts +82 -0
- package/dist/esm/calendar.d.ts.map +1 -0
- package/dist/esm/calendar.js +154 -0
- package/dist/esm/constants.d.ts +35 -0
- package/dist/esm/constants.d.ts.map +1 -0
- package/dist/esm/constants.js +17 -0
- package/dist/esm/format.d.ts +25 -0
- package/dist/esm/format.d.ts.map +1 -0
- package/dist/esm/format.js +189 -0
- package/dist/esm/index.d.ts +17 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +28 -0
- package/dist/esm/interval.d.ts +30 -0
- package/dist/esm/interval.d.ts.map +1 -0
- package/dist/esm/interval.js +86 -0
- package/dist/esm/parse.d.ts +31 -0
- package/dist/esm/parse.d.ts.map +1 -0
- package/dist/esm/parse.js +217 -0
- package/dist/esm/performance.d.ts +110 -0
- package/dist/esm/performance.d.ts.map +1 -0
- package/dist/esm/performance.js +222 -0
- package/dist/esm/rangePresets.d.ts +45 -0
- package/dist/esm/rangePresets.d.ts.map +1 -0
- package/dist/esm/rangePresets.js +124 -0
- package/dist/esm/timezone.d.ts +38 -0
- package/dist/esm/timezone.d.ts.map +1 -0
- package/dist/esm/timezone.js +99 -0
- package/dist/esm/validate.d.ts +62 -0
- package/dist/esm/validate.d.ts.map +1 -0
- package/dist/esm/validate.js +108 -0
- package/dist/esm/workingHours.d.ts +25 -0
- package/dist/esm/workingHours.d.ts.map +1 -0
- package/dist/esm/workingHours.js +107 -0
- package/dist/format.d.ts +25 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +189 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/interval.d.ts +30 -0
- package/dist/interval.d.ts.map +1 -0
- package/dist/interval.js +86 -0
- package/dist/parse.d.ts +31 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +217 -0
- package/dist/performance.d.ts +110 -0
- package/dist/performance.d.ts.map +1 -0
- package/dist/performance.js +222 -0
- package/dist/rangePresets.d.ts +45 -0
- package/dist/rangePresets.d.ts.map +1 -0
- package/dist/rangePresets.js +124 -0
- package/dist/timezone.d.ts +38 -0
- package/dist/timezone.d.ts.map +1 -0
- package/dist/timezone.js +99 -0
- package/dist/validate.d.ts +62 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +108 -0
- package/dist/workingHours.d.ts +25 -0
- package/dist/workingHours.d.ts.map +1 -0
- package/dist/workingHours.js +107 -0
- package/package.json +102 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Predefined date range helpers (returns [start,end]) */
|
|
2
|
+
export interface DateRange {
|
|
3
|
+
start: Date;
|
|
4
|
+
end: Date;
|
|
5
|
+
}
|
|
6
|
+
export declare function today(now?: Date): DateRange;
|
|
7
|
+
export declare function yesterday(now?: Date): DateRange;
|
|
8
|
+
export declare function tomorrow(now?: Date): DateRange;
|
|
9
|
+
export declare function lastNDays(n: number, now?: Date): DateRange;
|
|
10
|
+
export declare function nextNDays(n: number, now?: Date): DateRange;
|
|
11
|
+
export declare function thisWeek(now?: Date): DateRange;
|
|
12
|
+
export declare function lastWeek(now?: Date): DateRange;
|
|
13
|
+
export declare function nextWeek(now?: Date): DateRange;
|
|
14
|
+
export declare function thisMonth(now?: Date): DateRange;
|
|
15
|
+
export declare function lastMonth(now?: Date): DateRange;
|
|
16
|
+
export declare function nextMonth(now?: Date): DateRange;
|
|
17
|
+
export declare function thisYear(now?: Date): DateRange;
|
|
18
|
+
export declare function lastYear(now?: Date): DateRange;
|
|
19
|
+
export declare function nextYear(now?: Date): DateRange;
|
|
20
|
+
export declare function rollingWindowDays(days: number, now?: Date): DateRange;
|
|
21
|
+
export declare function quarterRange(date?: Date): DateRange;
|
|
22
|
+
export declare function lastQuarter(date?: Date): DateRange;
|
|
23
|
+
export declare function nextQuarter(date?: Date): DateRange;
|
|
24
|
+
/** Map of preset functions for dynamic access */
|
|
25
|
+
export declare const RANGE_PRESETS: {
|
|
26
|
+
today: typeof today;
|
|
27
|
+
yesterday: typeof yesterday;
|
|
28
|
+
tomorrow: typeof tomorrow;
|
|
29
|
+
last7Days: (now?: Date) => DateRange;
|
|
30
|
+
last30Days: (now?: Date) => DateRange;
|
|
31
|
+
next7Days: (now?: Date) => DateRange;
|
|
32
|
+
thisWeek: typeof thisWeek;
|
|
33
|
+
lastWeek: typeof lastWeek;
|
|
34
|
+
nextWeek: typeof nextWeek;
|
|
35
|
+
thisMonth: typeof thisMonth;
|
|
36
|
+
lastMonth: typeof lastMonth;
|
|
37
|
+
nextMonth: typeof nextMonth;
|
|
38
|
+
thisYear: typeof thisYear;
|
|
39
|
+
lastYear: typeof lastYear;
|
|
40
|
+
nextYear: typeof nextYear;
|
|
41
|
+
quarter: typeof quarterRange;
|
|
42
|
+
lastQuarter: typeof lastQuarter;
|
|
43
|
+
nextQuarter: typeof nextQuarter;
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=rangePresets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rangePresets.d.ts","sourceRoot":"","sources":["../src/rangePresets.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAE1D,MAAM,WAAW,SAAS;IAAG,KAAK,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,IAAI,CAAC;CAAE;AAQtD,wBAAgB,KAAK,CAAC,GAAG,OAAa,GAAG,SAAS,CAA4B;AAC9E,wBAAgB,SAAS,CAAC,GAAG,OAAa,GAAG,SAAS,CAKrD;AAED,wBAAgB,QAAQ,CAAC,GAAG,OAAa,GAAG,SAAS,CAKpD;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,OAAa,GAAG,SAAS,CAIhE;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,OAAa,GAAG,SAAS,CAIhE;AAED,wBAAgB,QAAQ,CAAC,GAAG,OAAa,GAAG,SAAS,CAOpD;AAED,wBAAgB,QAAQ,CAAC,GAAG,OAAa,GAAG,SAAS,CAKpD;AAED,wBAAgB,QAAQ,CAAC,GAAG,OAAa,GAAG,SAAS,CAKpD;AAED,wBAAgB,SAAS,CAAC,GAAG,OAAa,GAAG,SAAS,CAIrD;AAED,wBAAgB,SAAS,CAAC,GAAG,OAAa,GAAG,SAAS,CAKrD;AAED,wBAAgB,SAAS,CAAC,GAAG,OAAa,GAAG,SAAS,CAKrD;AAED,wBAAgB,QAAQ,CAAC,GAAG,OAAa,GAAG,SAAS,CAIpD;AAED,wBAAgB,QAAQ,CAAC,GAAG,OAAa,GAAG,SAAS,CAKpD;AAED,wBAAgB,QAAQ,CAAC,GAAG,OAAa,GAAG,SAAS,CAKpD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAa,GAAG,SAAS,CAI3E;AAED,wBAAgB,YAAY,CAAC,IAAI,OAAa,GAAG,SAAS,CAKzD;AAED,wBAAgB,WAAW,CAAC,IAAI,OAAa,GAAG,SAAS,CAKxD;AAED,wBAAgB,WAAW,CAAC,IAAI,OAAa,GAAG,SAAS,CAKxD;AAED,iDAAiD;AACjD,eAAO,MAAM,aAAa;;;;sBAEN,IAAI;uBACH,IAAI;sBACL,IAAI;;;;;;;;;;;;;CAKvB,CAAC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/** Predefined date range helpers (returns [start,end]) */
|
|
2
|
+
function todayRange(now = new Date()) {
|
|
3
|
+
const start = new Date(now);
|
|
4
|
+
start.setHours(0, 0, 0, 0);
|
|
5
|
+
const end = new Date(start);
|
|
6
|
+
end.setDate(end.getDate() + 1);
|
|
7
|
+
return { start, end };
|
|
8
|
+
}
|
|
9
|
+
export function today(now = new Date()) { return todayRange(now); }
|
|
10
|
+
export function yesterday(now = new Date()) {
|
|
11
|
+
const t = todayRange(now);
|
|
12
|
+
const end = new Date(t.start); // yesterday end equals today start
|
|
13
|
+
const start = new Date(end);
|
|
14
|
+
start.setDate(start.getDate() - 1);
|
|
15
|
+
return { start, end };
|
|
16
|
+
}
|
|
17
|
+
export function tomorrow(now = new Date()) {
|
|
18
|
+
const t = todayRange(now);
|
|
19
|
+
t.start.setDate(t.start.getDate() + 1);
|
|
20
|
+
t.end.setDate(t.end.getDate() + 1);
|
|
21
|
+
return t;
|
|
22
|
+
}
|
|
23
|
+
export function lastNDays(n, now = new Date()) {
|
|
24
|
+
const end = new Date(now);
|
|
25
|
+
const start = new Date(end);
|
|
26
|
+
start.setDate(start.getDate() - n);
|
|
27
|
+
return { start, end };
|
|
28
|
+
}
|
|
29
|
+
export function nextNDays(n, now = new Date()) {
|
|
30
|
+
const start = new Date(now);
|
|
31
|
+
const end = new Date(start);
|
|
32
|
+
end.setDate(end.getDate() + n);
|
|
33
|
+
return { start, end };
|
|
34
|
+
}
|
|
35
|
+
export function thisWeek(now = new Date()) {
|
|
36
|
+
const start = new Date(now);
|
|
37
|
+
const day = start.getDay(); // 0 Sunday
|
|
38
|
+
start.setHours(0, 0, 0, 0);
|
|
39
|
+
start.setDate(start.getDate() - day); // start of week Sunday
|
|
40
|
+
const end = new Date(start);
|
|
41
|
+
end.setDate(end.getDate() + 7);
|
|
42
|
+
return { start, end };
|
|
43
|
+
}
|
|
44
|
+
export function lastWeek(now = new Date()) {
|
|
45
|
+
const w = thisWeek(now);
|
|
46
|
+
w.start.setDate(w.start.getDate() - 7);
|
|
47
|
+
w.end.setDate(w.end.getDate() - 7);
|
|
48
|
+
return w;
|
|
49
|
+
}
|
|
50
|
+
export function nextWeek(now = new Date()) {
|
|
51
|
+
const w = thisWeek(now);
|
|
52
|
+
w.start.setDate(w.start.getDate() + 7);
|
|
53
|
+
w.end.setDate(w.end.getDate() + 7);
|
|
54
|
+
return w;
|
|
55
|
+
}
|
|
56
|
+
export function thisMonth(now = new Date()) {
|
|
57
|
+
const start = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
58
|
+
const end = new Date(now.getFullYear(), now.getMonth() + 1, 1);
|
|
59
|
+
return { start, end };
|
|
60
|
+
}
|
|
61
|
+
export function lastMonth(now = new Date()) {
|
|
62
|
+
const m = thisMonth(now);
|
|
63
|
+
m.start.setMonth(m.start.getMonth() - 1);
|
|
64
|
+
m.end.setMonth(m.end.getMonth() - 1);
|
|
65
|
+
return m;
|
|
66
|
+
}
|
|
67
|
+
export function nextMonth(now = new Date()) {
|
|
68
|
+
const m = thisMonth(now);
|
|
69
|
+
m.start.setMonth(m.start.getMonth() + 1);
|
|
70
|
+
m.end.setMonth(m.end.getMonth() + 1);
|
|
71
|
+
return m;
|
|
72
|
+
}
|
|
73
|
+
export function thisYear(now = new Date()) {
|
|
74
|
+
const start = new Date(now.getFullYear(), 0, 1);
|
|
75
|
+
const end = new Date(now.getFullYear() + 1, 0, 1);
|
|
76
|
+
return { start, end };
|
|
77
|
+
}
|
|
78
|
+
export function lastYear(now = new Date()) {
|
|
79
|
+
const y = thisYear(now);
|
|
80
|
+
y.start.setFullYear(y.start.getFullYear() - 1);
|
|
81
|
+
y.end.setFullYear(y.end.getFullYear() - 1);
|
|
82
|
+
return y;
|
|
83
|
+
}
|
|
84
|
+
export function nextYear(now = new Date()) {
|
|
85
|
+
const y = thisYear(now);
|
|
86
|
+
y.start.setFullYear(y.start.getFullYear() + 1);
|
|
87
|
+
y.end.setFullYear(y.end.getFullYear() + 1);
|
|
88
|
+
return y;
|
|
89
|
+
}
|
|
90
|
+
export function rollingWindowDays(days, now = new Date()) {
|
|
91
|
+
const end = new Date(now);
|
|
92
|
+
const start = new Date(end);
|
|
93
|
+
start.setDate(start.getDate() - days);
|
|
94
|
+
return { start, end };
|
|
95
|
+
}
|
|
96
|
+
export function quarterRange(date = new Date()) {
|
|
97
|
+
const q = Math.floor(date.getMonth() / 3); // 0-3
|
|
98
|
+
const start = new Date(date.getFullYear(), q * 3, 1);
|
|
99
|
+
const end = new Date(date.getFullYear(), q * 3 + 3, 1);
|
|
100
|
+
return { start, end };
|
|
101
|
+
}
|
|
102
|
+
export function lastQuarter(date = new Date()) {
|
|
103
|
+
const q = quarterRange(date);
|
|
104
|
+
q.start.setMonth(q.start.getMonth() - 3);
|
|
105
|
+
q.end.setMonth(q.end.getMonth() - 3);
|
|
106
|
+
return q;
|
|
107
|
+
}
|
|
108
|
+
export function nextQuarter(date = new Date()) {
|
|
109
|
+
const q = quarterRange(date);
|
|
110
|
+
q.start.setMonth(q.start.getMonth() + 3);
|
|
111
|
+
q.end.setMonth(q.end.getMonth() + 3);
|
|
112
|
+
return q;
|
|
113
|
+
}
|
|
114
|
+
/** Map of preset functions for dynamic access */
|
|
115
|
+
export const RANGE_PRESETS = {
|
|
116
|
+
today, yesterday, tomorrow,
|
|
117
|
+
last7Days: (now) => lastNDays(7, now),
|
|
118
|
+
last30Days: (now) => lastNDays(30, now),
|
|
119
|
+
next7Days: (now) => nextNDays(7, now),
|
|
120
|
+
thisWeek, lastWeek, nextWeek,
|
|
121
|
+
thisMonth, lastMonth, nextMonth,
|
|
122
|
+
thisYear, lastYear, nextYear,
|
|
123
|
+
quarter: quarterRange, lastQuarter, nextQuarter,
|
|
124
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timezone utilities (rely on Intl API, fallbacks where possible)
|
|
3
|
+
*/
|
|
4
|
+
export interface ZonedTime {
|
|
5
|
+
date: Date;
|
|
6
|
+
zone: string;
|
|
7
|
+
offsetMinutes: number;
|
|
8
|
+
}
|
|
9
|
+
/** Get offset (minutes) for a zone at a given date */
|
|
10
|
+
export declare function getTimezoneOffset(zone: string, date?: Date): number | null;
|
|
11
|
+
/** Format date/time in a zone */
|
|
12
|
+
export declare function formatInTimeZone(date: Date, zone: string, options?: Intl.DateTimeFormatOptions): string;
|
|
13
|
+
/** Get a lightweight ZonedTime object */
|
|
14
|
+
export declare function getZonedTime(date: Date, zone: string): ZonedTime | null;
|
|
15
|
+
/** Convert a date (treated as absolute moment) to another zone's clock components */
|
|
16
|
+
export declare function convertDateToZone(date: Date, zone: string): {
|
|
17
|
+
year: number;
|
|
18
|
+
month: number;
|
|
19
|
+
day: number;
|
|
20
|
+
hour: number;
|
|
21
|
+
minute: number;
|
|
22
|
+
second: number;
|
|
23
|
+
} | null;
|
|
24
|
+
/** Check if provided zone string is a valid IANA zone */
|
|
25
|
+
export declare function isValidTimeZone(zone: string): boolean;
|
|
26
|
+
/** List a subset of common timezones (cannot enumerate all via API) */
|
|
27
|
+
export declare const COMMON_TIMEZONES: string[];
|
|
28
|
+
/** Get current local offset in minutes */
|
|
29
|
+
export declare function getLocalOffset(): number;
|
|
30
|
+
/** Compare two timezones offset at given date */
|
|
31
|
+
export declare function compareZoneOffsets(zoneA: string, zoneB: string, date?: Date): number | null;
|
|
32
|
+
/** Shift a date by target zone offset difference relative to current local zone.
|
|
33
|
+
* This does not change the absolute moment; instead returns a new Date representing
|
|
34
|
+
* the same wall clock time in the target zone interpreted as local.
|
|
35
|
+
* For example useful for naive scheduling.
|
|
36
|
+
*/
|
|
37
|
+
export declare function reinterpretAsZone(date: Date, targetZone: string): Date | null;
|
|
38
|
+
//# sourceMappingURL=timezone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timezone.d.ts","sourceRoot":"","sources":["../src/timezone.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,sDAAsD;AACtD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,IAAiB,GAAG,MAAM,GAAG,IAAI,CAgBtF;AAED,iCAAiC;AACjC,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,IAAI,CAAC,qBAA0B,GAAG,MAAM,CAG3G;AAED,yCAAyC;AACzC,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAIvE;AAED,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;CAAE,GAAG,IAAI,CAoB9J;AAED,yDAAyD;AACzD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOrD;AAED,uEAAuE;AACvE,eAAO,MAAM,gBAAgB,UAK5B,CAAC;AAEF,0CAA0C;AAC1C,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,iDAAiD;AACjD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,IAAiB,GAAG,MAAM,GAAG,IAAI,CAKvG;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAI7E"}
|
package/dist/timezone.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timezone utilities (rely on Intl API, fallbacks where possible)
|
|
3
|
+
*/
|
|
4
|
+
/** Get offset (minutes) for a zone at a given date */
|
|
5
|
+
export function getTimezoneOffset(zone, date = new Date()) {
|
|
6
|
+
try {
|
|
7
|
+
const dtf = new Intl.DateTimeFormat('en-US', { timeZone: zone, timeZoneName: 'shortOffset', hour: '2-digit' });
|
|
8
|
+
const parts = dtf.formatToParts(date);
|
|
9
|
+
const tzPart = parts.find(p => p.type === 'timeZoneName');
|
|
10
|
+
if (!tzPart)
|
|
11
|
+
return null;
|
|
12
|
+
// Examples: "GMT+2", "UTC-05:00"
|
|
13
|
+
const match = tzPart.value.match(/([+-])(\d{1,2})(?::?(\d{2}))?/);
|
|
14
|
+
if (!match)
|
|
15
|
+
return 0; // could be GMT or UTC with no offset
|
|
16
|
+
const sign = match[1] === '-' ? -1 : 1;
|
|
17
|
+
const hours = parseInt(match[2], 10);
|
|
18
|
+
const mins = match[3] ? parseInt(match[3], 10) : 0;
|
|
19
|
+
return sign * (hours * 60 + mins);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Format date/time in a zone */
|
|
26
|
+
export function formatInTimeZone(date, zone, options = {}) {
|
|
27
|
+
const fmt = new Intl.DateTimeFormat('en-US', { timeZone: zone, ...options });
|
|
28
|
+
return fmt.format(date);
|
|
29
|
+
}
|
|
30
|
+
/** Get a lightweight ZonedTime object */
|
|
31
|
+
export function getZonedTime(date, zone) {
|
|
32
|
+
const offset = getTimezoneOffset(zone, date);
|
|
33
|
+
if (offset == null)
|
|
34
|
+
return null;
|
|
35
|
+
return { date: new Date(date), zone, offsetMinutes: offset };
|
|
36
|
+
}
|
|
37
|
+
/** Convert a date (treated as absolute moment) to another zone's clock components */
|
|
38
|
+
export function convertDateToZone(date, zone) {
|
|
39
|
+
try {
|
|
40
|
+
const fmt = new Intl.DateTimeFormat('en-US', {
|
|
41
|
+
timeZone: zone,
|
|
42
|
+
year: 'numeric', month: '2-digit', day: '2-digit',
|
|
43
|
+
hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false
|
|
44
|
+
});
|
|
45
|
+
const parts = fmt.formatToParts(date);
|
|
46
|
+
const get = (type) => parseInt(parts.find(p => p.type === type)?.value || '0', 10);
|
|
47
|
+
return {
|
|
48
|
+
year: get('year'),
|
|
49
|
+
month: get('month'),
|
|
50
|
+
day: get('day'),
|
|
51
|
+
hour: get('hour'),
|
|
52
|
+
minute: get('minute'),
|
|
53
|
+
second: get('second')
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Check if provided zone string is a valid IANA zone */
|
|
61
|
+
export function isValidTimeZone(zone) {
|
|
62
|
+
try {
|
|
63
|
+
new Intl.DateTimeFormat('en-US', { timeZone: zone });
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** List a subset of common timezones (cannot enumerate all via API) */
|
|
71
|
+
export const COMMON_TIMEZONES = [
|
|
72
|
+
'UTC', 'Etc/UTC', 'Europe/London', 'Europe/Paris', 'Europe/Berlin', 'Europe/Madrid', 'Europe/Rome',
|
|
73
|
+
'America/New_York', 'America/Chicago', 'America/Denver', 'America/Los_Angeles', 'America/Toronto', 'America/Sao_Paulo',
|
|
74
|
+
'Asia/Tehran', 'Asia/Dubai', 'Asia/Tokyo', 'Asia/Shanghai', 'Asia/Singapore', 'Asia/Kolkata', 'Asia/Hong_Kong',
|
|
75
|
+
'Australia/Sydney', 'Pacific/Auckland'
|
|
76
|
+
];
|
|
77
|
+
/** Get current local offset in minutes */
|
|
78
|
+
export function getLocalOffset() {
|
|
79
|
+
return -new Date().getTimezoneOffset();
|
|
80
|
+
}
|
|
81
|
+
/** Compare two timezones offset at given date */
|
|
82
|
+
export function compareZoneOffsets(zoneA, zoneB, date = new Date()) {
|
|
83
|
+
const a = getTimezoneOffset(zoneA, date);
|
|
84
|
+
const b = getTimezoneOffset(zoneB, date);
|
|
85
|
+
if (a == null || b == null)
|
|
86
|
+
return null;
|
|
87
|
+
return a - b; // positive if A ahead of B
|
|
88
|
+
}
|
|
89
|
+
/** Shift a date by target zone offset difference relative to current local zone.
|
|
90
|
+
* This does not change the absolute moment; instead returns a new Date representing
|
|
91
|
+
* the same wall clock time in the target zone interpreted as local.
|
|
92
|
+
* For example useful for naive scheduling.
|
|
93
|
+
*/
|
|
94
|
+
export function reinterpretAsZone(date, targetZone) {
|
|
95
|
+
const target = convertDateToZone(date, targetZone);
|
|
96
|
+
if (!target)
|
|
97
|
+
return null;
|
|
98
|
+
return new Date(Date.UTC(target.year, target.month - 1, target.day, target.hour, target.minute, target.second));
|
|
99
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a date is valid
|
|
3
|
+
* @param date - date to validate
|
|
4
|
+
*/
|
|
5
|
+
export declare function isValidDate(date: Date | string | number): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Check if a year is a leap year
|
|
8
|
+
* @param year - year to check
|
|
9
|
+
*/
|
|
10
|
+
export declare function isLeapYear(year: number): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Check if a date is in the past
|
|
13
|
+
* @param date - date to check
|
|
14
|
+
*/
|
|
15
|
+
export declare function isPast(date: Date): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Check if a date is in the future
|
|
18
|
+
* @param date - date to check
|
|
19
|
+
*/
|
|
20
|
+
export declare function isFuture(date: Date): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Check if a date is today
|
|
23
|
+
* @param date - date to check
|
|
24
|
+
*/
|
|
25
|
+
export declare function isToday(date: Date): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Check if a date is yesterday
|
|
28
|
+
* @param date - date to check
|
|
29
|
+
*/
|
|
30
|
+
export declare function isYesterday(date: Date): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Check if a date is tomorrow
|
|
33
|
+
* @param date - date to check
|
|
34
|
+
*/
|
|
35
|
+
export declare function isTomorrow(date: Date): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Check if two dates are the same day
|
|
38
|
+
* @param date1 - first date
|
|
39
|
+
* @param date2 - second date
|
|
40
|
+
*/
|
|
41
|
+
export declare function isSameDay(date1: Date, date2: Date): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Check if a date is a weekend (Saturday or Sunday)
|
|
44
|
+
* @param date - date to check
|
|
45
|
+
*/
|
|
46
|
+
export declare function isWeekend(date: Date): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Check if a date is a weekday (Monday through Friday)
|
|
49
|
+
* @param date - date to check
|
|
50
|
+
*/
|
|
51
|
+
export declare function isWeekday(date: Date): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Check if a time string is valid (HH:MM or HH:MM:SS format)
|
|
54
|
+
* @param time - time string to validate
|
|
55
|
+
*/
|
|
56
|
+
export declare function isValidTimeString(time: string): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Check if a date string is valid ISO 8601 format
|
|
59
|
+
* @param dateString - date string to validate
|
|
60
|
+
*/
|
|
61
|
+
export declare function isValidISOString(dateString: string): boolean;
|
|
62
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAWjE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAE1C;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAE5C;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAO3C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAQ/C;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAQ9C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,GAAG,OAAO,CAM3D;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAG7C;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAE7C;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGvD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAG5D"}
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a date is valid
|
|
3
|
+
* @param date - date to validate
|
|
4
|
+
*/
|
|
5
|
+
export function isValidDate(date) {
|
|
6
|
+
if (date instanceof Date) {
|
|
7
|
+
return !isNaN(date.getTime());
|
|
8
|
+
}
|
|
9
|
+
if (typeof date === 'string' || typeof date === 'number') {
|
|
10
|
+
const parsed = new Date(date);
|
|
11
|
+
return !isNaN(parsed.getTime());
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Check if a year is a leap year
|
|
17
|
+
* @param year - year to check
|
|
18
|
+
*/
|
|
19
|
+
export function isLeapYear(year) {
|
|
20
|
+
return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Check if a date is in the past
|
|
24
|
+
* @param date - date to check
|
|
25
|
+
*/
|
|
26
|
+
export function isPast(date) {
|
|
27
|
+
return date.getTime() < new Date().getTime();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Check if a date is in the future
|
|
31
|
+
* @param date - date to check
|
|
32
|
+
*/
|
|
33
|
+
export function isFuture(date) {
|
|
34
|
+
return date.getTime() > new Date().getTime();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Check if a date is today
|
|
38
|
+
* @param date - date to check
|
|
39
|
+
*/
|
|
40
|
+
export function isToday(date) {
|
|
41
|
+
const today = new Date();
|
|
42
|
+
return (date.getDate() === today.getDate() &&
|
|
43
|
+
date.getMonth() === today.getMonth() &&
|
|
44
|
+
date.getFullYear() === today.getFullYear());
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Check if a date is yesterday
|
|
48
|
+
* @param date - date to check
|
|
49
|
+
*/
|
|
50
|
+
export function isYesterday(date) {
|
|
51
|
+
const yesterday = new Date();
|
|
52
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
53
|
+
return (date.getDate() === yesterday.getDate() &&
|
|
54
|
+
date.getMonth() === yesterday.getMonth() &&
|
|
55
|
+
date.getFullYear() === yesterday.getFullYear());
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Check if a date is tomorrow
|
|
59
|
+
* @param date - date to check
|
|
60
|
+
*/
|
|
61
|
+
export function isTomorrow(date) {
|
|
62
|
+
const tomorrow = new Date();
|
|
63
|
+
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
64
|
+
return (date.getDate() === tomorrow.getDate() &&
|
|
65
|
+
date.getMonth() === tomorrow.getMonth() &&
|
|
66
|
+
date.getFullYear() === tomorrow.getFullYear());
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Check if two dates are the same day
|
|
70
|
+
* @param date1 - first date
|
|
71
|
+
* @param date2 - second date
|
|
72
|
+
*/
|
|
73
|
+
export function isSameDay(date1, date2) {
|
|
74
|
+
return (date1.getDate() === date2.getDate() &&
|
|
75
|
+
date1.getMonth() === date2.getMonth() &&
|
|
76
|
+
date1.getFullYear() === date2.getFullYear());
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if a date is a weekend (Saturday or Sunday)
|
|
80
|
+
* @param date - date to check
|
|
81
|
+
*/
|
|
82
|
+
export function isWeekend(date) {
|
|
83
|
+
const dayOfWeek = date.getDay();
|
|
84
|
+
return dayOfWeek === 0 || dayOfWeek === 6; // Sunday or Saturday
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Check if a date is a weekday (Monday through Friday)
|
|
88
|
+
* @param date - date to check
|
|
89
|
+
*/
|
|
90
|
+
export function isWeekday(date) {
|
|
91
|
+
return !isWeekend(date);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Check if a time string is valid (HH:MM or HH:MM:SS format)
|
|
95
|
+
* @param time - time string to validate
|
|
96
|
+
*/
|
|
97
|
+
export function isValidTimeString(time) {
|
|
98
|
+
const timeRegex = /^([01]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$/;
|
|
99
|
+
return timeRegex.test(time);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Check if a date string is valid ISO 8601 format
|
|
103
|
+
* @param dateString - date string to validate
|
|
104
|
+
*/
|
|
105
|
+
export function isValidISOString(dateString) {
|
|
106
|
+
const isoRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/;
|
|
107
|
+
return isoRegex.test(dateString) && isValidDate(dateString);
|
|
108
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Working hours utilities */
|
|
2
|
+
export interface WorkingHoursConfig {
|
|
3
|
+
workingDays: number[];
|
|
4
|
+
hours: {
|
|
5
|
+
start: number;
|
|
6
|
+
end: number;
|
|
7
|
+
};
|
|
8
|
+
breaks?: {
|
|
9
|
+
start: number;
|
|
10
|
+
end: number;
|
|
11
|
+
}[];
|
|
12
|
+
timezone?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const DEFAULT_WORKING_HOURS: WorkingHoursConfig;
|
|
15
|
+
/** Check if a date is a configured working day */
|
|
16
|
+
export declare function isWorkingDay(date: Date, config?: WorkingHoursConfig): boolean;
|
|
17
|
+
/** Check if inside working hours (excluding breaks) */
|
|
18
|
+
export declare function isWorkingTime(date: Date, config?: WorkingHoursConfig): boolean;
|
|
19
|
+
/** Move date forward to next working minute */
|
|
20
|
+
export declare function nextWorkingTime(date: Date, config?: WorkingHoursConfig): Date;
|
|
21
|
+
/** Compute working time (ms) between two dates */
|
|
22
|
+
export declare function workingTimeBetween(start: Date, end: Date, config?: WorkingHoursConfig): number;
|
|
23
|
+
/** Advance by working hours amount (simple iterative approach) */
|
|
24
|
+
export declare function addWorkingHours(start: Date, hours: number, config?: WorkingHoursConfig): Date;
|
|
25
|
+
//# sourceMappingURL=workingHours.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workingHours.d.ts","sourceRoot":"","sources":["../src/workingHours.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAE9B,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,qBAAqB,EAAE,kBAInC,CAAC;AAEF,kDAAkD;AAClD,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,GAAE,kBAA0C,GAAG,OAAO,CAEpG;AAOD,uDAAuD;AACvD,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,GAAE,kBAA0C,GAAG,OAAO,CAUrG;AAED,+CAA+C;AAC/C,wBAAgB,eAAe,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,GAAE,kBAA0C,GAAG,IAAI,CAMpG;AAYD,kDAAkD;AAClD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,GAAE,kBAA0C,GAAG,MAAM,CA+BrH;AAED,kEAAkE;AAClE,wBAAgB,eAAe,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,kBAA0C,GAAG,IAAI,CAcpH"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/** Working hours utilities */
|
|
2
|
+
export const DEFAULT_WORKING_HOURS = {
|
|
3
|
+
workingDays: [1, 2, 3, 4, 5],
|
|
4
|
+
hours: { start: 9, end: 17 },
|
|
5
|
+
breaks: [{ start: 12, end: 13 }]
|
|
6
|
+
};
|
|
7
|
+
/** Check if a date is a configured working day */
|
|
8
|
+
export function isWorkingDay(date, config = DEFAULT_WORKING_HOURS) {
|
|
9
|
+
return config.workingDays.includes(date.getDay());
|
|
10
|
+
}
|
|
11
|
+
/** Convert date to fractional hour */
|
|
12
|
+
function toHourFraction(date) {
|
|
13
|
+
return date.getHours() + date.getMinutes() / 60 + date.getSeconds() / 3600;
|
|
14
|
+
}
|
|
15
|
+
/** Check if inside working hours (excluding breaks) */
|
|
16
|
+
export function isWorkingTime(date, config = DEFAULT_WORKING_HOURS) {
|
|
17
|
+
if (!isWorkingDay(date, config))
|
|
18
|
+
return false;
|
|
19
|
+
const h = toHourFraction(date);
|
|
20
|
+
if (h < config.hours.start || h >= config.hours.end)
|
|
21
|
+
return false;
|
|
22
|
+
if (config.breaks) {
|
|
23
|
+
for (const b of config.breaks) {
|
|
24
|
+
if (h >= b.start && h < b.end)
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
/** Move date forward to next working minute */
|
|
31
|
+
export function nextWorkingTime(date, config = DEFAULT_WORKING_HOURS) {
|
|
32
|
+
const d = new Date(date);
|
|
33
|
+
while (!isWorkingTime(d, config)) {
|
|
34
|
+
d.setMinutes(d.getMinutes() + 1);
|
|
35
|
+
}
|
|
36
|
+
return d;
|
|
37
|
+
}
|
|
38
|
+
/** Clamp a date into working window of its day */
|
|
39
|
+
function clampToWorkingWindow(date, config) {
|
|
40
|
+
if (!isWorkingDay(date, config))
|
|
41
|
+
return null;
|
|
42
|
+
const start = new Date(date);
|
|
43
|
+
start.setHours(config.hours.start, 0, 0, 0);
|
|
44
|
+
const end = new Date(date);
|
|
45
|
+
end.setHours(config.hours.end, 0, 0, 0);
|
|
46
|
+
if (date < start)
|
|
47
|
+
return start;
|
|
48
|
+
if (date > end)
|
|
49
|
+
return null;
|
|
50
|
+
return date;
|
|
51
|
+
}
|
|
52
|
+
/** Compute working time (ms) between two dates */
|
|
53
|
+
export function workingTimeBetween(start, end, config = DEFAULT_WORKING_HOURS) {
|
|
54
|
+
if (end <= start)
|
|
55
|
+
return 0;
|
|
56
|
+
let total = 0;
|
|
57
|
+
const cursor = new Date(start);
|
|
58
|
+
while (cursor < end) {
|
|
59
|
+
if (isWorkingDay(cursor, config)) {
|
|
60
|
+
const windowStart = new Date(cursor);
|
|
61
|
+
windowStart.setHours(config.hours.start, 0, 0, 0);
|
|
62
|
+
const windowEnd = new Date(cursor);
|
|
63
|
+
windowEnd.setHours(config.hours.end, 0, 0, 0);
|
|
64
|
+
const rangeStart = cursor > windowStart ? cursor : windowStart;
|
|
65
|
+
const rangeEnd = end < windowEnd ? end : windowEnd;
|
|
66
|
+
if (rangeStart < rangeEnd) {
|
|
67
|
+
let segment = (rangeEnd.getTime() - rangeStart.getTime()) / 1000 / 60 / 60; // hours
|
|
68
|
+
// subtract breaks
|
|
69
|
+
if (config.breaks) {
|
|
70
|
+
for (const b of config.breaks) {
|
|
71
|
+
const bStart = new Date(rangeStart);
|
|
72
|
+
bStart.setHours(Math.floor(b.start), (b.start % 1) * 60, 0, 0);
|
|
73
|
+
const bEnd = new Date(rangeStart);
|
|
74
|
+
bEnd.setHours(Math.floor(b.end), (b.end % 1) * 60, 0, 0);
|
|
75
|
+
const overlapStart = bStart > rangeStart ? bStart : rangeStart;
|
|
76
|
+
const overlapEnd = bEnd < rangeEnd ? bEnd : rangeEnd;
|
|
77
|
+
if (overlapStart < overlapEnd) {
|
|
78
|
+
segment -= (overlapEnd.getTime() - overlapStart.getTime()) / 1000 / 60 / 60;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
total += segment;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// advance to next day start
|
|
86
|
+
cursor.setHours(24, 0, 0, 0);
|
|
87
|
+
}
|
|
88
|
+
return total * 60 * 60 * 1000; // ms
|
|
89
|
+
}
|
|
90
|
+
/** Advance by working hours amount (simple iterative approach) */
|
|
91
|
+
export function addWorkingHours(start, hours, config = DEFAULT_WORKING_HOURS) {
|
|
92
|
+
if (hours <= 0)
|
|
93
|
+
return new Date(start);
|
|
94
|
+
let remaining = hours * 60; // minutes
|
|
95
|
+
let cursor = new Date(start);
|
|
96
|
+
cursor = nextWorkingTime(cursor, config);
|
|
97
|
+
while (remaining > 0) {
|
|
98
|
+
if (isWorkingTime(cursor, config)) {
|
|
99
|
+
cursor.setMinutes(cursor.getMinutes() + 1);
|
|
100
|
+
remaining -= 1;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
cursor = nextWorkingTime(cursor, config);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return cursor;
|
|
107
|
+
}
|