# 工学1号馆

home

## strftime函数深入剖析

Wu Yudong    November 26, 2015     C   957

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;

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 - 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 */
}


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 */
}


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).

/*
* 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]