新浪博客

时区格式说明和解析

2017-12-10 11:46阅读:
最近调试onvif协议,涉及到了时区格式的解析,没想到还挺复杂,反复看了几遍终于看懂。主要是参考Posix 1003.1 Section 8.3,下面算是翻译过来,加上正则表达式。
TZ
代表时区信息,TZ环境变量的内容用于ctimelocaltimemktime等等。
TZ的两种形式
1. :characters
该种形式以冒号开始,后面的字符处理与实现相关。linux上表示从某个文件读时区信息,例如TZ=':Pacific/Auckland'
2. std offset dst offset, rule
不以冒号开始的格式都算此种格式,扩展开来如下,[]里的字段代表是可选的:
stdoffset[dst[offset][,startdate[/time],enddate[/time]]]

各字段的含义
std && dst
代表标准时区和日光节约制时区,std是必须的,dst是可选的,名字随意,有dst字段则代表支持夏令时。这两个字段有两种格式:
1. 一种带引用符号<>,引用符号内的字符串可以是字母、数字、+-,解析时不包含引用符号<>
2. 另一种不带引用符号<>,字符串只能是字母。
字段长度大于等于3,小于等于TZNAME_MAXlinux6),如果字符串长度不符合规定,解析规则未定义(linux不识别)。

offset
代表本地时间加上多少能得到UTC时间。
格式为hh[:mm[:ss]]
hh是必须的,并且可以是1位,mmss是可选的。
std后面的offset是必须的,dst后面的offset是可选的,如果没有,则默认比标准时间提前一小时。
0<=hh<=240<=mm&&ss<=59
如果前面带了一个减号-,表示本初子午线以东,否则表示本初子午线以西,加号+可带可不带。可以理解为本地时间加上或减去多少时间才能得到UTC时间。

rule
表示什么时候开始夏令时,什么时候结束夏令时,协议没有提到怎么处理没有rule的情况,从linux系统来看,没有rule默认按M3.2.0/02:00:00,M11.1.0/02:00:00处理
格式为date[/time],date[/time]
date
有三种形式:
1. Jn1 <= n <= 365,不包括闰年的229日;
2. n0 <= n <= 365,包括闰年的229日;
3. Mm.n.dm表示哪个月份(1 <= m <= 12),n表示一个月的第几周(1 <= n <= 5),5代表最后一个,d表示一周的第几天(0 <= d <= 6,从周日开始)。
time
格式与offset相同,除了没有+-,如果没带time字段,则默认为02:00:00


Notes:
考虑到解析复杂度和客户端界面的一般设置,TZ采用第二种格式, stddst采用第二种格式,date采用第三种格式。

上述格式的时区信息存放在/etc/TZ,必须以结束
tzset一般由依赖时区的时间函数自动调用,会从/etc/TZ去刷新几个全局变量:
extern char *tzname[2];//stddst
extern long timezone;//带符号的偏移秒数
extern int daylight;//是否带dst
/etc/TZ称为环境变量,/etc/localtime称为系统时区
如果/etc/TZ不存在,会尝试去从/etc/localtime去读
如果/etc/TZ存在但是不合法,则使用UTC时间
修改/etc/TZ后如果要使用上述全局变量,可立即调用一下tzset


时区解析
时区信息的规则虽然比较简单,但是格式不固定,很多字段都是可选的,如果单纯手工解析字符串是非常复杂的,下面尝试用正则表达式去解析,仍然是只考虑最常用的格式(TZ采用第二种格式,stddst采用第二种格式,date采用第三种格式),正则表达式用的linux自带的libc中的接口。
首先给出一个完整的时区字符串,
const char *timezone = 'UTC-8:00:00DST-09:00:00,M3.2.0/02:00:00,M11.1.0/02:00:00';
这个字符串从格式上可以拆分成几个部分:stddstoffsettimedate,此外包括一些特殊符号(+-,/),下面先分别定义这几部分的正则表达式,然后最终组合成stdoffset[dst[offset][,rule]]
sdt&&dst
非常简单,36个字母,如下:
const char *stdPattern = '([a-zA-Z]{3,6})';
const char *dstPattern = stdPattern;
模式中的括号是为了后面组合复杂正则表达式方便,下同。
time
时间格式为hh[:mm[:ss]],可以只有1位,也可以是0x形式,并且分和秒前面肯定都有一个冒号,下面是小时和分秒的模式:
const char *hourPattern = '([0-9]|[01][0-9]|2[0-4])';
const char *minutePattern = '(:([6-9]|[0-5][0-9]?))';
const char *secondPattern = minutePattern;
hourPattern0901开头,09的数字结尾;2开头,04的数字结尾。加起来是0~24
minutePattern690~5开头,后面没有或者只有一个0~9的数字。加起来是0~59
secondPattern:同上。
分和秒的格式相同,两者都是可选的,所以总共有0~2个该模式,可以如下表示:
const char *minuteAndSecond = '(:([6-9]|[0-5][0-9]?)){0,2})';
再与小时的模式组合起来,最终时间的模式如下:
const char *timePattern = '(([0-9]|[01][0-9]|2[0-4])(:([6-9]|[0-5][0-9]?)){0,2})';
offset
offset只比time多一个符号
char offsetPattern[64] = {0};
sprintf(offsetPattern, '([+-]?%s)', timePattern);
date
日期的格式是固定的,比较简单:
const char* datePattern = '(M([1-9]|[1][0-2]).[1-5].[0-6])';
接下来组合rulepattern,格式为“,date[/time],date[/time]date必选,time可选:
sprintf(rulePattern, '(,%s(/%s)?,%s(/%s)?)', datePattern, timePattern, datePattern, timePattern);

综上,时区的正则表达式为:
char timezonePattern[512] = {0};
sprintf(timezonePattern, '^(%s%s(%s%s?%s?)?)$', sdtPattern, offsetPattern, dstPattern, offsetPattern, rulePattern);
注意前后加了^$,表示要完全匹配,否则匹配到一部分也算成功。
看一下最终的正则表达式什么样子,满足一下好奇宝宝。
^(([a-zA-Z]{3,6})([+-]?(([0-9]|[01][0-9]|2[0-4])(:([6-9]|[0-5][0-9]?)){0,2}))(([a-zA-Z]{3,6})([+-]?(([0-9]|[01][0-9]|2[0-4])(:([6-9]|[0-5][0-9]?)){0,2}))?(,(M([1-9]|[1][0-2]).[1-5].[0-6])(/(([0-9]|[01][0-9]|2[0-4])(:([6-9]|[0-5][0-9]?)){0,2}))?,(M([1-9]|[1][0-2]).[1-5].[0-6])(/(([0-9]|[01][0-9]|2[0-4])(:([6-9]|[0-5][0-9]?)){0,2}))?)?)?)$(鬼才能看懂)
上面有一些还可以简化,为了好理解才没有简化。

有了正则表达式就好说了,下面是完整的过程:
#include
#include
bool onvif_parse_timezone(const std::string& tzStr)
{
char sdtPattern[32] = {0};
sprintf(sdtPattern, '([a-zA-Z]{3,%d})', _POSIX_TZNAME_MAX);
const char *dstPattern = sdtPattern;
const char *timePattern = '(([0-9]|[01][0-9]|2[0-4])(:([6-9]|[0-5][0-9]?)){0,2})';
char offsetPattern[64] = {0};
sprintf(offsetPattern, '([+-]?%s)', timePattern);
const char* datePattern = '(M([1-9]|[1][0-2]).[1-5].[0-6])';
char rulePattern[256] = {0};
sprintf(rulePattern,
'(,%s(/%s)?,%s(/%s)?)',
datePattern,
timePattern,
datePattern,
timePattern);
char timezonePattern[512] = {0};
sprintf(timezonePattern,
'^(%s%s(%s%s?%s?)?)$',
sdtPattern,
offsetPattern,
dstPattern,
offsetPattern,
rulePattern);

//编译正则表达式
regex_t re;
int ret = regcomp(&re, timezonePattern, REG_EXTENDED);
if (ret)
{
printf('regcomp failed %d', ret);
return false;
}

//执行匹配
regmatch_t matched;
ret = regexec(&re, tzStr.c_str(), 1, &matched, 0);
if (ret)
{
regfree(&re);
printf('match failed ret=%d, tz: %s', ret, tzStr.c_str());
return false;
}

//查看匹配到的字符串是不是期望的结果,应该与入参一样
//char buf[64] = {0};
//int len = matched.rm_eo - matched.rm_so;
//memcpy(buf, tzString + matched.rm_so, len);
//buf[len] = '\0';
//printf('matched: %s', buf);

regfree(&re);
return true;
}

我的更多文章

下载客户端阅读体验更佳

APP专享