web-dev-qa-db-ja.com

1970年からの秒数を日付に、またはその逆に変換する数学

1970年1月1日00:00からのナノ秒単位のint64としての秒数があり、それを月/日/年/曜日に変換しようとしています。

これを反復的に行うのは簡単です、私はその作業をしていますが、定型的にやりたいです。実際の数学を探しています。

26
David

古い質問に対する新しい回答:

この新しい答えの根拠:既存の答えは、ナノ秒から年/月/日への変換のアルゴリズムを示していない(たとえば、ソースが非表示のライブラリを使用している)か、表示しているアルゴリズムで反復を使用しています。

この答えにはまったく反復がありません。

アルゴリズムはここにあります 、そして非常に詳細に説明されています。また、+ /-100万年(必要以上に長い期間)にわたってユニットの正確性がテストされています。

アルゴリズムはうるう秒をカウントしません。必要な場合は実行できますが、テーブル検索が必要であり、そのテーブルは時間とともに成長します。

日付アルゴリズムは日数の単位のみを扱い、ナノ秒は扱いません。日をナノ秒に変換するには、_86400*1000000000_を掛けます(64ビット演算を使用していることを確認してください)。ナノ秒を日に変換するには、同じ量で割ります。または、C++ 11 _<chrono>_ライブラリを使用してください。

この質問に答えるために必要な、このペーパーの3つの日付アルゴリズムがあります。

_1._ _days_from_civil_:

_// Returns number of days since civil 1970-01-01.  Negative values indicate
//    days prior to 1970-01-01.
// Preconditions:  y-m-d represents a date in the civil (Gregorian) calendar
//                 m is in [1, 12]
//                 d is in [1, last_day_of_month(y, m)]
//                 y is "approximately" in
//                   [numeric_limits<Int>::min()/366, numeric_limits<Int>::max()/366]
//                 Exact range of validity is:
//                 [civil_from_days(numeric_limits<Int>::min()),
//                  civil_from_days(numeric_limits<Int>::max()-719468)]
template <class Int>
constexpr
Int
days_from_civil(Int y, unsigned m, unsigned d) noexcept
{
    static_assert(std::numeric_limits<unsigned>::digits >= 18,
             "This algorithm has not been ported to a 16 bit unsigned integer");
    static_assert(std::numeric_limits<Int>::digits >= 20,
             "This algorithm has not been ported to a 16 bit signed integer");
    y -= m <= 2;
    const Int era = (y >= 0 ? y : y-399) / 400;
    const unsigned yoe = static_cast<unsigned>(y - era * 400);      // [0, 399]
    const unsigned doy = (153*(m + (m > 2 ? -3 : 9)) + 2)/5 + d-1;  // [0, 365]
    const unsigned doe = yoe * 365 + yoe/4 - yoe/100 + doy;         // [0, 146096]
    return era * 146097 + static_cast<Int>(doe) - 719468;
}
_

_2._ _civil_from_days_:

_// Returns year/month/day triple in civil calendar
// Preconditions:  z is number of days since 1970-01-01 and is in the range:
//                   [numeric_limits<Int>::min(), numeric_limits<Int>::max()-719468].
template <class Int>
constexpr
std::Tuple<Int, unsigned, unsigned>
civil_from_days(Int z) noexcept
{
    static_assert(std::numeric_limits<unsigned>::digits >= 18,
             "This algorithm has not been ported to a 16 bit unsigned integer");
    static_assert(std::numeric_limits<Int>::digits >= 20,
             "This algorithm has not been ported to a 16 bit signed integer");
    z += 719468;
    const Int era = (z >= 0 ? z : z - 146096) / 146097;
    const unsigned doe = static_cast<unsigned>(z - era * 146097);          // [0, 146096]
    const unsigned yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365;  // [0, 399]
    const Int y = static_cast<Int>(yoe) + era * 400;
    const unsigned doy = doe - (365*yoe + yoe/4 - yoe/100);                // [0, 365]
    const unsigned mp = (5*doy + 2)/153;                                   // [0, 11]
    const unsigned d = doy - (153*mp+2)/5 + 1;                             // [1, 31]
    const unsigned m = mp + (mp < 10 ? 3 : -9);                            // [1, 12]
    return std::Tuple<Int, unsigned, unsigned>(y + (m <= 2), m, d);
}
_

_3._ _weekday_from_days_:

_// Returns day of week in civil calendar [0, 6] -> [Sun, Sat]
// Preconditions:  z is number of days since 1970-01-01 and is in the range:
//                   [numeric_limits<Int>::min(), numeric_limits<Int>::max()-4].
template <class Int>
constexpr
unsigned
weekday_from_days(Int z) noexcept
{
    return static_cast<unsigned>(z >= -4 ? (z+4) % 7 : (z+5) % 7 + 6);
}
_

これらのアルゴリズムは、C++ 14用に作成されています。 C++ 11がある場合は、constexprを削除します。 C++ 98/03を使用している場合は、constexprnoexcept、および_static_assert_ sを削除します。

これら3つのアルゴリズムのいずれにも反復がないことに注意してください。

次のように使用できます。

_#include <iostream>

int
main()
{
    int64_t z = days_from_civil(2015LL, 8, 22);
    int64_t ns = z*86400*1000000000;
    std::cout << ns << '\n';
    const char* weekdays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
    unsigned wd = weekday_from_days(z);
    int64_t y;
    unsigned m, d;
    std::tie(y, m, d) = civil_from_days(ns/86400/1000000000);
    std::cout << y << '-' << m << '-' << d << ' ' << weekdays[wd] << '\n';
}
_

どの出力:

_1440201600000000000
2015-8-22 Sat
_

アルゴリズムはパブリックドメインにあります。好きなように使用してください。 日付アルゴリズムペーパー には、必要に応じていくつかの有用な日付アルゴリズムがあります(たとえば、_weekday_difference_は非常にシンプルであり、非常に便利です)。

これらのアルゴリズムは、必要に応じて オープンソース、クロスプラットフォーム、タイプセーフな日付ライブラリ にまとめられています。

タイムゾーンまたはうるう秒のサポートが必要な場合は、 タイムゾーンライブラリ日付ライブラリ の上に構築されています。

更新:同じアプリ内の異なるローカルゾーン

異なるタイムゾーン間で変換する の方法を参照してください。

更新:この方法で日付計算を行うときにうるう秒を無視する落とし穴はありますか?

これは、以下のコメントからの良い質問です。

回答:落とし穴がいくつかあります。そして、いくつかの利点があります。両者が何であるかを知るのは良いことです。

OSからのほぼすべての時間ソースは、 nix Time に基づいています。 nix Time は、1970-01-01以降の時間のカウントを除くうるう秒。これには、C time(nullptr)やC++ std::chrono::system_clock::now()などの関数、およびPOSIX gettimeofdayおよび_clock_gettime_が含まれます。これは標準で指定されたファクトではありません(POSIXで指定されている場合を除く)が、事実上の標準です。

したがって、秒のソース(ナノ秒など)がうるう秒を無視する場合、_{year, month, day, hours, minutes, seconds, nanoseconds}_などのフィールド型に変換するときにうるう秒を無視するのは正確です。実際、そのようなコンテキストでうるう秒を考慮すると、実際にはintroduceエラーになります。

そのため、時間のソースを知ること、特に nix Time のようにうるう秒も無視するかどうかを知ることは良いことです。

時間のソースがうるう秒を無視しない場合、still秒まで正しい答えを得ることができます。挿入されたうるう秒のセットを知る必要があるだけです。 これが現在のリストです

たとえば、1970-01-01 00:00:00 UTCからincludesうるう秒を含む秒数を取得し、これが「今」を表すことがわかっている場合(現在は2016-09-26)、現在と1970-01-01の間に挿入された現在のうるう秒数は26です。したがって、カウントから26を減算し、thenに従ってくださいアルゴリズム、正確な結果を取得します。

このライブラリ は、うるう秒に対応した計算を自動化できます。たとえば、2016-09-26 00:00:00 UTCと1970-01-01 00:00:00 UTCの間の秒数を取得するには、うるう秒を使用します。これを行う:

_#include "date/tz.h"
#include <iostream>

int
main()
{
    using namespace date;
    auto now  = clock_cast<utc_clock>(sys_days{2016_y/September/26});
    auto then = clock_cast<utc_clock>(sys_days{1970_y/January/1});
    std::cout << now - then << '\n';
}
_

どの出力:

_1474848026s
_

うるう秒を無視すると( nix Time )次のようになります。

_#include "date/date.h"
#include <iostream>

int
main()
{
    using namespace date;
    using namespace std::chrono_literals;
    auto now  = sys_days{2016_y/September/26} + 0s;
    auto then = sys_days{1970_y/January/1};
    std::cout << now - then << '\n';
}
_

どの出力:

_1474848000s
_

_26s_の違いについて。

今回の新年(2017-01-01)では、27を挿入しますth うるう秒。

1958-01-01から1970-01-01の間に「うるう秒」が挿入されましたが、12月または6月の終わりだけでなく、1秒未満の単位で挿入されました。大ざっぱであり、信頼できるソースを追跡することができませんでした。

原子時間管理サービスは1955年に実験的に開始され、最初の原子ベースの国際時間標準TAIのエポックは1958-01-01 00:00:00 GMT(現在のUTC)です。それ以前は、うるう秒を心配するほど正確ではなかったクォーツベースの時計が最高でした。

29
Howard Hinnant

シングルUnix仕様では、 エポックからの秒数 の式が提供されています。

エポック以降に経過した秒数を概算する値。協定世界時名(秒(tm_sec)、分(tm_min)、時間(tm_hour)、年の1月1日からの日数(tm_yday)、および暦年から1900(tm_year)を引いた日)が時間に関連しています以下の式に従って、エポックからの秒数として表されます。

年が<1970であるか、値が負の場合、関係は未定義です。年が> = 1970で値が負でない場合、値はC言語の式に従って協定世界時の名前に関連付けられます。tm_sec、tm_min、tm_hour、tm_yday、tm_yearはすべて整数型です。

tm_sec + tm_min*60 + tm_hour*3600 + tm_yday*86400 +
    (tm_year-70)*31536000 + ((tm_year-69)/4)*86400 -
    ((tm_year-1)/100)*86400 + ((tm_year+299)/400)*86400

エポックが指定されていないため、実際の時刻と秒の現在の値との関係。

現在の実際の時間との望ましい関係に合わせてエポック以降の秒の値を変更する方法は、実装によって定義されます。エポック以降の秒数で表されるように、毎日は正確に86400秒で説明されます。

注:式の最後の3つの用語は、エポック以降の最初のうるう年から始まるうるう年に続く各年に1日を追加します。最初の項は、1973年から4年ごとに1日を加算し、2番目は2001年から100年ごとに1日を減算し、3番目は2001年から400年ごとに1日を加算します。式の除算は整数除算です。 ;つまり、整数の商のみを残して残りが破棄されます。

この式を使用するには、月と日をtm_ydayに変換する必要があり、それもうるう年を考慮して行う必要があります。式の残りの部分は簡単です。

秒から日付と時刻を取得する方法を理解してみてください。

[〜#〜] edit [〜#〜]

this answer で整数演算のコンバーターを実装しました。

ideoneでのテスト実行を参照

10
Alexey Frunze
bool FloatToTime(float seconds_since_Epoch, bool local_time, struct tm *timest)
{
   struct tm *ret;
   time_t t=(time_t) seconds_since_Epoch;
   if (local_time) ret=localtime(&t);
      else ret=gmtime(&t);
   if(ret==NULL) return false;
   memcpy(timest, ret, sizeof(struct tm));
   return true;
}

最初のパラメーターとして秒を渡します。 2番目のパラメーターは、現地時間ではtrue、GMTではfalseでなければなりません。 3番目のパラメーターは、応答を保持する構造体へのポインターです。

戻り構造は(manページから)です:

tm_sec:1分後の秒数。通常は0から59の範囲ですが、うるう秒を考慮して最大60まで指定できます。

tm_min:1時間後の分数。範囲は0から59です。

tm_hour:深夜0時から23時までの時間数。

tm_mday:1から31の範囲の月の日。

tm_mon:1月以降の月数。範囲は0〜11です。

tm_year:1900年からの年数。

tm_wday:日曜日からの日数。範囲は0から6です。

tm_yday:1月1日からの日数。範囲は0〜365です。

tm_isdst:記載された時間に夏時間が有効かどうかを示すフラグ。夏時間が有効な場合、値は正であり、有効でない場合はゼロ、情報が利用できない場合は負です。

2
David Schwartz

希望する時間に依存します gmtime または localtime を読むだけで struct_tm

2
Martin Beckett

これを行うための関数はたくさんあります。 http://www.cplusplus.com/reference/clibrary/ctime/ 、つまり strftime を参照してください。

1
Seth Carnegie

このコードは動作します...

使用法:uint32_t getSecsSinceEpoch(1970、month、day、years_since_Epoch、hour、minutes、second);

例:timestamp = getSecsSinceEpoch(1970、6、12、(2014-1970)、15、29、0)

戻り値:1402586940

Www.epochconverter.comで確認できます。

それを書くのに約20分かかり、そのほとんどは、うるう秒、ナノ秒などを含めるべきかどうかについて友人と議論するのに費やされました。Blech。

楽しんで...

ブライアン・ウィルカット博士

#define DAYSPERWEEK (7)
#define DAYSPERNORMYEAR (365U)
#define DAYSPERLEAPYEAR (366U)

#define SECSPERDAY (86400UL) /* == ( 24 * 60 * 60) */
#define SECSPERHOUR (3600UL) /* == ( 60 * 60) */
#define SECSPERMIN (60UL) /* == ( 60) */

#define LEAPYEAR(year)          (!((year) % 4) && (((year) % 100) || !((year) % 400)))

const int _ytab[2][12] = {
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};

/****************************************************
* Class:Function    : getSecsSomceEpoch
* Input     : uint16_t Epoch date (ie, 1970)
* Input     : uint8 ptr to returned month
* Input     : uint8 ptr to returned day
* Input     : uint8 ptr to returned years since Epoch
* Input     : uint8 ptr to returned hour
* Input     : uint8 ptr to returned minute
* Input     : uint8 ptr to returned seconds
* Output        : uint32_t Seconds between Epoch year and timestamp
* Behavior      :
*
* Converts MM/DD/YY HH:MM:SS to actual seconds since Epoch.
* Epoch year is assumed at Jan 1, 00:00:01am.
****************************************************/
uint32_t getSecsSinceEpoch(uint16_t Epoch, uint8_t month, uint8_t day, uint8_t years, uint8_t hour, uint8_t minute, uint8_t second)
{
unsigned long secs = 0;
int countleap = 0;
int i;
int dayspermonth;

secs = years * (SECSPERDAY * 365);
for (i = 0; i < (years - 1); i++)
{   
    if (LEAPYEAR((Epoch + i)))
      countleap++;
}
secs += (countleap * SECSPERDAY);

secs += second;
secs += (hour * SECSPERHOUR);
secs += (minute * SECSPERMIN);
secs += ((day - 1) * SECSPERDAY);

if (month > 1)
{
    dayspermonth = 0;

    if (LEAPYEAR((Epoch + years))) // Only counts when we're on leap day or past it
    {
        if (month > 2)
        {
            dayspermonth = 1;
        } else if (month == 2 && day >= 29) {
            dayspermonth = 1;
        }
    }

    for (i = 0; i < month - 1; i++)
    {   
        secs += (_ytab[dayspermonth][i] * SECSPERDAY);
    }
}

return secs;
}
1
user3735867

HW乗算器のないローエンドの8ビットMCUでUnix時間への変換を実装する必要がありました。以下は、一般的な8ビットの乗算と定数値4および100による除算のみを必要とするC#コードです。両方とも32ビット(長い)オペランドで。 C#コードは、最終的なフレームワークに簡単に移植できます。 .NETのDateTimeOffset.ToUnixTimeSeconds()と同じ結果が得られます。

static long UnixTime ( int sec, int min, int hour, int day, int month, int year )
{
  // Cumulative days for each previous month of the year
  int[] mdays = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
  // Year is to be relative to the Epoch start
  year -= 1970;
  // Compensation of the non-leap years
  int minusYear = 0;
  // Detect potential lead day (February 29th) in this year?
  if ( month >= 3 )
  {
    // Then add this year into "sum of leap days" computation
    year++;
    // Compute one year less in the non-leap years sum
    minusYear = 1;
  }

  return 
    // + Seconds from computed minutes
    60 * (
      // + Minutes from computed hours
      60 * (
        // + Hours from computed days
        24 * (
          // + Day (zero index)
          day - 1
          // + days in previous months (leap day not included)
          + mdays[month - 1]
          // + days for each year divisible by 4 (starting from 1973)
          + ( ( year + 1 ) / 4 )
          // - days for each year divisible by 100 (starting from 2001)
          - ( ( year + 69 ) / 100 )
          // + days for each year divisible by 400 (starting from 2001)
          + ( ( year + 369 ) / 100 / 4 )
          // + days for each year (as all are non-leap years) from 1970 (minus this year if potential leap day taken into account)
          + ( 5 * 73 /*=365*/ ) * ( year - minusYear )
          // + Hours
        ) + hour
        // + Minutes
      ) + min 
      // + Seconds
    ) + sec;
}

それが役に立てば幸い。

編集済み:

以下は、8ビットPIC MCUおよびCC5Xコンパイラ用に最適化されたコードです。

uns32 unixTime;

...
  // Test data returning 0xFfFfFfFf UnixTime
  uns8 year = 2106 - 1970;
  uns8 month = 2;
  uns8 day = 7;
  uns8 hour = 6;
  uns8 min = 28;
  uns8 sec = 15;

  // See original C# code below

  //### Compute days
  // ( 5 * 73 /*=365*/ ) * year
  unixTime = year;
  mulUnixTime( 5 );
  mulUnixTime( 73 );

  // if ( month >= 3 ) year++;
  if ( month > 3 )
    year++;

  // if ( year > 130 ) => minus 1 total days ( year-=4 makes a result of the next division by 4 less by 1)
  if ( year > 130 )
    year -= 4;
  // + ( ( year + 1 ) / 4 )
  addUnixTime( ( year + 1 ) / 4 );
  // + mdays[month - 1]
  addUnixTime( daysInMonths( month ) );
  // + day - 1
  addUnixTime( day - 1 );
  //### Compute hours
  // Hours from computed days
  mulUnixTime( 24 );
  // + Hours
  addUnixTime( hour );
  //### Compute minutes
  // Minutes from computed hours 
  mulUnixTime( 60 );
  // + Minutes
  addUnixTime( min );
  //### Compute seconds
  // Seconds from computed minutes
  mulUnixTime( 60 );
  // + Seconds
  addUnixTime( sec );
...

void mulUnixTime( uns8 mul )
{
  unixTime *= mul;
}

void addUnixTime( uns8 add )
{
  unixTime += add;
}

uns8 daysInMonths( uns8 month @ W )
{
  skip( month );
#pragma computedGoto 1
  return 0xFF;// Dummy value for month 0
  return   0; // January
  return  31; // February
  return  59; // ...
  return  90;
  return 120;
  return 151;
  return 181;
  return 212;
  return 243;
  return 273;
  return 304; // ...
  return 334; // December
#pragma computedGoto 0
}


/*
 static long UnixTime ( int sec, int min, int hour, int day, int month, int year )
  {
    // Cumulative days for each previous month of the year
    int[] mdays = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
    // Year is to be relative to the Epoch start
    year -= 1970;
    // Compensation of the non-leap years
    int minusYear = 0;
    // Detect potential lead day (February 29th) in this year?
    if ( month >= 3 )
    {
      // Then add this year into "sum of leap days" computation
      year++;
      // Compute one year less in the non-leap years sum
      minusYear = 1;
    }

    return
      // + Seconds from computed minutes
      60 * (
        // + Minutes from computed hours
        60 * (
          // + Hours from computed days
          24L * (
            // + Day (zero index)
            day - 1
            // + days in previous months (leap day not included)
            + mdays[month - 1]
            // + days for each year divisible by 4 (starting from 1973)
            + ( ( year + 1 ) / 4 )
            // - days after year 2000
            - ( ( year > 130 ) ? 1 : 0 )
            // + days for each year (as all are non-leap years) from 1970 (minus this year if potential leap day taken into account)
            + ( 5 * 73 ) * ( year - minusYear )
          // + Hours
          ) + hour
        // + Minutes
        ) + min
      // + Seconds
      ) + sec;
  }
*/
0
Syr

    for (i = 0; i < (years - 1); i++)
    {   
        if (LEAPYEAR((Epoch + i)))
        countleap++;
    }

後:

    for (i = 0; i < years; i++)
 {   
   if (LEAPYEAR((Epoch + i)))
    countleap++;
 }

修正後、コードは機能しました。

0
Shahbaz

まず、秒をフロートとして保存しないでください。マイクロ/ナノ秒が必要な場合は、個別に保存してください。これらの計算を行うには整数が必要になります。

タイムゾーン(DSTルール、うるう年、うるう秒)によって異なりますが、まず86400で整数を除算して日数を取得します。次に86400を法として除算することで、残りを見つけます。日数を365で除算した最初の整数で経過した年数を計算し、残りの日数からうるう日数を減算します(日数を365で除算したモジュロにより計算)。また、残りの秒数(既に計算済み)からうるう秒数を引くこともできます。その減算がこれらの数値をゼロ未満に駆動する場合、次に大きい金額から減算します。その後、カレンダーの明示的なロジックを使用して月の日を計算できます。 DSTに着陸する場合は、必ず1時間(またはDSTオフセットが何であれ)追加してください。

個人的には、私は Boost.Date_Time を使用します。これは、これ以上のこと(おそらく、最初の数回の反復で行う間違いよりも少ない間違いで)を行うためですが、あなたの質問を撮影...

0
gred