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

news2025/1/19 9:59:56

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.coloradmin.cn/o/1496279.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

视频扩散模型介绍 Video Diffusion Models Introduction

视频扩散模型介绍 Video Diffusion Models Introduction Diffusion 扩散模型中的一些概念DDPMDDIMCLIPLatent DiffusionStable DiifusionLoRADreamBoothControlNet 视频生成评估标准图片层面视频层面 前人的工作Make-A-VideoAlign your Latents 开源视频生成模型ModelScopeT2V&…

MATLAB KL变换

1. 原理 KL变换步骤&#xff1a; 1.求样本X的协方差矩阵R 2.求 R的特征值λ。选取前d个较大的特征值。 3.计算d个特征值对应的特征向量&#xff0c;归一化后构成变换矩阵U。 4.对{X}中每一个X进行K-L变换&#xff0c;得到变换后向量YU’ * X&#xff0c;d维向量Y就是…

UE4 Niagara 关卡3.4官方案例解析

Texture sampling is only supported on the GPU at the moment.(纹理采样目前仅在GPU上受支持) 效果&#xff1a;textures can be referenced within GPU particle systems。this demo maps a texture to a grid of particles&#xff08;纹理可以在GPU粒子系统中被引用这个演…

Android 恢复出厂设置后如何恢复短信?5 个值得尝试的方法

对于 Android 用户来说&#xff0c;安全问题、定制工作或软件问题等不可预见的情况可能会促使需要采取严厉措施&#xff1a;恢复出厂设置。这种重置虽然通常是必要的&#xff0c;但可能会导致重要数据&#xff08;包括短信&#xff09;的无意丢失。 当您面临恢复这些丢失消息的…

数据分析-Pandas数据分组箱线图

数据分析-Pandas数据分组箱线图 数据分析和处理中&#xff0c;难免会遇到各种数据&#xff0c;那么数据呈现怎样的规律呢&#xff1f;不管金融数据&#xff0c;风控数据&#xff0c;营销数据等等&#xff0c;莫不如此。如何通过图示展示数据的规律&#xff1f; 数据表&#x…

SpringBoot 多环境的配置(附截图)

文章目录 概要整体配置流程配置详细说明技术细节小结 概要 多环境开发 在实际项目开发中&#xff0c;一般需要针对不同的运行环境&#xff0c;如开发环境、测试环境、生产环境等&#xff0c;每个运行环境的数据库...等配置都不相同&#xff0c;每次发布测试、更新生产都需要手…

基于SpringBoot+Vue+ElementUI+Mybatis前后端分离管理系统超详细教程(一)

Vue.js 是一个流行的前端框架&#xff0c;用于构建用户界面和单页应用程序。Vue 2 是其第二个主要版本&#xff0c;它提供了数据绑定、组件化、虚拟DOM等核心特性。要搭建一个 Vue 2 的工程化项目&#xff0c;可以遵循以下步骤&#xff1a; 一、前端环境搭建 &#xff08;一&a…

【小黑嵌入式系统第十八课】结课总结(二)——软件部分(系统架构调试测试运行系统软件设计)

上一课&#xff1a; 【小黑嵌入式系统第十七课】结课总结&#xff08;一&#xff09;——硬件部分&#xff08;系统&总线&处理器&外设&通信&#xff09; 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分…

CKA考试必备:解锁Pod封装多容器的高级技巧!

往期精彩文章 : 提升CKA考试胜算&#xff1a;一文带你全面了解RBAC权限控制&#xff01;揭秘高效运维&#xff1a;如何用kubectl top命令实时监控K8s资源使用情况&#xff1f;CKA认证必备&#xff1a;掌握k8s网络策略的关键要点提高CKA认证成功率&#xff0c;CKA真题中的节点维…

Python 爬虫快速入门

1. 背景 最近在工作中有需要使用到爬虫的地方&#xff0c;需要根据 Gitlab Python 实现一套定时爬取数据的工具&#xff0c;所以借此机会&#xff0c;针对 Python 爬虫方面的知识进行了学习&#xff0c;也算 Python 爬虫入门了。 需要了解的知识点&#xff1a; Python 基础语…

three.js如何实现简易3D机房?(三)显示信息弹框/标签

接上一篇&#xff1a; three.js如何实现简易3D机房&#xff1f;(二&#xff09;模型加载的过渡动画&#xff1a;http://t.csdnimg.cn/onbWY 目录 七、创建信息展示弹框 1.整体思路 &#xff08;1&#xff09;需求&#xff1a; &#xff08;2&#xff09;思路&#xff1a;…

猜猜:哪句古诗与古代女子妆容有关?2024.3.8蚂蚁庄园今日答案:金盆水里拨红泥

蚂蚁庄园是一款爱心公益游戏&#xff0c;用户可以通过喂养小鸡&#xff0c;产生鸡蛋&#xff0c;并通过捐赠鸡蛋参与公益项目。用户每日完成答题就可以领取鸡饲料&#xff0c;使用鸡饲料喂鸡之后&#xff0c;会可以获得鸡蛋&#xff0c;可以通过鸡蛋来进行爱心捐赠。其中&#…

Java 中创建线程多种方式介绍

在 Java 中&#xff0c;创建线程有多种方式&#xff0c;以下是最常见的四种&#xff1a; 1. **通过继承 Thread 类** 2. **通过实现 Runnable 接口** 3. **通过实现 Callable 接口** 4. **通过使用 Executor 框架** 每种方式都有其特点和适用场…

ElasticSearch之通过search after和scroll解决深度分页问题

写在前面 通过from&#xff0c;size来进行分页查询时&#xff0c;如下&#xff1a; 当from比较大时会有深度分页问题&#xff0c;问题产生的核心是coordinate node需要从每个分片中获取fromsize条数据&#xff0c;当from比较大&#xff0c;整体需要获取的数据量也会比较大&am…

【Java_JSON】如何从JSON数据中提取value值

如何从JSON数据中提取value值&#xff1f; 首先将JSON数据转成字符串 创建JSONObject 对象 通过kv键值对的特性 使用key值来获取value 值 并输出 结果&#xff1a;

Redis(十七)分布式锁

文章目录 面试题分布式锁锁的种类分布式锁需要具备的条件和刚需分布式锁 案例nginx分布式微服务部署&#xff0c;单机锁问题分布式锁注意事项lock/unlocklua脚本自研版的redis分布式锁搞定lua脚本 可重入锁可重入锁种类可重入锁hset实现&#xff0c;对比setnx&#xff08;重要&…

Jmeter压测分配业务比例

在进行综合场景压测时&#xff0c;由于不同的请求&#xff0c;要求所占比例不同&#xff0c;如何实现呢&#xff1f; 不同的请求&#xff0c;服务器对其处理能力不同&#xff0c;有的处理快&#xff0c;有的处理慢。 真实模拟按比例进行并发&#xff1a; 在使用LR进行过类似…

在winform中如何嵌入第三方软件窗体✨

相关win32api的学习✨ SetParent [DllImport("user32.dll ", EntryPoint "SetParent")] private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); //将外部窗体嵌入程序语法&#xff1a; HWND SetParent([in] H…

windows关闭copilot预览版

如果用户不想在windows系统当中启用Copilot&#xff0c;可以通过以下三种方式禁用。 第一种&#xff1a;隐藏Copilot 按钮 右键点击任务栏&#xff0c;取消勾选“显示 Copilot&#xff08;预览版&#xff09;按钮”&#xff0c;任务栏则不再显示&#xff0c;用户可以通过快捷键…

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

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