如何找到当前系统时区?
- 2024-10-30 08:35:00
- admin 原创
- 47
问题描述:
在 Linux 上,我需要找到当前配置的时区作为 Olson 位置。我希望我的 (C 或 C++) 代码能够移植到尽可能多的 Linux 系统。
例如。我住在伦敦,所以我当前的 Olson 位置是“欧洲/伦敦”。我对“BST”、“EST”等时区 ID不感兴趣。
Debian 和 Ubuntu 有一个包含此信息的文件/etc/timezone
,但我认为我不能依赖该文件始终存在,对吗?Gnome 有一个函数oobs_time_config_get_timezone()
也可以返回正确的字符串,但我希望我的代码可以在没有 Gnome 的系统上运行。
那么,在 Linux 上,将当前配置的时区作为 Olson 位置获取的最佳通用方法是什么?
解决方案 1:
很难得到可靠的答案。依靠诸如此类的东西/etc/timezone
可能是最好的选择。
(正如其他答案所建议的那样,变量tzname
和tm_zone
的成员struct tm
通常包含缩写,例如GMT
/BST
等,而不是问题中要求的 Olson 时间字符串)。
在基于 Debian 的系统(包括 Ubuntu)上,
/etc/timezone
有一个包含正确答案的文件。在一些基于 Redhat 的系统上(包括至少一些版本的 CentOS、RHEL、Fedora),您可以使用 上的 readlink() 获取所需的信息
/etc/localtime
,它是(例如) 的符号链接/usr/share/zoneinfo/Europe/London
。OpenBSD 似乎使用与 RedHat 相同的方案。
但是,上述方法存在一些问题。该/usr/share/zoneinfo
目录还包含诸如GMT
和 之类的文件GB
,因此用户可能会配置符号链接指向那里。
另外,没有什么可以阻止用户复制正确的时区文件而不是创建符号链接。
解决此问题的一种可能性(似乎在 Debian、RedHat 和 OpenBSD 上有效)是将 /etc/localtime 文件的内容与 /usr/share/zoneinfo 下的文件进行比较,看看哪些匹配:
eta:~% md5sum /etc/localtime
410c65079e6d14f4eedf50c19bd073f8 /etc/localtime
eta:~% find /usr/share/zoneinfo -type f | xargs md5sum | grep 410c65079e6d14f4eedf50c19bd073f8
410c65079e6d14f4eedf50c19bd073f8 /usr/share/zoneinfo/Europe/London
410c65079e6d14f4eedf50c19bd073f8 /usr/share/zoneinfo/Europe/Belfast
410c65079e6d14f4eedf50c19bd073f8 /usr/share/zoneinfo/Europe/Guernsey
410c65079e6d14f4eedf50c19bd073f8 /usr/share/zoneinfo/Europe/Jersey
410c65079e6d14f4eedf50c19bd073f8 /usr/share/zoneinfo/Europe/Isle_of_Man
...
...
当然,缺点是,这会告诉您所有与当前时区相同的时区。(这意味着完全意义上的相同 - 不仅仅是“当前同一时间”,还包括“就系统所知,始终在同一天更改时钟”。)
最好的选择可能是结合上述方法:/etc/timezone
如果存在则使用;否则尝试解析/etc/localtime
为符号链接;如果失败,则搜索匹配的时区定义文件;如果失败 - 放弃并回家;-)
(并且我不知道上述内容是否适用于 AIX......)
解决方案 2:
针对 C++20 的更新
这个 C++11/14/17 库逐渐发展成为标准提案,并被接受为 C++20 的一部分。您现在可以使用以下方法执行此操作:
#include <chrono>
#include <iostream>
int
main()
{
std::cout << std::chrono::current_zone()->name() << '
';
}
表达式std::chrono::current_zone()->name()
返回一个std::string
。
示例输出:
Europe/London
演示。
我一直在研究一个免费的开源 C++11/14 库,它用一行代码解决了这个问题:
std::cout << date::current_zone()->name() << '
';
它旨在跨所有最新版本的 Linux、macOS 和 Windows 进行移植。对我来说,这个程序输出:
America/New_York
如果您下载了此库,但它无法使用,欢迎报告错误。
解决方案 3:
没有标准的c 或 c++ 函数来实现这一点。但是,GNU libc 有一个扩展。它struct tm
有两个额外的成员:
long tm_gmtoff; /* Seconds east of UTC */
const char *tm_zone; /* Timezone abbreviation */
这意味着如果您使用填充 的函数之一struct tm
(例如localtime
或gmtime
),则可以使用这些额外字段。当然,这仅当您使用 GNU libc(以及它的足够新版本)时才有效。
此外,许多系统都有一个int gettimeofday(struct timeval *tv, struct timezone *tz);
函数(POSIX)可以填充struct timezone
。它具有以下字段:
struct timezone {
int tz_minuteswest; /* minutes west of Greenwich */
int tz_dsttime; /* type of DST correction */
};
不完全符合您的要求,但接近......
解决方案 4:
虽然已经很晚了,但我在寻找类似的东西,并发现 ICU 库可以获取 Olson 时区 ID:http ://userguide.icu-project.org/datetime/timezone
它现在安装在大多数 Linux 发行版上(安装 libicu-dev 包或同等版本)。代码:
#include <unicode/timezone.h>
#include <iostream>
using namespace U_ICU_NAMESPACE;
int main() {
TimeZone* tz = TimeZone::createDefault();
UnicodeString us;
tz->getID(us);
std::string s;
us.toUTF8String(s);
std::cout << "Current timezone ID: " << s << '
';
delete tz;
return 0;
}
并获取缩写/POSIX 时区名称(也适用于 Windows):
#include <time.h>
int main() {
time_t ts = 0;
struct tm t;
char buf[16];
::localtime_r(&ts, &t);
::strftime(buf, sizeof(buf), "%z", &t);
std::cout << "Current timezone (POSIX): " << buf << std::endl;
::strftime(buf, sizeof(buf), "%Z", &t);
std::cout << "Current timezone: " << buf << std::endl;
解决方案 5:
我看到两个主要的 Linux 案例:
Ubuntu。应该有一个 /etc/timezone 文件。此文件应该只包含时区,不包含任何其他内容。
Red Hat。应该有一个 /etc/sysconfig/clock 包含类似以下内容的内容:ZONE="America/Chicago"
此外,Solaris 应该有一个 /etc/TIMEZONE 文件,其中包含类似以下内容的行:TZ=US/Mountain
因此,基于上述内容,以下是一些我认为可以回答 OP 问题的纯 C。我已经在 Ubuntu、CentOS (Red Hat) 和 Solaris (额外奖励) 上对其进行了测试。
#include <string.h>
#include <strings.h>
#include <stdio.h>
char *findDefaultTZ(char *tz, size_t tzSize);
char *getValue(char *filename, char *tag, char *value, size_t valueSize);
int main(int argc, char **argv)
{
char tz[128];
if (findDefaultTZ(tz, sizeof(tz)))
printf("Default timezone is %s.
", tz);
else
printf("Unable to determine default timezone.
");
return 0;
}
char *findDefaultTZ(char *tz, size_t tzSize)
{
char *ret = NULL;
/* If there is an /etc/timezone file, then we expect it to contain
* nothing except the timezone. */
FILE *fd = fopen("/etc/timezone", "r"); /* Ubuntu. */
if (fd)
{
char buffer[128];
/* There should only be one line, in this case. */
while (fgets(buffer, sizeof(buffer), fd))
{
char *lasts = buffer;
/* We don't want a line feed on the end. */
char *tag = strtok_r(lasts, "
", &lasts);
/* Idiot check. */
if (tag && strlen(tag) > 0 && tag[0] != '#')
{
strncpy(tz, tag, tzSize);
ret = tz;
}
}
fclose(fd);
}
else if (getValue("/etc/sysconfig/clock", "ZONE", tz, tzSize)) /* Redhat. */
ret = tz;
else if (getValue("/etc/TIMEZONE", "TZ", tz, tzSize)) /* Solaris. */
ret = tz;
return ret;
}
/* Look for tag=someValue within filename. When found, return someValue
* in the provided value parameter up to valueSize in length. If someValue
* is enclosed in quotes, remove them. */
char *getValue(char *filename, char *tag, char *value, size_t valueSize)
{
char buffer[128], *lasts;
int foundTag = 0;
FILE *fd = fopen(filename, "r");
if (fd)
{
/* Process the file, line by line. */
while (fgets(buffer, sizeof(buffer), fd))
{
lasts = buffer;
/* Look for lines with tag=value. */
char *token = strtok_r(lasts, "=", &lasts);
/* Is this the tag we are looking for? */
if (token && !strcmp(token, tag))
{
/* Parse out the value. */
char *zone = strtok_r(lasts, "
", &lasts);
/* If everything looks good, copy it to our return var. */
if (zone && strlen(zone) > 0)
{
int i = 0;
int j = 0;
char quote = 0x00;
/* Rather than just simple copy, remove quotes while we copy. */
for (i = 0; i < strlen(zone) && i < valueSize - 1; i++)
{
/* Start quote. */
if (quote == 0x00 && zone[i] == '"')
quote = zone[i];
/* End quote. */
else if (quote != 0x00 && quote == zone[i])
quote = 0x00;
/* Copy bytes. */
else
{
value[j] = zone[i];
j++;
}
}
value[j] = 0x00;
foundTag = 1;
}
break;
}
}
fclose(fd);
}
if (foundTag)
return value;
return NULL;
}
解决方案 6:
仅供参考,RHEL/Fedora/CentOS 有/etc/sysconfig/clock
:
ZONE="Europe/Brussels"
UTC=true
ARC=false
解决方案 7:
我喜欢 psmears 的帖子,并实现了这个脚本来读取列表的第一个输出。当然,肯定有更优雅的方式来做到这一点,但这就是……
/**
* Returns the (Linux) server default timezone abbreviation
* To be used when no user is logged in (Ex.: batch job)
* Tested on Fedora 12
*
* @param void
* @return String (Timezone abbreviation Ex.: 'America/Sao_Paulo')
*/
public function getServerTimezone()
{
$shell = 'md5sum /etc/localtime';
$q = shell_exec($shell);
$shell = 'find /usr/share/zoneinfo -type f | xargs md5sum | grep ' . substr($q, 0, strpos($q, '/') - 2);
$q = shell_exec($shell);
$q = substr($q, strpos($q, 'info/') + 5, strpos($q, " "));
return substr($q, 0, strpos($q, chr(10)));
}
在我的巴西 Fedora 12 中,它返回:
巴西/东部
并且完全满足我的需要。
谢谢 psmears
解决方案 8:
这是适用于大多数 Linux 版本的代码。
#include <iostream>
#include <time.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
using namespace std;
void main()
{
char filename[256];
struct stat fstat;
int status;
status = lstat("/etc/localtime", &fstat);
if (S_ISLNK(fstat.st_mode))
{
cout << "/etc/localtime Is a link" << endl;
int nSize = readlink("/etc/localtime", filename, 256);
if (nSize > 0)
{
filename[nSize] = 0;
cout << " linked filename " << filename << endl;
cout << " Timezone " << filename + 20 << endl;
}
}
else if (S_ISREG(fstat.st_mode))
cout << "/etc/localtime Is a file" << endl;
}
解决方案 9:
根据此页面,看起来如果您#include <time.h>
将声明以下内容。
void tzset (void);
extern char *tzname[2];
extern long timezone;
extern int daylight;
这能给你提供你需要的信息吗?
解决方案 10:
在 Linux 上,我需要找到当前时区作为 Olson 位置。我希望我的 (C 或 C++) 代码能够移植到尽可能多的 Linux 系统。
如果您想要可移植,则仅在内部使用 GMT。由于多用户继承,*NIX 系统时钟通常采用 GMT,并且没有系统范围的时区 - 因为连接到系统的不同用户可能生活在不同的时区。
用户特定的时区反映在TZ
环境变量中,您可能仅在将内部日期/时间转换为用户可读格式时才需要使用它。否则,localtime()
系统会自动为您处理。
解决方案 11:
当调用 tzset时,libc 会访问 Olson 数据库,然后使用简化的时区。首先tzset
查看TZ
环境变量,然后回退到解析中的二进制数据/etc/localtime
。
最初systemd标准化了 Olson 时区名称/etc/timezone
,Debian 风格。在 systemd 190 和 /usr 合并之后,systemd 仅读取和更新/etc/localtime
,额外要求该文件是指向 的符号链接/usr/share/zoneinfo/${OLSON_NAME}
。
TZ
因此,查看readlink("/etc/localtime")
是匹配 libctzset
逻辑并保留 Olson 符号名称的最可靠方法。对于不遵循 systemd 符号链接约定的系统,读取/etc/timezone
(并可能检查 是否/usr/share/zoneinfo/$(</etc/timezone)
与 相同/etc/localtime
)是一种很好的后备方法。
如果您可以不使用符号名称,那么解析 tzfile就非常方便了,尽管要复杂得多。仅读取最后一个字段即可获得 Posix 时区(例如:),它可以与一些时间处理库进行互操作,但会删除一些元数据(没有历史转换信息)。/etc/localtime
CST5CDT,M3.2.0/0,M11.1.0/1
解决方案 12:
由于没有人提到 tzselect,而您确实需要一个几乎万无一失的解决方案,请按照 Olson 的做法操作。从 elsie 获取 tzcode 和 tzdata 文件,以及 tab 文件。
ftp://elsie.nci.nih.gov
2017 年 3 月,正确的下载位置是ftp://ftp.iana.org/tz/releases(以及下载tzcode2017a.tar.gz
和tzdata2017a.tar.gz
)。
然后从 glibc 下载中获取 tzselect.ksh。然后你可以看到如何对时区进行逆向工程。一个关键点:你有时必须询问 Linux 机器位于哪个国家和城市。如果需要,你可以序列化该数据,然后根据你可以找到的时区数据对其进行验证。
如果没有用户干预(例如作为程序安装的一部分),就无法始终可靠地执行此操作。
祝愿亚利桑那州和印第安纳州西部好运......希望你的代码能够在其他地方运行。
解决方案 13:
下面的代码在 bash shell 中测试成功
SUSE Linux Enterprise Server 11
Ubuntu 20.04.5 LTS
CentOS Linux 版本 7.9.2009
外壳代码:
echo $(hash=$(md5sum /etc/localtime | cut -d " " -f 1) ; find /usr/share/zoneinfo -type f -print0 | while read -r -d '' f; do md5sum "$f" | grep "$hash" && break ; done) | rev | cut -d "/" -f 2,1 | rev
结果示例:
Europe/Vienna
更短,但在 SLES 11 上不起作用:
readlink /etc/localtime | rev | cut -d "/" -f 2,1 | rev
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件