Como faço para encontrar o fuso horário do sistema atual?

No Linux, preciso encontrar o fuso horário atualmente configurado como um local do Olson. Eu quero que meu código (C ou C ++) seja portável para tantos sistemas Linux quanto possível.

Por exemplo. Eu moro em Londres, então minha localização atual em Olson é “Europa / Londres”. Eu não estou interessado em identificações de fuso horário como “BST”, “EST” ou qualquer outra coisa.

O Debian e o Ubuntu têm um arquivo /etc/timezone que contém esta informação, mas eu não acho que posso confiar em que o arquivo esteja sempre lá, posso? O Gnome tem uma function oobs_time_config_get_timezone() que também retorna a string correta, mas eu quero que meu código funcione em sistemas sem o Gnome.

Então, qual é a melhor maneira geral de obter o fuso horário atualmente configurado como um local do Olson, no Linux?

É difícil obter uma resposta confiável . Confiar em coisas como /etc/timezone pode ser a melhor aposta.

(A variável tzname e o membro tm_zone da struct tm , como sugerido em outras respostas, geralmente contém uma abreviação como GMT / BST etc, em vez da cadeia de tempo do Olson, conforme solicitado na pergunta).

  • Nos sistemas baseados em Debian (incluindo o Ubuntu), o /etc/timezone é um arquivo que contém a resposta correta.
  • Em alguns sistemas baseados em Redhat (incluindo pelo menos algumas versões do CentOS, RHEL, Fedora), você pode obter as informações necessárias usando readlink () em /etc/localtime , que é um link simbólico para (por exemplo) /usr/share/zoneinfo/Europe/London .
  • O OpenBSD parece usar o mesmo esquema que o RedHat.

No entanto, existem alguns problemas com as abordagens acima. O /usr/share/zoneinfo também contém arquivos como GMT e GB , então é possível que o usuário configure o link simbólico para apontar para lá.

Também não há nada que impeça o usuário de copiar o arquivo de fuso horário certo para lá, em vez de criar um symlink.

Uma possibilidade de contornar isso (que parece funcionar no Debian, RedHat e OpenBSD) é comparar o conteúdo do arquivo / etc / localtime com os arquivos em / usr / share / zoneinfo, e ver quais combinam:

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

Claro que a desvantagem é que isso lhe dirá todos os fusos horários que são idênticos aos atuais. (Isso significa idêntico no sentido pleno – não apenas “atualmente ao mesmo tempo”, mas também “sempre muda seus relógios no mesmo dia, tanto quanto o sistema sabe”).

Sua melhor aposta pode ser combinar os methods acima: use /etc/timezone se existir; Caso contrário, tente analisar o /etc/localtime como um symlink; se isso falhar, procure por arquivos de definição de fuso horário correspondentes; se isso falhar – desista e vá para casa 😉

(E eu não tenho idéia se alguma das situações acima se aplica no AIX …)

Não existe uma function padrão c ou c ++ para isso. No entanto, o GNU libc tem uma extensão. seu struct tm tem dois membros extras:

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

Isso significa que se você usar uma das funções que preenche uma struct tm (como localtime ou gmtime ), você pode usar esses campos extras. Isto é claro somente se você estiver usando o GNU libc (e uma versão suficientemente recente dele).

Também muitos sistemas possuem um int gettimeofday(struct timeval *tv, struct timezone *tz); function (POSIX), que irá preencher um struct timezone . Isso tem os seguintes campos:

 struct timezone { int tz_minuteswest; /* minutes west of Greenwich */ int tz_dsttime; /* type of DST correction */ }; 

Não exatamente o que você pediu, mas perto …

Muito tarde no dia, mas eu estava procurando por algo semelhante e descobri que a biblioteca ICU tem a disposição para obter o ID do fuso horário Olson: http://userguide.icu-project.org/datetime/timezone

Agora ele está instalado na maioria das distribuições do Linux (instale o pacote libicu-dev ou equivalente). Código:

 #include  #include  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; } 

E para obter os nomes de fusos horários abreviados / POSIX (também devem funcionar no Windows):

 #include  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; 

Eu vejo dois casos principais de linux:

  1. Ubuntu. Deve haver um arquivo / etc / timezone. Este arquivo deve conter apenas o fuso horário e nada mais.
  2. Chapéu vermelho. Deve haver um / etc / sysconfig / clock que contém algo como: ZONE = “America / Chicago”

Além disso, o Solaris deve ter um arquivo / etc / TIMEZONE que contenha uma linha como: TZ = US / Mountain

Então, com base no que foi dito acima, aqui estão alguns C diretos que, acredito, respondem à pergunta do OP. Eu testei no Ubuntu, CentOS (Red Hat) e Solaris (bônus).

 #include  #include  #include  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; } 

Eu tenho trabalhado em uma biblioteca C ++ 11/14 livre e de código aberto que aborda essa questão em uma única linha de código:

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

Ele deve ser portátil em todos os tipos recentes de Linux, macOS e Windows. Para mim este programa produz:

 America/New_York 

Se você baixar essa biblioteca e não funcionar, os relatórios de erros serão bem-vindos.

Gostei da postagem feita pelo psmears e implementei esse script para ler a primeira saída da lista. Claro que deve haver maneiras mais elegantes de fazer isso, mas aí está você …

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

No meu Fedora 12 brasileiro, ele retorna:
Brasil / Oriente

E faz exatamente o que eu preciso.

Obrigado psmears

FWIW, RHEL / Fedora / CentOS tem o /etc/sysconfig/clock :

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

Aqui está o código que funciona para a maioria das versões do Linux.

 #include  #include  #include  #include  #include  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; } 

De acordo com esta página, parece que se você #include ele irá declarar o seguinte.

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

Isso te dá a informação que você precisa?

No Linux, preciso encontrar o fuso horário atual como um local do Olson. Eu quero que meu código (C ou C ++) seja portável para tantos sistemas Linux quanto possível.

Se você quer ser portátil, use apenas GMT internamente. Devido à inheritance multiusuário, o clock do sistema * NIX normalmente é em GMT e não há um fuso horário do sistema – porque diferentes usuários conectados ao sistema podem estar vivendo em diferentes fusos horários.

O fuso horário específico do usuário é refletido na variável de ambiente TZ e talvez seja necessário usá-lo somente ao converter data / hora interna no formato legível pelo usuário. Caso contrário, o localtime() cuida disso automaticamente para você.

A libc acessa o database Olson quando tzset é chamado e depois usa fusos horários simplificados. tzset examina primeiro a variável de ambiente TZ e volta a analisar os dados binários em /etc/localtime .

No primeiro systemd padronizado em ter o nome do fuso horário Olson em /etc/timezone , estilo Debian . Depois do systemd 190 e da fusão / usr, o systemd apenas lê e atualiza o /etc/localtime , com o requisito extra de que o arquivo seja um link simbólico para /usr/share/zoneinfo/${OLSON_NAME} .

Olhando para o TZ , então readlink("/etc/localtime") , é a maneira mais confiável de combinar a lógica tzset da libc e ainda manter os nomes simbólicos do Olson. Para sistemas que não seguem a convenção symlink systemd, a leitura de /etc/timezone (e possivelmente a verificação de que /usr/share/zoneinfo/$( é o mesmo que /etc/localtime ) é um bom substituto .

Se você pode viver sem nomes simbólicos, analisar o arquivo tz /etc/localtime é tão portátil quanto possível, embora muito mais complexo. Ler apenas o último campo obtém um fuso horário Posix (por exemplo: CST5CDT,M3.2.0/0,M11.1.0/1 ), que pode interoperar com algumas bibliotecas de controle de tempo, mas descarta alguns dos metadados (sem histórico informação de transição).

Como o tzselect não foi mencionado por ninguém e você precisa de uma solução quase imperceptível, trabalhe com o que Olson fez. Obtenha os arquivos tzcode e tzdata do elsie, além dos arquivos de guias.

ftp://elsie.nci.nih.gov

Em março de 2017, o local correto para download seria ftp://ftp.iana.org/tz/releases (e faça o download de tzcode2017a.tar.gz e tzdata2017a.tar.gz ).

Então pegue o tzselect.ksh do download da glibc. Então você pode ver como reverter os fusos horários. Um ponto de discórdia: às vezes, você terá que perguntar em que país e cidade a checkbox do Linux está. Você pode serializar esses dados se desejar e, em seguida, verificá-los em relação aos dados de fuso horário que encontrar.

Não há como fazer isso de maneira confiável o tempo todo sem a possibilidade de intervenção do usuário, por exemplo, como parte da instalação do programa.

Boa sorte no Arizona em geral, e no oeste de Indiana … espero que seu código seja executado em outro lugar.