如何找到当前系统时区?

2024-10-30 08:35:00
admin
原创
47
摘要:问题描述:在 Linux 上,我需要找到当前配置的时区作为 Olson 位置。我希望我的 (C 或 C++) 代码能够移植到尽可能多的 Linux 系统。例如。我住在伦敦,所以我当前的 Olson 位置是“欧洲/伦敦”。我对“BST”、“EST”等时区 ID不感兴趣。Debian 和 Ubuntu 有一个包含...

问题描述:

在 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可能是最好的选择。

(正如其他答案所建议的那样,变量tznametm_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(例如localtimegmtime),则可以使用这些额外字段。当然,这仅当您使用 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 案例:

  1. Ubuntu。应该有一个 /etc/timezone 文件。此文件应该只包含时区,不包含任何其他内容。

  2. 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.gztzdata2017a.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
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用