代码之家  ›  专栏  ›  技术社区  ›  alex tingle

如何找到当前系统时区?

  •  22
  • alex tingle  · 技术社区  · 14 年前

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

    例如。我住在伦敦,所以我现在的奥尔森地点是“欧洲/伦敦”。我是 interested in timezone IDs like "BST", "EST" or whatever.

    Debian和Ubuntu有一个文件 /etc/timezone 它包含了这些信息,但我认为我不能总是依赖那个文件,对吗?gnome有一个函数 oobs_time_config_get_timezone() 它还返回正确的字符串,但我希望我的代码在没有GNOME的系统上工作。

    那么,在Linux上,获得当前配置为olson位置的时区的最佳一般方法是什么?

    12 回复  |  直到 7 年前
        1
  •  23
  •   Loïc Faure-Lacroix    10 年前

    它是 hard to get a reliable answer . 依靠像这样的东西 /etc/timezone 可能是最好的选择。

    (变量 tzname 以及 tm_zone 成员 struct tm 如其他答案中建议的,通常包含缩写词,如 GMT / BST 而不是问题中要求的olson时间字符串)。

    • 在基于Debian的系统(包括Ubuntu)上, /ETC /时区 是包含正确答案的文件。
    • 在一些基于RedHat的系统(至少包括CentOS、Rhel、Fedora的某些版本)上,可以使用readLink()在 /etc/localtime ,这是到的符号链接(例如) /usr/share/zoneinfo/Europe/London .
    • OpenBSD似乎使用与RedHat相同的方案。

    However, there are some issues with the above approaches. 这个 /usr/share/zoneinfo 目录还包含诸如 格林尼治时间 GB 因此,用户可能会将symlink配置为指向那里。

    Also there's nothing to stop the user copying the right timezone file there instead of creating a symlink.

    One possibility to get round this (which seems to work on Debian, RedHat and OpenBSD) is to compare the contents of the /etc/localtime file to the files under /usr/share/zoneinfo, and see which ones match:

    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
    ...
    ...
    

    当然了 缺点 is that this will tell you all timezones that are identical to the current one. (That means identical in the full sense - not just "currently at the same time", but also "always change their clocks on the same day as far as the system knows".)

    您的最佳选择可能是结合上述方法:使用 /ETC /时区 如果它存在;否则尝试解析 /ETC/LoalTimes 作为符号链接;如果失败,搜索匹配的时区定义文件;如果失败-放弃并回家;-)

    (And I have no idea whether any of the above applies on AIX...)

        2
  •  5
  •   Evan Teran    14 年前

    没有 标准 C或C++函数用于此。然而,GNU LIBC有一个扩展。它的 struct tm 有两个额外成员:

    long tm_gmtoff;           /* Seconds east of UTC */
    const char *tm_zone;      /* Timezone abbreviation */
    

    这意味着如果使用一个函数来填充 将时间 (如 localtime gmtime )您可以使用这些额外的字段。当然,只有在使用gnu libc(以及它的最新版本)的情况下,才需要这样做。

    Also many systems have a 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 */
    };
    

    不完全是你要的,但接近…

        3
  •  5
  •   Howard Hinnant    8 年前

    I've been working on a free, open source C++11/14 library 它用一行代码来解决这个问题:

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

    It is meant to be portable across all recent flavors of Linux, macOS and Windows. For me this program outputs:

    America/New_York
    

    如果你 download 这个图书馆不适合你, bug reports 欢迎光临。

        4
  •  4
  •   sumwale    10 年前

    Pretty late in the day, but I was looking for something similar and found that ICU library has the provision to get the Olson timezone 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;
    
        5
  •  4
  •   Duane McCully    9 年前

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

    1. Ubuntu。There should be a /etc/timezone file. 此文件只应包含时区,而不应包含其他内容。
    2. 红帽子。There should be a /etc/sysconfig/clock that contains something like: ZONE="America/Chicago"

    此外,Solaris应该有一个/etc/timezone文件,其中包含一行,如:tz=us/mountain

    基于以上,这里有一些直的C,我相信回答了OP的问题。我在Ubuntu、Centos(Red Hat)和Solaris(Bonus)上测试过它。

    #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;
    }
    
        6
  •  2
  •   Almir de Oliveira Duarte Jr.    13 年前

    I liked the post made by psmears and implemented this script to read the first output of the list. Of course there must have more elegant ways of doing this, but there you are...

        /**
         * 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)));
    
        }
    

    在我的巴西软呢帽12中,它返回:
    巴西/ East

    做我所需要的。

    谢谢你,psmears

        7
  •  2
  •   Serge Wautier    13 年前

    FWIW,Rhel/Fedora/Centos已经 /etc/sysconfig/clock :

    ZONE="Europe/Brussels"
    UTC=true
    ARC=false
    
        8
  •  2
  •   user361446    12 年前

    这是适用于大多数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
  •  0
  •   Evan Teran    14 年前

    根据 this 佩奇,如果你 #include <time.h> 它将声明以下内容。

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

    这能给你所需要的信息吗?

        10
  •  0
  •   Tobu    10 年前

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

    如果您想便携式,那么只能在内部使用gmt。由于多用户继承,*nix系统时钟通常是格林尼治标准时间,没有系统范围的时区-因为连接到系统的不同用户可能生活在不同的时区。

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

        11
  •  0
  •   Tobu    10 年前

    libc在以下情况下访问olson数据库: tzset is called, and uses simplified time zones afterwards. tzset 看着 TZ 首先是环境变量,然后返回到分析中的二进制数据 /etc/localtime .

    起初 systemd /etc/timezone , Debian-style . After systemd 190 and the /usr merge, systemd only reads and updates /ETC/LoalTimes /usr/share/zoneinfo/${OLSON_NAME} .

    看着 然后 readlink("/etc/localtime") , is the most reliable way to match the libc's TZSET logic and still keep symbolic Olson names. For systems that don't follow the systemd symlink convention, reading /ETC /时区 /usr/share/zoneinfo/$(</etc/timezone) 是一样的 /ETC/LoalTimes

    如果你可以生活在没有象征性的名字下, parsing 这个 /ETC/LoalTimes tzfile is as portable as it gets, though a lot more complex. Reading just the last field gets you a Posix time zone (for example: CST5CDT,M3.2.0/0,M11.1.0/1 ), which can interoperate with a few time-handling libraries, but drops some of the metadata (no historical transition info).

        12
  •  0
  •   Jonathan Leffler Toon Krijthe    7 年前

    Since tzselect was not mentioned by anyone and you do need a nearly goof-proof solution, work with what Olson did. Get the tzcode and tzdata files from elsie, plus tab files.

    ftp://elsie.nci.nih.gov

    In March 2017, the correct location to download from would be ftp://ftp.iana.org/tz/releases (下载) tzcode2017a.tar.gz tzdata2017a.tar.gz )

    Then get tzselect.ksh from the glibc download. Then you can see how to reverse engineer timezones. One sticking point: you WILL sometimes have to ask what country and city the linux box is in. You can serialize that data if you want, and then verify it against the timezone data you can find.

    There is no way to do this reliably all the time without the possibility of user intervention, for example, as part of program installation.

    祝亚利桑那州和西印第安纳州好运……希望您的代码可以在其他地方运行。