@umituz/react-native-design-system 2.6.114 → 2.6.116

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@umituz/react-native-design-system",
3
- "version": "2.6.114",
4
- "description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID and image utilities",
3
+ "version": "2.6.116",
4
+ "description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID, image and timezone utilities",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
7
7
  "exports": {
@@ -20,6 +20,7 @@
20
20
  "./safe-area": "./src/safe-area/index.ts",
21
21
  "./device": "./src/device/index.ts",
22
22
  "./image": "./src/image/index.ts",
23
+ "./timezone": "./src/timezone/index.ts",
23
24
  "./package.json": "./package.json"
24
25
  },
25
26
  "scripts": {
@@ -42,7 +43,8 @@
42
43
  "typography",
43
44
  "responsive",
44
45
  "safe-area",
45
- "image"
46
+ "image",
47
+ "timezone"
46
48
  ],
47
49
  "author": "Ümit UZ <umit@umituz.com>",
48
50
  "license": "MIT",
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Timezone Exports
3
+ *
4
+ * Timezone utilities and hooks for React Native
5
+ */
6
+
7
+ export * from '../timezone';
package/src/index.ts CHANGED
@@ -72,6 +72,11 @@ export * from './exports/infinite-scroll';
72
72
  // =============================================================================
73
73
  export * from './exports/uuid';
74
74
 
75
+ // =============================================================================
76
+ // TIMEZONE EXPORTS
77
+ // =============================================================================
78
+ export * from './exports/timezone';
79
+
75
80
  // =============================================================================
76
81
  // VARIANT UTILITIES
77
82
  // =============================================================================
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Timezone Domain Entities
3
+ *
4
+ * Core timezone types and interfaces for device timezone detection
5
+ * and date/time manipulation
6
+ *
7
+ * Features:
8
+ * - Auto-detects device timezone
9
+ * - Locale-aware date/time formatting
10
+ * - Calendar utilities
11
+ * - Date manipulation helpers
12
+ *
13
+ * Zero external dependencies - uses native Intl API
14
+ */
15
+
16
+ /**
17
+ * Timezone information from device
18
+ */
19
+ export interface TimezoneInfo {
20
+ /** IANA timezone identifier (e.g., "America/New_York", "Europe/Istanbul") */
21
+ timezone: string;
22
+
23
+ /** UTC offset in minutes (negative for west, positive for east) */
24
+ offset: number;
25
+
26
+ /** Human-readable timezone display name */
27
+ displayName: string;
28
+ }
29
+
30
+ /**
31
+ * Calendar day representation
32
+ * Generic structure - can be extended by apps for app-specific data
33
+ */
34
+ export interface CalendarDay {
35
+ /** Date object for this day */
36
+ date: Date;
37
+
38
+ /** Day of month (1-31) */
39
+ day: number;
40
+
41
+ /** Day of week (0 = Sunday, 6 = Saturday) */
42
+ dayOfWeek: number;
43
+
44
+ /** Month (0-11, 0 = January) */
45
+ month: number;
46
+
47
+ /** Year (e.g., 2024) */
48
+ year: number;
49
+
50
+ /** Whether this day is in the current month */
51
+ isCurrentMonth: boolean;
52
+
53
+ /** Whether this day is today */
54
+ isToday: boolean;
55
+
56
+ /** ISO date string (YYYY-MM-DD) */
57
+ isoDate: string;
58
+ }
59
+
60
+ /**
61
+ * Timezone Service Interface
62
+ * Defines contract for timezone operations
63
+ */
64
+ export interface ITimezoneService {
65
+ /** Get current device timezone (IANA identifier) */
66
+ getCurrentTimezone(): string;
67
+
68
+ /** Get timezone offset in minutes */
69
+ getTimezoneOffset(): number;
70
+
71
+ /** Get complete timezone information */
72
+ getTimezoneInfo(): TimezoneInfo;
73
+
74
+ /** Format date with locale support */
75
+ formatDate(
76
+ date: Date | string | number,
77
+ locale?: string,
78
+ options?: Intl.DateTimeFormatOptions,
79
+ ): string;
80
+
81
+ /** Format time with locale support */
82
+ formatTime(
83
+ date: Date | string | number,
84
+ locale?: string,
85
+ options?: Intl.DateTimeFormatOptions,
86
+ ): string;
87
+
88
+ /** Get calendar days for a month */
89
+ getCalendarDays(year: number, month: number): CalendarDay[];
90
+
91
+ /** Check if date is today */
92
+ isToday(date: Date | string | number): boolean;
93
+
94
+ /** Check if two dates are the same day */
95
+ isSameDay(date1: Date | string | number, date2: Date | string | number): boolean;
96
+
97
+ /** Add days to a date */
98
+ addDays(date: Date | string | number, days: number): Date;
99
+
100
+ /** Get start of day (00:00:00) */
101
+ startOfDay(date: Date | string | number): Date;
102
+
103
+ /** Get end of day (23:59:59.999) */
104
+ endOfDay(date: Date | string | number): Date;
105
+
106
+ /** Format date to ISO string (YYYY-MM-DD) */
107
+ formatDateToString(date: Date | string | number): string;
108
+
109
+ /** Format to display date string (DD.MM.YYYY) */
110
+ formatToDisplayDate(date: Date | string | number): string;
111
+
112
+ /** Format to display date time string (DD.MM.YYYY HH:mm) */
113
+ formatToDisplayDateTime(date: Date | string | number): string;
114
+
115
+ /** Get current date as ISO string (YYYY-MM-DDTHH:mm:ss.sssZ) */
116
+ getCurrentISOString(): string;
117
+
118
+ /** Get current date object */
119
+ getNow(): Date;
120
+
121
+ /** Parse input to Date object */
122
+ parse(date: Date | string | number): Date;
123
+
124
+ /** Check if date is in the future */
125
+ isFuture(date: Date | string | number): boolean;
126
+
127
+ /** Check if date is in the past */
128
+ isPast(date: Date | string | number): boolean;
129
+
130
+ /** Get days until a future date (returns 0 if past) */
131
+ getDaysUntil(date: Date | string | number): number;
132
+
133
+ /** Get difference in days between two dates */
134
+ getDifferenceInDays(date1: Date | string | number, date2: Date | string | number): number;
135
+
136
+ /** Format date to ISO datetime string (YYYY-MM-DDTHH:mm:ss.sssZ) */
137
+ formatToISOString(date: Date | string | number): string;
138
+
139
+ /** Get list of common timezones for selection */
140
+ getTimezones(): TimezoneInfo[];
141
+
142
+ /** Check if date is valid */
143
+ isValid(date: Date | string | number): boolean;
144
+
145
+ /** Calculate age from birth date */
146
+ getAge(birthDate: Date | string | number): number;
147
+
148
+ /** Check if date is between two dates (inclusive) */
149
+ isBetween(
150
+ date: Date | string | number,
151
+ start: Date | string | number,
152
+ end: Date | string | number,
153
+ ): boolean;
154
+
155
+ /** Get earliest date from array */
156
+ min(dates: Array<Date | string | number>): Date;
157
+
158
+ /** Get latest date from array */
159
+ max(dates: Array<Date | string | number>): Date;
160
+
161
+ /** Get ISO week number (1-53) */
162
+ getWeek(date: Date | string | number): number;
163
+
164
+ /** Get quarter (1-4) */
165
+ getQuarter(date: Date | string | number): number;
166
+
167
+ /** Get timezone offset for specific timezone in minutes */
168
+ getTimezoneOffsetFor(timezone: string, date?: Date | string | number): number;
169
+
170
+ /** Convert date from one timezone to another */
171
+ convertTimezone(
172
+ date: Date | string | number,
173
+ fromTimezone: string,
174
+ toTimezone: string,
175
+ ): Date;
176
+
177
+ /** Format duration in milliseconds to human readable string */
178
+ formatDuration(milliseconds: number): string;
179
+
180
+ /** Check if date is on weekend */
181
+ isWeekend(date: Date | string | number): boolean;
182
+
183
+ /** Add business days (skip weekends) */
184
+ addBusinessDays(date: Date | string | number, days: number): Date;
185
+
186
+ /** Check if date is first day of month */
187
+ isFirstDayOfMonth(date: Date | string | number): boolean;
188
+
189
+ /** Check if date is last day of month */
190
+ isLastDayOfMonth(date: Date | string | number): boolean;
191
+
192
+ /** Get number of days in month */
193
+ getDaysInMonth(date: Date | string | number): number;
194
+
195
+ /** Get array of dates in range */
196
+ getDateRange(
197
+ start: Date | string | number,
198
+ end: Date | string | number,
199
+ ): Date[];
200
+
201
+ /** Check if two date ranges overlap */
202
+ areRangesOverlapping(
203
+ start1: Date | string | number,
204
+ end1: Date | string | number,
205
+ start2: Date | string | number,
206
+ end2: Date | string | number,
207
+ ): boolean;
208
+
209
+ /** Clamp date to range */
210
+ clampDate(
211
+ date: Date | string | number,
212
+ min: Date | string | number,
213
+ max: Date | string | number,
214
+ ): Date;
215
+
216
+ /** Check if two dates are same hour */
217
+ areSameHour(date1: Date | string | number, date2: Date | string | number): boolean;
218
+
219
+ /** Check if two dates are same minute */
220
+ areSameMinute(date1: Date | string | number, date2: Date | string | number): boolean;
221
+
222
+ /** Get middle of day (12:00:00) */
223
+ getMiddleOfDay(date: Date | string | number): Date;
224
+
225
+ /** Get relative time from now ("5 minutes ago", "in 2 hours") */
226
+ fromNow(date: Date | string | number, locale?: string): string;
227
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @umituz/react-native-timezone - Public API
3
+ *
4
+ * Timezone detection and date/time utilities for React Native apps
5
+ *
6
+ * Usage:
7
+ * import { useTimezone, timezoneService } from '@umituz/react-native-timezone';
8
+ */
9
+
10
+ // =============================================================================
11
+ // DOMAIN ENTITIES
12
+ // =============================================================================
13
+
14
+ export type {
15
+ TimezoneInfo,
16
+ CalendarDay,
17
+ ITimezoneService,
18
+ } from './domain/entities/Timezone';
19
+
20
+ // =============================================================================
21
+ // INFRASTRUCTURE SERVICES
22
+ // =============================================================================
23
+
24
+ export { timezoneService, TimezoneService } from './infrastructure/services/TimezoneService';
25
+
26
+ // =============================================================================
27
+ // PRESENTATION HOOKS
28
+ // =============================================================================
29
+
30
+ export { useTimezone } from './presentation/hooks/useTimezone';
31
+ export type { UseTimezoneReturn } from './presentation/hooks/useTimezone';
@@ -0,0 +1,70 @@
1
+ /**
2
+ * BusinessCalendarManager
3
+ *
4
+ * Business date utilities for work days and month boundaries
5
+ * Handles weekend detection and business day calculations
6
+ */
7
+
8
+ export class BusinessCalendarManager {
9
+ /**
10
+ * Check if date is on weekend (Saturday or Sunday)
11
+ */
12
+ isWeekend(date: Date | string | number): boolean {
13
+ const d = this.parse(date);
14
+ const day = d.getDay();
15
+ return day === 0 || day === 6;
16
+ }
17
+
18
+ /**
19
+ * Add business days (skips weekends)
20
+ * Positive days adds, negative days subtracts
21
+ */
22
+ addBusinessDays(date: Date | string | number, days: number): Date {
23
+ const result = this.parse(date);
24
+ const direction = days >= 0 ? 1 : -1;
25
+ let remainingDays = Math.abs(days);
26
+
27
+ while (remainingDays > 0) {
28
+ result.setDate(result.getDate() + direction);
29
+
30
+ if (!this.isWeekend(result)) {
31
+ remainingDays--;
32
+ }
33
+ }
34
+
35
+ return result;
36
+ }
37
+
38
+ /**
39
+ * Check if date is first day of month
40
+ */
41
+ isFirstDayOfMonth(date: Date | string | number): boolean {
42
+ const d = this.parse(date);
43
+ return d.getDate() === 1;
44
+ }
45
+
46
+ /**
47
+ * Check if date is last day of month
48
+ */
49
+ isLastDayOfMonth(date: Date | string | number): boolean {
50
+ const d = this.parse(date);
51
+ const tomorrow = new Date(d);
52
+ tomorrow.setDate(tomorrow.getDate() + 1);
53
+ return tomorrow.getMonth() !== d.getMonth();
54
+ }
55
+
56
+ /**
57
+ * Get number of days in month
58
+ */
59
+ getDaysInMonth(date: Date | string | number): number {
60
+ const d = this.parse(date);
61
+ const year = d.getFullYear();
62
+ const month = d.getMonth();
63
+ return new Date(year, month + 1, 0).getDate();
64
+ }
65
+
66
+ private parse(date: Date | string | number): Date {
67
+ if (date instanceof Date) return new Date(date.getTime());
68
+ return new Date(date);
69
+ }
70
+ }
@@ -0,0 +1,183 @@
1
+ import { CalendarDay } from '../../domain/entities/Timezone';
2
+
3
+ /**
4
+ * CalendarManager
5
+ * Handles calendar grid generation and date comparisons
6
+ */
7
+ export class CalendarManager {
8
+ /**
9
+ * Get calendar days for a specific month
10
+ */
11
+ getCalendarDays(
12
+ year: number,
13
+ month: number,
14
+ formatDateFn: (date: Date) => string,
15
+ ): CalendarDay[] {
16
+ const days: CalendarDay[] = [];
17
+ const firstDay = new Date(year, month, 1);
18
+ const firstDayOfWeek = firstDay.getDay();
19
+ const lastDay = new Date(year, month + 1, 0);
20
+ const lastDayOfMonth = lastDay.getDate();
21
+ const today = new Date();
22
+
23
+ // Previous month filler
24
+ const prevMonthDate = new Date(year, month, 0);
25
+ const prevMonthLastDay = prevMonthDate.getDate();
26
+ const prevMonth = prevMonthDate.getMonth();
27
+ const prevYear = prevMonthDate.getFullYear();
28
+
29
+ for (let i = firstDayOfWeek - 1; i >= 0; i--) {
30
+ const day = prevMonthLastDay - i;
31
+ const date = new Date(prevYear, prevMonth, day);
32
+ days.push(this.createDay(date, false, today, formatDateFn));
33
+ }
34
+
35
+ // Current month
36
+ for (let day = 1; day <= lastDayOfMonth; day++) {
37
+ const date = new Date(year, month, day);
38
+ days.push(this.createDay(date, true, today, formatDateFn));
39
+ }
40
+
41
+ // Next month filler
42
+ const remainingDays = 42 - days.length;
43
+ const nextMonthDate = new Date(year, month + 1, 1);
44
+ const nextMonth = nextMonthDate.getMonth();
45
+ const nextYear = nextMonthDate.getFullYear();
46
+
47
+ for (let day = 1; day <= remainingDays; day++) {
48
+ const date = new Date(nextYear, nextMonth, day);
49
+ days.push(this.createDay(date, false, today, formatDateFn));
50
+ }
51
+
52
+ return days;
53
+ }
54
+
55
+ private createDay(
56
+ date: Date,
57
+ isCurrentMonth: boolean,
58
+ today: Date,
59
+ formatDateFn: (date: Date) => string,
60
+ ): CalendarDay {
61
+ return {
62
+ date,
63
+ day: date.getDate(),
64
+ dayOfWeek: date.getDay(),
65
+ month: date.getMonth(),
66
+ year: date.getFullYear(),
67
+ isCurrentMonth,
68
+ isToday: this.isSameDay(date, today),
69
+ isoDate: formatDateFn(date),
70
+ };
71
+ }
72
+
73
+ isSameDay(date1: Date | string | number, date2: Date | string | number): boolean {
74
+ const d1 = this.parse(date1);
75
+ const d2 = this.parse(date2);
76
+ return (
77
+ d1.getFullYear() === d2.getFullYear() &&
78
+ d1.getMonth() === d2.getMonth() &&
79
+ d1.getDate() === d2.getDate()
80
+ );
81
+ }
82
+
83
+ isToday(date: Date | string | number): boolean {
84
+ return this.isSameDay(date, new Date());
85
+ }
86
+
87
+ addDays(date: Date | string | number, days: number): Date {
88
+ const result = this.parse(date);
89
+ result.setDate(result.getDate() + days);
90
+ return result;
91
+ }
92
+
93
+ startOfDay(date: Date | string | number): Date {
94
+ const result = this.parse(date);
95
+ result.setHours(0, 0, 0, 0);
96
+ return result;
97
+ }
98
+
99
+ endOfDay(date: Date | string | number): Date {
100
+ const result = this.parse(date);
101
+ result.setHours(23, 59, 59, 999);
102
+ return result;
103
+ }
104
+
105
+ getDifferenceInDays(date1: Date | string | number, date2: Date | string | number): number {
106
+ const d1 = this.parse(date1);
107
+ const d2 = this.parse(date2);
108
+ const diffInMs = d1.getTime() - d2.getTime();
109
+ return Math.ceil(diffInMs / (1000 * 60 * 60 * 24));
110
+ }
111
+
112
+ getDaysUntil(date: Date | string | number): number {
113
+ const diff = this.getDifferenceInDays(this.parse(date), new Date());
114
+ return Math.max(0, diff);
115
+ }
116
+
117
+ isFuture(date: Date | string | number): boolean {
118
+ return this.parse(date).getTime() > Date.now();
119
+ }
120
+
121
+ isPast(date: Date | string | number): boolean {
122
+ return this.parse(date).getTime() < Date.now();
123
+ }
124
+
125
+ parse(date: Date | string | number): Date {
126
+ if (date instanceof Date) return new Date(date.getTime());
127
+ return new Date(date);
128
+ }
129
+
130
+ isValid(date: Date | string | number): boolean {
131
+ const d = this.parse(date);
132
+ return d instanceof Date && !isNaN(d.getTime());
133
+ }
134
+
135
+ getAge(birthDate: Date | string | number): number {
136
+ const birth = this.parse(birthDate);
137
+ const today = new Date();
138
+ let age = today.getFullYear() - birth.getFullYear();
139
+ const monthDiff = today.getMonth() - birth.getMonth();
140
+
141
+ if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
142
+ age--;
143
+ }
144
+
145
+ return age;
146
+ }
147
+
148
+ isBetween(
149
+ date: Date | string | number,
150
+ start: Date | string | number,
151
+ end: Date | string | number,
152
+ ): boolean {
153
+ const d = this.parse(date).getTime();
154
+ const s = this.parse(start).getTime();
155
+ const e = this.parse(end).getTime();
156
+ return d >= s && d <= e;
157
+ }
158
+
159
+ min(dates: Array<Date | string | number>): Date {
160
+ const parsedDates = dates.map((d) => this.parse(d).getTime());
161
+ const minTime = Math.min(...parsedDates);
162
+ return new Date(minTime);
163
+ }
164
+
165
+ max(dates: Array<Date | string | number>): Date {
166
+ const parsedDates = dates.map((d) => this.parse(d).getTime());
167
+ const maxTime = Math.max(...parsedDates);
168
+ return new Date(maxTime);
169
+ }
170
+
171
+ getWeek(date: Date | string | number): number {
172
+ const d = this.parse(date);
173
+ d.setHours(0, 0, 0, 0);
174
+ d.setDate(d.getDate() + 4 - (d.getDay() || 7));
175
+ const yearStart = new Date(d.getFullYear(), 0, 1);
176
+ return Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);
177
+ }
178
+
179
+ getQuarter(date: Date | string | number): number {
180
+ const d = this.parse(date);
181
+ return Math.floor(d.getMonth() / 3) + 1;
182
+ }
183
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * DateComparisonUtils
3
+ *
4
+ * Precise date comparison utilities and relative time formatting
5
+ * Handles hour/minute precision comparisons and "from now" formatting
6
+ */
7
+
8
+ export class DateComparisonUtils {
9
+ /**
10
+ * Check if two dates are same hour
11
+ */
12
+ areSameHour(date1: Date | string | number, date2: Date | string | number): boolean {
13
+ const d1 = this.parse(date1);
14
+ const d2 = this.parse(date2);
15
+
16
+ return (
17
+ d1.getFullYear() === d2.getFullYear() &&
18
+ d1.getMonth() === d2.getMonth() &&
19
+ d1.getDate() === d2.getDate() &&
20
+ d1.getHours() === d2.getHours()
21
+ );
22
+ }
23
+
24
+ /**
25
+ * Check if two dates are same minute
26
+ */
27
+ areSameMinute(date1: Date | string | number, date2: Date | string | number): boolean {
28
+ const d1 = this.parse(date1);
29
+ const d2 = this.parse(date2);
30
+
31
+ return (
32
+ d1.getFullYear() === d2.getFullYear() &&
33
+ d1.getMonth() === d2.getMonth() &&
34
+ d1.getDate() === d2.getDate() &&
35
+ d1.getHours() === d2.getHours() &&
36
+ d1.getMinutes() === d2.getMinutes()
37
+ );
38
+ }
39
+
40
+ /**
41
+ * Get middle of day (12:00:00)
42
+ */
43
+ getMiddleOfDay(date: Date | string | number): Date {
44
+ const d = this.parse(date);
45
+ d.setHours(12, 0, 0, 0);
46
+ return d;
47
+ }
48
+
49
+ /**
50
+ * Get relative time from now ("5 minutes ago", "in 2 hours")
51
+ */
52
+ fromNow(date: Date | string | number, locale: string): string {
53
+ const d = this.parse(date);
54
+ const now = new Date();
55
+ const diffInMs = d.getTime() - now.getTime();
56
+ const diffInSeconds = Math.abs(diffInMs) / 1000;
57
+ const diffInMinutes = diffInSeconds / 60;
58
+ const diffInHours = diffInMinutes / 60;
59
+ const diffInDays = diffInHours / 24;
60
+
61
+ const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
62
+
63
+ if (diffInSeconds < 60) {
64
+ return rtf.format(Math.round(diffInMs / 1000), 'second');
65
+ }
66
+
67
+ if (diffInMinutes < 60) {
68
+ return rtf.format(Math.round(diffInMs / (1000 * 60)), 'minute');
69
+ }
70
+
71
+ if (diffInHours < 24) {
72
+ return rtf.format(Math.round(diffInMs / (1000 * 60 * 60)), 'hour');
73
+ }
74
+
75
+ if (diffInDays < 30) {
76
+ return rtf.format(Math.round(diffInMs / (1000 * 60 * 60 * 24)), 'day');
77
+ }
78
+
79
+ if (diffInDays < 365) {
80
+ return rtf.format(Math.round(diffInDays / 30), 'month');
81
+ }
82
+
83
+ return rtf.format(Math.round(diffInDays / 365), 'year');
84
+ }
85
+
86
+ private parse(date: Date | string | number): Date {
87
+ if (date instanceof Date) return new Date(date.getTime());
88
+ return new Date(date);
89
+ }
90
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * DateFormatter
3
+ * Handles locale-aware formatting of dates and times
4
+ */
5
+ export class DateFormatter {
6
+ formatDate(
7
+ date: Date | string | number,
8
+ locale: string,
9
+ options?: Intl.DateTimeFormatOptions,
10
+ ): string {
11
+ const defaultOptions: Intl.DateTimeFormatOptions = {
12
+ year: 'numeric',
13
+ month: 'long',
14
+ day: 'numeric',
15
+ ...options,
16
+ };
17
+ return new Intl.DateTimeFormat(locale, defaultOptions).format(this.parse(date));
18
+ }
19
+
20
+ formatTime(
21
+ date: Date | string | number,
22
+ locale: string,
23
+ options?: Intl.DateTimeFormatOptions,
24
+ ): string {
25
+ const defaultOptions: Intl.DateTimeFormatOptions = {
26
+ hour: 'numeric',
27
+ minute: '2-digit',
28
+ ...options,
29
+ };
30
+ return new Intl.DateTimeFormat(locale, defaultOptions).format(this.parse(date));
31
+ }
32
+
33
+ formatDateTime(
34
+ date: Date | string | number,
35
+ locale: string,
36
+ options?: Intl.DateTimeFormatOptions,
37
+ ): string {
38
+ const defaultOptions: Intl.DateTimeFormatOptions = {
39
+ year: 'numeric',
40
+ month: 'short',
41
+ day: 'numeric',
42
+ hour: '2-digit',
43
+ minute: '2-digit',
44
+ ...options,
45
+ };
46
+ return new Intl.DateTimeFormat(locale, defaultOptions).format(this.parse(date));
47
+ }
48
+
49
+ formatDateToString(date: Date | string | number): string {
50
+ const d = this.parse(date);
51
+ const y = d.getFullYear();
52
+ const m = String(d.getMonth() + 1).padStart(2, '0');
53
+ const day = String(d.getDate()).padStart(2, '0');
54
+ return `${y}-${m}-${day}`;
55
+ }
56
+
57
+ formatToISOString(date: Date | string | number): string {
58
+ return this.parse(date).toISOString();
59
+ }
60
+
61
+ formatToDisplayDate(date: Date | string | number): string {
62
+ const d = this.parse(date);
63
+ const day = String(d.getDate()).padStart(2, '0');
64
+ const m = String(d.getMonth() + 1).padStart(2, '0');
65
+ const y = d.getFullYear();
66
+ return `${day}.${m}.${y}`;
67
+ }
68
+
69
+ formatToDisplayDateTime(date: Date | string | number): string {
70
+ const d = this.parse(date);
71
+ const day = String(d.getDate()).padStart(2, '0');
72
+ const m = String(d.getMonth() + 1).padStart(2, '0');
73
+ const y = d.getFullYear();
74
+ const th = String(d.getHours()).padStart(2, '0');
75
+ const tm = String(d.getMinutes()).padStart(2, '0');
76
+ return `${day}.${m}.${y} ${th}:${tm}`;
77
+ }
78
+
79
+ formatRelativeTime(date: Date | string | number, locale: string): string {
80
+ const d = this.parse(date);
81
+ const now = new Date();
82
+ const diffInMs = d.getTime() - now.getTime();
83
+ const diffInDays = Math.round(diffInMs / (1000 * 60 * 60 * 24));
84
+
85
+ // Use Intl.RelativeTimeFormat for generic localizable relative time
86
+ const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
87
+
88
+ if (Math.abs(diffInDays) < 7) {
89
+ return rtf.format(diffInDays, 'day');
90
+ }
91
+
92
+ // For longer periods, fall back to simple date
93
+ return this.formatDate(d, locale, {
94
+ month: 'short',
95
+ day: 'numeric',
96
+ year: Math.abs(diffInDays) > 365 ? 'numeric' : undefined,
97
+ });
98
+ }
99
+
100
+ parse(date: Date | string | number): Date {
101
+ if (date instanceof Date) return new Date(date.getTime());
102
+ return new Date(date);
103
+ }
104
+
105
+ formatDuration(milliseconds: number): string {
106
+ const seconds = Math.floor(milliseconds / 1000);
107
+ const minutes = Math.floor(seconds / 60);
108
+ const hours = Math.floor(minutes / 60);
109
+ const days = Math.floor(hours / 24);
110
+
111
+ const parts = [];
112
+
113
+ if (days > 0) {
114
+ parts.push(`${days}d`);
115
+ }
116
+
117
+ const remainingHours = hours % 24;
118
+ if (remainingHours > 0) {
119
+ parts.push(`${remainingHours}h`);
120
+ }
121
+
122
+ const remainingMinutes = minutes % 60;
123
+ if (remainingMinutes > 0) {
124
+ parts.push(`${remainingMinutes}m`);
125
+ }
126
+
127
+ const remainingSeconds = seconds % 60;
128
+ if (remainingSeconds > 0 || parts.length === 0) {
129
+ parts.push(`${remainingSeconds}s`);
130
+ }
131
+
132
+ return parts.join(' ');
133
+ }
134
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * DateRangeUtils
3
+ *
4
+ * Date range utilities for working with date intervals
5
+ * Handles range generation, overlap detection, and clamping
6
+ */
7
+
8
+ export class DateRangeUtils {
9
+ /**
10
+ * Get array of dates in range (inclusive)
11
+ */
12
+ getDateRange(start: Date | string | number, end: Date | string | number): Date[] {
13
+ const startDate = this.parse(start);
14
+ const endDate = this.parse(end);
15
+ const dates: Date[] = [];
16
+
17
+ const current = new Date(startDate);
18
+ while (current <= endDate) {
19
+ dates.push(new Date(current));
20
+ current.setDate(current.getDate() + 1);
21
+ }
22
+
23
+ return dates;
24
+ }
25
+
26
+ /**
27
+ * Check if two date ranges overlap
28
+ */
29
+ areRangesOverlapping(
30
+ start1: Date | string | number,
31
+ end1: Date | string | number,
32
+ start2: Date | string | number,
33
+ end2: Date | string | number,
34
+ ): boolean {
35
+ const s1 = this.parse(start1).getTime();
36
+ const e1 = this.parse(end1).getTime();
37
+ const s2 = this.parse(start2).getTime();
38
+ const e2 = this.parse(end2).getTime();
39
+
40
+ return s1 <= e2 && e1 >= s2;
41
+ }
42
+
43
+ /**
44
+ * Clamp date to range
45
+ */
46
+ clampDate(
47
+ date: Date | string | number,
48
+ min: Date | string | number,
49
+ max: Date | string | number,
50
+ ): Date {
51
+ const d = this.parse(date).getTime();
52
+ const minTime = this.parse(min).getTime();
53
+ const maxTime = this.parse(max).getTime();
54
+
55
+ if (d < minTime) return this.parse(min);
56
+ if (d > maxTime) return this.parse(max);
57
+ return this.parse(date);
58
+ }
59
+
60
+ private parse(date: Date | string | number): Date {
61
+ if (date instanceof Date) return new Date(date.getTime());
62
+ return new Date(date);
63
+ }
64
+ }
@@ -0,0 +1,132 @@
1
+ import { TimezoneInfo } from '../../domain/entities/Timezone';
2
+ import { SimpleCache } from '../utils/SimpleCache';
3
+
4
+ /**
5
+ * TimezoneProvider
6
+ * Responsible for discovering device timezone and providing available timezones
7
+ */
8
+ export class TimezoneProvider {
9
+ private cache = new SimpleCache<TimezoneInfo[]>(300000); // 5 min cache
10
+ /**
11
+ * Get current device timezone using Intl API
12
+ */
13
+ getCurrentTimezone(): string {
14
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
15
+ }
16
+
17
+ /**
18
+ * Get current timezone offset in minutes
19
+ */
20
+ getTimezoneOffset(): number {
21
+ return new Date().getTimezoneOffset() * -1;
22
+ }
23
+
24
+ /**
25
+ * Get complete timezone information
26
+ */
27
+ getTimezoneInfo(): TimezoneInfo {
28
+ const timezone = this.getCurrentTimezone();
29
+ const offset = this.getTimezoneOffset();
30
+
31
+ const formatter = new Intl.DateTimeFormat('en-US', {
32
+ timeZone: timezone,
33
+ timeZoneName: 'long',
34
+ });
35
+
36
+ const parts = formatter.formatToParts(new Date());
37
+ const timeZoneNamePart = parts.find((part) => part.type === 'timeZoneName');
38
+ const displayName = timeZoneNamePart?.value || timezone;
39
+
40
+ return {
41
+ timezone,
42
+ offset,
43
+ displayName,
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Get list of common timezones (cached)
49
+ * Generic implementation for all apps
50
+ */
51
+ getTimezones(): TimezoneInfo[] {
52
+ const cached = this.cache.get('timezones');
53
+ if (cached) {
54
+ return cached;
55
+ }
56
+
57
+ const commonZones = [
58
+ 'UTC',
59
+ 'Europe/London',
60
+ 'Europe/Paris',
61
+ 'Europe/Istanbul',
62
+ 'America/New_York',
63
+ 'America/Los_Angeles',
64
+ 'America/Chicago',
65
+ 'Asia/Tokyo',
66
+ 'Asia/Dubai',
67
+ 'Asia/Shanghai',
68
+ 'Australia/Sydney',
69
+ ];
70
+
71
+ const result = commonZones.map((zone) => {
72
+ try {
73
+ const formatter = new Intl.DateTimeFormat('en-US', {
74
+ timeZone: zone,
75
+ timeZoneName: 'long',
76
+ });
77
+ const parts = formatter.formatToParts(new Date());
78
+ const namePart = parts.find((p) => p.type === 'timeZoneName');
79
+
80
+ const offset = this.getTimezoneOffsetFor(zone);
81
+
82
+ return {
83
+ timezone: zone,
84
+ displayName: namePart?.value || zone,
85
+ offset,
86
+ };
87
+ } catch (e) {
88
+ return {
89
+ timezone: zone,
90
+ displayName: zone,
91
+ offset: 0,
92
+ };
93
+ }
94
+ });
95
+
96
+ this.cache.set('timezones', result);
97
+ return result;
98
+ }
99
+
100
+ /**
101
+ * Get timezone offset for specific timezone in minutes
102
+ */
103
+ getTimezoneOffsetFor(timezone: string, date: Date | string | number = new Date()): number {
104
+ try {
105
+ const d = date instanceof Date ? date : new Date(date);
106
+
107
+ const utcDate = new Date(d.toLocaleString('en-US', { timeZone: 'UTC' }));
108
+ const tzDate = new Date(d.toLocaleString('en-US', { timeZone: timezone }));
109
+
110
+ return (tzDate.getTime() - utcDate.getTime()) / (1000 * 60);
111
+ } catch (e) {
112
+ return 0;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Convert date from one timezone to another
118
+ */
119
+ convertTimezone(
120
+ date: Date | string | number,
121
+ fromTimezone: string,
122
+ toTimezone: string,
123
+ ): Date {
124
+ const d = date instanceof Date ? date : new Date(date);
125
+
126
+ const fromOffset = this.getTimezoneOffsetFor(fromTimezone, d);
127
+ const toOffset = this.getTimezoneOffsetFor(toTimezone, d);
128
+
129
+ const offsetDiff = toOffset - fromOffset;
130
+ return new Date(d.getTime() + offsetDiff * 60000);
131
+ }
132
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Timezone Service Implementation
3
+ * Infrastructure Layer - Facade for all timezone operations
4
+ */
5
+
6
+ import {
7
+ ITimezoneService,
8
+ TimezoneInfo,
9
+ CalendarDay,
10
+ } from '../../domain/entities/Timezone';
11
+ import { TimezoneProvider } from './TimezoneProvider';
12
+ import { CalendarManager } from './CalendarManager';
13
+ import { BusinessCalendarManager } from './BusinessCalendarManager';
14
+ import { DateRangeUtils } from './DateRangeUtils';
15
+ import { DateComparisonUtils } from './DateComparisonUtils';
16
+ import { DateFormatter } from './DateFormatter';
17
+
18
+ export class TimezoneService implements ITimezoneService {
19
+ private provider = new TimezoneProvider();
20
+ private calendar = new CalendarManager();
21
+ private businessCalendar = new BusinessCalendarManager();
22
+ private rangeUtils = new DateRangeUtils();
23
+ private comparisonUtils = new DateComparisonUtils();
24
+ private formatter = new DateFormatter();
25
+
26
+ getCurrentTimezone(): string { return this.provider.getCurrentTimezone(); }
27
+ getTimezoneOffset(): number { return this.provider.getTimezoneOffset(); }
28
+ getTimezoneInfo(): TimezoneInfo { return this.provider.getTimezoneInfo(); }
29
+ getTimezones(): TimezoneInfo[] { return this.provider.getTimezones(); }
30
+
31
+ formatDate(date: Date | string | number, locale: string, options?: Intl.DateTimeFormatOptions): string {
32
+ return this.formatter.formatDate(date, locale, options);
33
+ }
34
+
35
+ formatTime(date: Date | string | number, locale: string, options?: Intl.DateTimeFormatOptions): string {
36
+ return this.formatter.formatTime(date, locale, options);
37
+ }
38
+
39
+ formatDateTime(date: Date | string | number, locale: string, options?: Intl.DateTimeFormatOptions): string {
40
+ return this.formatter.formatDateTime(date, locale, options);
41
+ }
42
+
43
+ getCalendarDays(year: number, month: number): CalendarDay[] {
44
+ return this.calendar.getCalendarDays(year, month, (d) => this.formatter.formatDateToString(d));
45
+ }
46
+
47
+ isToday(date: Date | string | number): boolean { return this.calendar.isToday(date); }
48
+ isSameDay(date1: Date | string | number, date2: Date | string | number): boolean {
49
+ return this.calendar.isSameDay(date1, date2);
50
+ }
51
+
52
+ addDays(date: Date | string | number, days: number): Date { return this.calendar.addDays(date, days); }
53
+ startOfDay(date: Date | string | number): Date { return this.calendar.startOfDay(date); }
54
+ endOfDay(date: Date | string | number): Date { return this.calendar.endOfDay(date); }
55
+
56
+ formatDateToString(date: Date | string | number): string { return this.formatter.formatDateToString(date); }
57
+ getCurrentISOString(): string { return this.formatter.formatToISOString(new Date()); }
58
+ formatToISOString(date: Date | string | number): string { return this.formatter.formatToISOString(date); }
59
+ formatToDisplayDate(date: Date | string | number): string { return this.formatter.formatToDisplayDate(date); }
60
+ getNow(): Date { return new Date(); }
61
+
62
+ parse(date: Date | string | number): Date { return this.calendar.parse(date); }
63
+
64
+ formatToDisplayDateTime(date: Date | string | number): string {
65
+ return this.formatter.formatToDisplayDateTime(date);
66
+ }
67
+
68
+ formatRelativeTime(date: Date | string | number, locale: string): string {
69
+ return this.formatter.formatRelativeTime(date, locale);
70
+ }
71
+
72
+ isFuture(date: Date | string | number): boolean { return this.calendar.isFuture(date); }
73
+ isPast(date: Date | string | number): boolean { return this.calendar.isPast(date); }
74
+ getDaysUntil(date: Date | string | number): number { return this.calendar.getDaysUntil(date); }
75
+
76
+ getDifferenceInDays(date1: Date | string | number, date2: Date | string | number): number {
77
+ return this.calendar.getDifferenceInDays(date1, date2);
78
+ }
79
+
80
+ isValid(date: Date | string | number): boolean { return this.calendar.isValid(date); }
81
+ getAge(birthDate: Date | string | number): number { return this.calendar.getAge(birthDate); }
82
+
83
+ isBetween(date: Date | string | number, start: Date | string | number, end: Date | string | number): boolean {
84
+ return this.calendar.isBetween(date, start, end);
85
+ }
86
+
87
+ min(dates: Array<Date | string | number>): Date { return this.calendar.min(dates); }
88
+ max(dates: Array<Date | string | number>): Date { return this.calendar.max(dates); }
89
+ getWeek(date: Date | string | number): number { return this.calendar.getWeek(date); }
90
+ getQuarter(date: Date | string | number): number { return this.calendar.getQuarter(date); }
91
+
92
+ getTimezoneOffsetFor(timezone: string, date?: Date | string | number): number {
93
+ return this.provider.getTimezoneOffsetFor(timezone, date);
94
+ }
95
+
96
+ convertTimezone(date: Date | string | number, fromTimezone: string, toTimezone: string): Date {
97
+ return this.provider.convertTimezone(date, fromTimezone, toTimezone);
98
+ }
99
+
100
+ formatDuration(milliseconds: number): string { return this.formatter.formatDuration(milliseconds); }
101
+
102
+ isWeekend(date: Date | string | number): boolean { return this.businessCalendar.isWeekend(date); }
103
+
104
+ addBusinessDays(date: Date | string | number, days: number): Date {
105
+ return this.businessCalendar.addBusinessDays(date, days);
106
+ }
107
+
108
+ isFirstDayOfMonth(date: Date | string | number): boolean {
109
+ return this.businessCalendar.isFirstDayOfMonth(date);
110
+ }
111
+
112
+ isLastDayOfMonth(date: Date | string | number): boolean {
113
+ return this.businessCalendar.isLastDayOfMonth(date);
114
+ }
115
+
116
+ getDaysInMonth(date: Date | string | number): number { return this.businessCalendar.getDaysInMonth(date); }
117
+
118
+ getDateRange(start: Date | string | number, end: Date | string | number): Date[] {
119
+ return this.rangeUtils.getDateRange(start, end);
120
+ }
121
+
122
+ areRangesOverlapping(
123
+ start1: Date | string | number,
124
+ end1: Date | string | number,
125
+ start2: Date | string | number,
126
+ end2: Date | string | number,
127
+ ): boolean { return this.rangeUtils.areRangesOverlapping(start1, end1, start2, end2); }
128
+
129
+ clampDate(date: Date | string | number, min: Date | string | number, max: Date | string | number): Date {
130
+ return this.rangeUtils.clampDate(date, min, max);
131
+ }
132
+
133
+ areSameHour(date1: Date | string | number, date2: Date | string | number): boolean {
134
+ return this.comparisonUtils.areSameHour(date1, date2);
135
+ }
136
+
137
+ areSameMinute(date1: Date | string | number, date2: Date | string | number): boolean {
138
+ return this.comparisonUtils.areSameMinute(date1, date2);
139
+ }
140
+
141
+ getMiddleOfDay(date: Date | string | number): Date { return this.comparisonUtils.getMiddleOfDay(date); }
142
+
143
+ fromNow(date: Date | string | number, locale?: string): string {
144
+ return this.comparisonUtils.fromNow(date, locale || 'en');
145
+ }
146
+ }
147
+
148
+ export const timezoneService = new TimezoneService();
@@ -0,0 +1,64 @@
1
+ /**
2
+ * SimpleCache
3
+ *
4
+ * Lightweight in-memory cache for performance optimization
5
+ * No external dependencies - pure TypeScript implementation
6
+ */
7
+
8
+ interface CacheEntry<T> {
9
+ value: T;
10
+ expires: number;
11
+ }
12
+
13
+ export class SimpleCache<T> {
14
+ private cache = new Map<string, CacheEntry<T>>();
15
+ private defaultTTL: number;
16
+
17
+ constructor(defaultTTL: number = 60000) {
18
+ this.defaultTTL = defaultTTL;
19
+ this.cleanup();
20
+ }
21
+
22
+ set(key: string, value: T, ttl?: number): void {
23
+ const expires = Date.now() + (ttl ?? this.defaultTTL);
24
+ this.cache.set(key, { value, expires });
25
+ }
26
+
27
+ get(key: string): T | undefined {
28
+ const entry = this.cache.get(key);
29
+
30
+ if (!entry) {
31
+ return undefined;
32
+ }
33
+
34
+ if (Date.now() > entry.expires) {
35
+ this.cache.delete(key);
36
+ return undefined;
37
+ }
38
+
39
+ return entry.value;
40
+ }
41
+
42
+ has(key: string): boolean {
43
+ return this.get(key) !== undefined;
44
+ }
45
+
46
+ clear(): void {
47
+ this.cache.clear();
48
+ }
49
+
50
+ delete(key: string): void {
51
+ this.cache.delete(key);
52
+ }
53
+
54
+ private cleanup(): void {
55
+ const now = Date.now();
56
+ for (const [key, entry] of this.cache.entries()) {
57
+ if (now > entry.expires) {
58
+ this.cache.delete(key);
59
+ }
60
+ }
61
+
62
+ setTimeout(() => this.cleanup(), 60000);
63
+ }
64
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * useTimezone Hook
3
+ *
4
+ * React hook for timezone operations with automatic locale integration
5
+ * Integrates with localization package for locale-aware date/time formatting
6
+ */
7
+
8
+ import { useMemo, useCallback } from 'react';
9
+ import { useLocalization } from '@umituz/react-native-localization';
10
+ import { timezoneService } from '../../infrastructure/services/TimezoneService';
11
+ import type { TimezoneInfo, CalendarDay } from '../../domain/entities/Timezone';
12
+
13
+ export interface UseTimezoneReturn {
14
+ timezone: string;
15
+ timezoneInfo: TimezoneInfo;
16
+ formatDate: (date: Date, options?: Intl.DateTimeFormatOptions) => string;
17
+ formatTime: (date: Date, options?: Intl.DateTimeFormatOptions) => string;
18
+ getCalendarDays: (year: number, month: number) => CalendarDay[];
19
+ isToday: (date: Date) => boolean;
20
+ isSameDay: (date1: Date, date2: Date) => boolean;
21
+ addDays: (date: Date, days: number) => Date;
22
+ startOfDay: (date: Date) => Date;
23
+ endOfDay: (date: Date) => Date;
24
+ formatDateToString: (date: Date) => string;
25
+ getCurrentISOString: () => string;
26
+ formatToISOString: (date: Date) => string;
27
+ formatRelativeTime: (date: Date) => string;
28
+ formatDateTime: (date: Date, options?: Intl.DateTimeFormatOptions) => string;
29
+ getTimezones: () => TimezoneInfo[];
30
+ isValid: (date: Date) => boolean;
31
+ getAge: (birthDate: Date) => number;
32
+ isBetween: (date: Date, start: Date, end: Date) => boolean;
33
+ min: (dates: Date[]) => Date;
34
+ max: (dates: Date[]) => Date;
35
+ getWeek: (date: Date) => number;
36
+ getQuarter: (date: Date) => number;
37
+ getTimezoneOffsetFor: (timezone: string, date?: Date) => number;
38
+ convertTimezone: (date: Date, fromTimezone: string, toTimezone: string) => Date;
39
+ formatDuration: (milliseconds: number) => string;
40
+ isWeekend: (date: Date) => boolean;
41
+ addBusinessDays: (date: Date, days: number) => Date;
42
+ isFirstDayOfMonth: (date: Date) => boolean;
43
+ isLastDayOfMonth: (date: Date) => boolean;
44
+ getDaysInMonth: (date: Date) => number;
45
+ getDateRange: (start: Date, end: Date) => Date[];
46
+ areRangesOverlapping: (start1: Date, end1: Date, start2: Date, end2: Date) => boolean;
47
+ clampDate: (date: Date, min: Date, max: Date) => Date;
48
+ areSameHour: (date1: Date, date2: Date) => boolean;
49
+ areSameMinute: (date1: Date, date2: Date) => boolean;
50
+ getMiddleOfDay: (date: Date) => Date;
51
+ fromNow: (date: Date) => string;
52
+ }
53
+
54
+ export const useTimezone = (): UseTimezoneReturn => {
55
+ const { currentLanguage } = useLocalization();
56
+ const timezoneInfo = useMemo(() => timezoneService.getTimezoneInfo(), []);
57
+
58
+ const formatDate = useCallback((date: Date, options?: Intl.DateTimeFormatOptions) =>
59
+ timezoneService.formatDate(date, currentLanguage, options), [currentLanguage]);
60
+
61
+ const formatTime = useCallback((date: Date, options?: Intl.DateTimeFormatOptions) =>
62
+ timezoneService.formatTime(date, currentLanguage, options), [currentLanguage]);
63
+
64
+ const formatDateTime = useCallback((date: Date, options?: Intl.DateTimeFormatOptions) =>
65
+ timezoneService.formatDateTime(date, currentLanguage, options), [currentLanguage]);
66
+
67
+ const formatRelativeTime = useCallback((date: Date) =>
68
+ timezoneService.formatRelativeTime(date, currentLanguage), [currentLanguage]);
69
+
70
+ const fromNow = useCallback((date: Date) =>
71
+ timezoneService.fromNow(date, currentLanguage), [currentLanguage]);
72
+
73
+ const getCalendarDays = useCallback((year: number, month: number) =>
74
+ timezoneService.getCalendarDays(year, month), []);
75
+
76
+ const isToday = useCallback((date: Date) => timezoneService.isToday(date), []);
77
+ const isSameDay = useCallback((date1: Date, date2: Date) => timezoneService.isSameDay(date1, date2), []);
78
+ const addDays = useCallback((date: Date, days: number) => timezoneService.addDays(date, days), []);
79
+ const startOfDay = useCallback((date: Date) => timezoneService.startOfDay(date), []);
80
+ const endOfDay = useCallback((date: Date) => timezoneService.endOfDay(date), []);
81
+ const formatDateToString = useCallback((date: Date) => timezoneService.formatDateToString(date), []);
82
+ const getCurrentISOString = useCallback(() => timezoneService.getCurrentISOString(), []);
83
+ const formatToISOString = useCallback((date: Date) => timezoneService.formatToISOString(date), []);
84
+ const getTimezones = useCallback(() => timezoneService.getTimezones(), []);
85
+ const isValid = useCallback((date: Date) => timezoneService.isValid(date), []);
86
+ const getAge = useCallback((birthDate: Date) => timezoneService.getAge(birthDate), []);
87
+ const isBetween = useCallback((date: Date, start: Date, end: Date) =>
88
+ timezoneService.isBetween(date, start, end), []);
89
+ const min = useCallback((dates: Date[]) => timezoneService.min(dates), []);
90
+ const max = useCallback((dates: Date[]) => timezoneService.max(dates), []);
91
+ const getWeek = useCallback((date: Date) => timezoneService.getWeek(date), []);
92
+ const getQuarter = useCallback((date: Date) => timezoneService.getQuarter(date), []);
93
+ const getTimezoneOffsetFor = useCallback((timezone: string, date?: Date) =>
94
+ timezoneService.getTimezoneOffsetFor(timezone, date), []);
95
+ const convertTimezone = useCallback((date: Date, fromTimezone: string, toTimezone: string) =>
96
+ timezoneService.convertTimezone(date, fromTimezone, toTimezone), []);
97
+ const formatDuration = useCallback((milliseconds: number) => timezoneService.formatDuration(milliseconds), []);
98
+ const isWeekend = useCallback((date: Date) => timezoneService.isWeekend(date), []);
99
+ const addBusinessDays = useCallback((date: Date, days: number) =>
100
+ timezoneService.addBusinessDays(date, days), []);
101
+ const isFirstDayOfMonth = useCallback((date: Date) => timezoneService.isFirstDayOfMonth(date), []);
102
+ const isLastDayOfMonth = useCallback((date: Date) => timezoneService.isLastDayOfMonth(date), []);
103
+ const getDaysInMonth = useCallback((date: Date) => timezoneService.getDaysInMonth(date), []);
104
+ const getDateRange = useCallback((start: Date, end: Date) => timezoneService.getDateRange(start, end), []);
105
+ const areRangesOverlapping = useCallback((start1: Date, end1: Date, start2: Date, end2: Date) =>
106
+ timezoneService.areRangesOverlapping(start1, end1, start2, end2), []);
107
+ const clampDate = useCallback((date: Date, min: Date, max: Date) =>
108
+ timezoneService.clampDate(date, min, max), []);
109
+ const areSameHour = useCallback((date1: Date, date2: Date) =>
110
+ timezoneService.areSameHour(date1, date2), []);
111
+ const areSameMinute = useCallback((date1: Date, date2: Date) =>
112
+ timezoneService.areSameMinute(date1, date2), []);
113
+ const getMiddleOfDay = useCallback((date: Date) => timezoneService.getMiddleOfDay(date), []);
114
+
115
+ return {
116
+ timezone: timezoneInfo.timezone || '',
117
+ timezoneInfo,
118
+ formatDate,
119
+ formatTime,
120
+ getCalendarDays,
121
+ isToday,
122
+ isSameDay,
123
+ addDays,
124
+ startOfDay,
125
+ endOfDay,
126
+ formatDateToString,
127
+ getCurrentISOString,
128
+ formatToISOString,
129
+ formatRelativeTime,
130
+ formatDateTime,
131
+ getTimezones,
132
+ isValid,
133
+ getAge,
134
+ isBetween,
135
+ min,
136
+ max,
137
+ getWeek,
138
+ getQuarter,
139
+ getTimezoneOffsetFor,
140
+ convertTimezone,
141
+ formatDuration,
142
+ isWeekend,
143
+ addBusinessDays,
144
+ isFirstDayOfMonth,
145
+ isLastDayOfMonth,
146
+ getDaysInMonth,
147
+ getDateRange,
148
+ areRangesOverlapping,
149
+ clampDate,
150
+ areSameHour,
151
+ areSameMinute,
152
+ getMiddleOfDay,
153
+ fromNow,
154
+ };
155
+ };