时间戳是一种用来表示日期和时间的数字格式,在不同的编程语言里时间戳的长度和单位都不一样:
C:以秒为单位,目前的时间戳是10位数。
Python:以秒为单位并且有精确到7位小数的毫秒,目前的时间戳整数部分是10位数,毫秒是7位小数。
JavaScript:以毫秒为单位,目前的时间戳是13位数。
虽然时间戳在计算机内部处理时间非常方便,但对于人类来说显得不直观。在日常编程工作中经常遇到需要将时间戳转换为日期时间格式方便,有以下好处:
1. 可读性:将时间戳转换为日期时间格式后,时间数据变得更容易理解。这对于用户界面、日志记录和数据可视化非常重要。
2. 数据处理:日期时间格式允许我们执行各种时间相关的操作,如排序、筛选和计算时间间隔。
3. 报告和分析:日期时间格式更容易传达时间信息,使数据更易于解释。
在C语言中使用 time.h 库来执行时间戳字符串到日期时间格式的转换。下面是演示代码,使用localtime函数将时间戳转换为 tm 结构,然后使用 sprintf 将其格式化为日期时间字符串。
char* timestamp_to_datetime(long long timestamp){
struct tm *tm;
time_t time_seconds;
tm = localtime(&time_seconds);
// 容错处理
if (tm == NULL) {
perror("localtime");
return NULL;
}
char* result=malloc(25);
sprintf(result, "%04d-%02d-%02d %02d:%02d:%02d",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
return result;
}
我的设想是:对于像JavaScript产生的13位数时间戳,先处理前10位数转换日期时间,然后把末尾的3位数作为毫秒数添加到日期时间里。小于13位数的时间戳则直接转换。因此需要判断时间戳字符串长度,该函数修改如下:
char* timestamp_to_datetime(long long timestamp){
int timestamp_length = snprintf(NULL, 0, "%lld", timestamp);
struct tm *tm;
time_t time_seconds;
// 如果时间戳长度为13,则取前10位数为时间戳
time_seconds = timestamp_length == 13 ? (time_t)(timestamp / 1000) : (time_t)(timestamp);
tm = localtime(&time_seconds);
// 容错处理
if (tm == NULL) {
perror("localtime");
return NULL;
}
// 时间戳的前10位数转换为日期和时间
char* result=malloc(25);
sprintf(result, "%04d-%02d-%02d %02d:%02d:%02d",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
// 如果时间戳长度为13,则将11~13位数作为毫秒数
if (timestamp_length == 13) {
char ms[5]; // Make sure to have space for the null terminator
sprintf(ms, ".%03lld", timestamp % 1000);
strcat(result, ms);
}
return result;
}
接下来是主程序的代码,接收命令行输入的第一个参数作为时间戳字符串:
int main(int argc, char *argv[]) {
long long timestamp;
char* result=malloc(25);
if (argc != 2) {
printf("\n时间戳转换成日期时间。\n\n使用方法:%s <时间戳>\n\n注:时间戳长度不应该超过13位数字。\n", argv[0]);
// 获取当前时间
time(×tamp);
printf("\n当前时间戳:%lld\n", timestamp);
result = timestamp_to_datetime(timestamp);
printf("时间戳转换为日期时间是:%s\n", result);
free(result);
return 1;
}
// 从命令行参数获取第一个参数作为时间戳字符串
char *timestamp_str = argv[1];
int timestamp_length = strlen(timestamp_str);
timestamp = atoll(timestamp_str);
printf("你输入的时间戳是:%lld\n长度为%d位数。\n", timestamp, timestamp_length);
result = timestamp_to_datetime(timestamp);
if (result != NULL){
printf("时间戳转换为日期时间是:%s\n", result);
free(result);
}
return 0;
}
程序基本上写好了。但这个代码存在多出bug:
Bug #1:输入参数的合法性
首先没有检查输入参数是否合法,如输入的字符串必须是数字才行,如果输入的参数是混有字母或其它符号,localtime 函数转换不了会直接报错退出程序。
在Python里有检查一个字符串是否全是数字的方法:string.isdigit()。该方法简单有效。C语言虽然也有 isdigit(),但是它只负责检查一个字符是否数字而不是判断字符串,而C语言的库里没有现成的函数检查一个字符串是否数字,所以只好手写一个:
// 检查字符串是否全是数字
int isStringAllDigits(const char *str) {
for (int i = 0; str[i] != '\0'; i++) {
if (!isdigit((unsigned char)str[i])) {
return 0; // 只要检测到非数字的字符串就直接返回0
}
}
return 1; // 全是数字的话返回1
}
或者写得高级一些,使用指针和while循环,更简练高效:
// 检查字符串是否全是数字,高级写法:
int isStringAllDigits(const char *str) {
while (*str)
if (!isdigit((unsigned char)(*str++)))
return 0;
return 1;
}
Bug #2:检查输入参数边界的有效性。
输入的时间戳字符串必须限制不超过13位数。
if (timestamp_length > 13) {
printf("长度不正确!注:时间戳长度不应该超过13位数字。\n\n");
return 1;
}
编译后运行测试,发现当时间戳是11、12位数大数字情况下,会返回 localtime 报错信息,数值越过边界了。
查阅相关文档(localtime、_localtime32、_localtime64 | Microsoft Learn),得知:localtime 返回的最大时间是 3000年12月31日 23:59:59,那么对应的时间戳是32535158399。
但是经过我调试,时间戳大于32535158399仍可以输出日期时间:
只要时间戳长度小于13,时间戳的最大值不能超过 32536799999,日期最大值可以到达3001年1月19日15:59:59。
因此需要加一段检查参数是否越过边界的代码:
if (timestamp>32536799999 && timestamp<1000000000000){
printf("注:%d位数的数值不能大于32536799999。\n你可以尝试输入13位数的时间戳。", timestamp_length);
return 1;
}
Bug #3:输入参数的确是数字,但却是以0开头的长串数字。
这个情况很容易被忽视,假如输入的是0开头的数字:001、0123456789,就需要先出去多余的0。
要先转换字符串,从头开始检查非零的位置,然后截取到末尾,最后转成数字吗?
没这么麻烦,C语言已提供一系列灵活的转换函数当中就有:atoll(),把字符串格式化成长整数类型变量:
timestamp = atoll(timestamp_str);
这下就把前面多余的0清除掉了。但有个问题:
假如输入的是很长的0开头的数字,如:00000000000009876543210,
timestamp变量经过 atoll(timestamp_str) 转换后是9876543210。
timestamp_str 字符串储存了“00000000000009876543210”,长度为23。
如果判断timestamp_str的长度是否不超过13,那按照上面代码的判断,将直接输出:长度超过13,程序退出。
所以应该判断 timestamp 的长度。
由于timestamp的类型是long long长整数型,不能直接使用 strlen(timestamp) 来获取其长度,只能另外声明一个临时字符串,比如 formatted_str 来存储 timestamp 转换成字符串,最后判断 formatted_str 的长度:
// 如果输入值为0开头,如:0000123456789,则必须先用atoll转成长数值,去除前面所有0
timestamp = atoll(timestamp_str);
// 去除前面多余的0之后,用snprintf格式化并存入formatted_str
char formatted_str[20];
snprintf(formatted_str, sizeof(formatted_str), "%llu", timestamp);
// 判断有效的时间戳字符串长度,不要拿timestamp_str而应该拿formatted_str来判断;
int timestamp_length = strlen(formatted_str);
printf("你输入的时间戳是:%lld\n长度为%d位数。\n", timestamp, timestamp_length);
if (timestamp_length > 13) {
printf("长度不正确!注:时间戳长度不应该超过13位数字。\n\n");
return 1;
}
这样一来,即使输入长长的数字也能妥善处理,不至于返回 localtime 的错误提示。
Bug #4:输出的代码页问题
C代码编译运行的.exe程序,默认以UTF-8格式输出文字。UTF-8对应的代码页为65001。
而Windows系统的命令行的默认格式是GBK,代码页为936。
上述的代码编译运行在Windows的命令行里,所有中文会显示成乱码。为了适应Windows的命令行,C代码里应该在主程序加一行设定代码页为UTF-8,以确保输出的文字正确显示。
#include
。。。
int main(int argc, char *argv[]) {
// 切换至UTF-8(65001)环境输出
if (GetConsoleOutputCP() != CP_UTF8)
SetConsoleOutputCP(CP_UTF8);
。。。
}
好了,经过上面检查输入合法性、检查变量边界,代码的bug基本上修复好了,下面是完整的代码。
#include
#include
#include
#include
#include
#include
// 检查字符串是否全是数字
int isStringAllDigits(const char *str) {
while (*str)
if (!isdigit((unsigned char)(*str++)))
return 0;
return 1;
}
char* timestamp_to_datetime(long long timestamp){
int timestamp_length = snprintf(NULL, 0, "%lld", timestamp);
struct tm *tm;
time_t time_seconds;
// 如果时间戳长度为13,则取前10位数为时间戳
time_seconds = timestamp_length == 13 ? (time_t)(timestamp / 1000) : (time_t)(timestamp);
tm = localtime(&time_seconds);
// 容错处理
if (tm == NULL) {
perror("localtime");
return NULL;
}
// 时间戳的前10位数转换为日期和时间
char* result=malloc(25);
sprintf(result, "%04d-%02d-%02d %02d:%02d:%02d",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
// 如果时间戳长度为13,则将11~13位数作为毫秒数
if (timestamp_length == 13) {
char ms[5];
sprintf(ms, ".%03lld", timestamp % 1000);
strcat(result, ms);
}
return result;
}
int main(int argc, char *argv[]) {
// 切换至UTF-8(65001)环境输出
if (GetConsoleOutputCP() != CP_UTF8) SetConsoleOutputCP(CP_UTF8);
long long timestamp;
char* result=malloc(25);
if (argc != 2) {
printf("\n时间戳转换成日期时间。\n\n使用方法:%s <时间戳>\n\n注:时间戳长度不应该超过13位数字。\n", argv[0]);
// 获取当前时间
time(×tamp);
printf("\n当前时间戳:%lld\n", timestamp);
result = timestamp_to_datetime(timestamp);
printf("时间戳转换为日期时间是:%s\n", result);
free(result);
return 1;
}
// 从命令行参数获取第一个参数作为时间戳字符串
char *timestamp_str = argv[1];
// 判断输入参数是否全是数字
if (!isStringAllDigits(timestamp_str)) {
printf("输入不合法。请输入由数字组成的时间戳。\n");
return 1;
}
// 如果输入值为0开头,如:0000123456789,则必须先用atoll转成长数值,去除前面所有0
timestamp = atoll(timestamp_str);
// 去除前面多余的0之后,用snprintf格式化并存入formatted_str
char formatted_str[20];
snprintf(formatted_str, sizeof(formatted_str), "%llu", timestamp);
// 判断有效的时间戳字符串长度,不要拿timestamp_str而应该拿formatted_str来判断;
int timestamp_length = strlen(formatted_str);
printf("你输入的时间戳是:%lld\n长度为%d位数。\n", timestamp, timestamp_length);
if (timestamp_length > 13) {
printf("长度不正确!注:时间戳长度不应该超过13位数字。\n\n");
return 1;
}
if (timestamp>32536799999 && timestamp<1000000000000){
printf("注:%d位数的数值不能大于32536799999。\n你可以尝试输入13位数的时间戳。\n", timestamp_length);
return 1;
}
result = timestamp_to_datetime(timestamp);
if (result != NULL){
printf("时间戳转换为日期时间是:%s\n", result);
free(result);
}
return 0;
}
运行结果截图: