Timer decrement time changing if device date changes in Swift - ios

I am getting two dates from server. Current time and Unlock time.
Unlock date: 2021-07-23 05:55:44 +0000
Current date: 2021-07-23 05:54:44 +0000
So, I have to subtract from unlock date to current date and Remainder time, I have to run timer to unlock.
let client = TrueTimeClient.sharedInstance
override func viewDidLoad() {
super.viewDidLoad()
let when = DispatchTime.now() + 0.1
DispatchQueue.main.asyncAfter(deadline: when) {
self.countDownTimer = .scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.countDownTime()
}
}
}
#objc func countDownTime() {
let ntpTime = client.referenceTime?.now()
let unlockDuration = self.getUnlockCountDownTime(currentTime: unlocksTime ?? "" , unlockTime: unlocksTime ?? "", ntpDate: ntpTime ?? Date())
unlockHrsLabel.text = "\(unlockDuration)"
if unlockDuration == "0d :0h : 0: 0" {
self.stopTimer()
//call some api
}
}
func getUnlockCountDownTime(currentTime: String, unlockTime: String, ntpDate: Date) -> String {
let dateFormatter = DateFormatter()
let loc = Locale(identifier: "en_US_POSIX")
dateFormatter.locale = loc
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
// dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
let unlockDate = dateFormatter.date(from: "\(unlockTime)") ?? Date()
print("unlockDate \(unlockDate)")
print("ntpDate \(ntpDate)")
let currentDate = dateFormatter.date(from: "\(currentTime)") ?? Date()
print("currentDate \(currentDate)")
let calendar = Calendar.current
let diffDateComponents = calendar.dateComponents([.day, .hour, .minute, .second], from: unlockDate, to: ntpDate)
let countdown = "\(String(describing:diffDateComponents.day!))d :\(String(describing: diffDateComponents.hour!))h : \(String(describing: diffDateComponents.minute!)): \(String(describing: diffDateComponents.second!))"
// print(countdown)
return countdown
}
func stopTimer(){
guard self.countDownTimer != nil else {
fatalError("No timer active, start the timer before you stop it.")
}
self.countDownTimer?.invalidate()
}
Here, I have used pod 'TrueTime' to fetch ntp time, but if we change device time, the timer duration increasing automatically.
Suppose, i am getting remainder time 1:50 seconds, If I change date to june 20, 2021, Its showing more days and hours to unlock.
I have to show always unlock timer duration same irrespective time changes and time zones.
It should come as above screenshot. But, if I change date, it is coming as below screen which is wrong
How to fix this? Any suggestions?

I have found solution.
I just uninstalled pod 'TrueTime' and subtract current time to unlock time using timeinterval Then I have run timer with remainder seconds.
Then I run timer with decrement of remainder.
#objc func countDownTime() {
self.remainingTime -= 1
seunlockHrsLabel.text = "\(self.convertIntegerToFormat(remainingTime: self.remainingTime))"
if remainingTime == 0 {
self.stopTimer()
}
}
func convertIntegerToFormat(remainingTime: Int) -> String {
let days = remainingTime / (24 * 3600)
let hours = remainingTime / 3600
let seconds = remainingTime % 60
let minutes = (remainingTime / 60) % 60
return "\(days): \(hours): \(minutes): \(seconds)"
}
Now, It is working irrespective of device time and time zones.

Related

I have Start Date Time And End Date Time How To Show End Sale in iOS Swift 4

I have start date and end date strings, how to show the end Sale timer like this image?
"Start-date":"Dec 18, 2019 05:15:00 +0000","End-date":"Dec 27, 2019 11:15:39 +0000"
Please help!
You should convert the date string to Date first, then you can use timer to update the timer labels based on start and end date.
Use Calendar and dateComponents method to find the day, hour, minute and second differences between dates and then set the value on day, hour, minute and second labels as like you attached image.
Example implementation:
var timer:Timer?
var endDate:Date?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
startTimer()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
stopTimer()
}
func startTimer(){
let endDateStr = "Dec 27, 2019 11:15:39 +0000"
let dateFormat = "MMM d, yyyy HH:mm:ss Z"
let dateFormater = DateFormatter()
dateFormater.dateFormat = dateFormat
endDate = dateFormater.date(from: endDateStr)
//stop timer if it's already running
stopTimer()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateSaleTime), userInfo: nil, repeats: true)
}
func stopTimer(){
if timer != nil{
timer!.invalidate()
timer = nil
}
}
func updateSaleTime(){
guard let d2 = endDate else {
stopTimer()//Check if the date-format is correct for end date string.
return
}
let cal = Calendar.current
let components = cal.dateComponents([.day, .hour, .minute, .second], from: Date(), to: d2)
let day = components.day!
let hour = components.hour!
let minute = components.minute!
let second = components.second!
//set the value on day, hour, minute and second labels as like you attached image.
}
If you calculate the difference between the startDate and the endDate it will alway be the same . Instead you can calculate the difference to endDate from the current date.
Check the following implementation.
class ViewController: UIViewController {
let endDate : Date? = {
// To create Date from date string received from server
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM dd, yyyy HH:mm:ss Z"
// Convert to desired Timezone
return dateFormatter.date(from: "Dec 18, 2019 06:30:39 +0000")
}()
var timer : Timer?
private func starCountDown() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
}
private func stopCountDown() {
timer?.invalidate()
}
#objc func updateTime() {
guard let endDate = endDate else {
stopCountDown()
return
}
let countdown = Calendar.current.dateComponents([.day, .hour, .minute, .second], from: Date(), to: endDate)
let days = countdown.day!
let hours = countdown.hour!
let minutes = countdown.minute!
let seconds = countdown.second!
if days <= 0 && hours <= 0 && minutes <= 0 && seconds <= 0 {
stopCountDown()
print("Offer Expired")
return
}
print(String(format: "%02d Days , %02d Hours, %02d Mins, %02d Sec", days, hours, minutes, seconds))
// set values as per your requirement
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillAppear(animated)
stopCountDown()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
starCountDown()
}
}

Find difference in two dates in iOS

I am not sure what I am doing wrong, I need to find difference between two dates and extract seconds from it, below is my code. I am not getting correct seconds. There is difference of seconds.
public func captureStartTime() {
captureStartDateTime = Date()
}
public func captureEndTime(eventType: String, eventElement: String) {
let difference = Date().timeIntervalSince(captureStartDateTime)
let interval = Int(difference)
let seconds = interval % 60
let secondsDescrp = String(format: "%02d", seconds)
}
interval is the answer you want. That is the total number of seconds between the two dates.
Your seconds value would only be useful if you wanted to calculate the number of hours, minutes, and seconds or the number of minutes and seconds from the total number of seconds.
Use the following code to get the difference between two dates, Store current time in startTime when pressed button 1 and store current date time in endTime when pressed button 2, See this code, I hope this helps you.
var startTime:Date!
var endTime:Date!
#IBAction func buttonStartTime(_ sender: UIButton) {
startTime = Date()
}
#IBAction func buttonEndTime(_ sender: UIButton) {
endTime = Date()
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.second]
formatter.unitsStyle = .full
let difference = formatter.string(from: startTime, to: endTime)!
print(difference)//output "8 seconds"
}
Output
8 seconds
you can also use default date components and according to that compare your dates and you can get the difference in year, month, day etc
let dateString1 = "2019-03-07T14:20:20.000Z"
let dateString2 = "2019-03-07T14:20:40.000Z"
//set date formate
let Dateformatter = DateFormatter()
Dateformatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
//convert string to date
let dateold = Dateformatter.date(from: dateString1)!
let datenew = Dateformatter.date(from: dateString2)!
//use default datecomponents with two dates
let calendar1 = Calendar.current
let components = calendar1.dateComponents([.year,.month,.day,.hour,.minute,.second], from: dateold, to: datenew)
let seconds = components.second
print("Seconds: \(seconds)")

Creating a simple countdown to date in Swift 4

I'm working on a very simple app that counts down to a date. I found several tutorials but nothing in Swift 4. It seems like a lot has changed as I keep getting compiler errors.
Here is my code:
class ViewController: UIViewController {
#IBOutlet weak var CountdownText: UILabel!
let formatter = DateFormatter()
let userCalendar = NSCalendar.current
let requestedComponent: NSCalendar.Unit = [
NSCalendar.Unit.month,
NSCalendar.Unit.day,
NSCalendar.Unit.hour,
NSCalendar.Unit.minute,
NSCalendar.Unit.second,
]
func printTime()
{
formatter.dateFormat = "MM/dd/yy hh:mm:ss a"
let startTime = NSDate()
let endTime = formatter.date(from: "12/03/18 2:00:00 p")
func timeDifference (requestedComponent: NSCalendar.Unit, from: startTime, to: endTime!, options: [NSCalendar.Options]) {}
CountdownText.text = "\(timeDifference.day) Days \(timeDifference.minute) Minutes \(timeDifference.second) Seconds"
}
}
My errors are:
Use of undeclared type 'startTime'
Use of undeclared type 'endTime'
How to use
Copy the Code to your specific View Controller
Change the value of variable dateString with your date in the format
Date Format "< Month > < date >, < year > < hour >:< minute >:< second >"
Ex. "March 4, 2018 13:20:10"
Code
The below code will be useful for achieving a countdown timer of your custom date.
//
// DateCountDownTimer.swift
// CountDownTimerLearning
//
// Created by ThomasVEK on 04/03/18.
// Copyright © 2018 TVEK Solutions. All rights reserved.
//
import Foundation
func defaultUpdateActionHandler(string:String)->(){
}
func defaultCompletionActionHandler()->(){
}
public class DateCountDownTimer{
var countdownTimer: Timer!
var totalTime = 60
var dateString = "March 4, 2018 13:20:10" as String
var UpdateActionHandler:(String)->() = defaultUpdateActionHandler
var CompletionActionHandler:()->() = defaultCompletionActionHandler
public init(){
countdownTimer = Timer()
totalTime = 60
dateString = "March 4, 2018 13:20:10" as String
UpdateActionHandler = defaultUpdateActionHandler
CompletionActionHandler = defaultCompletionActionHandler
}
public func initializeTimer(pYear:Int, pMonth:String, pDay:Int, pHour:Int, pMin:Int, pSec:Int){
self.dateString = "\(pMonth) \(pDay), \(pYear) \(pHour):\(pMin):\(pSec)" as String
// Setting Today's Date
let currentDate = Date()
// Setting TargetDate
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy HH:mm:ss"
dateFormatter.timeZone = NSTimeZone.local
let targedDate = dateFormatter.date(from: dateString) as! Date
// Calculating the difference of dates for timer
let calendar = Calendar.current.dateComponents([.day, .hour, .minute, .second], from: currentDate, to: targedDate)
let days = calendar.day!
let hours = calendar.hour!
let minutes = calendar.minute!
let seconds = calendar.second!
totalTime = hours * 60 * 60 + minutes * 60 + seconds
totalTime = days * 60 * 60 * 24 + totalTime
}
func numberOfDaysInMonth(month:Int) -> Int{
let dateComponents = DateComponents(year: 2015, month: 7)
let calendar = Calendar.current
let date = calendar.date(from: dateComponents)!
let range = calendar.range(of: .day, in: .month, for: date)!
let numDays = range.count
print(numDays)
return numDays
}
public func startTimer(pUpdateActionHandler:#escaping (String)->(),pCompletionActionHandler:#escaping ()->()) {
countdownTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
self.CompletionActionHandler = pCompletionActionHandler
self.UpdateActionHandler = pUpdateActionHandler
}
#objc func updateTime() {
self.UpdateActionHandler(timeFormatted(totalTime))
if totalTime > 0 {
totalTime -= 1
} else {
endTimer()
}
}
func endTimer() {
self.CompletionActionHandler()
countdownTimer.invalidate()
}
func timeFormatted(_ totalSeconds: Int) -> String {
let seconds: Int = totalSeconds % 60
let minutes: Int = (totalSeconds / 60) % 60
let hours: Int = (totalSeconds / 60 / 60) % 24
let days: Int = (totalSeconds / 60 / 60 / 24)
return String(format: "%dD %02dH %02dM %02dS", days, hours, minutes, seconds)
}
}
You have specified timeDifference function inside printTime() function and in timeDifference() function you have defined from and to parameters which ones types are startTime and endTime which ones are not types. Replace them with NSDate like:
func timeDifference (requestedComponent: NSCalendar.Unit, from: NSDate, to: NSDate, options: [NSCalendar.Options]) {}
and then call this function with startTime and ednTime variables that you have defined.
Also I think that you should define timeDifference function outside of printTime function.

Filter array of all events and find which event is still going on

I did a lot of searching through Stackoverflow but I havent found answer for my problem.
I am developing an app and I get JSON data for some events. What I get is the start time of the event and the duration of the event. All data in recived as String.
In one screen of the app I would like to show only the event that are currently going on.
for example:
Class Event {
var startTime: String?
var duration: String?
}
let event1 = Event()
event1.starTime = "12-12-2016, 10:50 AM"
event1.duration = "50min"
let event2 = Event()
event2.starTime = "12-12-2016, 09:50 AM"
event2.duration = "40min"
let event3 = Event()
event3.starTime = "12-12-2016, 10:10 AM"
event3.duration = "90min"
let allEvents = [event1, event2, event3]
and let say the the current date and time is 12-12-2016, 11:00AM. How can I filter/find events in allEvents that are still going on if we compare them to the current date?
Thank you in advance.
EDIT: My solution
I created method for converting dateString and durationString to startDate: Date and endDate: Date
static func convertDateStringAndDurationStringToStartAndEndDate(date: String, duration: String) -> (start: Date, end: Date)? {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
guard let startDate = dateFormatter.date(from: date) else { return nil }
guard let duration = Int(duration) else { return nil }
// recived interval is in minutes, time interval must be calculated in seconds
let timeInterval = TimeInterval(Int(duration) * 60 )
let endDate = Date(timeInterval: timeInterval, since: startDate)
return (startDate, endDate)
}
For filtering I have created separated method. In my case I am using Realm database, but you will get the point.
static func filterResultsForNowPlaying(results: Results<Show>?) -> Results<Show>? {
let currentDate = NSDate()
let datePredicate = NSPredicate(format: "startDate <= %# AND %# <= endDate", currentDate, currentDate)
let filteredShows = results?.filter(datePredicate)
return filteredShows
}
You will need to convert them into dates, using DateFormatter, and then use a .filter over the array and have it match on if the current date is in range.
If you have the ability to change the Event class, you can greatly simplify your code if you replace your Event class with the DateInterval class, which does the same thing:
let minutes = 60;
let formatter = DateFormatter()
formatter.dateFormat = "MM-dd-yyyy"
let event1 = DateInterval(
start: formatter.date(from: "12-12-2016")!,
duration: TimeInterval(20 * minutes)
)
let now = Date()
if (event1.contains(now)) {
print("Event 1 is still active")
}

I'm trying to have my app pop viewControllers when it reaches 9:00 UTC time every day

I'm trying have my app pop viewControllers when it reaches 9:00 UTC time every day. I don't want it to use local time as that can change in different regions, and can be altered. I thought of using a server time, but I'm having issues getting that solution to work. I got an epoch timestamp and converted it to a Date.
let ref = FIRDatabase.database().reference()
let timestamp = FIRServerValue.timestamp()
ref.setValue(timestamp)
ref.observe(.value, with: {
snap in
if let t = snap.value as? TimeInterval {
// Cast the value to an NSTimeInterval
// and divide by 1000 to get seconds.
print("this is the time in seconds \(t)")
let date = Date(timeIntervalSince1970: t)
print("this is the time \(Date(timeIntervalSince1970: t/1000))")
It prints out this is the time 2016-10-10 18:40:21 +0000.
The problem is figuring out how to get only the hour minutes and seconds out of this so I can compare the time dates.
One of options might be:
To request a current time from the server on the start of the app.
Calculate a difference between 9:00 UTC and the current time.
Set a timer (NSTimer) which fires when the difference has passed.
And finally, handle the callback in any way you like: pop screens, show a popup, etc.
You could use something like this:
let calendar = Calendar.current
let components = NSDateComponents()
components.hour = 9
let nineOClock = calendar.nextDate(after: Date(), matching: components as DateComponents, matchingPolicy: .strict)
let timer = Timer(fireAt: nineOClock!, interval: 0, target: self, selector: #selector(doSomething), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)
func doSomething(){
print("Doing something")
}
A method which calculates time (in seconds) until the next 9 am based on the current time (seconds from 1970).
func untilNineAmSeconds(now: Int) -> Int {
let todaySeconds = now % 86400
let hourSeconds = 3600
let nineAmSeconds = 9 * hourSeconds
let daySeconds = 24 * hourSeconds
if todaySeconds < nineAmSeconds {
return nineAmSeconds - todaySeconds
} else {
return (daySeconds - todaySeconds) + nineAmSeconds
}
}
Now you (1) request a current time from Firebase, (2) get a time interval before 9 am, and (3) schedule a timer.
let ref = FIRDatabase.database().reference()
let timestamp = FIRServerValue.timestamp()
ref.setValue(timestamp)
ref.observe(.value, with: { snap in
guard let ts = snap.value as? TimeInterval else {
return
}
let seconds = untilNineAmSeconds(now: Int(ts))
let timer = Timer.scheduledTimer(withTimeInterval: TimeInterval(seconds), repeats: false) { _ in
// TODO: pop view controllers
}
// ...
}
Thanks to Artem and his help, I was directed on the right path. After a lot of time spent researching date conversion, I figured this out. The final goal is to convert the two values into Integers so I can compare them in seconds and use them in a timer to figure out how much time is remaining.
ref.observe(.value, with: {
snap in
if let t = snap.value as? TimeInterval {
let FinalTimeInterval = self.convertStringToDateToIntStartTime() - self.convertEpochTimeStampToDateToStringToDateToIntEndTime(timeInterval: t)
if FinalTimeInterval < 0 {
print("less than zero")
} else {
let timer = Timer.scheduledTimer(withTimeInterval: TimeInterval(FinalTimeInterval), repeats: false) { _ in
// TODO: pop view controllers
print("i'm so happy this is working")
}
RunLoop.current.add(timer, forMode: RunLoopMode.commonModes)
}
}
})
func convertEpochTimeStampToDateToStringToDateToIntEndTime(timeInterval: TimeInterval) -> Int {
let date = Date(timeIntervalSince1970: timeInterval/1000)
var dateFormater = DateFormatter()
dateFormater.timeZone = TimeZone(identifier: "UTC")
dateFormater.dateFormat = "HH:mm:ss"
let dateString = dateFormater.string(from: date)
let endTime = dateFormater.date(from: dateString)
dateFormater.dateFormat = "HH"
let endHours = Int(dateFormater.string(from: endTime!))
dateFormater.dateFormat = "mm"
let endMinutes = Int(dateFormater.string(from: endTime!))
dateFormater.dateFormat = "ss"
let endSeconds = Int(dateFormater.string(from: endTime!))
return endSeconds! + endMinutes! * 60 + endHours! * 3600
}
func convertStringToDateToIntStartTime() -> Int {
var dateFormater = DateFormatter()
dateFormater.timeZone = TimeZone(identifier: "UTC")
dateFormater.dateFormat = "HH:mm:ss"
let startTime = dateFormater.date(from: "09:00:00")
dateFormater.dateFormat = "HH"
let startHours = Int(dateFormater.string(from: startTime!))
dateFormater.dateFormat = "mm"
let startMinutes = Int(dateFormater.string(from: startTime!))
dateFormater.dateFormat = "ss"
let startSeconds = Int(dateFormater.string(from: startTime!))
return startSeconds! + startMinutes! * 60 + startHours! * 3600
}

Resources