0

I am making a live stream of current time with this code:

        let timer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { timer in
            print(self.time())
        }

    func time() -> String {
        
        let date = Date()
        let calendar = Calendar.current
        let hour: Int = calendar.component(.hour, from: date)
        let minutes: Int = calendar.component(.minute, from: date)
        let second: Int = calendar.component(.second, from: date)
        
        return String(describing: hour) + ":" + String(describing: minutes) + ":" + String(describing: second)
    }

The output would be:

16:28:21
16:28:21
16:28:21
16:28:21
16:28:21
16:28:22
16:28:22
16:28:22
16:28:22
16:28:22
16:28:23
16:28:23
16:28:23
16:28:23
16:28:23
16:28:24

As you can see my codes are not good enough to be efficient, because I calculate 16:28:21 five times which could be done just with 1 call, if I would make my codes more efficient then I have to bring my timer interval near to 1 but that make my codes not precise to telling the real and correct time, if I keep using 0.2 for timer interval I also have an inaccuracy of 0.2 sec so what should I do?

If I want my codes be more precise than what it is, then probably I have to use 0.01 for timer interval with that said I would have an inaccuracy of 0.01 sec of real time and also so many unnecessary calls for reading the current time.

So how can I solve this issue that my code be real sharp in telling correct current time meanwhile not putting so many load for cpu.


Update:

func nextRead() {
    
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "HH:mm:ss.SS"
    
    let diff = 1_000_000_000 - Calendar.current.component(.nanosecond, from: .now)

    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.nanoseconds(diff)) {
        print(dateFormatter.string(from: .now), diff)
        nextRead()
    }
    
}

output:

00:23:44.04 841395021
00:23:45.04 956941963
00:23:46.04 954411030
00:23:47.04 953773976
00:23:48.04 953575969
00:23:49.04 954488040
00:23:50.04 954362989
00:23:51.04 954159022
00:23:52.01 954460979
00:23:53.04 981551052
00:23:54.04 950816989

2 Answers2

1

One way is to calculate the difference from the time the code starts executing to the next whole second and then use this difference for a first timer that fires once to start the actual timer that continues to execute.

Doing it this way means we can then fire our repeating timer only once per second.

Here is a simple playground example that in my test prints the time quite accurately when a new second starts

let diff = Double(Calendar.current.component(.nanosecond, from: .now)) / 1_000_000_000.0
Timer.scheduledTimer(withTimeInterval: 1 - diff, repeats: false) { _ in
    print(dateFormatter.string(from: .now), diff)
    Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
        print(dateFormatter.string(from: timer.fireDate))
    }
}

19:25:22.00
19:25:23.00
19:25:24.00
19:25:25.00
19:25:26.00

The data formatter I used was defined as

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm:ss.SS"

Note that this isn't a perfect solution since there is a slight drift in the time interval, I saw that by copying the let diff line to inside the inner closure and print the value. So if you are going to run this for a long time you will need to adjust for this.

In my playground test the code drifted one millisecond in about 2 1/2 minutes.

So when the diff reaches some threshold you could invalidate the timer and restart the two timers using this latest diff as base for calculating the interval for the first timer.

Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52
  • That drift will probably actually be a lot worse when run in a real app that is also doing lots of other stuff. – HangarRash Apr 22 '23 at 17:44
  • I already stated that this isn't a perfect solution but since OP isn't mentioning anything about the app should also be doing a lot of other stuff that is not so relevant (unless OP changes the question). – Joakim Danielson Apr 22 '23 at 17:49
  • If you wanted to avoid drift you could have a non-repeating timer that fires on the next even second, then creates a new timer that does the same thing. – Duncan C Apr 22 '23 at 19:25
  • It is ok, if I start firing from next even second, how to do that? @DuncanC – GoodbyeCode Apr 22 '23 at 19:33
  • Could you please see my update in question, why it does not work? – GoodbyeCode Apr 22 '23 at 22:25
0

Starting with Joakim's answer, a timer that re-schedules itself every second might look like this:

func nextSecond() {
    let diff = Double(Calendar.current.component(.nanosecond, from: .now)) / 1_000_000_000.0
    Timer.scheduledTimer(withTimeInterval: 1 - diff, repeats: false) { _ in
        print(self.dateFormatter.string(from: .now), diff)
        self.nextSecond()
    }
}

Note that Timer objects are not super accurate, and their accuracy goes down if your app does long-running tasks on the main thread.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • Based on the test I just ran, it appears to fire like 0.0004 to 0.00135 after the target time. Within 2/1000s of a second is likely the best you will do. – Duncan C Apr 22 '23 at 19:42
  • Thanks, just for my understanding I am not talking for very first firing to get the time, after first fire, in your way you are going to calculate the diff each time, with that said, is true to say that your way is more accurate than the way Joakim's answer? – GoodbyeCode Apr 22 '23 at 20:20
  • Joakim's answer creates a timer that fires every second. Timers tend to drift slightly as time goes by. The code I wrote recalculates the interval until the next even second each time and sets the timer for that adjusted interval. It will probably still be off by a few thousandths of a second, but instead of drifting slowly to be less and less synchronized with the even second interval, it will correct itself to "lock on" to the even second interval. – Duncan C Apr 22 '23 at 22:17
  • Thanks, I just used your answer in my question update, I like to know why it is not working. – GoodbyeCode Apr 22 '23 at 22:28