繁体   English   中英

如何找到当前的系统时区?

[英]How do I find the current system timezone?

在 Linux 上,我需要找到当前配置的时区作为奥尔森位置。 我希望我的(C 或 C++)代码可移植到尽可能多的 Linux 系统。

例如。 我住在伦敦,所以我目前的奥尔森位置是“欧洲/伦敦”。 我对诸如“BST”、“EST”之类的时区 ID感兴趣。

Debian 和 Ubuntu 有一个文件/etc/timezone包含此信息,但我认为我不能依赖该文件始终存在,可以吗? Gnome 有一个 function oobs_time_config_get_timezone()也返回正确的字符串,但我希望我的代码在没有 Gnome 的系统上工作。

那么,在 Linux 上,将当前配置的时区作为奥尔森位置的最佳通用方法是什么?

很难得到可靠的答案 依赖/etc/timezone类的东西可能是最好的选择。

struct tm的变量tznametm_zone成员,如其他答案中所建议的,通常包含缩写,例如GMT / BST等,而不是问题中请求的Olson时间字符串)。

  • 在基于Debian的系统(包括Ubuntu)上, /etc/timezone是一个包含正确答案的文件。
  • 在一些基于Redhat的系统(包括至少某些版本的CentOS,RHEL,Fedora)上,您可以使用/etc/localtime上的readlink()获取所需信息,这是(例如) /usr/share/zoneinfo/Europe/London的符号链接/usr/share/zoneinfo/Europe/London
  • OpenBSD似乎使用与RedHat相同的方案。

但是,上述方法存在一些问题。 /usr/share/zoneinfo目录还包含GMTGB等文件,因此用户可以将符号链接配置为指向那里。

此外,没有什么可以阻止用户在那里复制正确的时区文件而不是创建符号链接。

绕过它的一种可能性(似乎适用于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 ......)

这没有标准的 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 */
};

不完全是你要求的,但关闭......

我一直在开发一个免费的开源C ++ 11/14库 ,它只需一行代码即可解决这个问题:

std::cout << date::current_zone()->name() << '\n';

它适用于所有最新版本的Linux,macOS和Windows。 对我来说,这个程序输出:

America/New_York

如果您下载此库,但它不起作用,欢迎使用错误报告

当天晚些时候,我正在寻找类似的东西,发现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 << '\n';
  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;

我看到两个主要的linux案例:

  1. Ubuntu Linux系统。 应该有/ etc / timezone文件。 此文件应仅包含时区而不包含任何其他内容。
  2. 红色的帽子。 应该有/ 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.\n", tz);
  else
    printf("Unable to determine default timezone.\n");
  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, " \t\n", &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, " \t\n", &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;
}

我喜欢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

FWIW,RHEL / Fedora / CentOS有/etc/sysconfig/clock

ZONE="Europe/Brussels"
UTC=true
ARC=false

这里的代码适用于大多数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;
} 

调用tzset时,libc访问Olson数据库,之后使用简化的时区。 tzset查看TZ环境变量,然后回退到解析/etc/localtime的二进制数据。

首先, systemd标准化了在/etc/timezone中的Olson时区名称, Debian风格 在systemd 190和/ usr合并之后,systemd只读取和更新/etc/localtime ,并额外要求该文件是/usr/share/zoneinfo/${OLSON_NAME}的符号链接。

看看TZ ,那么readlink("/etc/localtime")是匹配libc的tzset逻辑并且仍然保留符号Olson名称的最可靠方法。 对于不遵循systemd符号链接约定的系统,读取/etc/timezone (并可能检查/usr/share/zoneinfo/$(</etc/timezone)/etc/localtime相同)是一个很好的回退。

如果你可以没有符号名称, 解析 /etc/localtime tz文件就像它可以携带一样,虽然复杂得多。 CST5CDT,M3.2.0/0,M11.1.0/1最后一个字段会得到一个Posix时区(例如: CST5CDT,M3.2.0/0,M11.1.0/1 ),它可以与一些时间处理库互操作,但会删除一些元数据(没有历史记录)转型信息)。

根据这个页面,看起来如果#include <time.h>它会声明如下。

void tzset (void);
extern char *tzname[2];
extern long timezone;
extern int daylight;

这能为您提供所需的信息吗?

在Linux上,我需要找到当前时区作为Olson位置。 我希望我的(C或C ++)代码可以移植到尽可能多的Linux系统。

如果您想要便携,那么在内部只使用GMT。 由于多用户信息,* NIX系统时钟通常是GMT并且没有系统范围的时区 - 因为连接到系统的不同用户可能生活在不同的时区。

用户特定时区反映在TZ环境变量中,您可能只需在将内部日期/时间转换为用户可读形式时使用该时区。 否则, localtime()会自动为您处理。

由于tzselect没有被任何人提及,你确实需要一个近乎防范的解决方案,与Olson所做的一起工作。 从elsie获取tzcode和tzdata文件,以及标签文件。

ftp://elsie.nci.nih.gov

2017年3月,正确的下载位置是ftp://ftp.iana.org/tz/releases (并下载tzcode2017a.tar.gztzdata2017a.tar.gz )。

然后从glibc下载获取tzselect.ksh。 然后你可以看到如何逆向工程时区。 一个关键点:您有时需要询问linux框所在的国家和城市。您可以根据需要序列化该数据,然后根据您可以找到的时区数据进行验证。

如果没有用户干预的可能性,例如,作为程序安装的一部分,则无法始终可靠地执行此操作。

总的来说,在亚利桑那州和印第安纳州西部都好运....希望你的代码能在其他地方运行。

下面的代码在 bash shell 上测试成功

  • SUSE Linux 企业服务器 11
  • Ubuntu 20.04.5 LTS
  • CentOS Linux 发布 7.9.2009

Shell 代码:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM