[C]Lokale Uhrzeit (struct tm) auf andere Zeitzone berechnen

Herakles

Profifragensteller
Moin!

Ich schlage mich seit einige Zeit mit dem Problem herum, dass ich eine in lokaler Zeit (also CET) ausgedrücktes struct tm habe und dieses konvertieten möchte auf New Yorker Zeit. Ich habe mir dazu ein kleines Programm geschrieben:

Code:
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
  struct tm deutsche_zeit, new_yorker_zeit;
  time_t utc;

  memset(&deutsche_zeit, 0, sizeof(struct tm));
  memset(&new_yorker_zeit, 0, sizeof(struct tm));
  // setze irgendein datum mit daylight saving time
  deutsche_zeit.tm_sec = 1;
  deutsche_zeit.tm_min = 1;
  deutsche_zeit.tm_hour = 1;
  deutsche_zeit.tm_mday = 6;
  deutsche_zeit.tm_mon = 6;
  // in das tm_year-feld muessen die anzahl der jahre seit 1900 rein
  deutsche_zeit.tm_year = 2013 - 1900;
  deutsche_zeit.tm_isdst = 1;
  // lege fest, dass wir in der berliner zeitzone sind
  setenv( "TZ", "CET-1CEST", 1 );
  tzset();
  // konvertiere die lokale uhrzeit zu UTC
  utc = timegm( &deutsche_zeit );
  // erst DANACH wollen wir die zeit aber in new-yorker zeit dargestellt wissen
  setenv( "TZ", "EST5EDT", 1 );
  tzset();
  localtime_r( &utc, &new_yorker_zeit );
  printf("deutsch:  %snew_york: %s",asctime(&deutsche_zeit), asctime(&new_yorker_zeit));

  return 0;
}

Der Output ist aber folgender:

deutsch: Sat Jul 6 01:01:01 2013
new_york: Sat Jul 6 01:01:01 2013

Wieso sind die Zeiten nicht unterschiedlich und wie wird es richtig gemacht?

Viele Grüße
Herakles
 
Ich denke die einfachste Methode ist die Zeit in Sekunden aus dem Datum zu generieren (die Sekunden werden immer UTC gezählt) und daraus dann die New Yorker Daten zu generieren.
 
Hast du mal versucht deutsche_zeit.tm_zone = "CET" oder deutsche_zeit.tm_gmtoff = 3600 zu setzen?
 
Ups, ich sehe gerade, dass ich die manpages meines installierten OpenBSD mit denen von Linux unterscheiden. Arbeiten soll der ganze Spaß auf einem Linux, und dort gibt es kein "tm_zone" oder "tm_gmtoff". Sorry, mein Fehler!

struct tm {
int tm_sec; /* seconds */
int tm_min; /* minutes */
int tm_hour; /* hours */
int tm_mday; /* day of the month */
int tm_mon; /* month */
int tm_year; /* year */
int tm_wday; /* day of the week */
int tm_yday; /* day in the year */
int tm_isdst; /* daylight saving time */
};
 
Zunächst mal der Code, der funktioniert:

Code:
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
  struct tm deutsche_zeit, new_yorker_zeit;
  time_t utc;

  memset(&deutsche_zeit, 0, sizeof(struct tm));
  memset(&new_yorker_zeit, 0, sizeof(struct tm));
  // setze irgendein datum mit daylight saving time
  deutsche_zeit.tm_sec = 1;
  deutsche_zeit.tm_min = 1;
  deutsche_zeit.tm_hour = 1;
  deutsche_zeit.tm_mday = 6;
  deutsche_zeit.tm_mon = 6;
  deutsche_zeit.tm_mday = 21;
  deutsche_zeit.tm_mon = 7;
  // in das tm_year-feld muessen die anzahl der jahre seit 1900 rein
  deutsche_zeit.tm_year = 2014 - 1900;
  deutsche_zeit.tm_isdst = 1;
  // struct korrigieren
  utc = timegm(&deutsche_zeit);
  // lege fest, dass wir in der berliner zeitzone sind
  setenv( "TZ", "CET-1CEST", 1 );
  tzset();
  // konvertiere UTC zur lokalen uhrzeit CET
  localtime_r(&utc, &deutsche_zeit);
printf("%d.%d.%d %d:%d %d %ld\n",deutsche_zeit.tm_mday, deutsche_zeit.tm_mon, deutsche_zeit.tm_year, deutsche_zeit.tm_hour, deutsche_zeit.tm_min,deutsche_zeit.tm_isdst, deutsche_zeit.tm_gmtoff);
  // erst DANACH wollen wir die zeit aber in new-yorker zeit dargestellt wissen
  setenv( "TZ", "EST5EDT", 1 );
  tzset();
  // konvertiere UTC zur lokalen uhrzeit EST
  localtime_r( &utc, &new_yorker_zeit );
printf("%d.%d.%d %d:%d %d %ld\n",new_yorker_zeit.tm_mday, new_yorker_zeit.tm_mon, new_yorker_zeit.tm_year, new_yorker_zeit.tm_hour, new_yorker_zeit.tm_min,new_yorker_zeit.tm_isdst, new_yorker_zeit.tm_gmtoff);
printf("deutsch: %s",asctime(&deutsche_zeit));
printf("nyc  : %s",asctime(&new_yorker_zeit));

  return 0;
}

Hier ist der Output:

21.7.114 3:1 1 7200
20.7.114 21:1 1 -14400
deutsch: Thu Aug 21 03:01:01 2014
nyc : Wed Aug 20 21:01:01 2014

So, jetzt einige Erläuterungen:

1. tm_gmtoff und tm_zone gibt es auch in der libc meines verwendeten Systems sehr wohl, ein Verweis darauf findet sich am Ende der entsprechenden manpage:

The glibc version of struct tm has additional fields

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

defined when _BSD_SOURCE was set before including <time.h>. This is a BSD extension, present in 4.3BSD-Reno.

Soviel dazu. Bringen tut das aber rein gar nichts, weil die meisten der "time-Funktionen" diese Fehler zu ignorieren scheinen.

2. Die "time-Funktionen" überschreiben vorher gesetzte Daten. Wenn man zum Beispiel vorher manuell ein struct tm mit Daten füllt, aber den Wochentag nicht setzt, überschreibt timegm den Wert mit deinem korrekten, berechneten. Ein Aufruf dieser Funktion KANN also hilfreich sein, um alles sauber zu initialisieren.

3. localtime und localtime_r verhalten sich unterschiedlich. Auch hier ein Kommentar aus der Manpage:

According to POSIX.1-2004, localtime() is required to behave as though tzset(3) was called, while localtime_r() does not have this requirement. For portable code tzset(3) should be called
before localtime_r().

Deshalb sollte man wohl besser vorher die TZ setzen und dann locatime_r nutzen, damit das Ganze übersichtlich bleibt und man als Leser des Codes auch schnell wieder versteht, was dort geschieht.

4. asctime treibt Unsinn. Schreibt man zum Beispiel die beiden am Ende des Code befindlichen printfs in eine Zeile, gibt asctime bei beiden Werten dasselbe Ergebnis zurück, schreibt man es wie im Code oben sichtbar in zwei Aufrufe, gibt es die zu erwartenden Werte aus.

Wenn Ihr mich fragt, sind diese "time-Funktionen" ein Werk des Teufels!

Grüße
Herakles
 
4. asctime treibt Unsinn. Schreibt man zum Beispiel die beiden am Ende des Code befindlichen printfs in eine Zeile, gibt asctime bei beiden Werten dasselbe Ergebnis zurück, schreibt man es wie im Code oben sichtbar in zwei Aufrufe, gibt es die zu erwartenden Werte aus.

Siehe manpage:
The asctime() function converts the broken-down time value tm into a
null-terminated string with the same format as ctime(). The return
value points to a statically allocated string which might be overwrit‐
ten by subsequent calls to any of the date and time functions. The
asctime_r() function does the same, but stores the string in a user-
supplied buffer which should have room for at least 26 bytes.

Rob
 
Zurück
Oben