加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

时区格式说明和解析

(2017-12-10 11:46:51)
标签:

onvif

时区

posix1003.1

正则表达式

timezone

最近调试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 <= <= 365,包括闰年的229日;

3.         Mm.n.dm表示哪个月份(1 <= m <= 12),n表示一个月的第几周(1 <= n <= 5),5代表最后一个,d表示一周的第几天(0 <= <= 6,从周日开始)。

time

格式与offset相同,除了没有+-,如果没带time字段,则默认为02:00:00

 

 

Notes:

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

 

上述格式的时区信息存放在/etc/TZ,必须以\n结束

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\n", 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\n", 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\n\n", buf);

 

         regfree(&re);

         return true;

}

 

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有