wake-up-neo.com

Wie finde ich die aktuelle Zeitzone des Systems?

Unter Linux muss die aktuell konfigurierte Zeitzone als Olson-Speicherort gefunden werden. Ich möchte, dass mein (C- oder C++) Code auf so viele Linux-Systeme wie möglich portierbar ist.

Zum Beispiel. Ich lebe in London, daher ist mein derzeitiger Standort in Olson "Europe/London". Ich bin nicht an Zeitzonen-IDs wie "BST", "EST" oder was auch immer interessiert.

Debian und Ubuntu haben eine Datei /etc/timezone, die diese Informationen enthält, aber ich glaube nicht, dass ich mich darauf verlassen kann, dass diese Datei immer da ist, oder? Gnome hat eine Funktion oobs_time_config_get_timezone(), die auch die richtige Zeichenfolge zurückgibt, aber ich möchte, dass mein Code auf Systemen ohne Gnome funktioniert.

Was ist der beste allgemeine Weg, um die aktuell konfigurierte Zeitzone als Olson-Speicherort unter Linux abzurufen?

22
alex tingle

Es ist schwer, eine verlässliche Antwort zu erhalten . Sich auf Dinge wie /etc/timezone zu verlassen, ist möglicherweise die beste Wahl.

(Die Variable tzname und das tm_zone-Element von struct tm enthält, wie in anderen Antworten vorgeschlagen, normalerweise eine Abkürzung wie GMT/BST usw. und nicht die in der Frage angeforderte Olson-Zeitzeichenfolge.).

  • Auf Debian-basierten Systemen (einschließlich Ubuntu) enthält /etc/timezone eine Datei mit der richtigen Antwort.
  • Auf einigen Redhat-basierten Systemen (einschließlich mindestens einiger Versionen von CentOS, RHEL, Fedora) können Sie die erforderlichen Informationen mit readlink () auf /etc/localtime abrufen. Dies ist ein Symlink zu (beispielsweise) /usr/share/zoneinfo/Europe/London.
  • OpenBSD scheint das gleiche Schema wie RedHat zu verwenden.

Bei den oben genannten Ansätzen gibt es jedoch einige Probleme. Das Verzeichnis /usr/share/zoneinfo enthält auch Dateien wie GMT und GB. Der Benutzer kann den Symlink daher so konfigurieren, dass er dorthin verweist.

Es gibt auch nichts, was den Benutzer daran hindert, die richtige Zeitzonen-Datei dort zu kopieren, anstatt einen Symlink zu erstellen.

Eine Möglichkeit, dies zu umgehen (was bei Debian, RedHat und OpenBSD zu funktionieren scheint), besteht darin, den Inhalt der Datei/etc/localtime mit den Dateien unter/usr/share/zoneinfo zu vergleichen und festzustellen, welche der folgenden Elemente übereinstimmen:

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

Natürlich ist der Nachteil , dass dies alle Zeitzonen angibt, die mit der aktuellen identisch sind. (Das heißt identisch im vollen Sinn - nicht nur "aktuell zur gleichen Zeit", sondern auch "immer am selben Tag die Uhrzeit ändern, soweit das System weiß".)

Am besten kombinieren Sie die oben genannten Methoden: Verwenden Sie /etc/timezone, falls vorhanden; Andernfalls versuchen Sie, /etc/localtime als Symlink zu analysieren. Wenn dies fehlschlägt, suchen Sie nach übereinstimmenden Zeitzonen-Definitionsdateien. Wenn das nicht klappt - gib auf und geh nach Hause ;-)

(Und ich habe keine Ahnung, ob einer der oben genannten Punkte auf AIX zutrifft ...)

23
psmears

Ich habe an einer freien, Open Source-C++ 11/14-Bibliothek gearbeitet , die diese Frage in einer einzigen Codezeile behandelt:

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

Es ist für alle aktuellen Linux-, MacOS- und Windows-Versionen portabel. Für mich gibt dieses Programm aus:

America/New_York

Wenn Sie download diese Bibliothek verwenden und es nicht funktioniert, sind Sie Fehlerberichte willkommen.

5
Howard Hinnant

Hierfür gibt es keinestandardc- oder c ++ - Funktion. GNU libc hat jedoch eine Erweiterung. Sein struct tm hat zwei zusätzliche Mitglieder:

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

Das bedeutet, wenn Sie eine der Funktionen verwenden, die einen struct tm (wie localtime oder gmtime) enthält, können Sie diese zusätzlichen Felder verwenden. Dies ist natürlich nur möglich, wenn Sie GNU libc (und eine ausreichend aktuelle Version davon) verwenden.

Viele Systeme haben auch eine int gettimeofday(struct timeval *tv, struct timezone *tz);-Funktion (POSIX), die einen struct timezone ausfüllt. Dies hat die folgenden Felder:

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

Nicht genau das, wonach du gefragt hast, aber nah ...

5
Evan Teran

Ich sehe zwei große Linux-Fälle:

  1. Ubuntu. Es sollte eine/etc/timezone-Datei geben. Diese Datei sollte nur die Zeitzone enthalten und sonst nichts.
  2. Roter Hut. Es sollte eine/etc/sysconfig/clock geben, die Folgendes enthält: ZONE = "America/Chicago"

Außerdem sollte Solaris eine Datei/etc/TIMEZONE enthalten, die eine Zeile wie folgt enthält: TZ = US/Mountain

Basierend auf dem oben Gesagten, hier ist ein direktes C, von dem ich glaube, dass es die Frage des OP beantwortet. Ich habe es auf Ubuntu, CentOS (Red Hat) und Solaris (Bonus) getestet.

#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
Duane McCully

Ziemlich spät am Tag, aber ich suchte nach etwas Ähnlichem und fand heraus, dass die Bibliothek ICU die Olson-Zeitzonen-ID besitzt: http://userguide.icu-project.org/datetime/ Zeitzone

Es ist jetzt auf den meisten Linux-Distributionen installiert (installieren Sie das Paket libicu-dev oder ein gleichwertiges Paket). Code:

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

Um die abgekürzten/POSIX-Zeitzonen zu erhalten (sollte auch unter Windows funktionieren):

#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
sumwale

FWIW, RHEL/Fedora/CentOS haben /etc/sysconfig/clock:

ZONE="Europe/Brussels"
UTC=true
ARC=false
2
Serge Wautier

Ich habe den Beitrag von psmears gemocht und dieses Skript implementiert, um die erste Ausgabe der Liste zu lesen. Natürlich muss es elegantere Wege geben, aber da sind Sie ...

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

    }

In meiner brasilianischen Fedora 12 gibt es Folgendes zurück:
Brasilien/Osten

Und macht genau das, was ich brauche.

Vielen Dank, Psmears

Hier ist der Code, der für die meisten Linux-Versionen geeignet ist.

#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
user361446

Die libc greift auf die Olson-Datenbank zu, wenn tzset aufgerufen wird, und verwendet anschließend vereinfachte Zeitzonen. tzset betrachtet zuerst die Umgebungsvariable TZ und greift zurück, die binären Daten in /etc/localtime zu analysieren.

Zuerst systemd standardisiert auf den Olson-Zeitzonen-Namen in /etc/timezone, Debian-Stil . Nach systemd 190 und der/usr-Verknüpfung liest und aktualisiert systemd nur /etc/localtime, mit der zusätzlichen Anforderung, dass die Datei ein Symlink zu /usr/share/zoneinfo/${OLSON_NAME} ist.

Wenn Sie sich TZ und dann readlink("/etc/localtime") anschauen, ist dies die zuverlässigste Methode, um die tzset-Logik der libc abzugleichen und trotzdem symbolische Olson-Namen zu behalten. Für Systeme, die nicht der systemd-symbolischen Konvention folgen, ist das Lesen von /etc/timezone (und möglicherweise das Überprüfen von /usr/share/zoneinfo/$(</etc/timezone) mit /etc/localtime) ein guter Fallback.

Wenn Sie ohne symbolische Namen leben können, ist das Parsing der /etc/localtimetzfile so portabel wie es nur geht, wenn auch viel komplexer. Wenn Sie nur das letzte Feld lesen, erhalten Sie eine Posix-Zeitzone (zum Beispiel: CST5CDT,M3.2.0/0,M11.1.0/1), die mit einigen Zeitbearbeitungsbibliotheken zusammenarbeiten kann, aber einige Metadaten löscht (keine historischen Übergangsinformationen).

0
Tobu

Gemäss dieser Seite sieht es so aus, als würden Sie #include <time.h> folgendes angeben.

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

Gibt es Ihnen die Informationen, die Sie brauchen?

0
torak

Da tzselect von niemandem erwähnt wurde und Sie eine nahezu fehlersichere Lösung benötigen, sollten Sie mit Olson arbeiten. Holen Sie sich die Tzcode- und Tzdata-Dateien von elsie sowie die Tab-Dateien.

ftp://elsie.nci.nih.gov

Im März 2017 wäre der korrekte Speicherort zum Herunterladen ftp://ftp.iana.org/tz/releases (und download tzcode2017a.tar.gz und tzdata2017a.tar.gz).

Dann holen Sie sich tzselect.ksh vom glibc-Download. Dann können Sie sehen, wie Sie die Zeitzonen zurückentwickeln. Ein entscheidender Punkt: Sie müssen sich manchmal fragen, in welchem ​​Land und in welcher Stadt sich die Linux-Box befindet. Sie können diese Daten serialisieren, wenn Sie möchten, und sie anhand der Zeitzonen-Daten überprüfen, die Sie finden können. 

Ohne die Möglichkeit des Benutzereingriffs, beispielsweise im Rahmen der Programminstallation, ist dies nicht immer zuverlässig möglich. 

Viel Glück auf Arizona im Allgemeinen und im westlichen Indiana ... hoffentlich wird Ihr Code woanders ausgeführt.

0
jim mcnamara

Unter Linux muss ich die aktuelle Zeitzone als Olson-Standort finden. Ich möchte, dass mein (C- oder C++ -) Code auf möglichst viele Linux-Systeme portierbar ist.

Wenn Sie portabel sein möchten, verwenden Sie nur GMT intern. Aufgrund des Multi-User-Erbes ist die * NIX-Systemuhr normalerweise in GMT und es gibt keine systemweite Zeitzone - da verschiedene mit dem System verbundene Benutzer möglicherweise in unterschiedlichen Zeitzonen leben.

Die benutzerspezifische Zeitzone spiegelt sich in der Umgebungsvariablen TZ wider. Sie müssen diese möglicherweise nur verwenden, wenn Sie internes Datum/Uhrzeit in das vom Benutzer lesbare Format konvertieren. Andernfalls kümmert sich localtime() automatisch um Sie.

0
Dummy00001