web-dev-qa-db-fra.com

Comment trouver le fuseau horaire actuel du système?

Sous Linux, je dois rechercher le fuseau horaire actuellement configuré en tant qu'emplacement Olson. Je souhaite que mon code (C ou C++) soit portable sur autant de systèmes Linux que possible.

Par exemple. J'habite à Londres, mon emplacement actuel à Olson est "Europe/London". Je suis pas intéressé par les identifiants de fuseau horaire tels que "BST", "EST" ou autre chose.

Debian et Ubuntu ont un fichier /etc/timezone qui contient cette information, mais je ne pense pas pouvoir compter sur ce fichier toujours présent, je peux? Gnome a une fonction oobs_time_config_get_timezone() qui renvoie également la chaîne correcte, mais je veux que mon code fonctionne sur des systèmes sans Gnome.

Alors, quel est le meilleur moyen général d’obtenir le fuseau horaire actuellement configuré comme emplacement Olson, sous Linux?

22
alex tingle

C'est difficile d'obtenir une réponse fiable . S'appuyer sur des choses comme /etc/timezone peut être le meilleur choix.

(La variable tzname et le membre tm_zone de struct tm, comme suggéré dans d'autres réponses, contiennent généralement une abréviation telle que GMTBST etc., plutôt que la chaîne de temps Olson comme demandé dans la question).

  • Sur les systèmes basés sur Debian (y compris Ubuntu), /etc/timezone est un fichier contenant la bonne réponse.
  • Sur certains systèmes basés sur Redhat (y compris au moins certaines versions de CentOS, RHEL, Fedora), vous pouvez obtenir les informations requises à l'aide de readlink () sur /etc/localtime, qui est un lien symbolique vers (par exemple) /usr/share/zoneinfo/Europe/London.
  • OpenBSD semble utiliser le même schéma que RedHat.

Cependant, les approches ci-dessus posent quelques problèmes. Le répertoire /usr/share/zoneinfo contient également des fichiers tels que GMT et GB. Il est donc possible que l'utilisateur puisse configurer le lien symbolique pour y pointer.

De plus, rien n'empêche l'utilisateur de copier le bon fichier de fuseau horaire au lieu de créer un lien symbolique.

Une solution possible (qui semble fonctionner sous Debian, RedHat et OpenBSD) consiste à comparer le contenu du fichier/etc/localtime aux fichiers sous/usr/share/zoneinfo et à voir lesquels correspondent:

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

Bien sûr, le _/inconvénient est que cela vous indiquera tous les fuseaux horaires identiques à celui actuel. (Cela signifie identique dans le sens plein - pas seulement "actuellement au même moment", mais aussi "toujours changer leurs horloges le même jour à la connaissance du système".)

Votre meilleur choix peut être de combiner les méthodes ci-dessus: utilisez /etc/timezone si elle existe; sinon, essayez d'analyser /etc/localtime en tant que lien symbolique; si cela échoue, recherchez les fichiers de définition de fuseau horaire correspondants; si cela échoue, abandonnez et rentrez chez vous ;-)

(Et je ne sais pas du tout si l'une de ces solutions s'applique à AIX ...)

23
psmears

Je travaille sur une bibliothèque libre et open source C++ 11/14 } qui aborde cette question dans une seule ligne de code:

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

Il est conçu pour être portable avec toutes les versions récentes de Linux, MacOS et Windows. Pour moi, ce programme génère:

America/New_York

Si vous téléchargez cette bibliothèque et que cela ne vous fonctionne pas, rapports de bogues sont les bienvenus.

5
Howard Hinnant

Il n'y a pas de fonction standard c ou c ++ pour cela. Cependant, GNU libc a une extension. son struct tm a deux membres supplémentaires:

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

Cela signifie que si vous utilisez l'une des fonctions qui remplissent un struct tm (tel que localtime ou gmtime), vous pouvez utiliser ces champs supplémentaires. Ce n'est bien sûr que si vous utilisez GNU libc (et une version suffisamment récente de celle-ci).

De plus, beaucoup de systèmes ont une fonction int gettimeofday(struct timeval *tv, struct timezone *tz); (POSIX) qui remplit un struct timezone. Cela a les champs suivants:

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

Pas exactement ce que vous avez demandé, mais proche ...

5
Evan Teran

Je vois deux cas majeurs de Linux:

  1. Ubuntu. Il devrait y avoir un fichier/etc/timezone. Ce fichier ne doit contenir que le fuseau horaire et rien d’autre.
  2. Chapeau rouge. Il devrait y avoir un/etc/sysconfig/clock contenant quelque chose comme: ZONE = "America/Chicago"

De plus, Solaris doit avoir un fichier/etc/TIMEZONE contenant une ligne du type: TZ = US/Mountain.

Donc, sur la base de ce qui précède, voici quelques réponses simples C qui, je crois, répondent à la question du PO. Je l'ai testé sous Ubuntu, CentOS (Red Hat) et Solaris (bonus).

#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

Assez tard dans la journée, mais je recherchais quelque chose de similaire et j'ai constaté que la bibliothèque ICU avait la possibilité d'obtenir l'ID du fuseau horaire Olson: http://userguide.icu-project.org/datetime/timezone

Il est maintenant installé sur la plupart des distributions linux (installez le paquet libicu-dev ou l’équivalent). 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;
}

Et pour obtenir les noms de fuseaux horaires abrégés/POSIX (devrait également fonctionner sous 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
sumwale

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

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

J'ai aimé le message de psmears et j'ai implémenté ce script pour lire le premier résultat de la liste. Bien sûr, il doit y avoir des moyens plus élégants de le faire, mais voilà ...

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

    }

Dans mon Fedora 12 brésilien, il retourne:
Brésil/Est

Et fait exactement ce dont j'ai besoin.

Merci psmears

Voici du code qui fonctionne pour la plupart des versions de 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
user361446

La libc accède à la base de données Olson lorsque tzset est appelé et utilise ensuite des fuseaux horaires simplifiés. tzset examine d'abord la variable d'environnement TZ et revient à l'analyse des données binaires dans /etc/localtime.

Au début, systemd est normalisé pour avoir le nom du fuseau horaire Olson dans /etc/timezone, Debian-style . Après la fusion de systemd 190 et de/usr, systemd ne lit et ne met à jour que /etc/localtime, avec l'obligation supplémentaire que le fichier soit un lien symbolique vers /usr/share/zoneinfo/${OLSON_NAME}.

Observer TZ, puis readlink("/etc/localtime"), est le moyen le plus fiable de faire correspondre la logique tzset de la bibliothèque et de conserver les noms Olson symboliques. Pour les systèmes qui ne respectent pas la convention symbollink de systemd, lire /etc/timezone (et éventuellement vérifier que /usr/share/zoneinfo/$(</etc/timezone) est identique à /etc/localtime) constitue une solution de secours intéressante.

Si vous pouvez vivre sans noms symboliques, l'analyse le /etc/localtimetzfile est aussi portable qu'il est, bien que beaucoup plus complexe. En ne lisant que le dernier champ, vous obtenez un fuseau horaire Posix (par exemple: CST5CDT,M3.2.0/0,M11.1.0/1), qui peut interagir avec quelques bibliothèques de gestion du temps, mais supprime certaines métadonnées (aucune information de transition historique).

0
Tobu

Selon this page, cela ressemble à si vous #include <time.h> il déclarera ce qui suit.

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

Est-ce que cela vous donne l'information dont vous avez besoin?

0
torak

Puisque tzselect n’a été mentionné par personne et que vous avez besoin d’une solution presque irréprochable, travaillez avec ce que Olson a fait. Obtenez les fichiers tzcode et tzdata de elsie, ainsi que des fichiers de tabulation.

ftp://elsie.nci.nih.gov

En mars 2017, l'emplacement correct à partir duquel effectuer le téléchargement serait ftp://ftp.iana.org/tz/releases (et téléchargez tzcode2017a.tar.gz et tzdata2017a.tar.gz).

Ensuite, récupérez tzselect.ksh à partir du téléchargement de la glibc. Ensuite, vous pouvez voir comment procéder au reverse engineering de fuseaux horaires. Un point critique: vous devrez parfois demander dans quel pays et quelle ville se trouve la boîte Linux. Vous pouvez sérialiser ces données si vous le souhaitez, puis les comparer aux données de fuseau horaire que vous pouvez trouver. 

Il n’ya aucun moyen de le faire de manière fiable à tout moment sans la possibilité d’une intervention de l’utilisateur, par exemple lors de l’installation du programme. 

Bonne chance pour l'Arizona en général et pour l'ouest de l'Indiana .... espérons que votre code sera diffusé ailleurs.

0
jim mcnamara

Sous Linux, je dois trouver le fuseau horaire actuel en tant qu'emplacement Olson. Je souhaite que mon code (C ou C++) soit portable sur autant de systèmes Linux que possible.

Si vous voulez être portable, utilisez uniquement GMT en interne. En raison de l'héritage multi-utilisateur, * l'horloge système NIX est normalement en heure GMT et il n'y a pas de fuseau horaire global - car différents utilisateurs connectés au système vivent parfois dans des fuseaux horaires différents.

Le fuseau horaire spécifique à l'utilisateur est reflété dans la variable d'environnement TZ et vous devrez peut-être l'utiliser uniquement lors de la conversion de la date/heure interne dans un format lisible par l'utilisateur. Sinon, localtime() s'en charge automatiquement pour vous.

0
Dummy00001