Unix/Linux编程:时间转换

unix time stamp翻译为时间戳, 就是从1970年1月1日00:00::00以来的秒数。

程序可能会关注两种类型的时间

  • 真实时间。度量这一时间的起点有二:
    • 其一为某个标准点。 也叫做日历时间,适用于需要对数据库记录或文件打上时间戳的程序
    • 其二为进程生命周期内的某个固定时间点(通常为程序启动)。后者则称之为流逝(elapsed)时间或挂钟(wall
      clock)时间,主要针对需要周期性操作或定期从外部输入设备进行度量的程序
  • 进程时间:一个进程所使用的 CPU 时间总量,适用于对程序、算法性能的检查或优化

大多数计算机体系结构都内置有硬件时钟,使内核得以计算真实时间和进程时间。

结构体 struct tm中的属性

struct tm 
{
        int tm_sec;     /* seconds after the minute - [0,59] */
        int tm_min;     /* minutes after the hour - [0,59] */
        int tm_hour;    /* hours since midnight - [0,23] */
        int tm_mday;    /* day of the month - [1,31] */
        int tm_mon;     /* months since January - [0,11] */
        int tm_year;    /* years since 1900 */
        int tm_wday;    /* days since Sunday - [0,6] */
        int tm_yday;    /* days since January 1 - [0,365] */
        int tm_isdst;   /* daylight savings time flag */
};

在这里插入图片描述
这里有几个地方要注意:

  • tm_sec 在C89的范围是[0-61],在C99更正为[0-60]。通常范围是[0-59],只是某些系统会出现60秒的跳跃。
  • tm_mon 是从零开始的,所以一月份为0,十二月份为11,计算月份的时候需要加1。
  • tm_year 计算年份的时候需要加上1900

日历时间:gettimeofday()、time()

无论地理位置如何,UNIX 系统内部对时间的表示方式均是以自 Epoch 以来的秒数来度量的,Epoch 亦即通用协调时间(UTC,以前也称为格林威治标准时间,或 GMT)的 1970 年 1月 1 日早晨零点。这也是 UNIX 系统问世的大致日期。日历时间存储于类型为 time_t 的变量中,此类型是由 SUSv3 定义的整数类型

在 32 位 Linux 系统,time_t 是一个有符号整数,可以表示的日期范围从 1901 年 12 月13 日 20 时 45 分 52 秒至 2038 年 1 月 19 号 03:14:07。(SUSv3 未定义 time_t 值为负数时的含义。)因此,当前许多 32 位 UNIX 系统都面临一个 2038 年的理论问题,如果执行的计算工作涉及未来日期,那么在 2038 年之前就会与之遭遇。事实上,到了 2038 年,可能所有的 UNIX 系统都早已升级为 64 位或更多位数的系统,这一问题也许会随之而大为缓解。然而,32 位嵌入式系统,由于其寿命较之台式机硬件更长,故而仍然会受此问题的困扰。此外,对于依然以 32 位 time_t 格式保存时间的历史数据和应用程序,这个问题将依然存在

系统调用 gettimeofday(),可于 tv 指向的缓冲区中返回日历时间

NAME
       gettimeofday, settimeofday - get / set time

SYNOPSIS
       #include <sys/time.h>

       int gettimeofday(struct timeval *tv, struct timezone *tz);

       int settimeofday(const struct timeval *tv, const struct timezone *tz);



DESCRIPTION
       The functions gettimeofday() and settimeofday()可以获取并设置时间以及时区。. 参数 tv 是指向
       如下struct的一个指针 (定义在 <sys/time.h>):

           struct timeval {
               time_t      tv_sec;     /* seconds */
               suseconds_t tv_usec;    /* microseconds */
           };

       并给出自历元以来的秒数和微秒数(请参见time(2))
		
		虽然 tv_usec 字段能提供微秒级精度,但其返回值的准确性则由依赖于构架的具体实现来决定。tv_usec 中的
		 u 源于与之形似的希腊字母 μ(读音“mu”),在公制系统中表示百万分之一。在现代 X86-32 系统上,gettimeofday()
		 的确可以提供微秒级的准确度(例如, Pentium 系统内置有时间戳计数寄存器,随每个 CPU 时钟周期而加一)

		参数 tz 是个历史产物。早期的 UNIX 实现用其来获取系统的时区信息,目前已遭废弃,应始终将其置为 NULL

		如果提供了 tz 参数,那么将返回一个 timezone 的结构体,其内容为上次调用 settimeofday()时传入的 tz 参数(已废弃)值。
		该结构包含两个字段 tz_minuteswest 和 tz_dsttime。tz_minuteswest字段表示欲将本时区时间转换为 UTC 时间所必须增加的
		分钟数,如为负值,则表示此时区位于 UTC 以东(例如,如为欧洲中部时间,会提前 UTC 一小时,则将此字段设置为−60)。tz_dsttime 
		字段内为一个常量,意在表示这个时区是否强制施行夏令时(DST)制。正由于夏令时制度无法用一个简单算法加以表达,故而 tz 参数已遭废弃。	

RETURN VALUE
       gettimeofday() / settimeofday() 返回0表示成功,或-1表示失败(在这种情况下errno设置正确)。 

time()系统调用返回自 Epoch 以来的秒数(和函数 gettimeofday()所返回的 tv 参数中 tv_sec字段的数值相同)

NAME
       time - 以秒为单位获取时间 

SYNOPSIS
       #include <time.h>

       time_t time(time_t *t);

DESCRIPTION
       time() 返回自19701100:00:00 +0000(UTC)以来的秒数。 

      如果t为非NULL,则返回值也存储在t指向的内存中。 

RETURN VALUE
       成功后,返回自纪元以来的时间(以秒为单位)。 发生错误时,将返回((time_t)-1),并正确设置了errno。 

ERRORS
       EFAULT t点位于您可访问的地址空间之外。 




  • 如果 timep 参数不为 NULL,那么还会将自 Epoch 以来的秒数置于 timep 所指向的位置。由于 time()会以两种方式返回相同的值,而使用时唯一可能出错的地方是赋予 timep 参数一个无效地址(EFAULT),因此往往会简单地采用如下调用(不做错误检查)
t = time(NULL);
  • 之所以存在两个本质上目的相同的系统调用(time()和 gettimeofday()),自有其历史原因。早期的 UNIX 实现提供了 time()。而 4.3BSD 又补充了更为精确的 gettimeofday()系统调用。这时,再将 time()作为系统调用就显得多余,可以将其实现为一个调用 gettimeofday()的库函数

获取当前时间的秒数、微秒数、毫秒数

环境:Linux C

#include<iostream>
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>

int main(){
    struct timeval tv;
    gettimeofday(&tv,NULL);
    printf("second:%ld\n",tv.tv_sec);  //秒
    printf("millisecond:%ld\n",tv.tv_sec*1000 + tv.tv_usec/1000);  //毫秒
    printf("microsecond:%ld\n",tv.tv_sec*1000000 + tv.tv_usec);  //微秒

    sleep(3); // 为方便观看,让程序睡三秒后对比
    std::cout << "3s later:" << std::endl;

    gettimeofday(&tv,NULL);
    printf("second:%ld\n",tv.tv_sec);  //秒
    printf("millisecond:%ld\n",tv.tv_sec*1000 + tv.tv_usec/1000);  //毫秒
    printf("microsecond:%ld\n",tv.tv_sec*1000000 + tv.tv_usec);  //微秒
    return 0;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
#define likely(x)  __builtin_expect((x), 1)
#define unlikely(x)  __builtin_expect((x), 0)
#define USEC_PER_SEC    1000000ULL
#define MSEC_PER_SEC    1000ULL
#define NSEC_PER_USEC   1000ULL
#define NSEC_PER_SEC    1000000000ULL
typedef unsigned long long usec_t;

inline int clock_gettime(clockid_t clk_id, struct timespec *ts) {
    struct timeval tv;
    if(unlikely(gettimeofday(&tv, NULL) == -1)) {  //gettimeofday()会把目前的时间用tv 结构体返回,当地时区的信息则放到tz所指的结构中
        printf("(%s, %s(%d))  gettimeofday() failed.", __FILE__, __FUNCTION__ , __LINE__);
        return -1;
    }
    ts->tv_sec = tv.tv_sec;
    ts->tv_nsec = (tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC;
    return 0;
}
// 
static inline time_t now_sec(clockid_t clk_id) {
    struct timespec ts;
    if(unlikely(clock_gettime(clk_id, &ts) == -1)) {
        printf("clock_gettime(%d, &timespec) failed.", clk_id);
        return 0;
    }
    return ts.tv_sec;
}
static inline usec_t now_usec(clockid_t clk_id) {
    struct timespec ts;
    if(unlikely(clock_gettime(clk_id, &ts) == -1)) {
        printf("(%s, %s(%d)) clock_gettime(%d, &timespec) failed.", __FILE__, __FUNCTION__ , __LINE__, clk_id);
        exit(0);
    }                      // 1000000ULL                 //1000000000ULL  / 1000ULL
    return (usec_t)ts.tv_sec * USEC_PER_SEC + (ts.tv_nsec % NSEC_PER_SEC) / NSEC_PER_USEC;
}



int main(int argc, char **argv)
{
    printf("%llu", now_usec(0));

}

秒时间戳 使用了time.h
毫秒时间戳使用了 c++11 标准库: std::chrono

#include <time.h>
#include <chrono>  
#include <iostream>   // std::cout

std::time_t getTimeStamp()
{
    std::chrono::time_point<std::chrono::system_clock,std::chrono::milliseconds> tp = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now());//获取当前时间点
    std::time_t timestamp =  tp.time_since_epoch().count(); //计算距离1970-1-1,00:00的时间长度
    return timestamp;
}

int main(){
    time_t timestamp;
    std::cout << time(&timestamp) << std::endl;//秒级时间戳
    timestamp =  getTimeStamp();
    std::cout << timestamp << std::endl;//毫秒级时间戳
}



在这里插入图片描述

时间转换函数

用于在 time_t 值和其他时间格式之间相互转换的函数,其中包括打印输出。这些函数屏蔽了因时区、夏令时(DST)制和本地化等问题给转换所带来的种种复杂性。
在这里插入图片描述

strptime 将时间字符串转为 struct tm 格式

头文件:#include <time.h>

函数定义: char *strptime(const char *s, const char *format, struct tm *tm);

函数作用:将以字符串形式表示的时间转为 tm 结构体表示的时间。strptime函数功能与 strftime 函数正好相反。

函数返回值:
strptime 函数从左到右按字符进行匹配,一旦匹配格式不正确或者匹配错误就直接返回匹配错误的字符地址。

如果输入的字符串多余需要匹配的格式,返回正确匹配到的字符地址的下一个字符,也就是多出来的字符的首地址。

如果输入的字符串完全匹配正确,则返回值指向字符串的末尾,也就是’\0’的位置。如果匹配过程出错,返回NULL。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
 
void main(void)
{
    struct tm tm;
    
    memset(&tm, 0, sizeof(struct tm));
    strptime("2018-06-24 16:31:01", "%Y-%m-%d %H:%M:%S", &tm);
 
    //打印Sun Jun 24 16:31:01 2018
    printf("\ntime: %s\n", asctime(&tm));
 
    memset(&tm, 0, sizeof(struct tm));
    strptime("24 Jun 2018 16:31:01", "%d %b %Y %H:%M:%S", &tm);
 
    //打印Sun Jun 24 16:31:01 2018
    printf("\ntime: %s\n", asctime(&tm));
}

将 time_t 转换为可打印格式: ctime

NAME
       asctime, ctime, gmtime, localtime, mktime, asctime_r, ctime_r, gmtime_r, localtime_r - 将日期和时间转换为细分时间或ASCII 

SYNOPSIS
       #include <time.h>

       char *ctime(const time_t *timep);
       char *ctime_r(const time_t *timep, char *buf);


DESCRIPTION
      


为了将 time_t 转换为可打印格式,ctime()函数提供了一个简单方法把一个指向 time_t 的指针作为 timep 参数传入函数 ctime(),将返回一个长达 26 字节的字符串,内含标准格式的日期和时间,如下例所示
在这里插入图片描述
该字符串包含换行符和终止空字节各一。 ctime()函数在进行转换时,会自动对本地时区和 DST 设置加以考虑。返回的字符串经由静态分配,下一次对 ctime()的调用会将其覆盖

SUSv3 规定,调用 ctime()、gmtime()、localTime()或 asctime()中的任一函数,都可能会覆盖由其他函数返回,且经静态分配的数据结构。换言之,这些函数可以共享返回的字符数组和 tm 结构体,某些版本的 glibc 也正是这样实现的。如果有意在对这些函数的多次调用间维
护返回的信息,那么必须将其保存在本地副本中。

ctime_r()是 ctime()的可重入版本。该函数允许调用者额外指定一个指针参数,所指向的缓冲区(由调用者提供)用于返回时间字符串

time_t 和分解时间之间的转换

NAME
       asctime, ctime, gmtime, localtime, mktime, asctime_r, ctime_r, gmtime_r, localtime_r - 将日期和时间转换为细分时间或ASCII 

SYNOPSIS
       #include <time.h>

       struct tm *gmtime(const time_t *timep);
       struct tm *gmtime_r(const time_t *timep, struct tm *result);

       struct tm *localtime(const time_t *timep);
       struct tm *localtime_r(const time_t *timep, struct tm *result);


函数 gmtime()和 localtime()可将一 time_t 值转换为一个所谓的分解时间(broken-down time)。分解时间被置于一个经由静态分配的结构中,其地址则作为函数结果返回。

函数 gmtime()能够把日历时间转换为一个对应于 UTC 的分解时间。相形之下,函数 localtime()需要考虑时区和夏令时设置,返回对应于系统本地时间的一个分解时间

gmtime_r()和 localtime_r()分别是这些函数的可重入版

在这些函数所返回的 tm 结构中,日期和时间被分解为多个独立字段,其形式如下

  struct tm {
               int tm_sec;         /* seconds */
               int tm_min;         /* minutes */
               int tm_hour;        /* hours */
               int tm_mday;        /* day of the month */
               int tm_mon;         /* month */
               int tm_year;        /* year */
               int tm_wday;        /* day of the week */
               int tm_yday;        /* day in the year */
               int tm_isdst;       /* daylight saving time */
           };

 The members of the tm structure are:

       tm_sec    The number of seconds after the minute, normally in the range 0 to 59, but can be up to 60 to allow for leap seconds.

       tm_min    The number of minutes after the hour, in the range 0 to 59.

       tm_hour   The number of hours past midnight, in the range 0 to 23.

       tm_mday   The day of the month, in the range 1 to 31.

       tm_mon    The number of months since January, in the range 0 to 11.

       tm_year   The number of years since 1900.

       tm_wday   The number of days since Sunday, in the range 0 to 6.

       tm_yday   The number of days since January 1, in the range 0 to 365.

       tm_isdst  一个标志,指示夏时制是否在所述时间生效。 如果夏令时有效,则该值为正;如果不启用夏令时,则为零;如果该信息不可用,
       			则该值为负。 


  • 将字段 tm_sec 的上限设为 60(而非 59)以考虑闰秒,偶尔会用其将人类日历调整至精确的天文年(所谓的回归年)。
  • 如果定义了_BSD_SOURCE 功能测试宏,那么由 glibc 定义的 tm 结构还会包含两个额外字段,以描述关于所示时间的深入信息。第一个字段 long int tm_gmtoff,包含所示时间超出UTC 以东的秒数。第二个字段 const char* tm_zone,是时区名称的缩写(例如,CEST 为欧洲
    中部夏令时间)。SUSv3 并未定义这些字段,它们只见诸于少数其他 UNIX 实现(主要为 BSD衍生版本)。

函数 mktime() 将一个本地时区的分解时间翻译为 time_t 值,并将其作为函数结果返回。

NAME
       asctime, ctime, gmtime, localtime, mktime, asctime_r, ctime_r, gmtime_r, localtime_r - transform date and time to broken-down time or ASCII

SYNOPSIS
       #include <time.h>

       time_t mktime(struct tm *tm);

  • 调用者将分解时间置于一个 tm 结构,再以 timeptr 指针指向该结构。这一转换会忽略输入 tm结构中的 tm_wday 和 tm_yday 字段。
  • 函数 mktime()可能会修改 timeptr 所指向的结构体,至少会确保对 tm_wday 和 tm_yday 字段值的设置,会与其他输入字段的值能对应起来。
  • 此外,mktime()不要求 tm 结构体的其他字段受到前述范围的限制。任何一个字段的值超出范围,mktime()都会将其调整回有效范围之内,并适当调整其他字段。所有这些调整,均发生于 mktime()更新 tm_wday 和 tm_yday 字段并计算返回值 time_t 之前
    例如,如果输入字段 tm_sec 的值为 123,那么在返回时此字段的值将为 3,且 tm_min 字段值会在其之前值的基础上加 2。(如果这一改动造成 tm_min 溢出,那么将调整 tm_min 的值,并且递增 tm_hour 字段,以此类推。)这些调整甚至适用于字段负值。例如,指定 tm_sec 为−1 即意味着前一分钟的第 59 秒。此功能允许以分解时间来计算日期和时间,故而非常有用。

mktime()在进行转换时会对时区进行设置。此外,DST 设置的使用与否取决于输入字段tm_isdst 的值。

  • 若 tm_isdst 为 0,则将这一时间视为标准间(即,忽略夏令时,即使实际上每年的这一时刻处于夏令时阶段)。
  • 若 tm_isdst 大于 0,则将这一时间视为夏令时(即,夏令时生效,即使每年的此时不处于夏令时阶段)。
  • 若 tm_isdst 小于 0,则试图判定 DTS 在每年的这一时间是否生效。这往往是众望所归的设置。

(无论 tm_isdst 的初始设置如何)在转换完成时,如果针对给定的时间,DST 生效,mktime()会将 tm_isdst 字段置为正值,若 DST 未生效,则将 tm_isdst 置为 0

格式化本地当前时间

#include <iostream>
 
std::string GetNowTime() {
	time_t setTime;
	time(&setTime);
	tm* ptm = localtime(&setTime);
	std::string time = std::to_string(ptm->tm_year + 1900)
	                   + "/"
	                   + std::to_string(ptm->tm_mon + 1)
	                   + "/"
	                   + std::to_string(ptm->tm_mday)
	                   + " "
	                   + std::to_string(ptm->tm_hour) + ":"
	                   + std::to_string(ptm->tm_min) + ":"
	                   + std::to_string(ptm->tm_sec);		
	return time;	
}
 
//test
int main(int argc, char *argv[]) {
	std::string strTime = GetNowTime();
	std::cout << "now time: " << strTime << std::endl;
	return 0;
}

在这里插入图片描述

分解时间和打印格式之间的转换

从分解时间转换为打印格式

在参数 tm 中提供一个指向分解时间结构的指针,asctime()则会返回一指针,指向经由静态分配的字符串,内含时间,格式则与 ctime ()相同

SYNOPSIS
       #include <time.h>

       char *asctime(const struct tm *tm);
       char *asctime_r(const struct tm *tm, char *buf);

  • 相形于 ctime(),本地时区设置对 asctime()没有影响,因为其所转换的是一个分解时间,该时间通常要么已然通过 localtime()作了本地化处理,要么早已经由 gmtime()转换成了 UTC

  • 如同 ctime()一样,asctime()也无法控制其所生成字符串的格式

asctime()的可重入版为 asctime_r()。

应用:获取和转换日历时间

// 获取和转换日历时间
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>

#define SECONDS_IN_TROPICAL_YEAR (365.24219 * 24 * 60 * 60)
int main(int argc, char *argv[])
{
    time_t t;
    struct tm *gmp, *locp;
    struct tm gm, loc;
    struct timeval tv;

    /* Retrieve time, convert and display it in various forms */

    t = time(NULL);
    printf("Seconds since the Epoch (1 Jan 1970): %ld", (long) t);
    printf(" (about %6.3f years)\n", t / SECONDS_IN_TROPICAL_YEAR);

    if (gettimeofday(&tv, NULL) == -1){
        perror("gettimeofday");
        exit(EXIT_FAILURE);
    }

    printf("  gettimeofday() returned %ld secs, %ld microsecs\n",
           (long) tv.tv_sec, (long) tv.tv_usec);

    gmp = gmtime(&t);
    if (gmp == NULL){
        perror("gmtime");
        exit(EXIT_FAILURE);
    }

    gm = *gmp;          /* Save local copy, since *gmp may be modified
                           by asctime() or gmtime() */
    printf("Broken down by gmtime():\n");
    printf("  year=%d mon=%d mday=%d hour=%d min=%d sec=%d ", gm.tm_year,
           gm.tm_mon, gm.tm_mday, gm.tm_hour, gm.tm_min, gm.tm_sec);
    printf("wday=%d yday=%d isdst=%d\n", gm.tm_wday, gm.tm_yday, gm.tm_isdst);

    /* The TZ environment variable will affect localtime().
       Try, for example:

                TZ=Pacific/Auckland calendar_time
    */

    locp = localtime(&t);
    if (locp == NULL){
        perror("localtime");
        exit(EXIT_FAILURE);
    }

    loc = *locp;        /* Save local copy */

    printf("Broken down by localtime():\n");
    printf("  year=%d mon=%d mday=%d hour=%d min=%d sec=%d ",
           loc.tm_year, loc.tm_mon, loc.tm_mday,
           loc.tm_hour, loc.tm_min, loc.tm_sec);
    printf("wday=%d yday=%d isdst=%d\n\n",
           loc.tm_wday, loc.tm_yday, loc.tm_isdst);

    printf("asctime() formats the gmtime() value as: %s", asctime(&gm));
    printf("ctime() formats the time() value as:     %s", ctime(&t));

    printf("mktime() of gmtime() value:    %ld secs\n", (long) mktime(&gm));
    printf("mktime() of localtime() value: %ld secs\n", (long) mktime(&loc));

    exit(EXIT_SUCCESS);
}

在这里插入图片描述
当把一个分解时间转换成打印格式时,函数 strftime()可以提供更为精确的控制。令 timeptr指向分解时间,strftime()会将以 null 结尾、由日期和时间组成的相应字符串置于 outstr 所指向的缓冲区中

NAME
       strftime - format date and time

SYNOPSIS
       #include <time.h>

       size_t strftime(char *s, size_t max, const char *format,
                       const struct tm *tm);


outstr 中返回的字符串按照 format 参数定义的格式做了格式化。Maxsize 参数指定 outstr 的最大长度。不同于 ctime()和 asctime(),strftime()不会在字符串的结尾包括换行符(除非 format 中定义有换行符)。

如果成功,strftime()返回 outstr 所指缓冲区的字节长度,且不包括终止空字节。如果结果字符串的总长度,含终止空字节,超过了 maxsize 参数,那么 strftime()会返回 0 以示出错,且此时无法确定 outstr 的内容。

strftime()的转换说明符:
在这里插入图片描述
在这里插入图片描述

返回当前时间的字符串的函数

#include <time.h>

#define BUF_SIZE 1000

char *
currTime(const char *format)
{
    static char buf[BUF_SIZE];  /* Nonreentrant */
    time_t t;
    size_t s;
    struct tm *tm;

    t = time(NULL);
    tm = localtime(&t);
    if (tm == NULL)
        return NULL;

    s = strftime(buf, BUF_SIZE, (format != NULL) ? format : "%c", tm);

    return (s == 0) ? NULL : buf;
}

将打印格式时间转换为分解时间

函数 strptime()是 strftime()的逆向函数,将包含日期和时间的字符串转换成一分解时间

NAME
       strptime - convert a string representation of time to a time tm structure

SYNOPSIS
       #define _XOPEN_SOURCE       /* See feature_test_macros(7) */
       #include <time.h>

       char *strptime(const char *s, const char *format, struct tm *tm);

函数strptime()按照参数format内的格式要求,对由日期和时间组成的字符串str加以解析,并将转换后的分解时间置于指针 timeptr 所指向的结构体中。

如果成功,strptime()返回一指针,指向 str 中下一个未经处理的字符。(如果字符串中还包含有需要应用程序处理的额外信息,这一特性就能派上用场。)如果无法匹配整个格式字符串,strptime()返回 NULL,以示出现错误

使用示例:

#if ! defined(__sun)
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE
#endif
#endif
#include <time.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


#define SBUF_SIZE 1000
int
main(int argc, char *argv[])
{
    struct tm tm;
    char sbuf[SBUF_SIZE];
    char *ofmt;

    if (argc < 3 || strcmp(argv[1], "--help") == 0){
        printf("%s input-date-time in-format [out-format]\n", argv[0]);
        exit(EXIT_FAILURE);
    }


    if (setlocale(LC_ALL, "") == NULL) {
        perror("setlocale");
        exit(EXIT_FAILURE);
    }

    memset(&tm, 0, sizeof(struct tm));          /* Initialize 'tm' */
    if (strptime(argv[1], argv[2], &tm) == NULL){
        perror("strptime");
        exit(EXIT_FAILURE);
    }


    tm.tm_isdst = -1;           /* Not set by strptime(); tells mktime()
                                   to determine if DST is in effect */
    printf("calendar time (seconds since Epoch): %ld\n", (long) mktime(&tm));

    ofmt = const_cast<char *>((argc > 3) ? argv[3] : "%H:%M:%S %A, %d %B %Y %Z");
    if (strftime(sbuf, SBUF_SIZE, ofmt, &tm) == 0){
        perror("strftime");
        exit(EXIT_FAILURE);
    }
    printf("strftime() yields: %s\n", sbuf);

    exit(EXIT_SUCCESS);
}

在这里插入图片描述

更新系统时区

我们现在来看两个更新系统时钟的接口:settimeofday()和 adjtime()。这些接口都很少被应用程序使用,因为系统时间通常是由工具软件维护,如网络时间协议(Network Time Protocol)守护进程,并且它们需要调用者已被授权(CAP_SYS_TIME)。

settimeofday()

系统调用 settimeofday()是 gettimeofday()的逆向操作

int settimeofday(const struct timeval *tv, const struct timezone *tz)
  • 和函数 gettimeofday()一样,tz 参数已被废弃,这个参数应该始终指定为 NULL。
  • tv.tv_usec 字段的微秒精度并不意味着我们以微秒精度来设置系统时钟,因为时钟的精度可能会低于微秒

Linux 还提供了 stime()系统调用来设置系统时钟。settimeofday()和 stime()之间的区别是,后者调用允许使用秒的精度来表示新的日历时间。和函数 time()与 gettimeofday()相同,stime()和 settimeofday()的并存是由历史原因造成的:拥有更高精确度的后一个函数,是由4.3BSD 添加的

settimeofday()调用所造成的那种系统时间的忽然变化,可能会对依赖于系统时钟单调递增的应用炒成有害影响(比如,make(1),数据库系统使用的时间戳或包含时间戳记的日志文件)。处于这个原因,当对时间做微小调整时(几秒钟误差),推荐使用adjtime(),它将系统时钟逐步调整到正确时间。

adjtime()

NAME
       adjtime - 更正时间以同步系统时钟 

SYNOPSIS
       int adjtime(const struct timeval *delta, struct timeval *olddelta);

  glibc的功能测试宏要求  (see feature_test_macros(7)):

       adjtime(): _BSD_SOURCE

DESCRIPTION
       adjtime()  函数逐渐调整系统时钟。
       delta 参数指向一个 timeval 结构体,指定需要改变时间的秒和微秒数:

           struct timeval {
               time_t      tv_sec;     /* seconds */
               suseconds_t tv_usec;    /* microseconds */
           };

       如果delta 是正数,那么每秒系统时间都会额外拨快一点点,直到增加完所需的时间。如果 delta 
       值为负时,时钟以类似的方式减慢.(Linux/x86-32 以每 2000 秒变化 1 秒(或每天 43.2 秒)
       的频率调整时钟。)

       在 adjtime()函数执行的时间里,它可能无法完成时钟调整。在这种情况下,剩余未经调整的时间存放
       在 olddelta 指向的 timeval 结构体内。如果我们不关心这个值,我们可以指定olddelta 为 NULL。
       相反,如果我们只关心当前未完成时间校正的信息,而并不想改变它,我们可以指定 delta 参数为 NULL。

RETURN VALUE
      成功时,返回0。失败时,返回-1,并且设置errno

adjtime()在 Linux 上,基于更通用和复杂的特定于 Linux 的系统调用 adjtimex()来完成功能。这个系统调用也同时被网络时间协议(NTP)守护进程调用。如需进一步信息,请参阅 Linux 的源代码,Linux adjtimex(2)帮助手册页和 NTP 规范

软件时钟jiffies

上面时间相关的各种系统调用的精度是受限于系统软件时钟(software clock)的分辨率,它的单位度量被称为jiffies。jiffies的大小是定义在内核源代码的常量HZ。这是内核按照round-robin的分时调度算法分配CPU进行的单位。

在 2.4 或以上版本的 Linux/x86-32 内核中,软件时钟速度是 100 赫兹,也就是说,一个 jiffy是 10 毫秒

自 Linux 面世以来,由于 CPU 的速度已大大增加,Linux / x86- 32 2.6.0 内核的软件时钟速度已经提高到 1000 赫兹。更高的软件时钟速率意味着定时器可以有更高的操作精度和时间可以拥有更高的测量精度。然而,这并非可以任意提高时钟频率,因为每个时钟中断会消耗少量CPU时间,这部分时间CPU无法执行任何操作。

经过内核开发人员之间的的讨论,最终导致软件时钟频率成为一个可配置的内核的选项(包括处理器类型和特性,定时器的频率)。自 2.6.13 内核,时钟频率可以设置到 100、250(默认)或 1000 赫兹,对应的 jiffy 值分别为 10、4、1 毫秒。自内核 2.6.20,增加了一个频率:300 赫兹,它可以被两种常见的视频帧速率 25 帧每秒(PAL)和 30 帧每秒(NTSC)整除

进程时间

进程时间是进程创建之后使用CPU时间数量。处于记录的目的,内核把CPU时间分为一下两个部分:

  • 用户CPU时间是在用户模式下执行所花费的时间数量。有时也叫做虚拟时间。这对程序来说,是它已经得到CPU的时间
  • 系统CPU时间是在内核模式中执行所花费的时间数量。这是内核用于执行系统调用或代表程序执行的其他任务(例如,服务页错误)的时间。

有时候,进程时间是指处理过程中所消耗的总 CPU 时间。

当我们运行一个程序,我们可以使用time(1)命令,同时获得这两个部分的时间值,以及运行程序所需的实际时间:
在这里插入图片描述
系统调用times(),检索进程时间信息,并把结果通过buf指向的结构体返回:

NAME
       times - get process times

SYNOPSIS
       #include <sys/times.h>

       clock_t times(struct tms *buf);

DESCRIPTION
       times() 将当前处理时间存储在buf指向的struct tms中。 结构tms如<sys/times.h>中所定义 :

           struct tms {
               clock_t tms_utime;  /* user time */
               clock_t tms_stime;  /* system time */
               clock_t tms_cutime; /* user time of children */
               clock_t tms_cstime; /* system time of children */
           };

       tms_utime字段包含执行调用过程的指令所花费的CPU时间。 
       tms_stime字段包含代表调用进程执行任务时在系统中花费的CPU时间。 
       tms_cutime字段包含所有等待的终止子进程的tms_utime和tms_cutime值的总和。 
       tms_cstime字段包含所有等待的终止子进程的tms_stime和tms_cstime值的总和。 
			--- tms 结构体的前两个字段返回调用进程到目前为止使用的用户和系统组件的 CPU 时间。
				最后两个字段返回的信息是:父进程(比如,times()的调用者)执行了系统调用 wait()
				的所有已经终止的子进程使用的 CPU 时间
     
RETURN VALUE
       times()  返回自过去任意点以来经过的时钟滴答数。 返回值可能会溢出clock_t类型的可能范围。 
       如果出错,则返回(clock_t)-1,并正确设置errno。 

Note:
	每秒的时钟滴答数可以使用以下方法获得:

           sysconf(_SC_CLK_TCK);

	  在Linux上,可以将buf参数指定为NULL,其结果是times()仅返回函数结果。但是,POSIX未指定此行为,
	  并且大多数其他UNIX实现要求使用非NULL值来表示。
	  

	  请注意,clock(3)还会返回类型为clock_t的值,但是该值以CLOCKS_PER_SEC为单位进行度量,而不是
	  times()使用的时钟滴答。

	 在Linux上,用于测量times()的返回值的“过去的任意点”在不同的内核版本中有所不同。在Linux 2.4及更早
	 版本上,这一点是系统启动的时刻。从Linux 2.6开始,此点是(2 ^ 32 / HZ)-系统启动时间之前的300(即
	 约4.29亿)秒。跨内核版本(和跨UNIX实现)的这种可变性,加上返回值可能溢出clock_t范围的事实,这意味着
	 可移植的应用程序应避免使用该值。要测量经过时间的变化,请改用clock_gettime(2)。 
	  

数据类型clock_t 是用时钟计时单位(clock tick)为单位度量时间的整数值,习惯用于计算tms结构体的4个字段。我们可以调用sysconf(_SC_CLK_TCK)来获得每秒包含的时钟计时单元数,然后用这个数字除以clock_t转换为秒

times()的返回值仍然不可靠的,因为它可能会溢出clock_t的有效范围,这时times()的返回值将再次从 0 开始计算(也就是说,一个稍后的 times()的调用返回的数值可能会低于一个更早的 times()调用)。可靠的测量经过时间的方法是使用函数 gettimeofday()

在 Linux 上,我们可以指定 buf 参数为 NULL。在这种情况下,times()只是简单地返回一个函数结果。然而,这是没有意义的。 SUSv3 并未定义 buf 可以使用 NULL,因此许多其他UNIX 实现需要这个参数必须为一个非 NULL 值。

函数 clock()提供了一个简单的接口用于取得进程时间。它返回一个值描述了调用进程使用的总的 CPU 时间(包括用户和系统)。

NAME
       clock - determine processor time

SYNOPSIS
       #include <time.h>

       clock_t clock(void);

DESCRIPTION
       The clock()函数返回程序使用的处理器时间的近似值。.

RETURN VALUE
       返回的值是一个时钟所用的CPU时间;要获得使用的秒数,请除以每秒的时钟数。如果使用的处理器时间
       不可用或无法表示其值,则函数返回值(clock_t)-1

time()的返回值的计量单位是 CLOCKS_PER_SEC,所以我们必须除以这个值来获得进程所使用的 CPU 时间秒数。在 POSIX.1,CLOCKS_PER_SEC 是常量 10000,无论底层软件时钟(10.6 节)的分辨率是多少。clock()的精度最终仍然受限于软件时钟的分辨率

虽然 clock()和 times()返回相同的数据类型 clock_t,这两个接口使用的测量单位却并不相同。这是历史原因造成了 clock_t 定义的冲突,一个是 POSIX.1 标准,而另一个是 C 编程语言标准

#if ! defined(__sun)
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE
#endif
#endif
#include <time.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/times.h>
#include <time.h>
#include <zconf.h>


static void displayProcessTimes(const char *msg){
    struct tms t;
    clock_t clockTime;
    static long clockTicks = 0;

    if(msg != NULL){
        printf("%s", msg);
    }

    if (clockTicks == 0){
        clockTicks = sysconf(_SC_CLK_TCK);
        if (clockTicks == -1){
            perror("sysconf");
            exit(EXIT_FAILURE);
        }
    }

    clockTime = clock();
    if (clockTime == -1){
        perror("clock");
        exit(EXIT_FAILURE);
    }

    printf("        clock() returns: %ld clocks-per-sec (%.2f secs)\n",
           (long) clockTime, (double) clockTime / CLOCKS_PER_SEC);


    if (times(&t) == -1){
        perror("times");
        exit(EXIT_FAILURE);
    }
    printf("        times() yields: user CPU=%.2f; system CPU: %.2f\n",
           (double) t.tms_utime / clockTicks,
           (double) t.tms_stime / clockTicks);

}


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

    printf("CLOCKS_PER_SEC=%ld  sysconf(_SC_CLK_TCK)=%ld\n\n",
           (long) CLOCKS_PER_SEC, sysconf(_SC_CLK_TCK));

    //
    displayProcessTimes("At program start:\n");

    /* Call getppid() a large number of times, so that
       some user and system CPU time are consumed */

    numCalls = 100000000;
    for (j = 0; j < numCalls; j++)
        (void) getppid();

    displayProcessTimes("After getppid() loop:\n");

    exit(EXIT_SUCCESS);
}

在这里插入图片描述

函数封装

HTTP功能:将时间值转换成RFC1123所要求的格式

#include <iostream>


#define RFC1123_STRFTIME "%a, %d %b %Y %H:%M:%S GMT"
/**
 * 将时间值转换成RFC1123所要求的格式
 * @param buf {char*} 存储空间
 * @param size {size_t} buf 的空间大小
 * @param t {time_t} 时间值
 */
const char *http_mkrfc1123(char *buf, size_t size, time_t t)
{
#ifdef ACL_WINDOWS
    # if _MSC_VER >= 1500
	struct tm gmt_buf, *gmt = &gmt_buf;

	if (gmtime_s(gmt, &t) != 0)
		gmt = NULL;
# else
	struct tm *gmt = gmtime(&t);
# endif
#else
    struct tm gmt_buf, *gmt = gmtime_r(&t, &gmt_buf);
#endif

    buf[0] = 0;

    if (gmt != NULL)
        strftime(buf, size - 1, RFC1123_STRFTIME, gmt);
    return (buf);
}




int main() {
    time_t  t = time(NULL);;
    char  buf[128];


    buf[sizeof(buf) - 1] = '\0';

    (void) http_mkrfc1123(buf, sizeof(buf) - 1, t);

    printf("%s\n", buf);
    return 0;
}

将time_t转换为指定格式


using namespace std;

static const char* to_date(time_t t, string& out,
                           const char* fmt = "%Y-%m-%d %H:%M:%S"){
    char buf[256];

    if (fmt == NULL || *fmt == 0) {
        fmt = "%Y-%m-%d %H:%M:%S";
    }


    struct tm* local_ptr;

    struct tm local;

    if ((local_ptr = localtime_r(&t, &local)) == NULL) {
        logger_error("localtime_r failed, t: %ld", (long) t);
        return NULL;
    }

    if (strftime(buf, sizeof(buf), fmt, local_ptr) == 0) {
        logger_error("strftime failed, t: %ld, fmt: %s",
                     (long) t, fmt);
        return NULL;
    }

    out = buf;
    return out.c_str();
}
int main(int argc, char *argv[])
{
    time_t now = time(NULL);
    string buf;
    if (to_date(now, buf) == NULL)
        printf("to_date_time error, now: %ld\r\n", (long) now);
    else
        printf("to_date_time ok, now: %ld, %s\r\n",
               (long) now, buf.c_str());


    const char* date_fmt = "%Y-%m-%d";
    buf.clear();
    if (to_date(now, buf, date_fmt) == NULL)
        printf("to_date_time error, now: %ld\r\n", (long) now);
    else
        printf("to_date_time ok, now: %ld, %s\r\n",
               (long) now, buf.c_str());

    date_fmt = "%Y-%m";
    buf.clear();
    if (to_date(now, buf, date_fmt) == NULL)
        printf("to_date_time error, now: %ld, fmt: %s\r\n",
               (long) now, date_fmt);
    else
        printf("to_date_time ok, now: %ld, %s\r\n",
               (long) now, buf.c_str());
    return 0;
}

在这里插入图片描述

获取ms时间戳

std::time_t getTimeStamp()
{
    std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds> tp = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now());
    auto tmp = std::chrono::duration_cast<std::chrono::milliseconds>(tp.time_since_epoch());
    std::time_t timestamp = tmp.count();
    //std::time_t timestamp = std::chrono::system_clock::to_time_t(tp);
    return timestamp;
}

秒时间戳转换为标准时间格式

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <memory>
#include <ctime>
#include <iomanip>
#include <chrono>

using namespace std;

//时间戳转字符串(秒)
std::string Time_t2String(__time64_t stamp) {
    tm* stamp_tm = localtime(&stamp);
    std::ostringstream os;
    os << std::put_time(stamp_tm, "%Y:%m:%d %H:%M:%S");
    return os.str();
}
std::wstring Time_t2WString(__time64_t stamp) {
    tm* stamp_tm = localtime(&stamp);
    std::wostringstream os;
    os << std::put_time(stamp_tm, L"%Y:%m:%d %H:%M:%S");
    return os.str();
}
//字符串转时间戳
__time64_t String2Time_t(std::string time_string) {
    std::istringstream is(time_string);
    tm time_tm = {};
    is >> std::get_time(&time_tm, "%Y:%m:%d %H:%M:%S");

    return mktime(&time_tm);
}
__time64_t WString2Time_t(std::wstring time_string) {
    std::wistringstream is(time_string);
    tm time_tm = {};
    is >> std::get_time(&time_tm, L"%Y:%m:%d %H:%M:%S");

    return mktime(&time_tm);
}




int main11() {
    __time64_t cur_time_stamp = std::chrono::system_clock::to_time_t(
            std::chrono::system_clock::now());
    std::cout << cur_time_stamp <<"\t";
    std::string time_str = Time_t2String(cur_time_stamp);
    std::cout << time_str << std::endl;
    std::cout << String2Time_t(time_str) << std::endl;
    return 0;
}

毫秒时间戳转换为标准时间格式

#include <iostream>
#include <chrono>
#include <iomanip>
using namespace std;

std::string format_ms_time(long long ms, const std::string& fmt)
{
    auto milli = ms + (long long)8 * 60 * 60 * 1000; //此处转化为东八区北京时间,如果是其它时区需要按需求修改
    auto mTime = std::chrono::milliseconds(milli);
    auto tp = std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>(mTime);
    auto tt = std::chrono::system_clock::to_time_t(tp);
    std::tm *time_ptr = std::gmtime(&tt);
    std::ostringstream os;
    os << std::put_time(time_ptr, fmt.c_str());
    return os.str();
}


int main()
{
    auto t = 1669360130328;  // 2022:11:25 15:08:50
    auto time_str = format_time(t, "%Y:%m:%d %H:%M:%S");
    std::cout <<  time_str;
    return 0;
}

测试

#include <iostream>
#include <chrono>
#include <cstdio>
using namespace std;



std::tm *gettm(long long timestamp)
{
    auto milli = timestamp + (long long)8 * 60 * 60 * 1000; //此处转化为东八区北京时间,如果是其它时区需要按需求修改
    auto mTime = std::chrono::milliseconds(milli);
    auto tp = std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>(mTime);
    auto tt = std::chrono::system_clock::to_time_t(tp);
    std::tm *now = std::gmtime(&tt);
    printf("%4d年%02d月%02d日 %02d:%02d:%02d\n", now->tm_year + 1900, now->tm_mon + 1, now->tm_mday, now->tm_hour, now->tm_min, now->tm_sec);
    return now;
}


int main()
{
    char time_str[4][16];
    auto t = 1669359769093;  // 2022:11:25 15:02:49
    std::cout << "Millisecond timestamp is: " << t << std::endl;
    auto time_ptr = gettm(t);
    sprintf(time_str[0], "%02d", time_ptr->tm_mon + 1); //月份要加1
    sprintf(time_str[1], "%02d", time_ptr->tm_mday);//天
    sprintf(time_str[2], "%02d", time_ptr->tm_hour);//时
    sprintf(time_str[3], "%02d", time_ptr->tm_min);// 分
    for(int i = 0; i < 4; i++)
    {
        std::cout << "time_str[" << i << "] is: " << time_str[i] << std::endl;
    }

    std::ostringstream os;
    os << std::put_time(time_ptr, "%Y:%m:%d %H:%M:%S");
    std::cout <<  os.str();
}
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值