如何找到当前系统时区?

时间:2010-06-25 13:58:52

标签: c++ c linux timezone

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

例如。我住在伦敦,所以我现在的奥尔森位置是“欧洲/伦敦”。我对时区ID感兴趣,比如“BST”,“EST”等等。

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

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

12 个答案:

答案 0 :(得分:24)

这是hard to get a reliable answer。依赖/etc/timezone之类的东西可能是最好的选择。

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

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

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

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

解决此问题的一种可能性(似乎适用于Debian,RedHat和OpenBSD)是将/ etc / localtime文件的内容与/ usr / share / zoneinfo下的文件进行比较,并查看哪些匹配:

GB

当然缺点是告诉你所有与当前时区相同的时区。 (这意味着完全相同 - 不仅仅是“当前同时”,而且“在系统知道的同一天,”总是在同一天改变它们的时钟。“

您最好的选择可能是结合上述方法:如果存在,请使用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解析为符号链接;如果失败,搜索匹配的时区定义文件;如果失败了 - 放弃回家; - )

(我不知道上述任何一个是否适用于AIX ......)

答案 1 :(得分:6)

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

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

答案 2 :(得分:5)

我一直致力于free, open source C++11/14 library,只需一行代码即可解决这个问题:

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

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

America/New_York

如果你download这个图书馆,并且它不起作用,欢迎bug reports

答案 3 :(得分: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 << '\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;

答案 4 :(得分:4)

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

  1. Ubuntu的。应该有/ etc / timezone文件。此文件应仅包含时区,而不包含任何其他内容。
  2. 红帽。应该有一个/ etc / sysconfig / clock,其中包含:ZONE =&#34; America / Chicago&#34;
  3. 此外,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;
    }
    

答案 5 :(得分:2)

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

答案 6 :(得分:2)

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

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

答案 7 :(得分:2)

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

答案 8 :(得分:0)

根据this页面,如果您#include <time.h>,它会显示以下内容。

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

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

答案 9 :(得分:0)

  

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

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

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

答案 10 :(得分:0)

由于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框所在的国家和城市。您可以根据需要序列化该数据,然后根据您可以找到的时区数据进行验证。

在没有用户干预的情况下,例如,作为程序安装的一部分,无法始终可靠地执行此操作。

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

答案 11 :(得分:0)

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

首先systemd标准化了/etc/timezoneDebian-style中的Olson时区名称。在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是否相同)是一个很好的后备。

如果你可以没有符号名称,parsing /etc/localtime tzfile尽可能便携,但要复杂得多。只读取最后一个字段会获得一个Posix时区(例如:CST5CDT,M3.2.0/0,M11.1.0/1),它可以与一些时间处理库互操作,但会丢弃一些元数据(没有历史转换信息)。