如何在C语言(Linux)中将NTP时间转换为Unix纪元时间

7

我尝试了几个月来基于RFC5905 创建一个简单的SNTP单客户端/服务器。最终,我成功使其工作,至少我认为它可以正确运行,但当我试图测试我的代码与真实的NTP服务器(例如 0.se.pool.ntp.org:123)进行交互时,我接收到的时间戳需要重新计算。我已经尝试了几种不同的方法,但是无论我尝试了什么,都没有进展,这已经持续了3天。

有人知道如何将NTP时间戳转换为Unix纪元时间戳吗?

执行服务器的语法,例如 ./server 127.0.0.1:5000 以及客户端的语法,例如 ./client 127.0.0.1:5000。

执行客户端与真实NTP服务器交互的语法,例如 ./client 0.se.pool.ntp.org:123。

可运行客户端的示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>
#include <math.h>
#include <sys/timeb.h>
#include <inttypes.h>
#include <limits.h>
#include <assert.h>

#define UNIX_EPOCH 2208988800UL /* 1970 - 1900 in seconds */

typedef struct client_packet client_packet;
struct client_packet {
  uint8_t client_li_vn_mode;
  uint8_t client_stratum;
  uint8_t client_poll;
  uint8_t client_precision;
  uint32_t client_root_delay;
  uint32_t client_root_dispersion;
  uint32_t client_reference_identifier;
  uint32_t client_reference_timestamp_sec;
  uint32_t client_reference_timestamp_microsec;
  uint32_t client_originate_timestamp_sec;
  uint32_t client_originate_timestamp_microsec;
  uint32_t client_receive_timestamp_sec;
  uint32_t client_receive_timestamp_microsec;
  uint32_t client_transmit_timestamp_sec;
  uint32_t client_transmit_timestamp_microsec;
}__attribute__((packed));

typedef struct server_send server_send;
struct server_send {
  uint8_t server_li_vn_mode;
  uint8_t server_stratum;
  uint8_t server_poll;
  uint8_t server_precision;
  uint32_t server_root_delay;
  uint32_t server_root_dispersion;
  char server_reference_identifier[4];
  uint32_t server_reference_timestamp_sec;
  uint32_t server_reference_timestamp_microsec;
  uint32_t server_originate_timestamp_sec;
  uint32_t server_originate_timestamp_microsec;
  uint32_t server_receive_timestamp_sec;
  uint32_t server_receive_timestamp_microsec;
  uint32_t server_transmit_timestamp_sec;
  uint32_t server_transmit_timestamp_microsec;
}__attribute__((packed));

/* Linux man page bind() */
#define handle_error(msg)               \
  do {perror(msg); exit(EXIT_FAILURE);} while (0)

uint32_t ClockGetTime() {
  struct timespec ts;
  clock_gettime(CLOCK_REALTIME, &ts);
  return (uint32_t)ts.tv_sec * 1000000LL + (uint32_t)ts.tv_nsec / 1000LL;
}

int main(int argc, char *argv[]) {

  int sockfd , numbytes;
  struct addrinfo hints, *servinfo, *p;
  int rv;

  client_packet memsend;
  server_send memrcv;
  
  memset( &memsend , 0 , sizeof memsend );
  memset( &memrcv , 0 , sizeof memrcv );

    char IP[16]; /* IP = 15 digits 1 extra for \0 null terminating character string */

    char PORT_STR[6]; /* Port = 5 digits MAX 1 extra for \0 null terminating character string */

    memset(IP , '\0' , sizeof(IP));
    memset(PORT_STR , '\0' , sizeof(PORT_STR));

    strcpy(IP, strtok(argv[1], ":"));
    strcpy(PORT_STR, strtok(NULL, ":"));
    
    memset( &hints , 0 , sizeof hints );
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;

    if ( ( rv = getaddrinfo( IP , PORT_STR , &hints , &servinfo ) ) != 0 ) {
      fprintf( stderr , "getaddrinfo: %s\n" , gai_strerror(rv) );
      return 1;
    }

    // loop through all the results and make a socket
    for( p = servinfo; p != NULL; p = p->ai_next ) {
      if ( ( sockfd = socket( p->ai_family , p->ai_socktype , p->ai_protocol ) ) == -1 ) {
    handle_error( "socket" );
    continue;
      }
      break;
    }

    if (p == NULL) {
      fprintf(stderr, "Error while binding socket\n");
      return 2;
    }

    memsend.client_li_vn_mode = 0b00100011;
    memsend.client_stratum = 0;
    memsend.client_poll = 0;
    memsend.client_precision = 0;
    memsend.client_root_delay = 0;
    memsend.client_root_dispersion = 0;
    memsend.client_reference_identifier = 0;
    memsend.client_reference_timestamp_sec = 0;
    memsend.client_reference_timestamp_microsec = 0;

    memsend.client_receive_timestamp_sec = 0;
    memsend.client_receive_timestamp_microsec = 0;

    time_t time_originate_sec = time(NULL);
    memsend.client_originate_timestamp_sec = time_originate_sec;
    memsend.client_originate_timestamp_microsec = ClockGetTime();

    memsend.client_transmit_timestamp_sec = memsend.client_originate_timestamp_sec;
    memsend.client_transmit_timestamp_microsec = memsend.client_originate_timestamp_microsec;
    
    if ( ( numbytes = sendto( sockfd, &memsend , sizeof memsend , 0 ,
                  p->ai_addr , p->ai_addrlen ) ) == -1 ) {
      handle_error("sendto");
      exit(1);
    }
  
    if ( ( numbytes = recvfrom( sockfd , &memrcv , sizeof memrcv , 0 ,
                (struct sockaddr *) &p->ai_addr, &p->ai_addrlen ) ) == -1 ) {
      handle_error( "recvfrom" );
      exit(1);
    }

    time_t time_rcv_sec = time(NULL);
    uint32_t client_rcv_timestamp_sec = time_rcv_sec;
    uint32_t client_rcv_timestamp_microsec = ClockGetTime();

    freeaddrinfo(servinfo);

    char Identifier[5];
    memset(Identifier , '\0' , sizeof Identifier);
    memcpy(Identifier , memrcv.server_reference_identifier , sizeof memrcv.server_reference_identifier);

    printf("\t Reference Identifier \t %"PRIu32" \t\t\t %s\n",memsend.client_reference_identifier,Identifier);
    printf("\t Reference Timestamp \t %"PRIu32".%"PRIu32" \t\t\t %"PRIu32".%"PRIu32"\n",memsend.client_reference_timestamp_sec,memsend.client_reference_timestamp_microsec,memrcv.server_reference_timestamp_sec,memrcv.server_reference_timestamp_microsec);
    printf("\t Originate Timestamp \t %"PRIu32".%"PRIu32" \t %"PRIu32".%"PRIu32"\n",memsend.client_originate_timestamp_sec,memsend.client_originate_timestamp_microsec,memrcv.server_originate_timestamp_sec,memrcv.server_originate_timestamp_microsec);
    printf("\t Receive Timestamp \t %"PRIu32".%"PRIu32" \t %"PRIu32".%"PRIu32"\n",client_rcv_timestamp_sec,client_rcv_timestamp_microsec,memrcv.server_receive_timestamp_sec,memrcv.server_receive_timestamp_microsec);
    printf("\t Transmit Timestamp \t %"PRIu32".%"PRIu32" \t %"PRIu32".%"PRIu32"\n\n",memsend.client_transmit_timestamp_sec,memsend.client_transmit_timestamp_microsec,memrcv.server_transmit_timestamp_sec,memrcv.server_transmit_timestamp_microsec);
  
    close(sockfd);

    return 0;
}

服务器代码示例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>
#include <math.h>
#include <sys/timeb.h>
#include <inttypes.h>
#include <limits.h>

#define TRUE 1

typedef struct client_send client_send;
struct client_send {
  uint8_t client_li_vn_mode;
  uint8_t client_startum;
  uint8_t client_poll;
  uint8_t client_precision;
  uint32_t client_root_delay;
  uint32_t client_root_dispersion;
  uint32_t client_reference_identifier;
  uint32_t client_reference_timestamp_sec;
  uint32_t client_reference_timestamp_microsec;
  uint32_t client_originate_timestamp_sec;
  uint32_t client_originate_timestamp_microsec;
  uint32_t client_receive_timestamp_sec;
  uint32_t client_receive_timestamp_microsec;
  uint32_t client_transmit_timestamp_sec;
  uint32_t client_transmit_timestamp_microsec;
}__attribute__((packed));

typedef struct server_packet server_packet;
struct server_packet {
  uint8_t server_li_vn_mode;
  uint8_t server_startum;
  uint8_t server_poll;
  uint8_t server_precision;
  uint32_t server_root_delay;
  uint32_t server_root_dispersion;
  char server_reference_identifier[4];
  uint32_t server_reference_timestamp_sec;
  uint32_t server_reference_timestamp_microsec;
  uint32_t server_originate_timestamp_sec;
  uint32_t server_originate_timestamp_microsec;
  uint32_t server_receive_timestamp_sec;
  uint32_t server_receive_timestamp_microsec;
  uint32_t server_transmit_timestamp_sec;
  uint32_t server_transmit_timestamp_microsec;
}__attribute__((packed));

/* Linux man page bind() */
#define handle_error(msg)               \
  do {perror(msg); exit(EXIT_FAILURE);} while (0)

uint32_t ClockGetTime() {
  struct timespec ts;
  clock_gettime(CLOCK_REALTIME, &ts);
  return (uint32_t)ts.tv_sec * 1000000LL + (uint32_t)ts.tv_nsec / 1000LL;
}

unsigned long int precision() {

  struct timespec res;
  
  if ( clock_getres( CLOCK_REALTIME, &res) == -1 ) {
    perror( "clock get resolution" );
    return EXIT_FAILURE;
  }

  return res.tv_nsec / 1000;

}

void *get_in_addr(struct sockaddr *sa) {

  if (sa->sa_family == AF_INET) {
    return &(((struct sockaddr_in*)sa)->sin_addr);
  }

  return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(int argc, char *argv[]) {

  server_packet send_mem;
  client_send rcv_mem;

  /* Empty structs */
  memset( &send_mem , 0 , sizeof send_mem );
  memset( &rcv_mem , 0 , sizeof rcv_mem );

  char s[INET_ADDRSTRLEN];
  struct addrinfo hints, *servinfo, *p;
  struct sockaddr_storage their_addr;
  socklen_t addr_len;
  int get, numbytes;
  int sockfd;

    char IP[16];

    char PORT_STR[6];

    memset(IP , '\0' , sizeof(IP));
    memset(PORT_STR , '\0' , sizeof(PORT_STR));

    strcpy(IP, strtok(argv[1], ":"));
    strcpy(PORT_STR, strtok(NULL, ":"));

    memset( &hints , 0 , sizeof hints );
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_flags = AI_PASSIVE;
    hints.ai_protocol = IPPROTO_UDP;

    if ( ( get = getaddrinfo( NULL , PORT_STR , &hints , &servinfo ) ) != 0) {
      fprintf( stderr , "getaddrinfo: %s\n" , gai_strerror(get) );
      return 1;
    }

    for( p = servinfo; p != NULL; p = p->ai_next ) {
      if ( ( sockfd = socket( p->ai_family , p->ai_socktype ,
                  p->ai_protocol ) ) == -1 ) {
    handle_error("socket");
    continue;
      }

      if ( bind( sockfd , p->ai_addr , p->ai_addrlen ) == -1 ) {
    close(sockfd);
    handle_error("bind");
    continue;
      }

      break;
    }

    if (p == NULL) {
      fprintf(stderr, "Not able to bind socket\n");
      return 2;
    }

    freeaddrinfo(servinfo);

    printf("\nServer is up and running: waiting to recv msg at port: %s...\n",
       PORT_STR);

    while(TRUE) {

      time_t t_ref_sec = time(NULL);
      unsigned long int Ref_epoc_sec = t_ref_sec;
      send_mem.server_reference_timestamp_sec = Ref_epoc_sec;

      unsigned long int t_ref_nanosec = ClockGetTime();
      send_mem.server_reference_timestamp_microsec = t_ref_nanosec;

      addr_len = sizeof(their_addr);

      if ((numbytes = recvfrom(sockfd, &rcv_mem , sizeof rcv_mem , 0,
                   (struct sockaddr *)&their_addr, &addr_len)) == -1) {
    handle_error("recvfrom");
    exit(1);
      }

      time_t t_rcv_sec = time(NULL);
      send_mem.server_receive_timestamp_sec = t_rcv_sec;
      send_mem.server_receive_timestamp_microsec = ClockGetTime();

      printf("Peer address: %s\n",
         inet_ntop(their_addr.ss_family,
               get_in_addr((struct sockaddr *)&their_addr),
               s, sizeof(s)));

      printf("Peer port: %i\n",p->ai_socktype);

      send_mem.server_li_vn_mode = 0b00100100;
      send_mem.server_startum = 0b00000001;
      send_mem.server_poll = 0b00000110;
      send_mem.server_precision = precision();
      send_mem.server_root_delay = 0;
      send_mem.server_root_dispersion = 0;
      memcpy( send_mem.server_reference_identifier , "LOCL" , 
          sizeof send_mem.server_reference_identifier );
      send_mem.server_originate_timestamp_sec = rcv_mem.client_originate_timestamp_sec;
      send_mem.server_originate_timestamp_microsec = rcv_mem.client_originate_timestamp_microsec;
      time_t t_send_sec = time(NULL);
      send_mem.server_transmit_timestamp_sec = t_send_sec;
      send_mem.server_transmit_timestamp_microsec = ClockGetTime();

      if ( sendto( sockfd, &send_mem , sizeof send_mem , 0 ,
           (struct sockaddr *) &their_addr , addr_len ) == -1 ) {
    handle_error("sendto");
    exit(1);
      } 

    }

    close(sockfd);

    return 0;
}

使用服务器和客户端时打印输出的示例。
Reference Identifier     0                       LOCL
Reference Timestamp      0.0                     1426637081.3564398733
Originate Timestamp      1426637087.3570333925   1426637087.3570333925
Receive Timestamp        1426637087.3570334078   1426637087.3570334003
Transmit Timestamp       1426637087.3570333925   1426637087.3570334046

当我探测真实的NTP服务器(例如0.se.pool.ntp.org:123)时,打印输出的示例。

Reference Identifier     0                       �$�
Reference Timestamp      0.0                     3879449560.3503094062
Originate Timestamp      1426637090.3573978972   1426637090.3573978972
Receive Timestamp        1426637090.3573992772   2722083800.781009125
Transmit Timestamp       1426637090.3573978972   2722083800.937312997

预期输出应该类似于我之前发布的打印输出。
感谢大家抽出时间和努力来帮助我。
更新:相关问题,但不接近我正在寻找的答案 如何编写NTP客户端?[已关闭]

1
NTP使用1900-01-01 00:00:00作为纪元,因此偏移量应为2208988800秒。闰秒可能会成为一个问题。http://en.wikipedia.org/wiki/Network_Time_Protocol#Timestamps - Keith Thompson
你好@KeithThompson,感谢你的时间和努力。我也尝试添加闰秒,并在减去2208988800秒后,但时间仍然相差甚远。但再次感谢你的建议。 - Thanos
1
当前的NTP时间戳为3635628531秒,超过了2 ** 31-1。尝试使用int64_t进行算术运算。 - Keith Thompson
你好 @KeithThompson,很抱歉我回复晚了。我使用一百万这个因子的原因是我想将一个数字转换为微型10^(-6)。第二步,我将其加到原始数字上。当我继续阅读时,我发现还有另一个错误。我没有使用htonl()主机到网络长整型将我发送的数字进行转换,也没有使用ntohl()网络到主机长整型接收到的数字进行转换。这可能会影响我的服务器响应,我会应用它并发布我的结果。再次感谢您的时间和努力。 - Thanos
1
在将NTP时间戳转换为Unix时间戳时,不考虑闰秒,因为两个时间都是UTC时间。 UTC时间是一个连续的单调秒刻度。 当从原子时间更改为UTC或反之亦然时,会考虑闰秒。 - Luis Colorado
显示剩余6条评论
2个回答

21

将NTP时间戳转换为Unix时间戳(struct timeval)需要解决两个问题。

一个问题是两个时代之间的偏移量。Unix使用的时代位于1/1/1970-00: 00h(UTC),而NTP使用1/1/1900-00:00h。这导致偏移相当于70年的秒数(两个日期之间有17个闰年,因此偏移量为

(70*365 + 17)*86400 = 2208988800

要获取 Unix struct timeval 时间,需要从 NTP 时间中减去一个值。

struct timeval 使用 1/1000000 秒 作为亚秒的单位,而 NTP 则使用 1/2^32 秒 作为其小数部分的单位。为了将 NTP 转换为 struct timeval,可以将小数部分除以 2^32(这很容易,只需进行右移)然后乘以 1000000。为了处理这个问题,我们必须使用 64 位算术,因为数字范围在 02^32 之间,所以最好的方法是:

  • 要将 NTP 转换为 struct timeval,请将小数部分字段(NTP 时间戳的右 32 位)复制到 uint64_t 变量中,并将其乘以 1000000,然后右移 32 位以获取正确的值。您必须考虑到 NTP 时间戳采用网络字节顺序,因此可能需要进行一些调整才能对数字进行操作。

  • 要将 struct timeval 转换为 NTP,请将 Unix 时间的 tv_usec 字段复制到 uint64_t 中,并左移 32 位,然后将其除以 1000000 并转换为网络字节顺序(最重要的字节在前)

下面的代码示例说明了此过程。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>
#include <getopt.h>

#define OFFSET 2208988800ULL

void ntp2tv(uint8_t ntp[8], struct timeval *tv)
{
    uint64_t aux = 0;
    uint8_t *p = ntp;
    int i;

    /* we get the ntp in network byte order, so we must
     * convert it to host byte order. */
    for (i = 0; i < sizeof ntp / 2; i++) {
        aux <<= 8;
        aux |= *p++;
    } /* for */

    /* now we have in aux the NTP seconds offset */
    aux -= OFFSET;
    tv->tv_sec = aux;

    /* let's go with the fraction of second */
    aux = 0;
    for (; i < sizeof ntp; i++) {
        aux <<= 8;
        aux |= *p++;
    } /* for */

    /* now we have in aux the NTP fraction (0..2^32-1) */
    aux *= 1000000; /* multiply by 1e6 */
    aux >>= 32;     /* and divide by 2^32 */
    tv->tv_usec = aux;
} /* ntp2tv */

void tv2ntp(struct timeval *tv, uint8_t ntp[8])
{
    uint64_t aux = 0;
    uint8_t *p = ntp + sizeof ntp;
    int i;

    aux = tv->tv_usec;
    aux <<= 32;
    aux /= 1000000;

    /* we set the ntp in network byte order */
    for (i = 0; i < sizeof ntp/2; i++) {
        *--p = aux & 0xff;
        aux >>= 8;
    } /* for */

    aux = tv->tv_sec;
    aux += OFFSET;

    /* let's go with the fraction of second */
    for (; i < sizeof ntp; i++) {
        *--p = aux & 0xff;
        aux >>= 8;
    } /* for */

} /* ntp2tv */

size_t print_tv(struct timeval *t)
{
    return printf("%ld.%06ld\n", t->tv_sec, t->tv_usec);
}

size_t print_ntp(uint8_t ntp[8])
{
    int i;
    int res = 0;
    for (i = 0; i < sizeof ntp; i++) {
        if (i == sizeof ntp / 2)
            res += printf(".");
        res += printf("%02x", ntp[i]);
    } /* for */
    res += printf("\n");
    return res;
} /* print_ntp */


int main(int argc, char *argv[])
{
    struct timeval t;
    uint8_t ntp[8];

    gettimeofday(&t, NULL);

    printf("tv2ntp\n");
    tv2ntp(&t, ntp);
    printf("tv : "); print_tv(&t);
    printf("ntp: "); print_ntp(ntp);

    printf("ntp2tv\n");
    ntp2tv(ntp, &t);
    printf("tv : "); print_tv(&t);
    printf("ntp: "); print_ntp(ntp);
}

你说得完全正确,为了解决我的问题,你还需要将网络字节序的int转换为字节序。这可以通过htons()、htonl()、ntohs()、ntohl()来完成。再次感谢你的时间和努力。 - Thanos
@Thanos,它正在被转换,但不使用htons()、htonl()或htohs()函数。它是在for(;;)循环中实现的。你可以独立于架构的字节序验证它是否正确。试一试就知道了。 - Luis Colorado
你好@LuisColorado,我看到你已经手动实现了。我只是将其作为评论放置,因为昨晚在尝试解决问题时发现了这个函数。再次感谢您花费时间和精力回答我的问题。作为一个初学者,我有很多东西要学习。我会尽快更新问题并附上答案,一旦我有时间处理它。 - Thanos

7
接受的答案过于陈旧,无法在GCC 9.3.0上编译,但它激发了我编写C++等效代码,使用chrono库使转换变得非常简单:
#include <chrono>
#include <cstddef>
#include <iomanip>
#include <iostream>

using namespace std::chrono;
using namespace std::chrono_literals;

using fractions = duration<std::int64_t, std::ratio<1, 0x100000000>>; // 1/(2^32)
using ntp_t = std::uint64_t;

auto tp2ntp(system_clock::time_point tp)
{
    // "shift" epoch from unix 1/1/1970 to ntp 1/1/1900 (70 years + 17 leap days)
    tp += (70 * 365 + 17) * 24h;

    auto total = tp.time_since_epoch();
    auto secs  = duration_cast<seconds>(total);
    auto fracs = duration_cast<fractions>(total - secs);

    return static_cast<ntp_t>( (secs.count() << 32) | fracs.count() );
}

auto ntp2tp(ntp_t ntp)
{
    auto tp = system_clock::time_point(); // epoch

    // "shift" epoch from ntp 1/1/1900 to unix 1/1/1970 (70 years + 17 leap days)
    tp -= (70 * 365 + 17) * 24h;

    tp += seconds(ntp >> 32);
    tp += duration_cast<system_clock::duration>( fractions(ntp & 0xffffffff) );

    return tp;
}

void print_tp(system_clock::time_point tp)
{
    auto total = tp.time_since_epoch();
    auto secs  = duration_cast<seconds>(total);
    auto usecs = duration_cast<microseconds>(total - secs);

    std::cout << "tp : " << secs.count() << '.' << usecs.count() << std::endl;
}

void print_ntp(ntp_t ntp)
{
    std::cout << "ntp: " << std::hex << (ntp >> 32) << '.' << (ntp & 0xffffffff) << std::dec << std::endl;
}

int main(int argc, char *argv[])
{
    auto tp = system_clock::now();

    std::cout << "tp2ntp" << std::endl;
    auto ntp = tp2ntp(tp);
    print_tp(tp);
    print_ntp(ntp);

    std::cout << "ntp2tp" << std::endl;
    auto tp2 = ntp2tp(ntp);
    print_tp(tp2);
    print_ntp(ntp);

    return 0;
}

更新

根据Howard Hinnant在评论中的建议,这里提供另一种版本,该版本使用C++20新增的新数据类型:

#include <chrono>
#include <cstddef>
#include <iomanip>
#include <iostream>

using namespace std::chrono;
using namespace std::chrono_literals;

using fractions = duration<std::int64_t, std::ratio<1, 0x100000000>>; // 1/(2^32)
using ntp_t = std::uint64_t;

// difference between the unix epoch (1/1/1970) and the ntp epoch (1/1/1900)
constexpr auto epoch_diff = sys_days{ 1970y/1/1 } - sys_days{ 1900y/1/1 }; 

auto tp2ntp(system_clock::time_point tp)
{
    tp += epoch_diff; // shift epoch

    auto total = tp.time_since_epoch();
    auto secs  = duration_cast<seconds>(total);
    auto fracs = duration_cast<fractions>(total - secs);

    return static_cast<ntp_t>( (secs.count() << 32) | fracs.count() );
}

auto ntp2tp(ntp_t ntp)
{
    auto tp = system_clock::time_point();
    tp -= epoch_diff; // shift epoch

    tp += seconds{ ntp >> 32 };
    tp += duration_cast<system_clock::duration>( fractions{ ntp & 0xffffffff } );

    return tp;
}

void print_tp(system_clock::time_point tp)
{
    auto total = tp.time_since_epoch();
    auto secs  = duration_cast<seconds>(total);
    auto usecs = duration_cast<microseconds>(total - secs);

    std::cout << "tp : " << secs.count() << '.' << usecs.count() << std::endl;
}

void print_ntp(ntp_t ntp)
{
    std::cout << "ntp: " << std::hex << (ntp >> 32) << '.' << (ntp & 0xffffffff) << std::dec << std::endl;
}

int main(int argc, char *argv[])
{
    auto tp = system_clock::now();

    std::cout << "tp2ntp" << std::endl;
    auto ntp = tp2ntp(tp);
    print_tp(tp);
    print_ntp(ntp);

    std::cout << "ntp2tp" << std::endl;
    auto tp2 = ntp2tp(ntp);
    print_tp(tp2);
    print_ntp(ntp);

    return 0;
}

谢谢。这非常有帮助。但是,我希望你可以澄清一个方面:对于tp2ntp(),比率不应该是<1000000, 0x100000000>吗?简单地说,难道我们不也需要将小数部分除以1000000吗?可能类似的评论也适用于ntp2tp()。但现在,我的重点是tp2ntp()。 - venk
@venk 不确定我是否理解了你的问题,但是这是它的工作原理。我们接收 tp 并将其时代转换为 1900 年 1 月 1 日。然后计算 total 中的滴答数。滴答可以是微秒、毫秒或其他(系统相关)。它们的长度(也称为周期)是 total 类签名的一部分。我们调用 duration_cast 并要求将它们的滴答转换为 1 秒长的滴答,并将它们存储在 secs 中。剩余的滴答(total - secs)被转换为 1/(2^32) 长度的滴答(即 fractions),并存储在 fracs 中。有意义吗? - Super-intelligent Shade
1
使用C++20,现在有新的年份和日期字面量,因此(70 * 365 + 17) * 24h可以缩写为70y + 17d - Aaron Wright
@AaronWright 确实,他们在C++20中添加了std::chrono::daysstd::chrono::years,它们都是持续时间。然而,'d'和'y'字面量返回的是std::chrono::daystd::chrono::year的实例,它们并不相同,也不能与持续时间混合使用。 - Super-intelligent Shade
1
正确,但你也可以这样拼写:tp += sys_days{1970y/1/1} - sys_days{1900y/1/1};。虽然不够简洁,但更易读。你甚至不再需要注释(在我看来)。而且你也不必再纠结于17是否是正确的闰日数量(它就是)。 - Howard Hinnant
@HowardHinnant 魔鬼已经说话了。;) 感谢您提供的chrono库,Howard。我会更新我的答案。 - Super-intelligent Shade

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接