14

問題

C ++で日付を保持するためのクラスを作成していますが、次の問題が見つかりました。

N基準日(私の場合は西暦0001年1月1日)からの日数があります。これには、基準日から経過したうるう日も含まれます。この数値を年、月、日に効率的に変換するにはどうすればYMDよいですか?

私はこれを可能な限り効率的に行いたいので、最良の実装は明らかにO(1)の複雑さを持ちます。

次のセクションでは、私がすでに学んだことのいくつかを説明します。

うるう年

1年が飛躍するかどうかを判断するには、いくつかのルールがあります。

  1. 4で割り切れる年は飛躍的です
  2. ルール1の例外:100で割り切れる年は飛躍しません
  3. ルール2の例外:400で割り切れる年は飛躍的です

これは、次のようなコードに変換されます。

bool IsLeapYear(int year)
{
    // Corrected after Henrick's suggestion
    if (year % 400 == 0) return true;
    if ((year % 4 == 0) && (year % 100 != 0)) return true;
    return false;
}

1年前に何年飛躍しているかを計算する効率的な方法は次のとおりです。

int LeapDaysBefore(int year)
{
    // Years divisible by 4, not divisible by 100, but divisible by 400
    return ((year-1)/4 - (year-1)/100 + (year-1)/400);
}

月の計算

年を見つけたら、現在の年までの日数を計算し、この数値をNから引くことができます。これにより、その年の日がわかります。

毎月の開始日番号を表にしておくと、簡単に月を計算できます。また、年が飛躍し、月が2以上の場合に1を加算する関数を作成しました。

// What day each month starts on (counting from 0)
int MonthDaySt[] = { 0, 31, 59, 90, 120, 151, 181, 212, 
    243, 273, 304, 334, 365 };

int MonthDayStart(int month, bool leap)
{
   if (leap && month >= 2) return MonthDaySt[month]+1;
   return MonthDaySt[month];
}

私の考え

私のアルゴリズムはかなり複雑で、次のようになります。

void GetDate(int N, int &Y, int &M, int &D)
{
    int year_days;

    // Approximate the year, this will give an year greater or equal
    // to what year we are looking for.
    Y = N / 365 + 1;

    // Find the actual year, counting leap days
    do {
        Y--;

        // Calculate the actual number of days until the
        // approximate year
        year_days = Y * 365 + LeapDaysBefore(year);

    } while (year_days > N);

    // Add 1, because we start from year 1 AD (not 0)
    Y++;

    // Calculate month
    uint64_t diff = N - year_days; // Will give us the day of the year
    bool leap = IsLeapYear(Y);  // Is current year leap?

    // Use table to find month
    M = 0;
    while (MonthDayStart(M, leap) <= diff && M <= 12)
        M++;

    // Calculate day
    D = diff - MonthDayStart(M - 1, leap) + 1;
}

この関数にはいくつかのバグがある可能性があります(たとえば、Nが0の場合は機能しませんでした)。

その他の注意事項

この質問の元のアルゴリズムからいくつかの変更を加えたので、私のアルゴリズムがまだ正しいことを願っています。何かを見逃したり、何かが間違っていた場合は、それを変更するように知らせてください。そして、長い質問でごめんなさい。

4

10 に答える 10

5

古いジョークのオチを使うには、「ここから始めない」。

たとえば、1752年に起こったことなど、「現代」の時代の前に予定表に加えられたさまざまな変更について読みたいと思うでしょう。

于 2012-07-23T09:45:59.187 に答える
4

ここにいくつかの指針があります。注: この演習ではN=0Y % 400 == 0.

1: 400 年ごとに一定の日数があります(400 * 365) + 100 + 1 - 4

+100うるう年、+1は 400 年ごとのうるう年、-4は 100 年ごとのうるう年がないことを示します。

したがって、コードの最初の行は次のようになります。

GetDate(int N, int &Y, int &M, int &D) {
  const int DAYS_IN_400_YEARS = (400*365)+97;
  int year = (N / DAYS_IN_400_YEARS) * 400;
  N = N % DAYS_IN_400_YEARS;

2: 3 月 1 日を年の最初の日として扱うと、生活がとても楽になります。

3: (1)のコードに追加すると、年がわかります。4 世紀ごとにうるう年が始まることに注意してください。したがって、次のように年の計算を完了することができます。

  const int DAYS_IN_100_YEARS = (100*365) + 24;
  year += 100 * (N / DAYS_IN_100_YEARS) + (N < DAYS_IN_100_YEARS ? 1 : 0); // Add an extra day for the first leap year that occurs every 400 years.
  N = N - (N < DAYS_IN_100_YEARS ? 1 : 0);
  N = N % DAYS_IN_400_YEARS;

4:これで、年を整理できました。残りはパイのように簡単です((2)を覚えておいてください。プロセスは簡単です)。

あるいは、 boost::dateを使用することもできます。

于 2012-07-23T10:09:57.000 に答える
1

質問を単純化させてください、私は説明のために例外を考慮しません。4年ごとにうるう年が発生します。365*5日ある場合は、うるう年が必要です(例外2が適用されている場合を除く)。うるう年の数を計算するために除算を使用することもできます(例外を無視する場合)。

そうすれば、うるう年/月/日を持たないように除算と剰余を簡単に使用できます。

例外1を解決するために同じ基本的な直感を使用します(年数が100の倍数である場合は、例外2も確認します) 。

  1. 4で割り切れる年は飛躍的です
  2. ルール1の例外:100で割り切れる年は飛躍しません
  3. ルール2の例外:400で割り切れる年は飛躍的です
于 2012-07-23T09:47:31.380 に答える
1

明らかに、ボトルネックは年の計算です。これを行うことをお勧めします。カレンダーを初期化するときは、日を 365 で割って (非常に大雑把に) 年を概算します。その後、この推定の前にすべてのうるう年のリストを事前に作成します。すべてを数える必要はなく、毎回 4 年ずつ追加するだけなので、かなり高速になるはずです。また、それらをしている間、あなたがどれだけ持っているかを数えてください。実際には、それらをもっと大きなパックで数えることもできます (つまり、400 年ごとに 100 のうるう年があります)。

これが終わると、今年の大まかな見積もりと、その前のすべてのうるう年の量が得られます。これで、何も繰り返すことなく、正確な年を非常に簡単に数えることができます。

leapYearCount * 366 + (lastCalculatedYear - leapYearCount) * 365
于 2012-07-23T09:41:20.753 に答える
1

これ

bool IsLeapYear(int year) 
{ 
    if ((year % 4 == 0) && (year % 100 != 0) && (year % 400 == 0)) return true; 
    else return false; 
}

間違っています。false2000年に戻ります。より良い:

bool IsLeapYear(int year) 
{ 
    if (year % 400 == 0) return true; 
    if ((year % 4 == 0) && (year % 100 != 0)) return true; 
    return false; 
}
于 2012-07-23T09:33:36.107 に答える
1

基準日 (私の場合は西暦 0001 年 1 月 1 日) から N 日経っています...

その場合、4-100-400 ルールを適用して月の長さを調べる際の「効率」は、主な問題ではありません。また、今日のグレゴリオ暦をその開始以前の日付に適用することに固有の複数の問題と、グレゴリオ暦均一に導入されなかったという事実にも注意してください。(*)

ウィキペディアは、非常に複雑なテーマへの出発点として適しています。

(*): 国によって異なりますが、1582 年 10 月 15 日から 1923 年 2 月 15 日の間です。実際にはまったくありません。

于 2012-07-23T09:55:34.827 に答える
0

年の計算を高速化するために、ルックアップテーブルを作成できます

int[] YearStartDays =
{
    0,                     // 1 AD
    365,                   // 2 AD
    365 + 365,             // 3 AD
    365 + 365 + 365,       // 4 AD
    365 + 365 + 365 + 366, // 5 AD (4 was a leap year)
    /* ... */
};

次に、この配列でバイナリ検索を実行できます。これは、現在の年の検索アルゴリズムのO(N)の代わりにO(log N)です。

于 2012-07-23T09:45:25.307 に答える
0
bool IsLeapYear(int year)
{
    boost::gregorian::date d1(year, 1, 1);
    boost::gregorian::date d2 = d1 + boost::gregorian::years(1);
    boost::gregorian::date_duration diff = d2 - d1;
    return diff.days() != 365;
}
于 2014-08-20T20:18:51.073 に答える
-3

なぜ日付を再発明するのですか?

日付計算はよく理解されています。標準 C ライブラリ (そうです、C++ だけでなく C) には、長年にわたって日付関数がありました。

他の人が示したように、ブースト日付クラスも適切に設計されており、使いやすい.

答えを探すとき、最初の質問は、問題はすでに解決されているかどうかです。この問題は長年にわたって解決されてきました。

于 2012-07-23T15:36:15.613 に答える