Как мне найти текущий часовой пояс системы?
В Linux мне нужно найти текущий настроенный часовой пояс в качестве местоположения Олсона. Я хочу, чтобы мой код (C или C++) был переносим на максимально возможное количество систем Linux.
Например. Я живу в Лондоне, поэтому мое нынешнее местоположение Олсона - "Европа / Лондон". Меня не интересуют идентификаторы часовых поясов, такие как "BST", "EST" или что-то еще.
У Debian и Ubuntu есть файл /etc/timezone
которая содержит эту информацию, но я не думаю, что могу всегда полагаться на этот файл, не так ли? У гнома есть функция oobs_time_config_get_timezone()
которая также возвращает правильную строку, но я хочу, чтобы мой код работал на системах без Gnome.
Итак, каков наилучший общий способ получить текущий настроенный часовой пояс в качестве местоположения Олсона в Linux?
13 ответов
Трудно получить надежный ответ. Опираясь на такие вещи, как /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...)
Я работал над бесплатной библиотекой C++11/14 с открытым исходным кодом, которая решает этот вопрос в одной строке кода:
std::cout << date::current_zone()->name() << '\n';
Он предназначен для использования во всех последних версиях Linux, macOS и Windows. Для меня эта программа выводит:
America/New_York
Если вы загружаете эту библиотеку, и она не работает, сообщения об ошибках приветствуются.
Для этого не существует стандартной функции 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 */
};
Не совсем то, что вы просили, но близко...
Я вижу два основных случая Linux:
- Ubuntu. Там должен быть файл /etc/timezone. Этот файл должен содержать только часовой пояс и ничего больше.
- Красная шляпа. Должен быть файл / 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;
}
Довольно поздно, но я искал что-то подобное и обнаружил, что в библиотеке 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;
FWIW, RHEL/Fedora/CentOS имеют /etc/sysconfig/clock
:
ZONE="Europe/Brussels"
UTC=true
ARC=false
Мне понравился пост, сделанный 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
Вот код, который работает для большинства версий 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;
}
Согласно этой странице, похоже, если вы #include <time.h>
он объявит следующее.
void tzset (void);
extern char *tzname[2];
extern long timezone;
extern int daylight;
Это дает вам информацию, которая вам нужна?
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
), которая может взаимодействовать с несколькими библиотеками обработки времени, но отбрасывает некоторые метаданные (без информации о переходе за предыдущий период).
Так как tzselect не упоминался никем, и вам нужно решение, почти не допускающее ошибок, работайте с тем, что сделал Олсон. Получить файлы tzcode и tzdata от elsie, а также файлы вкладок.
В марте 2017 года правильное расположение для загрузки будет ftp://ftp.iana.org/tz/releases (и загрузить tzcode2017a.tar.gz
а также tzdata2017a.tar.gz
).
Затем получите tzselect.ksh из загрузки glibc. Тогда вы можете увидеть, как перепроектировать часовые пояса. Одно замечание: иногда вам нужно будет спросить, в какой стране и городе находится окно linux. Если хотите, вы можете сериализовать эти данные, а затем проверить их по данным часового пояса, которые вы можете найти.
Невозможно сделать это надежно все время без возможности вмешательства пользователя, например, в рамках установки программы.
Удачи в Аризоне в целом и в Западной Индиане.... надеюсь, ваш код будет работать в другом месте.
В Linux мне нужно найти текущий часовой пояс в качестве местоположения Олсона. Я хочу, чтобы мой код (C или C++) был переносим на максимально возможное количество систем Linux.
Если вы хотите быть портативным, то используйте только GMT для внутреннего использования. Из-за многопользовательского наследия системные часы *NIX обычно находятся в Гринвиче, и нет общесистемного часового пояса - потому что разные пользователи, подключенные к системе, могут жить в разных часовых поясах.
Пользовательский часовой пояс отражается в TZ
переменная окружения, и вам может понадобиться использовать ее только при преобразовании внутренней даты / времени в читаемую пользователем форму. Иначе, localtime()
позаботится об этом автоматически для вас.
Приведенный ниже код был успешно протестирован в оболочке bash на
- SUSE Linux корпоративный сервер 11
- Убунту 20.04.5 ЛТС
- Выпуск CentOS Linux от 7 сентября 2009 г.
Шелл-код:
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