5

ジュリアンの日付(年、日、時、分)を標準形式(年、月、日、時、分)に変換して文字列として表現する関数を作成する必要があります。年の日から月の日への変換を実行できるライブラリまたはコンポーネントをすでに作成している人がいるはずです。私はいくつかの有名な日時ライブラリを見てきました:

  • ctime-特にtm構造体を使用しmktime(tm *timeptr)ます。これにより、通常、tm構造体の値が適切な場所に設定されます。ただし、「timeptrのメンバーtm_wdayおよびtm_ydayの元の値は無視されます...」は役に立ちません。
  • Boost ::DateTime-グレゴリオ暦が構築さdate(greg_year, greg_month, greg_day)れていますが、これは役に立ちません。ただし、date_from_tm(tm datetm)「フィールド:tm_wday、tm_yday、tm_hour、tm_min、tm_sec、およびtm_isdstは無視されます。」があります。繰り返しますが、助けにはなりません。
  • COleDateTime-このプロジェクトにはCOMが含まれているので、なぜですか?COleDateTimeコンストラクターCOleDateTime( int nYear, int nMonth, int nDay, int nHour, int nMin, int nSec )は役に立ちません。そして、それに伴う他の変換機能は見当たりません。

ご覧のとおり、これらはすべて月と日が必要です。これは、私が最初に避けようとしていることです。私は何かが足りないか、適切な場所を探していなかったに違いありません(私が試している限り、完璧ではありません)。

誰でも助けることができますか?ほとんどの場合、私が見逃している落とし穴が必ずあるので、自分で書くのは避けたいと思います。

4

2 に答える 2

7

この古い質問に出くわし、新しい情報を追加できるかもしれないと思いました。Thomas Porninによってこれを書いている既存の単一の回答は良い回答であり、私はそれを支持しました。しかし、私はそれをより良くするための挑戦として受け止めました。同じ答えを 2 倍の速さで出すことができたらどうでしょうか? 多分もっと速い?

この取り組みをテストするために、Thomas の回答を関数でラップしました。

#include <tuple>

std::tuple<int, int, int>
ymd_from_ydoy1(int year, int day_of_year)
{
    static const int month_len[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

    int leap = (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0);
    int day_of_month = day_of_year;
    int month;
    for (month = 0; month < 12; month ++) {
        int mlen = month_len[month];
        if (leap && month == 1)
            mlen ++;
        if (day_of_month <= mlen)
            break;
        day_of_month -= mlen;
    }
    return {year, month, day_of_month};
}

そして、それを改善するための私の試みは、以下に基づいています:

クロノ互換の低レベル日付アルゴリズム

上記の記事では、この状況に直接対処していません。ただし、日付操作に関連するアルゴリズムの詳細な説明が含まれており、「年間通算日」の概念も含まれていますが、その概念はこの質問で指定されているものとは異なります。

この質問では、「年間通算日」は 1 から始まるカウントで、1 月 1 日が年の始まりです (1 月 1 日 == 1 日目)。 クロノ互換の低レベル日付アルゴリズムには、アルゴリズムに同様の「年間通算日」の概念がありますcivil_from_daysが、それは 3 月 1 日を過ぎた日数 (3 月 1 日 == 0 日) です。

私の考えでは、必要な結果を見つけるために 12 か月にわたって反復する必要のない、小片を選んcivil_from_daysで新しいものを作成できたということです。ymd_from_ydoyこれが私が思いついたものです:

std::tuple<int, int, int>
ymd_from_ydoy2(int year, int day_of_year)
{
    int leap = (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0);
    if (day_of_year < 60 + leap)
        return {year, day_of_year/32, day_of_year - (day_of_year/32)*31};
    day_of_year -= 60 + leap;
    int mp = (5*day_of_year + 2)/153;
    int day_of_month = day_of_year - (153*mp+2)/5 + 1;
    return {year, mp + 2, day_of_month};
}

支店はまだありますが、その数は少なくなっています。この代替案の正確性とパフォーマンスの両方をテストするために、次のように記述しました。

#include <iostream>
#include <chrono>
#include <cassert>

template <class Int>
constexpr
bool
is_leap(Int y) noexcept
{
    return  y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
}

constexpr
unsigned
last_day_of_month_common_year(unsigned m) noexcept
{
    constexpr unsigned char a[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    return a[m-1];
}

template <class Int>
constexpr
unsigned
last_day_of_month(Int y, unsigned m) noexcept
{
    return m != 2 || !is_leap(y) ? last_day_of_month_common_year(m) : 29u;
}
int
main()
{
    using namespace std;
    using namespace std::chrono;
    typedef duration<long long, pico> picoseconds;
    picoseconds ps1{0};
    picoseconds ps2{0};
    int count = 0;
    const int ymax = 1000000;
    for (int y = -ymax; y <= ymax; ++y)
    {
        bool leap = is_leap(y);
        for (int doy = 1; doy <= 365 + leap; ++doy, ++count)
        {
            auto d1 = ymd_from_ydoy1(y, doy);
            auto d2 = ymd_from_ydoy2(y, doy);
            assert(d1 == d2);
        }
    }
    auto t0 = high_resolution_clock::now();
    for (int y = -ymax; y <= ymax; ++y)
    {
        bool leap = is_leap(y);
        for (int doy = 1; doy <= 365 + leap; ++doy)
        {
            auto d1 = ymd_from_ydoy1(y, doy);
            auto d2 = ymd_from_ydoy1(y, doy);
            assert(d1 == d2);
        }
    }
    auto t1 = high_resolution_clock::now();
    for (int y = -ymax; y <= ymax; ++y)
    {
        bool leap = is_leap(y);
        for (int doy = 1; doy <= 365 + leap; ++doy)
        {
            auto d1 = ymd_from_ydoy2(y, doy);
            auto d2 = ymd_from_ydoy2(y, doy);
            assert(d1 == d2);
        }
    }
    auto t2 = high_resolution_clock::now();
    ps1 = picoseconds(t1-t0)/(count*2);
    ps2 = picoseconds(t2-t1)/(count*2);
    cout << ps1.count() << "ps\n";
    cout << ps2.count() << "ps\n";
}

このテストには 3 つのループがあります。

  1. 2 つのアルゴリズムが +/- 100 万年の範囲で同じ結果を生成することをテストします。
  2. 最初のアルゴリズムの時間を計ります。
  3. 2 番目のアルゴリズムの時間を計ります。

どちらのアルゴリズムも非常に高速であることが判明しました...私がテストしているiMac Core i5では数ナノ秒です。したがって、小数ナノ秒の一次推定値を取得するためにピコ秒が導入されました。

    typedef duration<long long, pico> picoseconds;

次の 2 点を指摘したいと思います。

  1. 測定単位としてピコ秒を使用し始めていることは、どれほど素晴らしいことでしょうか?
  2. std::chronoピコ秒と簡単に相互運用できるのは、どれほどクールなことでしょうか?

私にとって、このテストは(およそ)出力します:

8660ps
2631ps

は よりも約3.3ymd_from_ydoy2倍高速であることを示しymd_from_ydoy1ます。

お役に立てれば。この回答から得られる重要なこと:

  1. chrono-compatible Low-Level Date Algorithmsには、日付操作のための便利で効率的なアルゴリズムがあります。この例のように、アルゴリズムをバラバラにして再構築する必要がある場合でも、それらは役に立ちます。アルゴリズムの説明は、それらを分離して、このような例で再適用できるようにするためにあります。
  2. <chrono>非常に高速な機能を測定する際に非常に柔軟に対応できます。非常に速いよりも 3 倍速くても、それでも十分な勝利です。
于 2013-09-03T03:14:45.900 に答える
2

年の日から月と日を計算するのは簡単に思えます。これはそれを行う必要があります:

static const int month_len[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

int leap = (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0);
int day_of_month = day_of_year;
int month;
for (month = 0; month < 12; month ++) {
    int mlen = month_len[month];
    if (leap && month == 1)
        mlen ++;
    if (day_of_month <= mlen)
        break;
    day_of_month -= mlen;
}

これは、1 月のゼロから始まる月を計算しますが、日数 (年または月の日のいずれか) が 1 から始まることを前提としていることに注意してください。年間通算日が無効な場合 (年末以降)、結果のmonth値は 12 (「12 月の次の月」) になります。

「ユリウス」は、グレゴリオ暦と数十日異なる「ユリウス暦」と、閏年の計算も表すため、混乱の元です。ここで、特定のグレゴリオ暦のコンテキスト内で、「年間通算日」カウントを「月と日」に変換したいと仮定しました。

于 2010-04-14T16:26:22.323 に答える