首页 > 编程 > 软件的I18N问题

软件的I18N问题

今天读到byte写的一篇blog,记录了一下vc当中的时间表达方式,随手回复了一篇,当敲入mktime的时候,突然心中一震,有一种误人子弟的感觉,因为想到了之前一个项目当中对于时间处理的问题的切肤之痛。

当时那个问题是这样的:程序会调用time函数得到time_t(这个是GMT时间距离1970年1月1日的秒数),然后用localtime把它转换成可读的struct tm格式,即年月日时分秒的格式,然后根据它来创建文件夹,比如我现在时区是GMT+8,即北京时间,凌晨3点32分,程序就会创建一个名为20070617\0332的文件夹,然后把一些东西放在里面。过一会之后,程序会去枚举所有的文件夹然后对其中的东西进行处理,枚举的时候,程序会根据这个目录的字符串来切开做成一个struct tm格式,然后调用mktime去转换成time_t进行进一步的处理。程序会传递这个time_t,并在必要的时候重新根据它来找到文件所在的目录。简单的逻辑可以这样认为:

time_t now = time(NULL);
struct tm* tnow = localtime(now);
sprintf(szPath, "%4d%02d%02d\\%02d%02d",
tm->tm_year+1900, tm->tm_mon+1, tm->mday,
tm->tm_hour, tm->tm_min);
CreateDirectory(szPath);

// later
// find each directory to szPath, and get year, month, day, hour, min from the szPath.
// Then set struct tm dirTm;

time_t dirTime = mktime(&dirTm);
ProcessThem(dirTime);

void ProcessThem(time_t dirTime) {
struct tm* dirTm = localtime(dirTime);
szPath = getPathFromTime(dirTm);
// open the file
}

这个逻辑在中国的区域设置下面毫无问题,一切都如预想的一样运行。但是这个产品还需要被卖到美国,区域设置为太平洋标准时间的地方。那个地方有一种东西叫做夏令时,Daylight Saving Time, DST。每年的4月的第一个周日(现在已经是3月的第二个周日了,这是今年的新规定,所谓的DSTE,为此还有更多的I18N的麻烦,有机会再说),当种走到凌晨1:59:59过后,并不是正常的跳到2:00:00,而是变成3:00:00。也就是说,钟快了一个小时。到了10月的最后一个星期天(现在是11月的第一个星期天)的钟表指到1:59:59之后,钟会跳到1:00:00,也就把钟调了回来。

于是产品在进入夏令时之后出了问题,日志当中报告了大量的“找不到文件”错误。经过分析发现,在上面的逻辑当中,在夏令时切换之后,比如钟表时间3:01:00(实际上物理时间是2:01:00),第一个localtime的调用创建了一个名为20060402\0301的文件夹,之后根据这个路径初始化struct tm dirTm,但是struct tm当中的tm_idst域并没有赋值(也就是0),所以调用mktime的时候,这个函数会把struct tm结构当中的值当作非夏令时,这样转换回的time_t就指到了物理时间的3:01:00,之后再次调用localtime,获取的struct tm当中,tm_hour变成了4。这样一来,程序试图去20060402\0401下面去寻找文件,这就导致了大量的找不到文件的错误。

解决的方法有两个,一个是初始化struct tm dirTm的时候,将tm_idst设成负数,例如-1,这样mktime会根据当前时区来决定是否根据夏令时进行校对。另一种方法是完全使用GMT函数替换本地时间函数。例如localtime->gmtime,mktime->_mkgmtime。

这个问题本质上就是一个软件的I18N问题。I18N=Internationalization,即国际化,因为I和N中间有18个字符而得名,类似的还有L10N(Localization,本地化),以及G11N(Globalization,全球化)。软件的I18N问题之所以成为一个问题,是因为这个世界上有接近200个国家和地区,2000多个民族,4200多种语言,以及,地球是圆的。

应该庆幸我们生活在中国,因为中国的很多东西都很简单,尤其是牵涉到软件I18N的时候。没错,可以认为中国有世界上最大的字符集,最复杂的文字表达方式,但是我们的时区是GMT+8,没有零头,没有夏令时;中文的编码GB2312å’ŒGB18030都是上下文无关的编码,而且广泛使用的GB2312还是定长的编码(两个字节一个汉字),而且任何字节不会和7位ASCII码重叠;我们使用格列高里历法,使用公元纪年;我们的默认键盘布局是标准101é”®qwerty键盘;我们的文字像世界上绝大多数文字一样从左向右横向书写……所有这一切对于写软件的人来说是这是多么美好的一件事情啊。我们可以获取当前的时间,存储其年月日时分秒,将来无论什么时候拿到这个年月日时分秒,都不会有什么问题;我们可以在一个中文编码的字符串当中肆意地用strchr查找’\\’来切割文件路径,而不用担心0x5C在当前的上下文当中是表示反斜线还是日元符号(在日文编码shiftJIS当中,日元符号的编码和ASCII当中反斜线编码重叠,都是0x5C,这种问题在GB系列编码中不会存在);还有,我们的小数点就是圆点,负数就是在数字前面加上负号,而不是用逗号表示小数点(德文地区是这样的),而且不会用括号表示负数(欧洲某地如此,不记得哪国了)。

但是这样也会让我们中国的软件工程师低估了软件I18N的难度:不是所有国家的人们的时间都是连续的,很多国家在使用夏令时;很多国家或地区不使用公元纪年,比如日本和台湾;还有些地方不使用格列高里历法,例如中东的很多国家。说起时间,时区也不一定总是东八区,西五区这么简单。印度大部分地区都是GMT+5.5,尼泊尔是GMT+5.75;太平洋上某岛国(TONGA)是GMT+13,这是全球最早的时间;中东某小国由于宗教原因时区是GMT-89,也许是全球最晚的时间了。可以看看这张全球时区划分图来看看有多少诡异的时区吧(这张图有些老,98年的,所以很多诡异的都还没有,不过已经够多了)。

而谈及语言编码,那又有了更多的问题,现在太晚了,有空再写。

标签:
  1. 益荣 杨
    2007年6月17日04:35 | #1
    恩,的确是一个问题,现在知道windows中简单一个小小的时钟的功能,开发和测试起来也是多么复杂啊。
  2. 永涛 边
    2007年6月17日04:35 | #2
    hehe,幸好我现在写的东西只是消遣,还没有I18N的问题。
  1. 本文目前尚无任何 trackbacks 和 pingbacks.