timezoneinfo的裁剪移植之uclibc/gclibc/openwrt的最详细实战版!

1.需求背景

因为项目需要,产品售卖到国外各个地区,需要能适配各个国家的不同时区,一些国家可能会有多个不同时区,并且还存在冬夏令时问题,都需要做到一次性的兼容。而目前板子上可用的flash空间也已经不足200KB,需要同时考虑对flash空间节省。

网上的资料并不齐全,这里完成后特地进行总结。

这里需要做到,例如收到Australia/Canberra后,需要得出对应时区的UTC偏移,然后更改系统时间,并且是需要适应冬夏令时的情况。
按照网上资料进行zoneinfo移植并设置时区后,发现时间并不对,后来查找资料发现uclibc和glibc对时区的使用有差异

1.对于uclibc,重要文件是/etc/TZ,实际连接到/tmp/TZ,修改时区后,会根据配置文件system中的timezone的option修改/tmp/TZ

2.对于glibc,重要文件是/etc/localtime,实际连接到/tmp/localtime,而/tmp/localtime也是连接文件,根据配置文件system中的zonename的option修改该连接

而我是uclibc,所以方法会更复杂些,glibc的话会更简单,因为在尝试的过程中都试了,所以一并总结下。

2.方案实现

最终逻辑为:交叉编译timezoneinfo数据库后进行tar.bz2的压缩,节约出flash空间,再放到目标板中,当拿到服务端传来的Asia/Shanghai后,解压数据库到内存中,完成时区信息匹配后,再将内存中的文件删除,达成目的。

数据库timezoneinfo其中记载了全球各个地区的时区信息,以及冬夏令时信息等。
其中有tzdata2024a.tar.gz和tzcode2024a.tar.gz,tzdata是时区信息的一个数据库,而tzcode是时区的命令以及用于生成数据库的工具,需要用其中的工具,在target上生成出时区数据库。下载地址https://www.iana.org/time-zones

2.1 下载源码、数据库

mkdir /tmp/zoneinfo
tar -vxf tzcode2024a.tar.gz -C /tmp/zoneinfo
tar -vxf tzdata2024a.tar.gz -C /tmp/zoneinfo

解压后大致内容如下:
image.png

2.2 编译源码

因为要移植到目标板上,所以先在虚拟机上make 并make install看下执行了哪些操作,再用交叉编译工具链编译,并安装到目标板上。

2.2.1 虚拟机上编译

make
make install

install时大概执行的内容如下:

make BACKWARD='backward'  DESTDIR=''  LEAPSECONDS=''  PACKRATDATA=''  PACKRATLIST=''  TZDEFAULT='/etc/localtime'  TZDIR='/usr/share/zoneinfo'  ZIC='./zic ' LEAPSECONDS= install_data
make[1]: Entering directory '/tmp/zoneinfo'
./zic  -d '/usr/share/zoneinfo'  tzdata.zi
make[1]: Leaving directory '/tmp/zoneinfo'
rm -fr '/usr/share/zoneinfo-posix'
ln -s 'zoneinfo' '/usr/share/zoneinfo-posix' || \
          make BACKWARD='backward'  DESTDIR=''  LEAPSECONDS=''  PACKRATDATA=''  PACKRATLIST=''  TZDEFAULT='/etc/localtime'  TZDIR='/usr/share/zoneinfo'  ZIC='./zic ' TZDIR='/usr/share/zoneinfo-posix' posix_only
make BACKWARD='backward'  DESTDIR=''  LEAPSECONDS=''  PACKRATDATA=''  PACKRATLIST=''  TZDEFAULT='/etc/localtime'  TZDIR='/usr/share/zoneinfo'  ZIC='./zic ' TZDIR='/usr/share/zoneinfo-leaps' right_only
make[1]: Entering directory '/tmp/zoneinfo'
make BACKWARD='backward'  DESTDIR=''  LEAPSECONDS=''  PACKRATDATA=''  PACKRATLIST=''  TZDEFAULT='/etc/localtime'  TZDIR='/usr/share/zoneinfo-leaps'  ZIC='./zic ' LEAPSECONDS='-L leapseconds' \
                install_data
make[2]: Entering directory '/tmp/zoneinfo'
./zic  -d '/usr/share/zoneinfo-leaps' -L leapseconds tzdata.zi
make[2]: Leaving directory '/tmp/zoneinfo'
make[1]: Leaving directory '/tmp/zoneinfo'
mkdir -p '/usr/bin' \
                '/usr/bin' '/usr/sbin' \
                '/usr/lib' \
                '/usr/share/man/man3' '/usr/share/man/man5' \
                '/usr/share/man/man8'
./zic  -d '/usr/share/zoneinfo'  -l Factory \
                `case '-' in ?*) echo '-p';; esac \
                ` - \
                -t '/etc/localtime'
cp -f iso3166.tab leapseconds tzdata.zi zone.tab zone1970.tab zonenow.tab '/usr/share/zoneinfo/.'
cp tzselect '/usr/bin/.'
cp zdump '/usr/bin/.'
cp zic '/usr/sbin/.'
cp libtz.a '/usr/lib/.'
: '/usr/lib/libtz.a'
cp -f newctime.3 newtzset.3 '/usr/share/man/man3/.'
cp -f tzfile.5 '/usr/share/man/man5/.'
cp -f tzselect.8 zdump.8 zic.8 '/usr/share/man/man8/.'

那么在移植到嵌入式板上的时候,也是直接模仿这个过程

直接虚拟机上测试下效果。

export TZDIR="/usr/share/zoneinfo"
export TZ="Australia/Canberra"
date -R

能看到时间显示为+11时区,修改成功
在这里插入图片描述

2.2.2 移植到开发板上

在目标机上创建两个文件夹,zoneinfo用来放源码文件,zoneinfo_data用来存放生成的时区数据库文件

cd /tmp
mkdir zoneinfo
mkdir zoneinfo_data

虚拟机上先make clean一下,然后重新开始编译

sudo make CC=/home/xzx/share/project_ipc/hm1002_in/code/openwrt2/staging_dir/toolchain-mipsel_24kec+dsp_gcc-11.2.0_uClibc-0.9.33.2/bin/mipsel-openwrt-linux-uclibc-gcc CFLAGS="-DHAVE_GETTEXT=0 -DHAVE_GETRANDOM=0"

-DHAVE_GETTEXT=0 -DHAVE_GETRANDOM=0 是为了解决编译报错,undefined reference to textdomain'以及undefined reference to dcgettext’问题。
编译通过后,将编译后的整个文件夹拷贝到目标板上的 /tmp/zoneinfo 上。

模仿虚拟机上的make install操作,进行时区数据库生成

./zic  -d '/tmp/zoneinfo_data/zoneinfo'  tzdata.zi
rm -fr '/tmp/zoneinfo_data/zoneinfo-posix'

ln -s '/tmp/zoneinfo_data/zoneinfo' '/tmp/zoneinfo_data/zoneinfo-posix'

./zic  -d '/tmp/zoneinfo_data/zoneinfo-leaps' -L leapseconds tzdata.zi

mkdir -p '/usr/bin' \
                '/usr/bin' '/usr/sbin' \
                '/usr/lib' \

./zic  -d '/tmp/zoneinfo_data/zoneinfo'  -l Factory \
                `case '-' in ?*) echo '-p';; esac \
                ` - \
                -t '/etc/localtime'

cp -f iso3166.tab leapseconds tzdata.zi zone.tab zone1970.tab zonenow.tab '/tmp/zoneinfo_data/zoneinfo/.'

# 这里先确认下自己的目标板环境中有没有/etc/localtime文件。
# 如果是uclibc+openwet的环境,没有/etc/localtime文件,下面的指令全部不需要执行了!!执行了也没有作用,反而会影响最终结果!
# 这里也是我自己踩了很久坑的地方!
rm /usr/bin/tzselect /usr/bin/zdump /usr/sbin/zic /usr/lib/libtz.a
ln -s /tmp/zoneinfo/tzselect '/usr/bin/.'
ln -s /tmp/zoneinfo/zdump '/usr/bin/.'
ln -s /tmp/zoneinfo/zic '/usr/sbin/.'
ln -s /tmp/zoneinfo/libtz.a '/usr/lib/.'
: '/usr/lib/libtz.a'

export TZDIR="/tmp/zoneinfo_data/zoneinfo"
export TZ="Asia/Shanghai"

如果是glibc环境,执行完上面的之后,输入date -R便能成功看到时间发生了变化,这里之后的操作便不需要继续执行了,成功完成了数据库的移植与使用!

而如果是uclibc+openwrt环境,不需要执行上面下部分的命令,通过vi /tmp/zoneinfo_data/zoneinfo/Australia/Canberra 能看到最后一行便是对应的POSIX时区信息,将他们设置到openwrt环境中。
image.png

设置为openwrt的系统时间:

uci set system.@system[0].timezone='AEST-10AEDT,M10.1.0,M4.1.0/3'
uci commit system
/etc/init.d/system restart

root@OpenWrt:/tmp/zoneinfo# date -R
Wed, 06 Mar 2024 19:25:57 +1100

可以看到系统时间发生了变化,并且时间正确

2.2.3 TZ的格式

TZ = local_timezone,date/time,date/time
local_timezone是时区名称,其后两个date/time分别表示DST变更时间点(即何时开始,何时结束),date格式为Mm.n.d(注:“M”是字符),其中m范围为1-12月份,如M3表示3月份;n范围为1-5,1表示一个月中第一周,5表示最后一周;d范围为0~6,0表示星期日,6表示星期六。time为hh:mm:ss的格式。

则AEST-10AEDT,M10.1.0,M4.1.0/3代表 AEST-10AEDT时区,从第十个月开始的第一周的星期天开始变更为夏令时,从第四个月的第一周的星期天3点钟结束

3.压缩数据库并使用

因为板子上的flash空间不足,所以决定以压缩包形式放入,待需要时再解压出来。如果没有这个需求的话,可以不用看。

1)数据库文件传回虚拟机进行压缩

scp * xzx@192.168.80.228:/tmp/zoneinfo_data/

这里我是在虚拟机上进行.tar.bz2压缩

tar -vcjf zoneinfo_data.tar.bz2 /tmp/zoneinfo_data

压缩前的zoneinfo_data大约2.8MB,压缩后的zoneinfo_data.tar.bz2在95KB左右

2)用程序解压压缩包,并设置时区
uclibc+openwrt可以参考一下代码

#include <stdio.h>
#include <bzlib.h>
#include <string>
#include <sys/ioctl.h>  
#include <sys/types.h>  
#include <stdarg.h>
#include <sys/time.h>
#include <signal.h>
#include<stdexcept>

bool linuxPopenExecCmd(std::string &strOutData, const char * pFormat, ...)
{
    char acBuff[128] ={0};

    va_list ap;
    va_start(ap, pFormat);
    vsprintf(acBuff, pFormat, ap);
    va_end(ap);

    try {
        FILE *pFile = popen(acBuff, "r");
        if (!pFile) {
            throw std::runtime_error("linuxPopenExecCmd popen() failed!");
        }

        char acValue[512] = {0};
        while (!feof(pFile)) {
            if (fgets(acValue, sizeof(acValue), pFile) != nullptr) {
                strOutData += acValue;
            }
        }
        pclose(pFile);
    }

    catch (const std::exception& e)
        {
            printf("popen :%s failed: %s", acBuff, e.what());
            return false;
        }
    return true;
}


int deCompress(const char *srcFile, const char *dstFile)
{
    int iRet = 0;
    FILE *fileInput = NULL;
    FILE *fileOutput = NULL;
    BZFILE *bzInput = NULL;

    int iBytesRead = 0;
    int iResult = 0;
    char acBuffer[1024] = {0};

    fileInput = fopen(srcFile, "rb");
    if (!fileInput) 
    {
        printf("error opening fileInput file\n");
        iRet = -1;
        goto exit;
    }

    fileOutput = fopen(dstFile, "wb");
    if (!fileOutput) 
    {
        printf("error opening fileOutput file\n");
        iRet = -1;
        goto exit;
    }

    bzInput = BZ2_bzReadOpen(NULL, fileInput, 0, 0, NULL, 0);
    if (!bzInput) 
    {
        printf("error read open file\n");
        iRet = -1;
        goto exit;
    }

    while (1) 
        {
            iBytesRead = BZ2_bzRead(&iResult, bzInput, acBuffer, sizeof(acBuffer));
            if (iBytesRead == 0)
            {
                printf("bz2 read successful\n");
                break;
            }
            if (iResult != BZ_OK && iResult != BZ_STREAM_END) 
            {
                printf("error reading bz2 data:%d\n", iResult);
                break;
            }
            fwrite(acBuffer, 1, iBytesRead, fileOutput);
        }

    BZ2_bzReadClose(&iResult, bzInput);
    if (iResult != BZ_OK) 
    {
        iRet = -1;
        goto exit;
    }

    printf("decompress successful. written to %s\n", dstFile);
    iRet = 0;
    exit:
    if (fileInput)
    {
        fclose(fileInput);
        fileInput = NULL;
    }
    if (fileOutput)
    {
        fclose(fileOutput);
        fileOutput = NULL;
    }
    return iRet;
}

int syncTimezone(const char *infoFile, const char *region)
{
    std::string strRegionPach;
    strRegionPach.append(infoFile);
    strRegionPach.append("/");
    strRegionPach.append(region);
    FILE* file = fopen(strRegionPach.c_str(), "r");

    if (file == NULL) 
    {
        printf("open failed: %s\n", strRegionPach.c_str());
        return -1;
    }
    printf("open success: %s\n", strRegionPach.c_str());

    char last_line[512] = {0};
    while (fgets(last_line, sizeof(last_line), file) != NULL) {
        printf("xxxxx line: %s\n", last_line);
        // 什么都不做,只需保留最后一行
    }

    fclose(file);

    printf("last line: %s\n", last_line);

    //uci set system.@system[0].timezone='AEST-10AEDT,M10.1.0,M4.1.0/3'
    std::string strValue;
    linuxPopenExecCmd(strValue, "uci set system.@system[0].timezone=%s", last_line);
	linuxPopenExecCmd(strValue, "uci commit system");
	linuxPopenExecCmd(strValue, "/etc/init.d/system restart");




	return 0;
}


int main(int argc, char** argv)
{
	int iRet = 0;
	const char *inputFile = "/usr/share/zoneinfo_data.tar.bz2";
	const char *deCompressFile = "/tmp/zoneinfo_data.tar";
	const char *outputFile = "/tmp/zoneinfo_data";

    iRet = deCompress(inputFile, deCompressFile);
	if (iRet < 0)
	{
		printf("decompress failed\n");
		return -1;
	}
	std::string strValue;
	linuxPopenExecCmd(strValue, "tar -vxf %s", deCompressFile);


	// syncTimezone(outputFile, "Australia/Canberra");
	syncTimezone(outputFile, "Asia/Shanghai");

	linuxPopenExecCmd(strValue, "rm -rf %s", deCompressFile);
	linuxPopenExecCmd(strValue, "rm -rf %s", outputFile);
	return 0;
}

这里我用的是bz2压缩,大家根据实际情况选择,替换代码即可,如果是glibc等使用/etc/localtime方式的话则最后是代码实现建立软连接到 /etc/localtime,例如ln -s /tmp/zoneinfo_data/zoneinfo/Australia/Canberra /etc/localtime


http://www.niftyadmin.cn/n/5411772.html

相关文章

Python与FPGA——sobel边缘检测

文章目录 前言一、sobel边缘检测二、Python sobel边缘检测三、FPGA sobel边缘检测总结 前言 边缘存在于目标、背景区域之间&#xff0c;它是图像分割所依赖的较重要的依据&#xff0c;也是图像匹配的重要特征。边缘检测在图像处理和计算机视觉中&#xff0c;尤其在图像的特征提…

YOLOv8基础必需运用【目标检测、分割、姿势估计、跟踪和分类任务】

文章目录 前言1、环境安装2.1安装torch相关库2.2 获取yolov8最新版本&#xff0c;并安装依赖 3. 如何使用模型用于各种CV任务方式一&#xff1a;命令行形式方式二&#xff1a;python代码形式示例3.1 目标检测任务实现代码运行结果检测视频代码 3.2 分割任务实现代码运行效果分割…

2024 年 AI 辅助研发趋势:从研发数字化到 AI + 开发工具 2.0,不止于 Copilot

在上一年里&#xff0c;已经有不少的企业在工具链上落地了生成式 AI&#xff0c;结合我们对于这些企业的分析&#xff0c;以及最近在国内的一些 “新技术” 趋势&#xff0c;诸如于鸿蒙原生应用的初步兴起。从这些案例与趋势中&#xff0c;我们也看到了一些新的可能方向。 结合…

ArmSoM规划开发基于RK3576的开发套件

ArmSoM正计划推出一款新的产品&#xff0c;这款产品将采用强大的RK3576芯片。 本文将为您介绍我们的新产品搭载的RK3576性能参数&#xff0c;以及它如何为您提供卓越的性能和功能。 RK3576处理器 RK3576处理器是一款强大的处理器&#xff0c;具备出色的性能和多样化的功能&a…

大模型交互-超拟人合成

1、超拟人合成&#xff1a;将文字转化为自然流畅的人声&#xff0c;在实时语音合成的基础上&#xff0c;精准模拟人类的副语言现象&#xff0c;如呼吸、叹气、语速变化等&#xff0c;使得语音不仅流畅自然&#xff0c;更富有情感和生命力。 2、唤醒的持久运行--->合成能力加…

数据库中on条件与where条件的区别

数据库中on条件与where条件的区别 标签:数据库 mysql> SELECT e.empno,ename,e.deptno,e.is_deleted FROM emp_test e ; ---------------------------------- | empno | ename | deptno | is_deleted | ---------------------------------- | 1 | 张三 | 1 | …

曲线曲面 - 连续性, 坐标变换矩阵

连续性 有两种&#xff1a;参数连续性&#xff08;Parametric Continuity&#xff09;、几何连续性&#xff08;Geometric Continuity&#xff09;参数连续性&#xff1a; 零阶参数连续性&#xff0c;记为&#xff0c;指相邻两段曲线在结合点处具有相同的坐标 一阶参数连续性&…

【React】react的生命周期

react的生命周期 一、新生命周期1、挂载阶段1.1 constructor&#xff08;1&#xff09;在React组件挂载之前被调用&#xff08;2&#xff09; 初始化函数内部 state或者在this上挂载方法 1.2 getDerivedStateFromProps&#xff08;1&#xff09;为静态方法&#xff0c;不能访问…