Time Shield Library 2.0.0
C++ library for working with time
Loading...
Searching...
No Matches
ntp_time_service.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: MIT
2#pragma once
3#ifndef TIME_SHIELD_HEADER_NTP_NTP_TIME_SERVICE_HPP_INCLUDED
4#define TIME_SHIELD_HEADER_NTP_NTP_TIME_SERVICE_HPP_INCLUDED
5
7
8#if TIME_SHIELD_ENABLE_NTP_CLIENT
9
10#include "ntp_client_pool.hpp"
12
13#include <atomic>
14#include <chrono>
15#include <condition_variable>
16#include <cstdlib>
17#include <cstdint>
18#include <memory>
19#include <mutex>
20#include <thread>
21#include <utility>
22#include <vector>
23
24namespace time_shield {
25
26 template <class RunnerT>
27 class NtpTimeServiceT;
28
29 namespace detail {
30 template <class RunnerT>
31 struct NtpTimeServiceSingleton;
32
33 template <class RunnerT>
34 struct NtpTimeServiceTestAccess;
35
36#ifdef TIME_SHIELD_TEST_FAKE_NTP
39 public:
41 FakeNtpRunner() = default;
44
45 FakeNtpRunner(const FakeNtpRunner&) = delete;
47
50 stop();
51 }
52
54 bool start(std::chrono::milliseconds interval = std::chrono::seconds(30),
55 bool measure_immediately = true) {
56 if (m_is_running.load()) {
57 return false;
58 }
59 if (interval.count() <= 0) {
60 interval = std::chrono::milliseconds(1);
61 }
62 m_interval = interval;
63 m_is_stop_requested.store(false);
64 m_is_force_requested.store(false);
65 m_is_running.store(true);
66 m_measure_immediately = measure_immediately;
67 try {
68 m_thread = std::thread(&FakeNtpRunner::run_loop, this);
69 } catch (...) {
70 m_is_running.store(false);
71 return false;
72 }
73 return true;
74 }
75
77 bool start(int interval_ms, bool measure_immediately = true) {
78 return start(std::chrono::milliseconds(interval_ms), measure_immediately);
79 }
80
82 void stop() {
83 m_is_stop_requested.store(true);
84 m_cv.notify_all();
85 if (m_thread.joinable()) {
86 m_thread.join();
87 }
88 m_is_running.store(false);
89 }
90
92 bool running() const noexcept { return m_is_running.load(); }
93
96 if (!m_is_running.load()) {
97 return false;
98 }
99 m_is_force_requested.store(true);
100 m_cv.notify_one();
101 return true;
102 }
103
105 bool measure_now() {
106 return do_measure();
107 }
108
110 int64_t offset_us() const noexcept { return m_offset_us.load(); }
112 int64_t utc_time_us() const noexcept { return now_realtime_us() + m_offset_us.load(); }
114 int64_t utc_time_ms() const noexcept { return utc_time_us() / 1000; }
116 int64_t utc_time_sec() const noexcept { return utc_time_us() / 1000000; }
117
119 bool last_measure_ok() const noexcept { return m_last_measure_ok.load(); }
121 uint64_t measure_count() const noexcept { return m_measure_count.load(); }
123 uint64_t fail_count() const noexcept { return m_fail_count.load(); }
125 int64_t last_update_realtime_us() const noexcept { return m_last_update_realtime_us.load(); }
127 int64_t last_success_realtime_us() const noexcept { return m_last_success_realtime_us.load(); }
128
130 std::vector<NtpSample> last_samples() const { return {}; }
131
132 private:
134 void run_loop() {
135 const auto sleep_interval = m_interval;
136 bool is_first = m_measure_immediately;
137 while (!m_is_stop_requested.load()) {
138 if (is_first) {
139 do_measure();
140 is_first = false;
141 } else {
142 std::unique_lock<std::mutex> lk(m_cv_mtx);
143 m_cv.wait_for(lk, sleep_interval, [this]() {
144 return m_is_stop_requested.load() || m_is_force_requested.load();
145 });
146 if (m_is_stop_requested.load()) {
147 break;
148 }
149 m_is_force_requested.store(false);
150 do_measure();
151 }
152 }
153 m_is_running.store(false);
154 }
155
157 bool do_measure() {
158 const uint64_t count = m_measure_count.fetch_add(1) + 1;
159 m_offset_us.store(static_cast<int64_t>(count * 1000));
160 m_last_measure_ok.store(true);
161 const int64_t now = now_realtime_us();
164 return true;
165 }
166
167 private:
168 std::chrono::milliseconds m_interval{std::chrono::seconds(30)};
170
171 std::thread m_thread;
172 std::condition_variable m_cv;
173 std::mutex m_cv_mtx;
174
175 std::atomic<bool> m_is_running{false};
176 std::atomic<bool> m_is_stop_requested{false};
177 std::atomic<bool> m_is_force_requested{false};
178
179 std::atomic<bool> m_last_measure_ok{false};
180 std::atomic<uint64_t> m_measure_count{0};
181 std::atomic<uint64_t> m_fail_count{0};
182 std::atomic<int64_t> m_last_update_realtime_us{0};
183 std::atomic<int64_t> m_last_success_realtime_us{0};
184 std::atomic<int64_t> m_offset_us{0};
185 };
186#endif // _TIME_SHIELD_TEST_FAKE_NTP
187 } // namespace detail
188
197 template <class RunnerT>
199 friend struct detail::NtpTimeServiceSingleton<RunnerT>;
200 friend struct detail::NtpTimeServiceTestAccess<RunnerT>;
201
202 public:
208
211
214 bool init() {
216 }
217
222 bool init(std::chrono::milliseconds interval, bool measure_immediately = true) {
224 return false;
225 }
226
227 std::unique_ptr<RunnerT> local_runner;
228 std::chrono::milliseconds start_interval = interval;
229 bool use_measure_immediately = measure_immediately;
230 {
231 std::unique_lock<std::mutex> lk(m_mtx);
232 while (is_transitioning_locked()) {
233 m_cv.wait(lk);
234 }
236 return false;
237 }
239 return true;
240 }
241 if (start_interval.count() <= 0) {
242 start_interval = std::chrono::milliseconds(1);
243 }
244 m_interval = start_interval;
245 m_measure_immediately = use_measure_immediately;
247
248 local_runner = build_runner_locked();
249 if (!local_runner) {
251 lk.unlock();
252 m_cv.notify_all();
253 return false;
254 }
255 }
256
257 bool has_started = false;
258 bool is_ok = false;
259 try {
260 has_started = local_runner->start(start_interval, use_measure_immediately);
261 if (has_started) {
262 is_ok = local_runner->measure_now();
263 }
264 } catch (...) {
265 is_ok = false;
266 }
267
268 if (!has_started || !is_ok || is_process_shutting_down()) {
269 try {
270 if (has_started) {
271 local_runner->stop();
272 }
273 } catch (...) {
274 // no-throw
275 }
276 local_runner.reset();
277 }
278
279 {
280 std::lock_guard<std::mutex> lk(m_mtx);
281 if (local_runner) {
282 m_last_offset_us.store(local_runner->offset_us(), std::memory_order_relaxed);
283 m_runner = std::move(local_runner);
285 } else {
286 m_runner.reset();
288 }
289 }
290 m_cv.notify_all();
291 return is_ok;
292 }
293
298 bool init(int interval_ms, bool measure_immediately = true) {
299 return init(std::chrono::milliseconds(interval_ms), measure_immediately);
300 }
301
303 void shutdown() {
304 std::unique_ptr<RunnerT> local_runner;
305 {
306 std::unique_lock<std::mutex> lk(m_mtx);
307 while (is_transitioning_locked()) {
308 m_cv.wait(lk);
309 }
310 if (m_state == State::stopped || !m_runner) {
311 return;
312 }
314 local_runner = std::move(m_runner);
315 }
316 try {
317 local_runner->stop();
318 } catch (...) {
319 // no-throw
320 }
321 {
322 std::lock_guard<std::mutex> lk(m_mtx);
324 }
325 m_cv.notify_all();
326 }
327
330 bool running() const noexcept {
331 std::lock_guard<std::mutex> lk(m_mtx);
333 }
334
336 void ensure_started() noexcept {
338 return;
339 }
340 if (running()) {
341 return;
342 }
343 (void)init();
344 }
345
350 int64_t offset_us() noexcept {
352 return m_last_offset_us.load(std::memory_order_relaxed);
353 }
355 std::lock_guard<std::mutex> lk(m_mtx);
356 if (!m_runner) return 0;
357 const int64_t offset = m_runner->offset_us();
358 m_last_offset_us.store(offset, std::memory_order_relaxed);
359 return offset;
360 }
361
366 int64_t utc_time_us() noexcept {
368 return now_realtime_us() + m_last_offset_us.load(std::memory_order_relaxed);
369 }
371 std::lock_guard<std::mutex> lk(m_mtx);
372 if (!m_runner) return now_realtime_us();
373 m_last_offset_us.store(m_runner->offset_us(), std::memory_order_relaxed);
374 return m_runner->utc_time_us();
375 }
376
379 int64_t utc_time_ms() noexcept {
380 return utc_time_us() / 1000;
381 }
382
385 int64_t utc_time_sec() noexcept {
386 return utc_time_us() / 1000000;
387 }
388
391 bool last_measure_ok() const noexcept {
392 std::lock_guard<std::mutex> lk(m_mtx);
393 if (!m_runner) return false;
394 return m_runner->last_measure_ok();
395 }
396
399 uint64_t measure_count() const noexcept {
400 std::lock_guard<std::mutex> lk(m_mtx);
401 if (!m_runner) return 0;
402 return m_runner->measure_count();
403 }
404
407 uint64_t fail_count() const noexcept {
408 std::lock_guard<std::mutex> lk(m_mtx);
409 if (!m_runner) return 0;
410 return m_runner->fail_count();
411 }
412
415 int64_t last_update_realtime_us() const noexcept {
416 std::lock_guard<std::mutex> lk(m_mtx);
417 if (!m_runner) return 0;
418 return m_runner->last_update_realtime_us();
419 }
420
423 int64_t last_success_realtime_us() const noexcept {
424 std::lock_guard<std::mutex> lk(m_mtx);
425 if (!m_runner) return 0;
426 return m_runner->last_success_realtime_us();
427 }
428
432 bool stale(std::chrono::milliseconds max_age) const noexcept {
433 const int64_t last = last_update_realtime_us();
434 if (last == 0) {
435 return true;
436 }
437 const int64_t age = now_realtime_us() - last;
438 return age > static_cast<int64_t>(max_age.count()) * 1000;
439 }
440
444 bool stale(int max_age_ms) const noexcept {
445 return stale(std::chrono::milliseconds(max_age_ms));
446 }
447
451 bool set_servers(std::vector<NtpServerConfig> servers) {
452 std::lock_guard<std::mutex> lk(m_mtx);
454 return false;
455 }
457 m_servers = std::move(servers);
458 return true;
459 }
460
464 std::lock_guard<std::mutex> lk(m_mtx);
466 return false;
467 }
470 return true;
471 }
472
476 std::lock_guard<std::mutex> lk(m_mtx);
478 return false;
479 }
480 m_has_custom_servers = false;
481 m_servers.clear();
482 return true;
483 }
484
489 std::lock_guard<std::mutex> lk(m_mtx);
491 return false;
492 }
494 m_pool_cfg = std::move(cfg);
495 return true;
496 }
497
501 std::lock_guard<std::mutex> lk(m_mtx);
503 return m_pool_cfg;
504 }
505 return NtpPoolConfig{};
506 }
507
510 std::vector<NtpSample> last_samples() const {
511 std::lock_guard<std::mutex> lk(m_mtx);
512 if (!m_runner) return {};
513 return m_runner->last_samples();
514 }
515
517 NtpTimeServiceT() = default;
519 ~NtpTimeServiceT() = default;
520
525 return false;
526 }
527
528 std::unique_ptr<RunnerT> new_runner;
529 std::unique_ptr<RunnerT> old_runner;
530 std::chrono::milliseconds interval;
531 bool measure_immediately = true;
532 bool was_running = false;
533 {
534 std::unique_lock<std::mutex> lk(m_mtx);
535 while (is_transitioning_locked()) {
536 m_cv.wait(lk);
537 }
539 return false;
540 }
541 was_running = m_state == State::running && is_running_locked();
543 new_runner = build_runner_locked();
544 if (!new_runner) {
545 m_state = was_running ? State::running : State::stopped;
546 lk.unlock();
547 m_cv.notify_all();
548 return false;
549 }
550 interval = m_interval;
551 measure_immediately = m_measure_immediately;
552 old_runner = std::move(m_runner);
553 }
554
555 if (old_runner) {
556 try {
557 old_runner->stop();
558 } catch (...) {
559 }
560 }
561
562 bool has_started = false;
563 bool is_ok = false;
564 try {
565 has_started = new_runner->start(interval, measure_immediately);
566 if (has_started) {
567 is_ok = new_runner->measure_now();
568 }
569 } catch (...) {
570 is_ok = false;
571 }
572
573 if (!has_started || !is_ok || is_process_shutting_down()) {
574 try {
575 if (has_started) {
576 new_runner->stop();
577 }
578 } catch (...) {
579 // no-throw
580 }
581 new_runner.reset();
582 }
583
584 {
585 std::lock_guard<std::mutex> lk(m_mtx);
586 if (new_runner) {
587 m_last_offset_us.store(new_runner->offset_us(), std::memory_order_relaxed);
588 m_runner = std::move(new_runner);
590 } else {
591 m_runner.reset();
593 }
594 }
595 m_cv.notify_all();
596 return is_ok;
597 }
598
599 private:
601 enum class ProcessState : uint8_t {
602 alive,
604 };
605
607 enum class State : uint8_t {
608 stopped,
609 starting,
610 running,
612 };
613
617 m_atexit_registration_count.fetch_add(1, std::memory_order_relaxed);
618 }
619 }
620
622 void begin_process_shutdown() noexcept {
623 m_process_state.store(ProcessState::shutting_down, std::memory_order_release);
624
625 std::unique_ptr<RunnerT> local_runner;
626 {
627 std::unique_lock<std::mutex> lk(m_mtx);
628 while (is_transitioning_locked()) {
629 m_cv.wait(lk);
630 }
631 if (m_runner) {
632 m_last_offset_us.store(m_runner->offset_us(), std::memory_order_relaxed);
634 local_runner = std::move(m_runner);
635 } else {
637 }
638 }
639
640 if (local_runner) {
641 try {
642 local_runner->stop();
643 } catch (...) {
644 // no-throw
645 }
646 }
647
648 {
649 std::lock_guard<std::mutex> lk(m_mtx);
651 }
652 m_cv.notify_all();
653 }
654
656 bool is_process_shutting_down() const noexcept {
657 return m_process_state.load(std::memory_order_acquire) == ProcessState::shutting_down;
658 }
659
661 bool is_process_shutting_down_locked() const noexcept {
663 }
664
666 uint32_t atexit_registration_count() const noexcept {
667 return m_atexit_registration_count.load(std::memory_order_relaxed);
668 }
669
671 bool is_running_locked() const noexcept {
672 return m_runner && m_runner->running();
673 }
674
676 bool is_transitioning_locked() const noexcept {
678 }
679
681 bool is_reconfigurable_locked() const noexcept {
683 }
684
686 std::unique_ptr<RunnerT> build_runner_locked() {
687 std::vector<NtpServerConfig> servers;
689 servers = m_servers;
690 } else {
692 }
693
695 NtpClientPool pool(cfg);
696 pool.set_servers(std::move(servers));
697
698 std::unique_ptr<RunnerT> runner;
699 try {
700 runner.reset(new RunnerT(std::move(pool)));
701 } catch (...) {
702 return nullptr;
703 }
704 return runner;
705 }
706
707 private:
708 mutable std::mutex m_mtx;
709 std::condition_variable m_cv;
711 std::atomic<ProcessState> m_process_state{ProcessState::alive};
712 std::atomic<int64_t> m_last_offset_us{0};
713 std::atomic<uint32_t> m_atexit_registration_count{0};
714 std::chrono::milliseconds m_interval{std::chrono::seconds(30)};
716
718 std::vector<NtpServerConfig> m_servers;
719
722
723 std::unique_ptr<RunnerT> m_runner;
724 };
725
726 namespace detail {
727 template <class RunnerT>
730 static NtpTimeServiceT<RunnerT>* p_instance = []() noexcept {
733 return p_service;
734 }();
735 return *p_instance;
736 }
737
738 static void handle_process_exit() noexcept {
739 instance().begin_process_shutdown();
740 }
741 };
742
743 template <class RunnerT>
745 static void begin_process_shutdown() noexcept {
746 NtpTimeServiceSingleton<RunnerT>::instance().begin_process_shutdown();
747 }
748
749 static bool is_process_shutting_down() noexcept {
750 return NtpTimeServiceSingleton<RunnerT>::instance().is_process_shutting_down();
751 }
752
753 static uint32_t atexit_registration_count() noexcept {
754 return NtpTimeServiceSingleton<RunnerT>::instance().atexit_registration_count();
755 }
756 };
757 } // namespace detail
758
759#if defined(TIME_SHIELD_TEST_FAKE_NTP)
763#else
767#endif
768
769namespace ntp {
770
776 inline bool init(std::chrono::milliseconds interval = std::chrono::seconds(30),
777 bool measure_immediately = true) {
778 return NtpTimeService::instance().init(interval, measure_immediately);
779 }
780
786 inline bool init(int interval_ms,
787 bool measure_immediately = true) {
788 return NtpTimeService::instance().init(std::chrono::milliseconds(interval_ms), measure_immediately);
789 }
790
793 inline void shutdown() {
795 }
796
800 inline int64_t offset_us() noexcept {
802 }
803
807 inline int64_t utc_time_us() noexcept {
809 }
810
814 inline int64_t utc_time_ms() noexcept {
816 }
817
821 inline int64_t utc_time_sec() noexcept {
823 }
824
828 inline bool last_measure_ok() noexcept {
830 }
831
835 inline uint64_t measure_count() noexcept {
837 }
838
842 inline uint64_t fail_count() noexcept {
844 }
845
849 inline int64_t last_update_realtime_us() noexcept {
851 }
852
856 inline int64_t last_success_realtime_us() noexcept {
858 }
859
864 inline bool stale(std::chrono::milliseconds max_age) noexcept {
865 return NtpTimeService::instance().stale(max_age);
866 }
867
872 inline bool stale(int max_age_ms) noexcept {
873 return NtpTimeService::instance().stale(max_age_ms);
874 }
875
876} // namespace ntp
877
878} // namespace time_shield
879
880#else // TIME_SHIELD_ENABLE_NTP_CLIENT
881
882namespace time_shield {
884 public:
886 static_assert(sizeof(void*) == 0, "NtpTimeService is disabled by configuration.");
887 return *reinterpret_cast<NtpTimeService*>(0);
888 }
889 };
890} // namespace time_shield
891
892#endif // _TIME_SHIELD_ENABLE_NTP_CLIENT
893
894#endif // TIME_SHIELD_HEADER_NTP_NTP_TIME_SERVICE_HPP_INCLUDED
static std::vector< NtpServerConfig > build_default_servers()
Build a conservative default server list.
Singleton service for background NTP measurements.
std::unique_ptr< RunnerT > build_runner_locked()
Build a runner with current server list and pool config.
std::atomic< int64_t > m_last_offset_us
NtpTimeServiceT()=default
Construct service.
NtpTimeServiceT(const NtpTimeServiceT &)=delete
uint64_t measure_count() const noexcept
Return total number of measurement attempts.
bool is_running_locked() const noexcept
Check runner status under lock.
bool is_process_shutting_down_locked() const noexcept
Return true when process shutdown has started.
uint32_t atexit_registration_count() const noexcept
Return number of successful atexit registrations.
bool init(int interval_ms, bool measure_immediately=true)
Start background measurements using milliseconds.
int64_t last_success_realtime_us() const noexcept
Return realtime timestamp of last successful measurement.
NtpPoolConfig pool_config() const
Return current pool configuration.
State
Lifecycle state of the singleton runner.
void shutdown()
Stop background measurements and release resources.
bool clear_servers()
Clear custom server list and return to default behavior.
int64_t utc_time_sec() noexcept
Return current UTC time in seconds based on offset.
bool init()
Start background measurements using stored interval.
ProcessState
Global lifetime state of the immortal singleton.
std::atomic< ProcessState > m_process_state
bool is_reconfigurable_locked() const noexcept
Return true when configuration can be changed safely.
bool set_pool_config(NtpPoolConfig cfg)
Override pool configuration for new runner instances.
NtpTimeServiceT & operator=(const NtpTimeServiceT &)=delete
std::condition_variable m_cv
bool running() const noexcept
Return true when background runner is active.
bool stale(std::chrono::milliseconds max_age) const noexcept
Return true when last measurement is older than max_age.
int64_t utc_time_ms() noexcept
Return current UTC time in milliseconds based on offset.
static NtpTimeServiceT & instance() noexcept
Return the singleton instance.
bool last_measure_ok() const noexcept
Return whether last measurement updated the offset.
std::vector< NtpServerConfig > m_servers
void register_process_shutdown_handler() noexcept
Register one process-shutdown handler for this specialization.
bool set_servers(std::vector< NtpServerConfig > servers)
Replace server list used for new runner instances.
~NtpTimeServiceT()=default
Immortal singleton is stopped via process-shutdown handler.
bool stale(int max_age_ms) const noexcept
Return true when last measurement is older than max_age_ms.
int64_t offset_us() noexcept
Return last estimated offset in microseconds.
uint64_t fail_count() const noexcept
Return number of failed measurement attempts.
bool is_transitioning_locked() const noexcept
Return true when a start or stop transition is in progress.
bool set_default_servers()
Use conservative default servers for new runner instances.
bool apply_config_now()
Apply current config by rebuilding the runner.
void begin_process_shutdown() noexcept
Mark the singleton as shutting down and stop the runner.
std::unique_ptr< RunnerT > m_runner
int64_t utc_time_us() noexcept
Return current UTC time in microseconds based on offset.
bool init(std::chrono::milliseconds interval, bool measure_immediately=true)
Start background measurements with interval and immediate flag.
std::atomic< uint32_t > m_atexit_registration_count
std::vector< NtpSample > last_samples() const
Return copy of last measurement samples.
std::chrono::milliseconds m_interval
void ensure_started() noexcept
Ensure background runner is started with current config.
bool is_process_shutting_down() const noexcept
Return true when process shutdown has started.
int64_t last_update_realtime_us() const noexcept
Return realtime timestamp of last measurement attempt.
static NtpTimeService & instance()
Fake runner for tests without network access.
int64_t last_update_realtime_us() const noexcept
Return realtime timestamp of last measurement attempt.
int64_t utc_time_sec() const noexcept
Return current UTC time in seconds based on offset.
std::atomic< int64_t > m_last_success_realtime_us
FakeNtpRunner()=default
Construct fake runner.
int64_t utc_time_us() const noexcept
Return current UTC time in microseconds based on offset.
FakeNtpRunner(const FakeNtpRunner &)=delete
bool last_measure_ok() const noexcept
Return whether last measurement updated the offset.
int64_t last_success_realtime_us() const noexcept
Return realtime timestamp of last successful measurement.
bool force_measure()
Wake the worker thread and request a measurement.
bool running() const noexcept
Return true when background thread is running.
uint64_t fail_count() const noexcept
Return number of failed measurement attempts.
std::atomic< int64_t > m_last_update_realtime_us
bool start(std::chrono::milliseconds interval=std::chrono::seconds(30), bool measure_immediately=true)
Start fake measurements on a background thread.
int64_t offset_us() const noexcept
Return last estimated offset in microseconds.
bool do_measure()
Update fake offset and stats.
std::atomic< uint64_t > m_measure_count
void run_loop()
Background loop for fake measurements.
int64_t utc_time_ms() const noexcept
Return current UTC time in milliseconds based on offset.
std::chrono::milliseconds m_interval
bool measure_now()
Perform one measurement immediately.
~FakeNtpRunner()
Stop background thread on destruction.
uint64_t measure_count() const noexcept
Return total number of measurement attempts.
bool start(int interval_ms, bool measure_immediately=true)
Start fake measurements using milliseconds.
void stop()
Stop background measurements.
FakeNtpRunner(NtpClientPool)
Construct fake runner with an unused pool.
FakeNtpRunner & operator=(const FakeNtpRunner &)=delete
std::vector< NtpSample > last_samples() const
Return copy of most recent samples.
void init()
Initializes the Time Shield library.
int64_t utc_time_sec() noexcept
Return current UTC time in seconds based on offset.
uint64_t measure_count() noexcept
Return total number of measurement attempts.
bool stale(std::chrono::milliseconds max_age) noexcept
Return true when last measurement is older than max_age.
bool last_measure_ok() noexcept
Return whether last measurement updated the offset.
int64_t offset_us() noexcept
Return last estimated offset in microseconds.
void shutdown()
Stop NTP time service.
int64_t utc_time_ms() noexcept
Return current UTC time in milliseconds based on offset.
int64_t last_success_realtime_us() noexcept
Return realtime timestamp of last successful measurement.
uint64_t fail_count() noexcept
Return number of failed measurement attempts.
int64_t last_update_realtime_us() noexcept
Return realtime timestamp of last measurement attempt.
int64_t utc_time_us() noexcept
Return current UTC time in microseconds based on offset.
ts_ms_t now() noexcept
Get the current UTC timestamp in milliseconds.
int64_t now_realtime_us()
Get current real time in microseconds using a platform-specific method.
Main namespace for the Time Shield library.
static NtpTimeServiceT< RunnerT > & instance() noexcept