@greatdayhr/capacitor-datetime-setting 1.1.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +359 -58
- package/android/src/main/java/com/datetimesetting/DateTimeSettingPlugin.java +9 -90
- package/dist/esm/definitions.d.ts +185 -19
- package/dist/esm/definitions.d.ts.map +1 -1
- package/dist/esm/web.d.ts +25 -6
- package/dist/esm/web.d.ts.map +1 -1
- package/dist/esm/web.js +27 -3
- package/dist/plugin.cjs.js +27 -3
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +27 -3
- package/dist/plugin.js.map +1 -1
- package/ios/Plugin/AutoDateTimeDetector.swift +671 -0
- package/ios/Plugin/DateTimeSettingPlugin.m +17 -3
- package/ios/Plugin/DateTimeSettingPlugin.swift +184 -46
- package/ios/Plugin/NotificationManager.swift +259 -0
- package/package.json +8 -5
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Network
|
|
3
|
+
import UserNotifications
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* iOS implementation for detecting automatic date/time settings and date/time changes
|
|
7
|
+
*/
|
|
8
|
+
class AutoDateTimeDetector {
|
|
9
|
+
|
|
10
|
+
// MARK: - Properties
|
|
11
|
+
|
|
12
|
+
private static let timeChangeThreshold: TimeInterval = 5.0 // 5 seconds
|
|
13
|
+
private static let networkCheckInterval: TimeInterval = 300.0 // 5 minutes
|
|
14
|
+
private static var networkMonitor = NWPathMonitor()
|
|
15
|
+
private static var isNetworkAvailable = true
|
|
16
|
+
|
|
17
|
+
private static var storedTimestamp: Date?
|
|
18
|
+
private static var storedDateComponents: DateComponents?
|
|
19
|
+
private static var lastNetworkCheckTime: Date?
|
|
20
|
+
|
|
21
|
+
// MARK: - Date Change Detection Types
|
|
22
|
+
|
|
23
|
+
enum DateTimeChangeType {
|
|
24
|
+
case noChange
|
|
25
|
+
case timeOnly
|
|
26
|
+
case dateOnly
|
|
27
|
+
case dateAndTime
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
struct DateTimeChangeResult {
|
|
31
|
+
let changeType: DateTimeChangeType
|
|
32
|
+
let timeDifference: TimeInterval
|
|
33
|
+
let dateChanged: Bool
|
|
34
|
+
let timeChanged: Bool
|
|
35
|
+
let isAutoDateTimeEnabled: Bool
|
|
36
|
+
let previousDate: Date?
|
|
37
|
+
let currentDate: Date
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// MARK: - Initialization
|
|
41
|
+
static func initialize() {
|
|
42
|
+
startNetworkMonitoring()
|
|
43
|
+
storedTimestamp = getCurrentLocalTime()
|
|
44
|
+
storedDateComponents = Calendar.current.dateComponents([.year, .month, .day], from: getCurrentLocalTime())
|
|
45
|
+
|
|
46
|
+
// Request notification permission
|
|
47
|
+
NotificationManager.shared.requestNotificationPermission { granted in
|
|
48
|
+
print("Notification permission granted: \(granted)")
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// MARK: - Date/Time Change Detection
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Retrieves the device's current local time
|
|
56
|
+
* @return Current local time as Date object
|
|
57
|
+
*/
|
|
58
|
+
static func getCurrentLocalTime() -> Date {
|
|
59
|
+
return Date()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Converts local time to UTC
|
|
64
|
+
* @param localTime The local time to convert
|
|
65
|
+
* @return UTC time as Date object
|
|
66
|
+
*/
|
|
67
|
+
static func convertLocalTimeToUTC(_ localTime: Date) -> Date {
|
|
68
|
+
let timeZone = TimeZone.current
|
|
69
|
+
let utcOffset = timeZone.secondsFromGMT(for: localTime)
|
|
70
|
+
return localTime.addingTimeInterval(-TimeInterval(utcOffset))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Fetches accurate UTC time from internet time server
|
|
75
|
+
* @param completion Completion handler with Result containing UTC time or error
|
|
76
|
+
*/
|
|
77
|
+
static func fetchInternetUTCTime(completion: @escaping (Result<Date, Error>) -> Void) {
|
|
78
|
+
guard isNetworkAvailable else {
|
|
79
|
+
completion(.failure(NSError(domain: "NetworkError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Network not available"])))
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
guard let url = URL(string: "https://worldtimeapi.org/api/timezone/Etc/UTC") else {
|
|
84
|
+
completion(.failure(NSError(domain: "URLError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])))
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
var request = URLRequest(url: url)
|
|
89
|
+
request.timeoutInterval = 10.0
|
|
90
|
+
request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData
|
|
91
|
+
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
|
92
|
+
|
|
93
|
+
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
|
94
|
+
if let error = error {
|
|
95
|
+
completion(.failure(error))
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
guard let data = data,
|
|
100
|
+
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
101
|
+
let unixTimeString = json["unixtime"] as? Double else {
|
|
102
|
+
completion(.failure(NSError(domain: "ParseError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to parse server response"])))
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let utcTime = Date(timeIntervalSince1970: unixTimeString)
|
|
107
|
+
completion(.success(utcTime))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
task.resume()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Detects if the device's date/time has been manually changed
|
|
115
|
+
* Uses network time comparison for accuracy when available
|
|
116
|
+
* Falls back to local time comparison for offline scenarios
|
|
117
|
+
*
|
|
118
|
+
* @param completion Callback with detection result
|
|
119
|
+
*/
|
|
120
|
+
static func detectDateTimeChange(completion: @escaping (Bool) -> Void) {
|
|
121
|
+
let currentLocalTime = getCurrentLocalTime()
|
|
122
|
+
|
|
123
|
+
guard let storedTime = storedTimestamp else {
|
|
124
|
+
// First time check - store current time and return false
|
|
125
|
+
storedTimestamp = currentLocalTime
|
|
126
|
+
storedDateComponents = Calendar.current.dateComponents([.year, .month, .day], from: currentLocalTime)
|
|
127
|
+
completion(false)
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check if we should perform network-based detection
|
|
132
|
+
if shouldPerformNetworkTimeCheck() {
|
|
133
|
+
fetchInternetUTCTime { result in
|
|
134
|
+
switch result {
|
|
135
|
+
case .success(let internetTime):
|
|
136
|
+
lastNetworkCheckTime = Date()
|
|
137
|
+
|
|
138
|
+
// Compare with stored time considering network time as reference
|
|
139
|
+
let expectedLocalTime = internetTime.addingTimeInterval(TimeInterval(TimeZone.current.secondsFromGMT()))
|
|
140
|
+
let timeDifference = abs(currentLocalTime.timeIntervalSince(expectedLocalTime))
|
|
141
|
+
|
|
142
|
+
let changeDetected = timeDifference > timeChangeThreshold
|
|
143
|
+
|
|
144
|
+
if changeDetected {
|
|
145
|
+
print("Network-based time change detected: \(timeDifference) seconds difference")
|
|
146
|
+
storedTimestamp = currentLocalTime
|
|
147
|
+
storedDateComponents = Calendar.current.dateComponents([.year, .month, .day], from: currentLocalTime)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
completion(changeDetected)
|
|
151
|
+
|
|
152
|
+
case .failure(let error):
|
|
153
|
+
print("Failed to fetch internet time: \(error.localizedDescription)")
|
|
154
|
+
// Fallback to local time comparison
|
|
155
|
+
let changeDetected = detectLocalTimeChange(currentTime: currentLocalTime, storedTime: storedTime)
|
|
156
|
+
completion(changeDetected)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
// Use local time comparison to minimize network usage
|
|
161
|
+
let changeDetected = detectLocalTimeChange(currentTime: currentLocalTime, storedTime: storedTime)
|
|
162
|
+
completion(changeDetected)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Comprehensive date and time change detection with detailed analysis
|
|
168
|
+
* Distinguishes between date-only, time-only, and combined changes
|
|
169
|
+
*
|
|
170
|
+
* @param completion Callback with detailed change result
|
|
171
|
+
*/
|
|
172
|
+
static func detectComprehensiveDateTimeChange(completion: @escaping (DateTimeChangeResult) -> Void) {
|
|
173
|
+
let currentLocalTime = getCurrentLocalTime()
|
|
174
|
+
let currentDateComponents = Calendar.current.dateComponents([.year, .month, .day], from: currentLocalTime)
|
|
175
|
+
|
|
176
|
+
guard let storedTime = storedTimestamp,
|
|
177
|
+
let storedComponents = storedDateComponents else {
|
|
178
|
+
// First time check - store current time and date components
|
|
179
|
+
storedTimestamp = currentLocalTime
|
|
180
|
+
storedDateComponents = currentDateComponents
|
|
181
|
+
|
|
182
|
+
let result = DateTimeChangeResult(
|
|
183
|
+
changeType: .noChange,
|
|
184
|
+
timeDifference: 0,
|
|
185
|
+
dateChanged: false,
|
|
186
|
+
timeChanged: false,
|
|
187
|
+
isAutoDateTimeEnabled: isAutoDateTimeEnabled(),
|
|
188
|
+
previousDate: nil,
|
|
189
|
+
currentDate: currentLocalTime
|
|
190
|
+
)
|
|
191
|
+
completion(result)
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Check if we should perform network-based detection
|
|
196
|
+
if shouldPerformNetworkTimeCheck() {
|
|
197
|
+
fetchInternetUTCTime { result in
|
|
198
|
+
switch result {
|
|
199
|
+
case .success(let internetTime):
|
|
200
|
+
lastNetworkCheckTime = Date()
|
|
201
|
+
analyzeChangesWithNetworkTime(currentTime: currentLocalTime,
|
|
202
|
+
storedTime: storedTime,
|
|
203
|
+
currentDateComponents: currentDateComponents,
|
|
204
|
+
previousDateComponents: storedComponents,
|
|
205
|
+
internetTime: internetTime,
|
|
206
|
+
completion: completion)
|
|
207
|
+
|
|
208
|
+
case .failure(let error):
|
|
209
|
+
print("Failed to fetch internet time: \(error.localizedDescription)")
|
|
210
|
+
// Fallback to local analysis
|
|
211
|
+
analyzeChangesLocally(currentTime: currentLocalTime,
|
|
212
|
+
storedTime: storedTime,
|
|
213
|
+
currentDateComponents: currentDateComponents,
|
|
214
|
+
previousDateComponents: storedComponents,
|
|
215
|
+
completion: completion)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
// Use local analysis to minimize network usage
|
|
220
|
+
analyzeChangesLocally(currentTime: currentLocalTime,
|
|
221
|
+
storedTime: storedTime,
|
|
222
|
+
currentDateComponents: currentDateComponents,
|
|
223
|
+
previousDateComponents: storedComponents,
|
|
224
|
+
completion: completion)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Detects specifically if only the date has been changed while time remains similar
|
|
230
|
+
* This is useful for detecting manual date changes when auto date/time is disabled
|
|
231
|
+
*
|
|
232
|
+
* @param completion Callback with date-only change detection result
|
|
233
|
+
*/
|
|
234
|
+
static func detectDateOnlyChange(completion: @escaping (Bool) -> Void) {
|
|
235
|
+
detectComprehensiveDateTimeChange { result in
|
|
236
|
+
completion(result.changeType == .dateOnly)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Analyzes changes using network time as reference
|
|
242
|
+
*/
|
|
243
|
+
private static func analyzeChangesWithNetworkTime(currentTime: Date,
|
|
244
|
+
storedTime: Date,
|
|
245
|
+
currentDateComponents: DateComponents,
|
|
246
|
+
previousDateComponents: DateComponents,
|
|
247
|
+
internetTime: Date,
|
|
248
|
+
completion: @escaping (DateTimeChangeResult) -> Void) {
|
|
249
|
+
|
|
250
|
+
let expectedLocalTime = internetTime.addingTimeInterval(TimeInterval(TimeZone.current.secondsFromGMT()))
|
|
251
|
+
let timeDifference = currentTime.timeIntervalSince(expectedLocalTime)
|
|
252
|
+
|
|
253
|
+
// Check if date components have changed
|
|
254
|
+
let dateChanged = (currentDateComponents.year != previousDateComponents.year ||
|
|
255
|
+
currentDateComponents.month != previousDateComponents.month ||
|
|
256
|
+
currentDateComponents.day != previousDateComponents.day)
|
|
257
|
+
|
|
258
|
+
// Check if time has changed significantly (beyond normal progression)
|
|
259
|
+
let timeChanged = abs(timeDifference) > timeChangeThreshold
|
|
260
|
+
|
|
261
|
+
// Determine change type
|
|
262
|
+
let changeType: DateTimeChangeType
|
|
263
|
+
if dateChanged && timeChanged {
|
|
264
|
+
changeType = .dateAndTime
|
|
265
|
+
} else if dateChanged && !timeChanged {
|
|
266
|
+
changeType = .dateOnly
|
|
267
|
+
} else if !dateChanged && timeChanged {
|
|
268
|
+
changeType = .timeOnly
|
|
269
|
+
} else {
|
|
270
|
+
changeType = .noChange
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Update stored values if changes detected
|
|
274
|
+
if changeType != .noChange {
|
|
275
|
+
storedTimestamp = currentTime
|
|
276
|
+
storedDateComponents = currentDateComponents
|
|
277
|
+
print("Network-based change detected - Type: \(changeType), Time diff: \(timeDifference)s")
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
let result = DateTimeChangeResult(
|
|
281
|
+
changeType: changeType,
|
|
282
|
+
timeDifference: timeDifference,
|
|
283
|
+
dateChanged: dateChanged,
|
|
284
|
+
timeChanged: timeChanged,
|
|
285
|
+
isAutoDateTimeEnabled: isAutoDateTimeEnabled(),
|
|
286
|
+
previousDate: storedTime,
|
|
287
|
+
currentDate: currentTime
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
completion(result)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Shows appropriate notifications based on detected changes
|
|
295
|
+
*/
|
|
296
|
+
private static func showNotificationForChangeResult(_ result: DateTimeChangeResult) {
|
|
297
|
+
// Only show notifications if changes are detected
|
|
298
|
+
guard result.changeType != .noChange else { return }
|
|
299
|
+
|
|
300
|
+
// Show specific notification based on change type
|
|
301
|
+
switch result.changeType {
|
|
302
|
+
case .dateOnly:
|
|
303
|
+
NotificationManager.shared.showDateOnlyChangeNotification()
|
|
304
|
+
case .timeOnly:
|
|
305
|
+
NotificationManager.shared.showTimeOnlyChangeNotification()
|
|
306
|
+
case .dateAndTime:
|
|
307
|
+
NotificationManager.shared.showDateTimeChangeNotification()
|
|
308
|
+
case .noChange:
|
|
309
|
+
break
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Additionally show auto date/time disabled notification if applicable
|
|
313
|
+
if !result.isAutoDateTimeEnabled {
|
|
314
|
+
// Delay this notification slightly to avoid overwhelming the user
|
|
315
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
|
316
|
+
NotificationManager.shared.showAutoDateTimeDisabledNotification()
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Comprehensive date and time change detection with automatic notifications
|
|
323
|
+
* This method combines detection with user notification for better UX
|
|
324
|
+
*/
|
|
325
|
+
static func detectAndNotifyDateTimeChanges(completion: @escaping (DateTimeChangeResult) -> Void) {
|
|
326
|
+
detectComprehensiveDateTimeChange { result in
|
|
327
|
+
// Show notifications if changes detected
|
|
328
|
+
showNotificationForChangeResult(result)
|
|
329
|
+
|
|
330
|
+
// Return the result to the caller
|
|
331
|
+
completion(result)
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Analyzes changes using local time comparison (fallback method)
|
|
337
|
+
*/
|
|
338
|
+
private static func analyzeChangesLocally(currentTime: Date,
|
|
339
|
+
storedTime: Date,
|
|
340
|
+
currentDateComponents: DateComponents,
|
|
341
|
+
previousDateComponents: DateComponents,
|
|
342
|
+
completion: @escaping (DateTimeChangeResult) -> Void) {
|
|
343
|
+
|
|
344
|
+
let timeDifference = currentTime.timeIntervalSince(storedTime)
|
|
345
|
+
|
|
346
|
+
// Check if date components have changed
|
|
347
|
+
let dateChanged = (currentDateComponents.year != previousDateComponents.year ||
|
|
348
|
+
currentDateComponents.month != previousDateComponents.month ||
|
|
349
|
+
currentDateComponents.day != previousDateComponents.day)
|
|
350
|
+
|
|
351
|
+
// For local analysis, we need to be more careful about time changes
|
|
352
|
+
// Consider the expected time progression since last check
|
|
353
|
+
let expectedTimeDifference = Date().timeIntervalSince(storedTime)
|
|
354
|
+
let unexpectedTimeDifference = abs(timeDifference - expectedTimeDifference)
|
|
355
|
+
let timeChanged = unexpectedTimeDifference > timeChangeThreshold
|
|
356
|
+
|
|
357
|
+
// Determine change type
|
|
358
|
+
let changeType: DateTimeChangeType
|
|
359
|
+
if dateChanged && timeChanged {
|
|
360
|
+
changeType = .dateAndTime
|
|
361
|
+
} else if dateChanged && !timeChanged {
|
|
362
|
+
changeType = .dateOnly
|
|
363
|
+
} else if !dateChanged && timeChanged {
|
|
364
|
+
changeType = .timeOnly
|
|
365
|
+
} else {
|
|
366
|
+
changeType = .noChange
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Update stored values if changes detected
|
|
370
|
+
if changeType != .noChange {
|
|
371
|
+
storedTimestamp = currentTime
|
|
372
|
+
storedDateComponents = currentDateComponents
|
|
373
|
+
print("Local change detected - Type: \(changeType), Time diff: \(timeDifference)s")
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
let result = DateTimeChangeResult(
|
|
377
|
+
changeType: changeType,
|
|
378
|
+
timeDifference: timeDifference,
|
|
379
|
+
dateChanged: dateChanged,
|
|
380
|
+
timeChanged: timeChanged,
|
|
381
|
+
isAutoDateTimeEnabled: isAutoDateTimeEnabled(),
|
|
382
|
+
previousDate: storedTime,
|
|
383
|
+
currentDate: currentTime
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
completion(result)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Cache for auto date/time status to avoid repeated network calls
|
|
390
|
+
private static var cachedAutoDateTimeStatus: Bool?
|
|
391
|
+
private static var lastStatusCheckTime: Date?
|
|
392
|
+
private static let statusCacheInterval: TimeInterval = 30.0 // Cache for 30 seconds
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Checks if automatic date/time is enabled on iOS device (async version)
|
|
396
|
+
* Uses caching to provide instant results and avoid network delays
|
|
397
|
+
*/
|
|
398
|
+
static func isAutoDateTimeEnabled(completion: @escaping (Bool) -> Void) {
|
|
399
|
+
// Check if we have a recent cached result
|
|
400
|
+
if let cachedStatus = cachedAutoDateTimeStatus,
|
|
401
|
+
let lastCheck = lastStatusCheckTime,
|
|
402
|
+
Date().timeIntervalSince(lastCheck) < statusCacheInterval {
|
|
403
|
+
completion(cachedStatus)
|
|
404
|
+
return
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// First, try the timezone approach as a quick check
|
|
408
|
+
let autoUpdatingTimeZone = TimeZone.autoupdatingCurrent
|
|
409
|
+
let systemTimeZone = TimeZone.current
|
|
410
|
+
|
|
411
|
+
// If timezone auto-update is disabled, automatic date/time is likely disabled
|
|
412
|
+
if autoUpdatingTimeZone.identifier != systemTimeZone.identifier {
|
|
413
|
+
let result = false
|
|
414
|
+
cachedAutoDateTimeStatus = result
|
|
415
|
+
lastStatusCheckTime = Date()
|
|
416
|
+
completion(result)
|
|
417
|
+
return
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Perform network-based time comparison asynchronously
|
|
421
|
+
checkTimeWithNetworkServerAsync { isEnabled in
|
|
422
|
+
cachedAutoDateTimeStatus = isEnabled
|
|
423
|
+
lastStatusCheckTime = Date()
|
|
424
|
+
completion(isEnabled)
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Synchronous version for backward compatibility (uses cached result or fallback)
|
|
430
|
+
*/
|
|
431
|
+
static func isAutoDateTimeEnabled() -> Bool {
|
|
432
|
+
// Return cached result if available and recent
|
|
433
|
+
if let cachedStatus = cachedAutoDateTimeStatus,
|
|
434
|
+
let lastCheck = lastStatusCheckTime,
|
|
435
|
+
Date().timeIntervalSince(lastCheck) < statusCacheInterval {
|
|
436
|
+
return cachedStatus
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Fallback to offline check for immediate response
|
|
440
|
+
return isAutoDateTimeEnabledOffline()
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Compares device time with network time server (async version)
|
|
445
|
+
* Non-blocking approach for better performance
|
|
446
|
+
*/
|
|
447
|
+
private static func checkTimeWithNetworkServerAsync(completion: @escaping (Bool) -> Void) {
|
|
448
|
+
// Create URL request to a reliable time server
|
|
449
|
+
guard let url = URL(string: "https://worldtimeapi.org/api/timezone/Etc/UTC") else {
|
|
450
|
+
completion(true) // Fallback to true if URL creation fails
|
|
451
|
+
return
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
var request = URLRequest(url: url)
|
|
455
|
+
request.timeoutInterval = 3.0 // Reduced timeout for faster response
|
|
456
|
+
request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData
|
|
457
|
+
|
|
458
|
+
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
|
459
|
+
// Check for network errors
|
|
460
|
+
if let error = error {
|
|
461
|
+
print("Network error checking time: \(error.localizedDescription)")
|
|
462
|
+
completion(true) // Default to true if network check fails
|
|
463
|
+
return
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Parse response data
|
|
467
|
+
guard let data = data,
|
|
468
|
+
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
469
|
+
let unixTimeString = json["unixtime"] as? Double else {
|
|
470
|
+
print("Failed to parse time server response")
|
|
471
|
+
completion(true) // Default to true if parsing fails
|
|
472
|
+
return
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Compare server time with device time
|
|
476
|
+
let serverTime = Date(timeIntervalSince1970: unixTimeString)
|
|
477
|
+
let deviceTime = Date()
|
|
478
|
+
let timeDifference = abs(deviceTime.timeIntervalSince(serverTime))
|
|
479
|
+
|
|
480
|
+
// If time difference is more than 60 seconds, consider auto-time disabled
|
|
481
|
+
// This threshold accounts for network latency and minor clock drift
|
|
482
|
+
if timeDifference > 60.0 {
|
|
483
|
+
print("Time difference detected: \(timeDifference) seconds")
|
|
484
|
+
completion(false)
|
|
485
|
+
} else {
|
|
486
|
+
completion(true)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
task.resume()
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Compares device time with network time server (legacy synchronous version)
|
|
495
|
+
* Uses a synchronous approach with timeout for reliability
|
|
496
|
+
*/
|
|
497
|
+
private static func checkTimeWithNetworkServer() -> Bool {
|
|
498
|
+
let semaphore = DispatchSemaphore(value: 0)
|
|
499
|
+
var isAutoTimeEnabled = true // Default to true if network check fails
|
|
500
|
+
|
|
501
|
+
// Create URL request to a reliable time server
|
|
502
|
+
guard let url = URL(string: "https://worldtimeapi.org/api/timezone/Etc/UTC") else {
|
|
503
|
+
return true // Fallback to true if URL creation fails
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
var request = URLRequest(url: url)
|
|
507
|
+
request.timeoutInterval = 5.0 // 5 second timeout
|
|
508
|
+
request.cachePolicy = .reloadIgnoringLocalAndRemoteCacheData
|
|
509
|
+
|
|
510
|
+
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
|
511
|
+
defer { semaphore.signal() }
|
|
512
|
+
|
|
513
|
+
// Check for network errors
|
|
514
|
+
if let error = error {
|
|
515
|
+
print("Network error checking time: \(error.localizedDescription)")
|
|
516
|
+
return // Keep default value (true)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Parse response data
|
|
520
|
+
guard let data = data,
|
|
521
|
+
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
522
|
+
let unixTimeString = json["unixtime"] as? Double else {
|
|
523
|
+
print("Failed to parse time server response")
|
|
524
|
+
return // Keep default value (true)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Compare server time with device time
|
|
528
|
+
let serverTime = Date(timeIntervalSince1970: unixTimeString)
|
|
529
|
+
let deviceTime = Date()
|
|
530
|
+
let timeDifference = abs(deviceTime.timeIntervalSince(serverTime))
|
|
531
|
+
|
|
532
|
+
// If time difference is more than 60 seconds, consider auto-time disabled
|
|
533
|
+
// This threshold accounts for network latency and minor clock drift
|
|
534
|
+
if timeDifference > 60.0 {
|
|
535
|
+
isAutoTimeEnabled = false
|
|
536
|
+
print("Time difference detected: \(timeDifference) seconds")
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
task.resume()
|
|
541
|
+
|
|
542
|
+
// Wait for network request to complete (with timeout)
|
|
543
|
+
let timeoutResult = semaphore.wait(timeout: .now() + 6.0)
|
|
544
|
+
|
|
545
|
+
if timeoutResult == .timedOut {
|
|
546
|
+
print("Network time check timed out")
|
|
547
|
+
task.cancel()
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return isAutoTimeEnabled
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Alternative method for offline scenarios
|
|
555
|
+
* Checks system settings indirectly through available APIs
|
|
556
|
+
*/
|
|
557
|
+
static func isAutoDateTimeEnabledOffline() -> Bool {
|
|
558
|
+
// Check if timezone auto-update is enabled
|
|
559
|
+
let autoUpdatingTimeZone = TimeZone.autoupdatingCurrent
|
|
560
|
+
let systemTimeZone = TimeZone.current
|
|
561
|
+
|
|
562
|
+
// Additional check: compare with system uptime
|
|
563
|
+
let processInfo = ProcessInfo.processInfo
|
|
564
|
+
let systemUptime = processInfo.systemUptime
|
|
565
|
+
|
|
566
|
+
// If system has been up for a while and timezone matches auto-updating,
|
|
567
|
+
// it's likely that auto date/time is enabled
|
|
568
|
+
if systemUptime > 300 && autoUpdatingTimeZone.identifier == systemTimeZone.identifier {
|
|
569
|
+
return true
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return autoUpdatingTimeZone.identifier == systemTimeZone.identifier
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// MARK: - Helper Methods
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Starts network connectivity monitoring for battery optimization
|
|
579
|
+
*/
|
|
580
|
+
private static func startNetworkMonitoring() {
|
|
581
|
+
let queue = DispatchQueue(label: "NetworkMonitor")
|
|
582
|
+
networkMonitor.start(queue: queue)
|
|
583
|
+
|
|
584
|
+
networkMonitor.pathUpdateHandler = { path in
|
|
585
|
+
isNetworkAvailable = path.status == .satisfied
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Determines if a network time check should be performed based on battery optimization
|
|
591
|
+
* @return true if network check should be performed, false otherwise
|
|
592
|
+
*/
|
|
593
|
+
private static func shouldPerformNetworkTimeCheck() -> Bool {
|
|
594
|
+
guard isNetworkAvailable else { return false }
|
|
595
|
+
|
|
596
|
+
// Check if enough time has passed since last network check
|
|
597
|
+
if let lastCheck = lastNetworkCheckTime {
|
|
598
|
+
let timeSinceLastCheck = Date().timeIntervalSince(lastCheck)
|
|
599
|
+
return timeSinceLastCheck >= networkCheckInterval
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return true // First time check
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Detects time changes using local comparison (fallback method)
|
|
607
|
+
* @param currentTime Current local time
|
|
608
|
+
* @param storedTime Previously stored time
|
|
609
|
+
* @return true if significant time change detected, false otherwise
|
|
610
|
+
*/
|
|
611
|
+
private static func detectLocalTimeChange(currentTime: Date, storedTime: Date) -> Bool {
|
|
612
|
+
let expectedTimeDifference = currentTime.timeIntervalSince(storedTime)
|
|
613
|
+
|
|
614
|
+
// If the time difference is significantly different from expected (considering app lifecycle),
|
|
615
|
+
// it might indicate a manual time change
|
|
616
|
+
let processInfo = ProcessInfo.processInfo
|
|
617
|
+
let systemUptime = processInfo.systemUptime
|
|
618
|
+
|
|
619
|
+
// Simple heuristic: if time jumped more than expected based on system uptime
|
|
620
|
+
if abs(expectedTimeDifference) > timeChangeThreshold {
|
|
621
|
+
print("Local time change detected: \(expectedTimeDifference) seconds difference")
|
|
622
|
+
storedTimestamp = currentTime
|
|
623
|
+
return true
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return false
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Updates the stored timestamp for future reference
|
|
631
|
+
* @param newTimestamp New timestamp to store
|
|
632
|
+
*/
|
|
633
|
+
static func updateStoredTimestamp(_ newTimestamp: Date) {
|
|
634
|
+
storedTimestamp = newTimestamp
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Sets the stored timestamp and date components
|
|
639
|
+
* @param newTimestamp New timestamp to store
|
|
640
|
+
*/
|
|
641
|
+
static func setStoredTimestamp(_ newTimestamp: Date) {
|
|
642
|
+
storedTimestamp = newTimestamp
|
|
643
|
+
storedDateComponents = Calendar.current.dateComponents([.year, .month, .day], from: newTimestamp)
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Gets the currently stored timestamp
|
|
648
|
+
* @return Stored timestamp or nil if not set
|
|
649
|
+
*/
|
|
650
|
+
static func getStoredTimestamp() -> Date? {
|
|
651
|
+
return storedTimestamp
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Resets the stored timestamp, date components, network check time, and status cache
|
|
656
|
+
*/
|
|
657
|
+
static func reset() {
|
|
658
|
+
storedTimestamp = nil
|
|
659
|
+
storedDateComponents = nil
|
|
660
|
+
lastNetworkCheckTime = nil
|
|
661
|
+
cachedAutoDateTimeStatus = nil
|
|
662
|
+
lastStatusCheckTime = nil
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Stops network monitoring to conserve battery
|
|
667
|
+
*/
|
|
668
|
+
static func stopNetworkMonitoring() {
|
|
669
|
+
networkMonitor.cancel()
|
|
670
|
+
}
|
|
671
|
+
}
|
|
@@ -1,10 +1,24 @@
|
|
|
1
|
+
|
|
1
2
|
#import <Foundation/Foundation.h>
|
|
2
3
|
#import <Capacitor/Capacitor.h>
|
|
3
4
|
|
|
4
5
|
// Define the plugin using the CAP_PLUGIN Macro, and
|
|
5
6
|
// each method the plugin supports using the CAP_PLUGIN_METHOD macro.
|
|
6
7
|
CAP_PLUGIN(DateTimeSettingPlugin, "DateTimeSetting",
|
|
7
|
-
|
|
8
|
-
CAP_PLUGIN_METHOD(
|
|
9
|
-
CAP_PLUGIN_METHOD(
|
|
8
|
+
// Date/Time Change Detection
|
|
9
|
+
CAP_PLUGIN_METHOD(detectDateTimeChange, CAPPluginReturnPromise);
|
|
10
|
+
CAP_PLUGIN_METHOD(detectComprehensiveDateTimeChange, CAPPluginReturnPromise);
|
|
11
|
+
CAP_PLUGIN_METHOD(detectDateOnlyChange, CAPPluginReturnPromise);
|
|
12
|
+
CAP_PLUGIN_METHOD(detectAndNotifyDateTimeChanges, CAPPluginReturnPromise);
|
|
13
|
+
|
|
14
|
+
// Time Utilities
|
|
15
|
+
CAP_PLUGIN_METHOD(getLocalTime, CAPPluginReturnPromise);
|
|
16
|
+
CAP_PLUGIN_METHOD(getInternetUTCTime, CAPPluginReturnPromise);
|
|
17
|
+
CAP_PLUGIN_METHOD(convertToLocalTime, CAPPluginReturnPromise);
|
|
18
|
+
|
|
19
|
+
// Timestamp Management
|
|
20
|
+
CAP_PLUGIN_METHOD(setStoredTimestamp, CAPPluginReturnPromise);
|
|
21
|
+
CAP_PLUGIN_METHOD(getStoredTimestamp, CAPPluginReturnPromise);
|
|
22
|
+
CAP_PLUGIN_METHOD(resetDetector, CAPPluginReturnPromise);
|
|
10
23
|
)
|
|
24
|
+
|