Как мне найти текущий часовой пояс системы? - PullRequest
23 голосов
/ 25 июня 2010

В Linux мне нужно найти текущий настроенный часовой пояс как местоположение Олсона. Я хочу, чтобы мой код (C или C ++) был переносим на максимально возможное количество систем Linux.

Например. Я живу в Лондоне, поэтому мое нынешнее местоположение Олсона - "Европа / Лондон". Я не заинтересован в идентификаторах часовых поясов, таких как "BST", "EST" и т. Д.

У Debian и Ubuntu есть файл /etc/timezone, который содержит эту информацию, но я не думаю, что я могу полагаться на то, что этот файл всегда будет там, могу ли я? В Gnome есть функция oobs_time_config_get_timezone(), которая также возвращает правильную строку, но я хочу, чтобы мой код работал в системах без Gnome.

Итак, каков наилучший общий способ получения текущего настроенного часового пояса как местоположения Олсона в Linux?

Ответы [ 12 ]

24 голосов
/ 25 июня 2010

Трудно получить надежный ответ .Лучше всего полагаться на такие вещи, как /etc/timezone.

(переменная tzname и tm_zone член struct tm, как предлагается в других ответах, обычно содержат сокращение, такое как GMT / BST и т. Д., А не строка времени Олсона, как указано в вопросе).

  • В системах на основе 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 ...)

6 голосов
/ 25 июня 2010

Для этого нет стандартной функции 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 */
};

Не совсем то, что вы просили, но близко ...

5 голосов
/ 23 июня 2016

Я работал над бесплатной библиотекой C ++ 11/14 с открытым исходным кодом , которая решает этот вопрос в одной строке кода:

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

Это подразумеваетсябыть переносимым на все последние версии Linux, macOS и Windows.Для меня эта программа выводит:

America/New_York

Если вы скачали эту библиотеку, и она не работает, сообщения об ошибках приветствуются.

4 голосов
/ 24 ноября 2015

Я вижу два основных случая Linux:

  1. Ubuntu. Там должен быть файл / etc / timezone. Этот файл должен содержать только часовой пояс и ничего больше.
  2. Red Hat. Должен быть / etc / sysconfig / clock, который содержит что-то вроде: ZONE = "America / Chicago"

Кроме того, в Solaris должен быть файл / etc / TIMEZONE, содержащий строку типа: TZ = US / Mountain

Итак, исходя из вышеизложенного, здесь приведена некоторая прямая буква 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;
}
4 голосов
/ 08 августа 2014

Довольно поздно, но я искал что-то подобное и обнаружил, что в библиотеке ICU есть возможность получить идентификатор часового пояса Олсона: 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;
2 голосов
/ 04 февраля 2012

Вот код, который работает для большинства версий 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;
} 
2 голосов
/ 28 ноября 2011

FWIW, RHEL / Fedora / CentOS имеют /etc/sysconfig/clock:

ZONE="Europe/Brussels"
UTC=true
ARC=false
2 голосов
/ 28 июля 2011

Мне понравился пост, сделанный 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

0 голосов
/ 21 января 2015

libc обращается к базе данных Olson при вызове tzset и впоследствии использует упрощенные часовые пояса.tzset сначала просматривает переменную окружения TZ и возвращается к синтаксическому анализу двоичных данных в /etc/localtime.

Сначала systemd стандартизируется с использованием имени часового пояса Олсона в/etc/timezone, в стиле Debian .После слияния systemd 190 и / usr systemd считывает и обновляет только /etc/localtime с дополнительным требованием, чтобы файл был символической ссылкой на /usr/share/zoneinfo/${OLSON_NAME}.

Глядя на TZ, затем readlink("/etc/localtime"),это самый надежный способ соответствовать логике libc tzset и при этом сохранять символические имена Олсона.Для систем, которые не следуют соглашению символических ссылок systemd, чтение /etc/timezone (и, возможно, проверка того, что /usr/share/zoneinfo/$(</etc/timezone) совпадает с /etc/localtime) - хороший запасной вариант.

Если вы можете жить без символических имен, парсинг /etc/localtime tzfile настолько переносим, ​​насколько это возможно, хотя и намного сложнее.Чтение только последнего поля дает вам часовой пояс Posix (например: CST5CDT,M3.2.0/0,M11.1.0/1), который может взаимодействовать с несколькими библиотеками обработки времени, но отбрасывает некоторые метаданные (без информации о переходе за предыдущий период).

0 голосов
/ 26 июня 2010

Так как tzselect не упоминался никем, и вам нужно решение, почти не допускающее ошибок, работайте с тем, что сделал Олсон. Получить файлы tzcode и tzdata от elsie, а также файлы вкладок.

FTP: //elsie.nci.nih.gov

В марте 2017 года правильное местоположение для загрузки будет ftp: //ftp.iana.org/tz/releases (и загрузите tzcode2017a.tar.gz и tzdata2017a.tar.gz).

Тогда получите tzselect.ksh из загрузки glibc. Затем вы можете увидеть, как перепроектировать часовые пояса. Одно замечание: иногда вам нужно будет спросить, в какой стране и городе находится окно linux. При желании вы можете сериализовать эти данные, а затем проверить их по данным часового пояса, которые вы можете найти.

Невозможно сделать это надежно все время без возможности вмешательства пользователя, например, в рамках установки программы.

Удачи в Аризоне в целом и в Западной Индиане .... надеюсь, ваш код будет работать в другом месте.

...