SatCat5
ptp_time.cc
1 // Copyright 2022-2026 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <satcat5/ptp_time.h>
7 #include <satcat5/datetime.h>
8 #include <satcat5/io_core.h>
9 #include <satcat5/log.h>
10 
17 using satcat5::ptp::Time;
20 
21 Time::Time(u64 seconds, u32 nanoseconds, u16 subnanoseconds)
22  : m_secs((s64)seconds)
23  , m_subns(nanoseconds * SUBNS_PER_NSEC + subnanoseconds)
24 {
25  normalize();
26 }
27 
28 void Time::normalize() {
29  while (m_subns < 0) {
30  m_secs -= 1; // Underflow
31  m_subns += SUBNS_PER_SEC;
32  }
33  while (m_subns >= SUBNS_PER_SEC) {
34  m_secs += 1; // Wraparound
35  m_subns -= SUBNS_PER_SEC;
36  }
37 }
38 
39 // All "delta_*" unit-conversion methods follow the same template:
40 template<s64 UNITS_PER_SEC>
41 inline s64 delta_convert(s64 sec, s64 subns) {
42  constexpr s64 MAX_SAFE = INT64_MAX / UNITS_PER_SEC - 1;
43  constexpr s64 SUBNS_PER_UNIT = SUBNS_PER_SEC / UNITS_PER_SEC;
44  if (sec < -MAX_SAFE) {
45  return INT64_MIN;
46  } else if (sec > MAX_SAFE) {
47  return INT64_MAX;
48  } else if (SUBNS_PER_UNIT > 1) {
49  return UNITS_PER_SEC * sec + div_round(subns, SUBNS_PER_UNIT);
50  } else {
51  return UNITS_PER_SEC * sec + subns;
52  }
53 }
54 
55 s64 Time::delta_subns() const
56  { return delta_convert<SUBNS_PER_SEC>(m_secs, m_subns); }
57 s64 Time::delta_nsec() const
58  { return delta_convert<NSEC_PER_SEC>(m_secs, m_subns); }
59 s64 Time::delta_usec() const
60  { return delta_convert<USEC_PER_SEC>(m_secs, m_subns); }
61 s64 Time::delta_msec() const
62  { return delta_convert<MSEC_PER_SEC>(m_secs, m_subns); }
63 
65  if (src->get_read_ready() >= 10) {
66  s64 sec_msb = (s64)src->read_u16(); // MSBs of seconds
67  s64 sec_lsb = (s64)src->read_u32(); // LSBs of seconds
68  s64 nsec = (s64)src->read_u32(); // Nanoseconds
69  m_secs = (sec_msb << 32) + sec_lsb; // Set internal variables
70  m_subns = (nsec * SUBNS_PER_NSEC);
71  normalize(); return true; // Success!
72  } else {
73  return false; // Read error.
74  }
75 }
76 
78  dst->write_u16((u16)(m_secs >> 32)); // MSBs of seconds
79  dst->write_u32((u32)(m_secs >> 0)); // LSBs of seconds
80  dst->write_u32(field_nsec()); // Nanoseconds (floor)
81 }
82 
84  wr.wr_str(" = ");
85  wr.wr_s64(field_secs());
86  wr.wr_str(".");
87  wr.wr_d64(round_nsec()); // subns truncated
88 }
89 
90 // Offset (in milliseconds) from the PTP epoch (TAI @ 1970 Jan 1)
91 // to the GPS epoch (1980 Jan 6 + 19 leap seconds).
92 // Ref: IEEE 1588-2019 Section B.3
93 constexpr s64 GPS_EPOCH = satcat5::datetime::ONE_DAY * 3657LL
94  + satcat5::datetime::ONE_SECOND * 19LL;
95 
96 s64 Time::to_datetime() const {
97  // Calculate milliseconds since PTP epoch.
98  s64 tai_msec = MSEC_PER_SEC * m_secs + div_round(m_subns, SUBNS_PER_MSEC);
99  // Add the offset from PTP/TAI to GPS (see above).
100  return tai_msec - GPS_EPOCH;
101 }
102 
104  // Add the offset GPS to PTP/TAI (see above).
105  s64 tai_msec = gps_msec + GPS_EPOCH;
106  // Convert that to PTP format (seconds + nanoseconds)
107  u64 ptp_secs = (u64)satcat5::util::divide(tai_msec, MSEC_PER_SEC);
108  u32 ptp_msec = (u32)satcat5::util::modulo(tai_msec, MSEC_PER_SEC);
109  return Time(ptp_secs, ptp_msec * 1000000u);
110 }
111 
112 Time Time::abs() const {
113  Time temp(0);
114  temp.m_secs = (s64)abs_s64(m_secs);
115  if (m_secs >= 0) {
116  // Simple positive case (no change).
117  temp.m_subns = m_subns;
118  } else if (m_subns > 0) {
119  // Negative rollover (-4 + 0.6 --> -3.4)
120  temp.m_secs -= 1;
121  temp.m_subns = SUBNS_PER_SEC - m_subns;
122  } else {
123  // Negative boundary (-4 + 0.0 --> -4.0)
124  temp.m_subns = 0;
125  }
126  return temp;
127 }
128 
129 bool Time::operator==(const Time& other) const {
130  return (m_secs == other.m_secs) && (m_subns == other.m_subns);
131 }
132 
133 bool Time::operator<(const Time& other) const {
134  if (m_secs < other.m_secs) return true;
135  if (m_secs > other.m_secs) return false;
136  return m_subns < other.m_subns;
137 }
138 
139 bool Time::operator>(const Time& other) const {
140  if (m_secs > other.m_secs) return true;
141  if (m_secs < other.m_secs) return false;
142  return m_subns > other.m_subns;
143 }
144 
145 void Time::operator+=(const Time& other) {
146  m_secs += other.m_secs;
147  m_subns += other.m_subns;
148  normalize();
149 }
150 
151 void Time::operator-=(const Time& other) {
152  m_secs -= other.m_secs;
153  m_subns -= other.m_subns;
154  normalize();
155 }
156 
157 void Time::operator*=(unsigned scale) {
158  s64 tmp = m_subns * scale;
159  m_secs = tmp / SUBNS_PER_SEC + m_secs * scale;
160  m_subns = tmp % SUBNS_PER_SEC;
161  normalize();
162 }
163 
164 void Time::operator/=(unsigned scale) {
165  s64 tmp = (m_secs % scale) * SUBNS_PER_SEC;
166  m_secs = m_secs / scale;
167  m_subns = (m_subns + tmp) / scale;
168  normalize();
169 }
170 
171 Time Time::operator=(const Time& other) {
172  m_secs = other.m_secs;
173  m_subns = other.m_subns;
174  return *this;
175 }
176 
177 Time Time::operator+(const Time& other) const {
178  Time tmp(*this);
179  tmp += other;
180  return tmp;
181 }
182 
183 Time Time::operator-(const Time& other) const {
184  Time tmp(*this);
185  tmp -= other;
186  return tmp;
187 }
188 
190  Time tmp(0);
191  tmp -= *this;
192  return tmp;
193 }
194 
195 Time Time::operator*(unsigned scale) const {
196  Time tmp(*this);
197  tmp *= scale;
198  return tmp;
199 }
200 
201 Time Time::operator/(unsigned scale) const {
202  Time tmp(*this);
203  tmp /= scale;
204  return tmp;
205 }
Abstract API for reading byte-streams and packets.
Definition: io_readable.h:68
virtual unsigned get_read_ready() const =0
How many bytes can be read without blocking?
Abstract API for writing byte-streams and packets.
Definition: io_writeable.h:24
Internal buffer used by the Log class.
Definition: log.h:134
void wr_d64(u64 val, unsigned zpad=0)
Write an unsigned integer (u64) in decimal format.
Definition: log.cc:323
void wr_s64(s64 val, unsigned zpad=0)
Write a signed integer (s64) in decimal format.
Definition: log.cc:334
void wr_str(const char *str)
Write a null-terminated UTF-8 string.
Definition: log.cc:297
High-precision timestamp for use with PTP / IEEE1588.
Definition: ptp_time.h:41
s64 delta_subns() const
Convert time-differences to the designated unit.
Definition: ptp_time.cc:55
satcat5::ptp::Time operator=(const satcat5::ptp::Time &other)
Standard arithmetic operations.
Definition: ptp_time.cc:171
s64 delta_nsec() const
Convert time-differences to the designated unit.
Definition: ptp_time.cc:57
void operator*=(unsigned scale)
Scalar multiply and divide are used for weighted averaging.
Definition: ptp_time.cc:157
bool read_from(satcat5::io::Readable *src)
Read the standard 10-byte timestamp from a PTP message (e.g., originTimestamp: u48 seconds + u32 nano...
Definition: ptp_time.cc:64
u32 field_nsec() const
Read the "nanoseconds" field, rounding down.
Definition: ptp_time.h:66
s64 field_secs() const
Read the "seconds" field without intermediate rounding.
Definition: ptp_time.h:62
satcat5::ptp::Time operator+(const satcat5::ptp::Time &other) const
Standard arithmetic operations.
Definition: ptp_time.cc:177
satcat5::ptp::Time abs() const
Standard arithmetic operations.
Definition: ptp_time.cc:112
void log_to(satcat5::log::LogBuffer &wr) const
User-readable format for logging.
Definition: ptp_time.cc:83
bool operator>(const satcat5::ptp::Time &other) const
Standard comparison operators.
Definition: ptp_time.cc:139
u32 round_nsec() const
Return "nanoseconds" field after rounding to the nearest nanosecond.
Definition: ptp_time.h:78
satcat5::ptp::Time operator/(unsigned scale) const
Scalar multiply and divide are used for weighted averaging.
Definition: ptp_time.cc:201
void operator/=(unsigned scale)
Scalar multiply and divide are used for weighted averaging.
Definition: ptp_time.cc:164
satcat5::ptp::Time operator-() const
Standard arithmetic operations.
Definition: ptp_time.cc:189
s64 delta_usec() const
Convert time-differences to the designated unit.
Definition: ptp_time.cc:59
s64 to_datetime() const
Convert to SatCat5 date/time.
Definition: ptp_time.cc:96
void operator+=(const satcat5::ptp::Time &other)
Standard arithmetic operations.
Definition: ptp_time.cc:145
void write_to(satcat5::io::Writeable *dst) const
Read or write the 10-byte timestamp from a PTP message.
Definition: ptp_time.cc:77
satcat5::ptp::Time operator*(unsigned scale) const
Scalar multiply and divide are used for weighted averaging.
Definition: ptp_time.cc:195
void operator-=(const satcat5::ptp::Time &other)
Standard arithmetic operations.
Definition: ptp_time.cc:151
s64 delta_msec() const
Convert time-differences to the designated unit.
Definition: ptp_time.cc:61
bool operator==(const satcat5::ptp::Time &other) const
Standard comparison operators.
Definition: ptp_time.cc:129
bool operator<(const satcat5::ptp::Time &other) const
Standard comparison operators.
Definition: ptp_time.cc:133
Real-time clock conversion functions.
I/O interface core definitions.
Diagnostic logging to UART and/or Ethernet ports.
High-precision "Time" object for use with PTP / IEEE1588.
constexpr s64 SUBNS_PER_MSEC
Define commonly used scaling factors.
Definition: ptp_time.h:30
constexpr s64 SUBNS_PER_SEC
Define commonly used scaling factors.
Definition: ptp_time.h:31
constexpr s64 USEC_PER_SEC
Define commonly used scaling factors.
Definition: ptp_time.h:26
satcat5::ptp::Time from_datetime(s64 gps_msec)
Convert from SatCat5 date/time to a ptp::Time object.
Definition: ptp_time.cc:103
constexpr s64 NSEC_PER_SEC
Define commonly used scaling factors.
Definition: ptp_time.h:23
constexpr s64 SUBNS_PER_NSEC
Define commonly used scaling factors.
Definition: ptp_time.h:28
constexpr s64 MSEC_PER_SEC
Define commonly used scaling factors.
Definition: ptp_time.h:27
constexpr u64 abs_s64(s64 a)
Absolute value.
Definition: utils.h:148
constexpr T div_round(T a, T b)
Integer division functions with various rounding options:
Definition: utils.h:263