工学1号馆

home

strftime函数深入剖析

Wu Yudong    November 26, 2015     C   880   

strftime,是一种C库函数,strftime() 函数根据区域设置格式化本地时间/日期,函数的功能将时间格式化,或者说格式化一个时间字符串。

strftime在头文件<time.h>中包含,首先看一下time.h的源代码(取自minix2.0版):

/* The <time.h> header is used by the procedures that deal with time.
 * Handling time is surprisingly complicated, what with GMT, local time
 * and other factors.  Although the Bishop of Ussher (1581-1656) once
 * calculated that based on the Bible, the world began on 12 Oct. 4004 BC
 * at 9 o'clock in the morning, in the UNIX world time begins at midnight, 
 * 1 Jan. 1970 GMT.  Before that, all was NULL and (void).
 */

#ifndef _TIME_H
#define _TIME_H

#define CLOCKS_PER_SEC    60	/* MINIX always uses 60 Hz, even in Europe */

#ifdef _POSIX_SOURCE
#define CLK_TCK CLOCKS_PER_SEC	/* obsolescent name for CLOCKS_PER_SEC */
#endif

#define NULL    ((void *)0)

#ifndef _SIZE_T
#define _SIZE_T
typedef unsigned int size_t;
#endif

#ifndef _TIME_T
#define _TIME_T
typedef long time_t;		/* time in sec since 1 Jan 1970 0000 GMT */
#endif

#ifndef _CLOCK_T
#define _CLOCK_T
typedef long clock_t;		/* time in ticks since process started */
#endif

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 Saving Time flag */
};

extern char *tzname[];

/* Function Prototypes. */
#ifndef _ANSI_H
#include <ansi.h>
#endif

_PROTOTYPE( clock_t clock, (void)					);
_PROTOTYPE( double difftime, (time_t _time1, time_t _time0)		);
_PROTOTYPE( time_t mktime, (struct tm *_timeptr)			);
_PROTOTYPE( time_t time, (time_t *_timeptr)				);
_PROTOTYPE( char *asctime, (const struct tm *_timeptr)			);
_PROTOTYPE( char *ctime, (const time_t *_timer)			);
_PROTOTYPE( struct tm *gmtime, (const time_t *_timer)			);
_PROTOTYPE( struct tm *localtime, (const time_t *_timer)		);
_PROTOTYPE( size_t strftime, (char *_s, size_t _max, const char *_fmt,
				const struct tm *_timep)		);

#ifdef _POSIX_SOURCE
_PROTOTYPE( void tzset, (void)						);
#endif

#ifdef _MINIX
_PROTOTYPE( int stime, (time_t *_top)					);
#endif

#endif /* _TIME_H */

对上面的代码做几点解释:

1、typedef  long time_t;

这个time_t表示从 1970.01.01 00:00 (格林尼治时间) 开始到现在的秒数

2、typedef long clock_t;

clock_t表示进程开始到现在的滴答数

3、结构体tm

struct tm {
    int tm_sec; /* 秒 – 取值区间为[0,59] */
    int tm_min; /* 分 - 取值区间为[0,59] */
    int tm_hour; /* 时 - 取值区间为[0,23] */
    int tm_mday; /* 一个月中的日期 - 取值区间为[1,31] */
    int tm_mon; /* 月份(从一月开始,0代表一月) - 取值区间为[0,11] */
    int tm_year; /* 年份,其值等于实际年份减去1900 */
    int tm_wday; /* 星期 – 取值区间为[0,6],其中0代表星期天,1代表星期一,以此类推 */
    int tm_yday; /* 从每年的1月1日开始的天数 – 取值区间为[0,365],其中0代表1月1日,1代表1月2日,以此类推 */
    int tm_isdst; /* 夏令时标识符,实行夏令时的时候,tm_isdst为正。不实行夏令时的进候,tm_isdst为0;不了解情况时,tm_isdst为负。*/
};

下面具体看一下strftime函数的源代码(strftime.c):

/*
 * strftime - convert a structure to a string, controlled by an argument
 */
/* $Header: strftime.c,v 1.3 91/04/22 13:21:03 ceriel Exp $ */

#include	<time.h>
#include	"loc_time.h"

/* The width can be -1 in both s_prnt() as in u_prnt(). This
 * indicates that as many characters as needed should be printed.
 */
static char *
s_prnt(char *s, size_t maxsize, const char *str, int width)
{
	while (width > 0 || (width < 0 && *str)) {
		if (!maxsize) break;
		*s++ = *str++;
		maxsize--;
		width--;
	}
	return s;
}

static char *
u_prnt(char *s, size_t maxsize, unsigned val, int width)
{
	int c;

	c = val % 10;
	val = val / 10;
	if (--width > 0 || (width < 0 && val != 0))
		s = u_prnt(s, (maxsize ? maxsize - 1 : 0), val, width);
	if (maxsize) *s++ = c + '0';
	return s;
}

size_t
strftime(char *s, size_t maxsize,
		const char *format, const struct tm *timeptr)
{
	size_t n;
	char *firsts, *olds;

	if (!format) return 0;

	_tzset();	/* for %Z conversion */
	firsts = s;
	while (maxsize && *format) {
		while (maxsize && *format && *format != '%') {
			*s++ = *format++;
			maxsize--;
		}
		if (!maxsize || !*format) break;
		format++;

		olds = s;
		switch (*format++) {
		case 'a':
			s = s_prnt(s, maxsize,
					_days[timeptr->tm_wday], ABB_LEN);
			maxsize -= s - olds;
			break;
		case 'A':
			s = s_prnt(s, maxsize, _days[timeptr->tm_wday], -1);
			maxsize -= s - olds;
			break;
		case 'b':
			s = s_prnt(s, maxsize,
					_months[timeptr->tm_mon], ABB_LEN);
			maxsize -= s - olds;
			break;
		case 'B':
			s = s_prnt(s, maxsize, _months[timeptr->tm_mon], -1);
			maxsize -= s - olds;
			break;
		case 'c':
			n = strftime(s, maxsize,
					"%a %b %d %H:%M:%S %Y", timeptr);
			if (n) maxsize -= n;
			else maxsize = 0;
			s += n;
			break;
		case 'd':
			s = u_prnt(s, maxsize, timeptr->tm_mday, 2);
			maxsize -= s - olds;
			break;
		case 'H':
			s = u_prnt(s, maxsize, timeptr->tm_hour, 2);
			maxsize -= s - olds;
			break;
		case 'I':
			s = u_prnt(s, maxsize,
					(timeptr->tm_hour + 11) % 12 + 1, 2);
			maxsize -= s - olds;
			break;
		case 'j':
			s = u_prnt(s, maxsize, timeptr->tm_yday + 1, 3);
			maxsize -= s - olds;
			break;
		case 'm':
			s = u_prnt(s, maxsize, timeptr->tm_mon + 1, 2);
			maxsize -= s - olds;
			break;
		case 'M':
			s = u_prnt(s, maxsize, timeptr->tm_min, 2);
			maxsize -= s - olds;
			break;
		case 'p':
			s = s_prnt(s, maxsize,
				    (timeptr->tm_hour < 12) ? "AM" : "PM", 2);
			maxsize -= s - olds;
			break;
		case 'S':
			s = u_prnt(s, maxsize, timeptr->tm_sec, 2);
			maxsize -= s - olds;
			break;
		case 'U':
			s = u_prnt(s, maxsize,		/* ??? */
			    (timeptr->tm_yday + 7 - timeptr->tm_wday) / 7, 2);
			maxsize -= s - olds;
			break;
		case 'w':
			s = u_prnt(s, maxsize, timeptr->tm_wday, 1);
			maxsize -= s - olds;
			break;
		case 'W':
			s = u_prnt(s, maxsize,		/* ??? */
			    (timeptr->tm_yday+7-(timeptr->tm_wday+6)%7)/7,2);
			maxsize -= s - olds;
			break;
		case 'x':
			n = strftime(s, maxsize, "%a %b %d %Y", timeptr);
			if (n) maxsize -= n;
			else maxsize = 0;
			s += n;
			break;
		case 'X':
			n = strftime(s, maxsize, "%H:%M:%S", timeptr);
			if (n) maxsize -= n;
			else maxsize = 0;
			s += n;
			break;
		case 'y':
			s = u_prnt(s, maxsize, timeptr->tm_year % 100, 2);
			maxsize -= s - olds;
			break;
		case 'Y':
			s = u_prnt(s, maxsize, timeptr->tm_year + YEAR0, -1);
			maxsize -= s - olds;
			break;
		case 'Z':
			s = s_prnt(s, maxsize,
					_tzname[(timeptr->tm_isdst > 0)], -1);
			maxsize -= s - olds;
			break;
		case '%':
			*s++ = '%';
			maxsize--;
			break;
		default:
			/* A conversion error. Leave the loop. */
			while (*format) format++;
			break;
		}

	}
	if (maxsize) {
		*s = '\0';
		return s - firsts;
	}
	return 0;	/* The buffer is full */
}

有个函数_tzset()引起了我的注意,这个函数的源代码在misc.c中:

void
_tzset(void)
{
#if	defined(__BSD4_2)

	struct timeval tv;
	struct timezone tz;

	_gettimeofday(&tv, &tz);
	_daylight = tz.tz_dsttime;
	_timezone = tz.tz_minuteswest * 60L;

#elif	!defined(_POSIX_SOURCE) && !defined(__USG)

#if	!defined(_MINIX)		/* MINIX has no ftime() */
	struct timeb time;

	_ftime(&time);
	_timezone = time.timezone * 60L;
	_daylight = time.dstflag;
#endif

#endif	/* !_POSIX_SOURCE && !__USG */

	parseTZ(getenv("TZ"));		/* should go inside #if */

#if	defined(__USG) || defined(_POSIX_SOURCE)
	tzname[0] = _tzname[0];
	tzname[1] = _tzname[1];
#if	defined(__USG)
	timezone = _timezone;
	daylight = _daylight;
#endif
#endif	/* __USG || _POSIX_SOURCE */
}

参见linux文档:http://www.man7.org/linux/man-pages/man3/tzset.3.html

The tzset() function initializes the tzname variable from the TZ environment variable. This function is automatically called by the other time conversion functions that depend on the timezone. In a System-V-like environment, it will also set the variables timezone (seconds West of UTC) and daylight (to 0 if this timezone does not have any daylight saving time rules, or to nonzero if there is a time during the year when daylight saving time applies).

可见这个函数的主要是起到初始化时区环境的作用,经常配合其他函数一起使用。知道这个就可以了,太深入的话非得扯到POSIX等等

经过我注释后的代码如下:

/*
 * strftime - convert a structure to a string, controlled by an argument
 */
/* $Header: strftime.c,v 1.3 91/04/22 13:21:03 ceriel Exp $ */

#include	<time.h>
#include	"loc_time.h"

/* The width can be -1 in both s_prnt() as in u_prnt(). This
 * indicates that as many characters as needed should be printed.
 */

//将字符串str中的width个字符复制到maxsize字符串数组中
//前提:width <= strlen(str)
//if(maxsize >= width)全部复制
//else 复制maxsize个字符
static char *
s_prnt(char *s, size_t maxsize, const char *str, int width)
{
	while (width > 0 || (width < 0 && *str)) {
		if (!maxsize) break;
		*s++ = *str++;
		maxsize--;
		width--;
	}
	return s;
}

/*
val=256  width=5  maxsize=5
1  c=6  val=25 width=4  maxsize=4
2  c=5  val=2  width=3  maxsize=3
3  c=2  val=0  width=2  maxsize=2
4  c=0  val=0  width=1  maxsize=1
5  c=0  val=0  width=0  maxsize=0
*/
//运用递归,将val转化为宽度为width的字符
//val的位数<width,前面补0
static char *
u_prnt(char *s, size_t maxsize, unsigned val, int width)
{
	int c;

	c = val % 10; //保留val除法后的尾数
	val = val / 10; //保留商
	if (--width > 0 || (width < 0 && val != 0))
		s = u_prnt(s, (maxsize ? maxsize - 1 : 0), val, width);
	if (maxsize) *s++ = c + '0';
	return s;
}

//我们可以根据format指向字符串中格式命令把timeptr中保存的时间信息放在s指向的字符串中,
//最多向s中存放maxsize个字符。该函数返回向s指向的字符串中放置的字符数。
size_t
strftime(char *s, size_t maxsize,
		const char *format, const struct tm *timeptr)
{
	size_t n;
	char *firsts, *olds;

	if (!format) return 0; //format为空,不显示

	_tzset();	/* for %Z conversion */
	firsts = s;
	while (maxsize && *format) {
		while (maxsize && *format && *format != '%') {
			*s++ = *format++;
			maxsize--;
		}
		if (!maxsize || !*format) break; //当max减为0或format结束的时候退出
		format++; //确保上面的while循环是由于遇到format的%字符而退出

		olds = s;
		switch (*format++) { //遇到相关的字符做相应的处理
		case 'a':
			s = s_prnt(s, maxsize,
					_days[timeptr->tm_wday], ABB_LEN);
			maxsize -= s - olds;
			break;
		case 'A':
			s = s_prnt(s, maxsize, _days[timeptr->tm_wday], -1);
			maxsize -= s - olds;
			break;
		case 'b':
			s = s_prnt(s, maxsize,
					_months[timeptr->tm_mon], ABB_LEN);
			maxsize -= s - olds;
			break;
		case 'B':
			s = s_prnt(s, maxsize, _months[timeptr->tm_mon], -1);
			maxsize -= s - olds;
			break;
		case 'c':
			n = strftime(s, maxsize,
					"%a %b %d %H:%M:%S %Y", timeptr);
			if (n) maxsize -= n;
			else maxsize = 0;
			s += n;
			break;
		case 'd':
			s = u_prnt(s, maxsize, timeptr->tm_mday, 2);
			maxsize -= s - olds;
			break;
		case 'H':
			s = u_prnt(s, maxsize, timeptr->tm_hour, 2);
			maxsize -= s - olds;
			break;
		case 'I':
			s = u_prnt(s, maxsize,
					(timeptr->tm_hour + 11) % 12 + 1, 2);
			maxsize -= s - olds;
			break;
		case 'j':
			s = u_prnt(s, maxsize, timeptr->tm_yday + 1, 3);
			maxsize -= s - olds;
			break;
		case 'm':
			s = u_prnt(s, maxsize, timeptr->tm_mon + 1, 2);
			maxsize -= s - olds;
			break;
		case 'M':
			s = u_prnt(s, maxsize, timeptr->tm_min, 2);
			maxsize -= s - olds;
			break;
		case 'p':
			s = s_prnt(s, maxsize,
				    (timeptr->tm_hour < 12) ? "AM" : "PM", 2);
			maxsize -= s - olds;
			break;
		case 'S':
			s = u_prnt(s, maxsize, timeptr->tm_sec, 2);
			maxsize -= s - olds;
			break;
		case 'U':
			s = u_prnt(s, maxsize,		/* ??? */
			    (timeptr->tm_yday + 7 - timeptr->tm_wday) / 7, 2);
			maxsize -= s - olds;
			break;
		case 'w':
			s = u_prnt(s, maxsize, timeptr->tm_wday, 1);
			maxsize -= s - olds;
			break;
		case 'W':
			s = u_prnt(s, maxsize,		/* ??? */
			    (timeptr->tm_yday+7-(timeptr->tm_wday+6)%7)/7,2);
			maxsize -= s - olds;
			break;
		case 'x':
			n = strftime(s, maxsize, "%a %b %d %Y", timeptr);
			if (n) maxsize -= n;
			else maxsize = 0;
			s += n;
			break;
		case 'X':
			n = strftime(s, maxsize, "%H:%M:%S", timeptr);
			if (n) maxsize -= n;
			else maxsize = 0;
			s += n;
			break;
		case 'y':
			s = u_prnt(s, maxsize, timeptr->tm_year % 100, 2);
			maxsize -= s - olds;
			break;
		case 'Y':
			s = u_prnt(s, maxsize, timeptr->tm_year + YEAR0, -1);
			maxsize -= s - olds;
			break;
		case 'Z':
			s = s_prnt(s, maxsize,
					_tzname[(timeptr->tm_isdst > 0)], -1);
			maxsize -= s - olds;
			break;
		case '%':
			*s++ = '%';
			maxsize--;
			break;
		default:
			/* A conversion error. Leave the loop. */
			while (*format) format++;
			break;
		}

	}
	if (maxsize) {
		*s = '\0';
		return s - firsts;
	}
	return 0;	/* The buffer is full */
}
format相关格式如下:
%a 星期几的简写
%A 星期几的全称
%b 月份的简写
%B 月份的全称
%c 标准的日期的时间串
%C 年份的前两位数字
%d 十进制表示的每月的第几天
%D 月/天/年
%e 在两字符域中,十进制表示的每月的第几天
%F 年-月-日
%g 年份的后两位数字,使用基于周的年
%G 年份,使用基于周的年
%h 简写的月份名
%H 24小时制的小时
%I 12小时制的小时
%j 十进制表示的每年的第几天
%m 十进制表示的月份
%M 十时制表示的分钟数
%n 新行符
%p 本地的AM或PM的等价显示
%r 12小时的时间
%R 显示小时和分钟:hh:mm
%S 十进制的秒数
%t 水平制表符
%T 显示时分秒:hh:mm:ss
%u 每周的第几天,星期一为第一天 (值从1到7,星期一为1)
%U 第年的第几周,把星期日作为第一天(值从0到53)
%V 每年的第几周,使用基于周的年
%w 十进制表示的星期几(值从0到6,星期天为0)
%W 每年的第几周,把星期一做为第一天(值从0到53)
%x 标准的日期串
%X 标准的时间串
%y 不带世纪的十进制年份(值从0到99)
%Y 带世纪部分的十制年份
%z,%Z 时区名称,如果不能得到时区名称则返回空字符。
%% 百分号
///////////////////////////////////////////////////////////////////////////////////////////////
举个例子:
#include<stdio.h>
#include<time.h>

int main()
{ 
    time_t t;
	struct tm *pCurrentTime;
	char dateBuffer[32];
	t = time(NULL);
	pCurrentTime = localtime(&t);
	strftime(dateBuffer, sizeof(dateBuffer), "[%Y-%m-%d %X]", pCurrentTime);
	printf("%s\n", dateBuffer);
    return 0;
}

输出结果如下:

[2015-11-26 16:49:14]

如果文章对您有帮助,欢迎点击下方按钮打赏作者

Comments

非常详细


To verify that you are human, please fill in "七"(required)