Подтвердить что ты не робот

Как найти текущий часовой пояс системы?

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

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

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

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

4b9b3361

Ответ 1

трудно получить надежный ответ. Опираясь на такие вещи, как /etc/timezone, возможно, лучший выбор.

(Переменная tzname и член tm_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 или С++. Однако 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 */
};

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

Ответ 3

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

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

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

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

Итак, основываясь на вышесказанном, вот какой-то прямой C, на который я верю ответы на вопрос OP. Я тестировал его на 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

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

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

Он предназначен для переносимости всех последних нововведений Linux, MacOS и Windows. Для меня эта программа выводит:

America/New_York

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

Ответ 6

Мне понравилось сообщение, сделанное psmears, и реализовало этот script, чтобы прочитать первый вывод списка. Конечно, должны быть более элегантные способы сделать это, но там вы...

    /**
     * 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

Ответ 7

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

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

Ответ 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 мне нужно найти текущий часовой пояс в качестве местоположения Олсона. Я хочу, чтобы мой (C или С++) код был переносимым на максимально возможное количество Linux-систем.

Если вы хотите быть портативным, используйте только GMT. Из-за многопользовательского heritge * системные часы NIX обычно находятся в GMT и нет системного часового пояса, поскольку разные пользователи, подключенные к системе, могут жить в разных часовых поясах.

Пользовательский часовой пояс отражается в переменной среды TZ, и вам может потребоваться использовать это только при преобразовании внутренней даты/времени в читаемую пользователем форму. В противном случае localtime() позаботится об этом автоматически.

Ответ 11

Так как 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. Вы можете сериализовать эти данные, если хотите, а затем проверить их в отношении данных часового пояса, которые вы можете найти.

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

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

Ответ 12

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), которая может взаимодействовать с несколькими библиотеками обработки времени, но оставляет некоторые метаданные (без исторической информации перехода).