时区格式说明和解析
(2017-12-10 11:46:51)
标签:
onvif时区posix1003.1正则表达式timezone |
最近调试onvif协议,涉及到了时区格式的解析,没想到还挺复杂,反复看了几遍终于看懂。主要是参考Posix 1003.1 Section 8.3,下面算是翻译过来,加上正则表达式。
TZ
代表时区信息,TZ环境变量的内容用于ctime、localtime、mktime等等。
TZ的两种形式
1.
该种形式以冒号开始,后面的字符处理与实现相关。linux上表示从某个文件读时区信息,例如TZ=":Pacific/Auckland"。
2.
不以冒号开始的格式都算此种格式,扩展开来如下,[]里的字段代表是可选的:
stdoffset[dst[offset][,startdate[/time],enddate[/time]]]
各字段的含义
std && dst
代表标准时区和日光节约制时区,std是必须的,dst是可选的,名字随意,有dst字段则代表支持夏令时。这两个字段有两种格式:
1.
2.
字段长度大于等于3,小于等于TZNAME_MAX(linux是6),如果字符串长度不符合规定,解析规则未定义(linux不识别)。
offset
代表本地时间加上多少能得到UTC时间。
格式为hh[:mm[:ss]]。
hh是必须的,并且可以是1位,mm和ss是可选的。
std后面的offset是必须的,dst后面的offset是可选的,如果没有,则默认比标准时间提前一小时。
0<=hh<=24,0<=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.
2.
3.
time
格式与offset相同,除了没有+和-,如果没带time字段,则默认为02:00:00。
Notes:
考虑到解析复杂度和客户端界面的一般设置,TZ采用第二种格式, std和dst采用第二种格式,date采用第三种格式。
上述格式的时区信息存放在/etc/TZ,必须以\n结束
tzset一般由依赖时区的时间函数自动调用,会从/etc/TZ去刷新几个全局变量:
/etc/TZ称为环境变量,/etc/localtime称为系统时区
如果/etc/TZ不存在,会尝试去从/etc/localtime去读
如果/etc/TZ存在但是不合法,则使用UTC时间
修改/etc/TZ后如果要使用上述全局变量,可立即调用一下tzset
时区解析
时区信息的规则虽然比较简单,但是格式不固定,很多字段都是可选的,如果单纯手工解析字符串是非常复杂的,下面尝试用正则表达式去解析,仍然是只考虑最常用的格式(TZ采用第二种格式,std和dst采用第二种格式,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"; |
这个字符串从格式上可以拆分成几个部分:std、dst、offset、time、date,此外包括一些特殊符号(+-,/),下面先分别定义这几部分的正则表达式,然后最终组合成stdoffset[dst[offset][,rule]]。
sdt&&dst
非常简单,3到6个字母,如下:
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; |
hourPattern:0到9;0或1开头,0到9的数字结尾;2开头,0到4的数字结尾。加起来是0~24。
minutePattern:6到9;0~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])"; |
接下来组合rule的pattern,格式为“,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, |
注意前后加了^和$,表示要完全匹配,否则匹配到一部分也算成功。
看一下最终的正则表达式什么样子,满足一下好奇宝宝。
^(([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) { } |