web-dev-qa-db-ja.com

C#でDateTimeをユリウス日付に変換します(ToOADate Safe?)

標準のグレゴリオ暦日付からジュリアン日番号に変換する必要があります。

これを直接行うためにC#で文書化されたものは何も見ていませんが、 ToOADate の使用を示唆する多くの投稿(グーグル中)を見つけました。

ToOADate のドキュメントでは、ユリウス日付の有効な変換方法としてこれを示唆していません。

この関数が変換を正確に実行するか、DateTimeをユリウス形式の文字列に変換するより適切な方法かどうかを誰でも明確にできますか?.


これ は、検証されたときに期待される数を提供します Wikipediaのユリウス日ページ

public static long ConvertToJulian(DateTime Date)
{
    int Month = Date.Month;
    int Day = Date.Day;
    int Year = Date.Year;

    if (Month < 3)
    {
        Month = Month + 12;
        Year = Year - 1;
    }
    long JulianDay = Day + (153 * Month - 457) / 5 + 365 * Year + (Year / 4) - (Year / 100) + (Year / 400) + 1721119;
    return JulianDay;
}

ただし、これは使用されているマジックナンバーの理解なしです。

ありがとう


参照:

28
cweston

OADateはユリウス日付に似ていますが、異なる開始点(紀元前1899年12月30日と4713年1月1日)、および異なる「新しい日」点を使用します。ジュリアンの日付は正午を新しい日の始まりと見なし、OADatesは最新の定義である真夜中を使用します。

1899年12月30日午前0時のユリウス日は2415018.5です。このメソッドは、適切な値を提供する必要があります。

_public static double ToJulianDate(this DateTime date)
{
    return date.ToOADate() + 2415018.5;
}
_

アルゴリズムに関して:

  • if (Month < 3) ...:マジックナンバーを適切に機能させるために、2月を年の終わりに設定しています。
  • _(153 * Month - 457) / 5_:うわー、それは深刻な魔法の数字です。
    • 通常、各月の日数は31 28 31 30 31 30 31 31 30 31 30 31ですが、ifステートメントでの調整後は31 30 31 30 31 31 30 31 30 31 31 28になります。 30とすると、1 0 1 0 1 1 0 1 0 1 1 -2になります。整数空間でその除算を行うことで、1と0のパターンを作成しています。
    • 浮動小数点に書き換えると、_(int)(30.6 * Month - 91.4)_になります。 30.6は、2月を除く月あたりの平均日数です(正確には30.63が繰り返されます)。 91.4は、2月以外の平均3か月間のほぼ日数です。 (30.6 * 3は91.8です)。
    • それでは、30を削除して、その0.6日に焦点を当てましょう。それに月数を掛けてから整数に切り捨てると、0と1のパターンが得られます。
      • 0.6 * 0 = 0.0-> 0
      • 0.6 * 1 = 0.6-> 0(0の差)
      • 0.6 * 2 = 1.2-> 1(1の差)
      • 0.6 * 3 = 1.8-> 1(0の差)
      • 0.6 * 4 = 2.4-> 2(1の差)
      • 0.6 * 5 = 3.0-> 3(1の差)
      • 0.6 * 6 = 3.6-> 3(0の差)
      • 0.6 * 7 = 4.2-> 4(1の差)
      • 0.6 * 8 = 4.8-> 4(0の差)
    • その違いのパターンを右でご覧ください。これは上記のリストの同じパターンで、各月の日数から30を引いたものです。91.8を引くと、最初の3か月の日数が補正され、年末に移動し、調整されます。 0.4ずつ、1の連続する差(上記の表の0.6 * 4と0.6 * 5)を移動して、31日間の隣接する月に合わせます。
    • 2月は今年の「終わり」になっているので、その長さに対処する必要はありません。長さは45日(うるう年は46日)で、変更する必要があるのは、1年の日数の定数365だけです。
    • これは、30か月と31か月の日のパターンに依存していることに注意してください。 30日間で2か月連続した場合、これは不可能です。
  • _365 * Year_:1年あたりの日数
  • _(Year / 4) - (Year / 100) + (Year / 400)_:4年ごとに1うるう日、マイナス100ごとに1、プラス400ごとに1うるう日。
  • _+ 1721119_:これは紀元前1月3日のユリウス日です。カレンダーの「開始」を1月から3月に移動したため、1月1日ではなく、これをオフセットとして使用します。年ゼロがないので、BC 1は整数値0を取得します。3月1日ではなく3月2日の理由については、その月全体の計算がまだ少し遅れていたためだと思います。元のライターが_- 462_の代わりに_- 457_(浮動小数点演算で_- 92.4_の代わりに_- 91.4_)を使用していた場合、オフセットは3月1日まででした。
82
David Yaw

メソッドながら

public static double ToJulianDate(this DateTime date) { return date.ToOADate() + 2415018.5; }

現代の日付で動作しますが、重大な欠点があります。

ユリウス日は負の日付(つまり、紀元前(紀元前))の日付に対して定義され、天文計算では一般的です。年が0未満のDateTimeオブジェクトを作成することはできないため、上記の方法を使用してBCE日付のユリウス日を計算することはできません。

1582年のグレゴリオ暦の改革により、10月4日から15日までの間に11日間の穴が開いた。これらの日付はユリウス暦でもグレゴリオ暦でも定義されていませんが、DateTimeはそれらを引数として受け入れます。さらに、上記のメソッドを使用しても、ユリウス日付に対して正しい値が返されません。 System.Globalization.JulianCalendar.ToDateTime()を使用したり、JulianCalendar時代をDateTimeコンストラクターに渡したりする実験は、1582年10月5日より前のすべての日付に対して誤った結果を生成します。

次のルーチンは、Jean Meeusの「Astronomical Algorithms」から採用されており、ユリウス暦の-4712、1月1日の正午から始まるすべての日付に対して正しい結果を返します。また、無効な日付が渡された場合、ArgumentOutOfRangeExceptionをスローします。

 public class JulianDate
{
    public static bool isJulianDate(int year, int month, int day)
    {
        // All dates prior to 1582 are in the Julian calendar
        if (year < 1582)
            return true;
        // All dates after 1582 are in the Gregorian calendar
        else if (year > 1582)
            return false;
        else
        {
            // If 1582, check before October 4 (Julian) or after October 15 (Gregorian)
            if (month < 10)
                return true;
            else if (month > 10)
                return false;
            else
            {
                if (day < 5)
                    return true;
                else if (day > 14)
                    return false;
                else
                    // Any date in the range 10/5/1582 to 10/14/1582 is invalid 
                    throw new ArgumentOutOfRangeException(
                        "This date is not valid as it does not exist in either the Julian or the Gregorian calendars.");
            }
        }
    }

    static private double DateToJD(int year, int month, int day, int hour, int minute, int second, int millisecond)
    {
        // Determine correct calendar based on date
        bool JulianCalendar = isJulianDate(year, month, day);

        int M = month > 2 ? month : month + 12;
        int Y = month > 2 ? year : year - 1;
        double D = day + hour/24.0 + minute/1440.0 + (second + millisecond / 1000.0)/86400.0;
        int B = JulianCalendar ? 0 : 2 - Y/100 + Y/100/4;

        return (int) (365.25*(Y + 4716)) + (int) (30.6001*(M + 1)) + D + B - 1524.5;
    }

    static public double JD(int year, int month, int day, int hour, int minute, int second, int millisecond)
    {
        return DateToJD(year, month, day, hour, minute, second, millisecond);
    }


    static public double JD(DateTime date) 
    {
        return DateToJD(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Millisecond);
    }
}
7
user2016737

David Yawによる説明はすぐにわかりますが、特定の月の前の月の年間累積日数の計算は直感に反します。アルゴリズムをより明確にするために整数の配列が必要な場合は、次のようにします。

    /*
     * convert magic numbers created by:
     *    (153*month - 457)/5) 
     * into an explicit array of integers
     */
    int[] CumulativeDays = new int[]
    {
        -92   // Month = 0  (Should not be accessed by algorithm)
      , -61   // Month = 1  (Should not be accessed by algorithm)
      , -31   // Month = 2  (Should not be accessed by algorithm)
      ,   0   // Month = 3  (March)
      ,  31   // Month = 4  (April)
      ,  61   // Month = 5  (May)
      ,  92   // Month = 6  (June)
      , 122   // Month = 7  (July)
      , 153   // Month = 8  (August)
      , 184   // Month = 9  (September)
      , 214   // Month = 10 (October)
      , 245   // Month = 11 (November)
      , 275   // Month = 12 (December)
      , 306   // Month = 13 (January, next year)
      , 337   // Month = 14 (February, next year)
    };

計算の最初の3行は次のようになります。

  int julianDay = day
                  + CumulativeDays[month]
                  + 365*year
                  + (year/4)

表現

(153*month - 457)/5)

ただし、3〜14の範囲の値については、上記の配列と同じ整数とまったく同じシーケンスを生成します。包括的であり、ストレージ要件なしでそうします。ストレージ要件の欠如は、そのような難読化された方法で累積日数を計算することの長所です。

4
John Washburn

誰かがfromユリウス日付をDateTimeに変換する必要がある場合は、以下を参照してください。

public static DateTime FromJulianDate(double julianDate)
{
    return DateTime.FromOADate(julianDate - 2415018.5);
}
2
Malick

変更されたユリウス日付の私のコードは、同じアルゴリズムを使用しますが、結果の値が Wikipedia に表示される変更されたユリウス日付と一致するように、最後に異なるマジック番号を使用します。私は、この同じアルゴリズムを少なくとも10年間、毎日の時系列のキーとして使用しています(元々はJava)。

public static int IntegerDate(DateTime date)
    {
        int Month = date.Month;
        int Day = date.Day;
        int Year = date.Year;

        if (Month < 3)
        {
            Month = Month + 12;
            Year = Year - 1;
        }
        //modified Julian Date
        return Day + (153 * Month - 457) / 5 + 365 * Year + (Year / 4) - (Year / 100) + (Year / 400) - 678882;
    }

逆算では、アミューズメント用のマジックナンバーが増えます。

public static DateTime FromDateInteger(int mjd)
    {
        long a = mjd + 2468570;
        long b = (long)((4 * a) / 146097);
        a = a - ((long)((146097 * b + 3) / 4));
        long c = (long)((4000 * (a + 1) / 1461001));
        a = a - (long)((1461 * c) / 4) + 31;
        long d = (long)((80 * a) / 2447);
        int Day = (int)(a - (long)((2447 * d) / 80));
        a = (long)(d / 11);
        int Month = (int)(d + 2 - 12 * a);
        int Year = (int)(100 * (b - 49) + c + a);
        return new DateTime(Year, Month, Day);
    }
0
user3902302