8

C標準ライブラリ(プレーンISO C、POSIXなし、したがってtime_t「エポックからの秒数」で表される仮定なし)のみを使用して、 time_t1970年1月1日00:00:00に対応する値を取得する最も簡単な方法は何ですかUTC ?

UTC 部分が鍵です。それ以外の場合はmktime、適切に初期化されたを使用するだけでstruct tm、問題は自明に解決されます。

time_tあるいは(これは実際には質問の「ポイント」です)、指定された値(たとえば、を介して取得された現在の時刻time(0))とエポックの間のPOSIX秒数を移植可能に決定するにはどうすればよいですか?「POSIX秒」とは、うるう秒を使用しないPOSIXの「エポックからの秒数」で使用される定義を意味します。これが複雑すぎると思われる場合は、最初の段落で最初に述べた質問に戻って、エポックが で表現可能であると仮定してtime_tください。

4

5 に答える 5

4

これを行う方法のエントリは次のとおりです。誰もそれを打ち負かさない場合は「最も簡単」です。

  • mktimestruct tmために呼び出す02 Jan 1970 00:00:00
  • を呼び出しmktimeます。これは合理的に-1を返す可能性があり、その場合は0として扱います。struct tm31 Dec 1969 00:00:00
  • その値の2つの間の二分探索はtime_t、に渡されるとgmtime、次のようになります。01 Jan 1970 00:00:00

UTCと24時間以上異なる現地時間はないと仮定しますが、これは事実であると確信しています。必要に応じて境界を広げることができます。極端な場合は、との間を検索でき0ますtime(0)

二分探索は、例えば補間によって改善することができます。しかし、誰が知っているか、おそらくいくつかのクレイジーなタイムゾーン(または壊れたtzinfoデータ)が12月/1月の夏時間の変更を引き起こす可能性があります。私はそれがどんなリアルタイムゾーンでも起こったとは思わない。しかし、それは常識によってのみ、C標準によって禁止されていません。

そうでなければ、(タイムゾーンを取得するために)とvs (の精度を取得するためgmtime(mktime(01 Jan)))の比較に基づいて計算できると思います。01 Jan 1970 00:00:0001 Jan 1970 00:00:01time_t

于 2012-12-19T17:29:30.953 に答える
0

あなたの問題はかなり根本的なものです。ISOCはほぼ完全にタイムゾーンをパントし、夏時間のフックを使用して、変換を提供mktime()します。(実装します、あなたが決定します。localtime()gmtime()

したがって、実行できることは2つしかないようです。

  1. time_tそれがエポックUTCからの秒数であると想定し、それgmtime()を確認するために使用し、失敗した場合はパニックまたはアラートを送信します。また
  2. ISOCよりも包括的な標準に依存する
于 2012-12-19T17:30:11.227 に答える
0

ステップ1:time_t基準点として任意のものを選択します(現在の時刻は問題なく機能します)。それを呼び出しますt0

ステップ2:呼び出しgmtimet0、結果とエポックの差を分解したstruct tm形式で計算します。

ステップ3:呼び出しlocaltimet0、ステップ2の内訳の差を結果のに適用しますstruct tm。次に、それを呼び出しmktimeて、を取り戻しますtime_t

time_t結果は、エポックを表すものになるはずです。

これを実装する最初の試みでは、たとえば夏時間が追加または中止された地域や、あるゾーンの観測から別のゾーンの観測に切り替わった地域など、現地時間のオフセットが時間の経過とともに一定でない場合に問題が発生しました。struct tmこれは、タイムゾーン情報の基になっているデータが変更されたためと思われます。これが元の実装とその問題です。

time_t get_epoch(time_t t0)
{
    struct tm gmt = *gmtime(&t0);
    struct tm tmp = *localtime(&t0);
    tmp.tm_sec -= gmt.tm_sec;
    tmp.tm_min -= gmt.tm_min;
    tmp.tm_hour -= gmt.tm_hour;
    tmp.tm_mday -= gmt.tm_mday-1;
    tmp.tm_mon -= gmt.tm_mon;
    tmp.tm_year -= gmt.tm_year-70;
    return mktime(&tmp);
}

改良版では、POSIX式(http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_15)を使用してposix_time、特定のエポックからの秒数を計算する関数が追加されています。struct tm必要に応じて、1970年より前の年などを処理します。

time_t get_epoch(time_t t0)
{
    struct tm gmt = *gmtime(&t0);
    struct tm tmp = *localtime(&t0);
    long long offset = posix_time(&gmt);
    while (offset > INT_MAX) {
        offset -= INT_MAX;
        tmp.tm_sec -= INT_MAX;
        mktime(&tmp);
    }
    while (offset < -INT_MAX+61) {
        offset -= -INT_MAX+61;
        tmp.tm_sec -= -INT_MAX+61;
        mktime(&tmp);
    }
    tmp.tm_sec -= offset;
    return mktime(&tmp);
}

C89との互換性のためにlong longは、ドロップする必要があり、必要な呼び出しの数はmktime劇的に増加します。単一の値として計算することはできませんが、 1年に複数回offset呼び出すにはループが必要になります。mktime

于 2012-12-19T17:42:38.477 に答える
0

編集:おそらく、非標準の次の実装はtimegm、質問の非POSIX要件を満たすでしょう:

#include <stdio.h>
#include <string.h>
#include <time.h>

time_t my_timegm(struct tm *tm) {
   time_t t, g;
   double dt; // seconds
   struct tm *gm;
   t = mktime(tm);
   gm = gmtime(&t);
   gm->tm_isdst = 0;
   g = mktime(gm);
   dt = difftime(t, g);
   if (dt >= 0) {
      tm->tm_sec += fmod(dt, 60); // needed to handle 16-bit ints
      tm->tm_min += dt / 60;
   }
   else {
      tm->tm_sec -= fmod(-dt, 60);
      tm->tm_min -= -dt / 60;
   }
   return mktime(tm);
}

int main(void) { // prints time_t for 01 Jan 1970 00:00:00 UTC
   struct tm start;
   memset(&start, 0, sizeof start);
   start.tm_year = 70; // = 1970
   start.tm_mday = 1;  // = 1st
   printf("%ld\n" my_timegm(&start)); // gives 0 on any POSIX system
   return 0;
}

mktimeこれは、Linux のマニュアル ページに従って、 が動作structure members .. outside their valid interval ... will be normalizedするか、少なくとも意味のあるものが返されるようにすることを前提としています。プレーンな ISO C がこれを保証するかどうかはわかりません。

私の最初のバージョンはhttp://lists.samba.org/archive/samba-technical/2002-November/025571.htmlmy_timegmからのもので、そこには Beeman、Baushke、Sabol、Zawinski の功績が認められています。

time_t my_timegm(struct tm *tm) {
   time_t t, g;
   struct tm *gm;
   t = mktime(tm);
   if (t == -1) { // perhaps needed for DST changeover?
      tm->tm_hour--;
      if ((t = mktime(tm)) != -1)
         t += 3600;
   }
   gm = gmtime(&t);
   gm->tm_isdst = 0;
   g = mktime(gm);
   if (g == -1) {
      gm->tm_hour--;
      if ((g = mktime(gm)) != -1)
         g += 3600;
   }
   return (t == -1 || g == -1) ? -1 : t - (g - t); // or difftime
}

コードの必要性についてはまだ考えています:tm->tm_hour--など。

于 2012-12-19T21:47:56.077 に答える
0

私の以前のアプローチには、 が非正規化された時間表現をどのように解決するかについてのあいまいさに起因する問題が多すぎmktimeて、快適に使用できなかったので、より良いアプローチのために、このアイデアを Steve Jessop の推測/検索のアイデアとマージしてみます。

  1. struct tmオブジェクトtm0をエポックの暦時間に初期化します。

  2. に電話mktimeしてtm0ください。ただし、これにより現地時間として解釈されるため、結果は望ましい答えにはなりません。time_tこの値を呼び出しますt0

  3. に適用gmtimet0て、分解された世界時に変換します。目的のエポックとの差は 24 時間以内 (実際には最大で 12 時間) である必要があります。

  4. 差を調整tm0し、ステップ 2 に戻ります。ステップ 3 で適切に分割された世界時エポックが得られた場合、終了です。それ以外の場合は、手順 2 ~ 4 を繰り返します (必要ありません)。

コードでは、

time_t get_epoch()
{
        struct tm tm0 = { .tm_year = 70, .tm_mday = 1 }, gmt;
        time_t t0;
        for (;;) {
                t0 = mktime(&tm0);
                gmt = *gmtime(&t0);
                if (!gmt.tm_sec && !gmt.tm_min && !gmt.tm_hour &&
                        !gmt.tm_yday && gmt.tm_year==70) return t0;
                tm0.tm_sec -= gmt.tm_sec;
                tm0.tm_min -= gmt.tm_min;
                tm0.tm_hour -= gmt.tm_hour;
                tm0.tm_mday -= gmt.tm_mday-1;
                tm0.tm_mon -= gmt.tm_mon;
                tm0.tm_year -= gmt.tm_year-70;
        }
}
于 2012-12-20T06:49:36.903 に答える