Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
302 views
in Technique[技术] by (71.8m points)

c++ - Convert boost::posix_time::ptime to NTP datestamp

I need to convert a boost::posix_time::ptime into a NTP Datestamp according to RFC 5905 represented by the following structure:

struct NtpDatestamp {
  std::int32_t era_number;
  std::uint32_t seconds_since_era_epoch;
  std::uint64_t fraction_of_second;
};

RFC 5905 states the following:

To convert system time in any format to NTP date and timestamp formats requires that the number of seconds s from the prime epoch to the system time be determined. To determine the integer era and timestamp given s,

era = s / 2^(32) and timestamp = s - era * 2^(32),

which works for positive and negative dates. To determine s given the era and timestamp,

s = era * 2^(32) + timestamp.

Therefore I've tried the following:

const auto system_time = boost::posix_time::time_from_string("1899-12-31 00:00:00.000");

const boost::posix_time::ptime prime_epoch{boost::gregorian::date{1900, 1, 1}};
// Calculate the number of seconds from the prime epoch to the system time.
const boost::posix_time::time_duration time_duration{system_time - prime_epoch};
const std::int64_t s{time_duration.total_seconds()};
const std::int32_t era_number{static_cast<std::int32_t>(s / std::pow(2, 32))};
const std::uint64_t seconds_since_era_epoch{static_cast<std::uint64_t>(s - s / std::pow(2, 32) * std::pow(2, 32))};
// The fraction of a NTP Datestamp is measured in Attoseconds.
const std::uint64_t fraction_of_second{static_cast<std::uint64_t>(time_duration.total_microseconds() * 1e12)};

But that gives incorrect results.

I am completely stumped with this (actually simple) problem at the moment.

Can someone guide me into the correct direction? How can I obtain the era number, era offset and fraction of a NTP datestamp from a boost::posix_time::ptime?

Edit: Either the calculations in RFC 5905 are not accurate enough or I do misinterpret them. Thanks to the comments I've changed the calculation to the following (this time a complete example):

#include <cmath>
#include <cstdint>
#include <iostream>

#include <boost/date_time.hpp>

int main() {
  const auto system_time =
      boost::posix_time::time_from_string("1899-12-31 00:00:00.000");

  const boost::posix_time::ptime prime_epoch{
      boost::gregorian::date{1900, 1, 1}};
  // Calculate the number of seconds from the prime epoch to the system time.
  const boost::posix_time::time_duration time_duration{prime_epoch -
                                                       system_time};

  // s is correctly determined now.
  std::int64_t s{time_duration.total_seconds()};
  if (prime_epoch > system_time) {
    // boost::posix_time::time_duration does not take the sign into account.
    s *= -1;
  }

  // TODO(wolters): The following calculations do not return the correct
  // results, but the RFC 5905 states them
  const std::int32_t era{static_cast<std::int32_t>(s / std::pow(2, 32))};
  const std::uint64_t timestamp{
      static_cast<std::uint64_t>(s - era * std::pow(2, 32))};
  // The fraction of a NTP Datestamp is measured in Attoseconds.
  // TODO(wolters): `boost::posix_time::ptime` does NOT resolve to attoseconds,
  // but doesn't the target format expect the value to be specified as
  // attoseconds? Doesn't the following depend on Boost compile options?
  const std::uint64_t fraction{
      static_cast<std::uint64_t>(time_duration.fractional_seconds())};

  std::cout << "s = " << std::dec << s << '
';
  // TODO(wolters): This does still not match the expected results; taken from
  // Figure 4 of https://www.ietf.org/rfc/rfc5905.txt
  std::cout << "Era (expected: -1) = " << std::dec << era << '
';
  std::cout << "Timestamp (expected: 4294880896) = " << std::dec << timestamp
            << '
';
  std::cout << "Fraction (expected: 0) = " << std::dec << fraction << '
';
}

s is calculated correctly now, but the other calculations are wrong. I think I do miss something important completely...

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

It seems that I've figured out the missing pieces by myself. I've implemented the following algorithm in a reusable class ntp::Datestamp and unit tested it with the reference dates of RFC 5905. All tests finally are green. Here is the solution:

#include <cmath>
#include <cstdint>
#include <ctime>
#include <iostream>

#include <boost/date_time.hpp>

static std::time_t to_time(const boost::posix_time::ptime& time) {
  static const boost::posix_time::ptime epoch_time{
      boost::gregorian::date{1970, 1, 1}};
  const boost::posix_time::time_duration diff{time - epoch_time};

  return (diff.ticks() / diff.ticks_per_second());
}

int main() {
  const auto system_time =
      boost::posix_time::time_from_string("1899-12-31 00:00:00.123");
  const boost::posix_time::ptime prime_epoch{
      boost::gregorian::date{1900, 1, 1}};
  // Calculate the number of seconds from the prime epoch to the system time.
  std::time_t s{to_time(system_time) - to_time(prime_epoch)};

  const std::int32_t era{static_cast<std::int32_t>(std::floor(s / std::pow(2, 32)))};
  const std::uint32_t timestamp{
      static_cast<std::uint32_t>(s - era * std::pow(2, 32))};
  const std::uint64_t fraction{static_cast<std::uint64_t>(
      system_time.time_of_day().fractional_seconds())};

  std::cout << "s = " << std::dec << s << '
';
  std::cout << "Era (expected: -1) = " << std::dec << era << '
';
  std::cout << "Timestamp (expected: 4294880896) = " << std::dec << timestamp
            << '
';
  std::cout << "Fraction (expected: 123000) = " << std::dec << fraction << '
';
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...