在 C# 中将日期时间转换为儒略日期(ToOADate 安全吗?)

发布于 2024-10-20 23:12:22 字数 1257 浏览 2 评论 0原文

我需要从标准的公历日期转换为儒略日数。

我在 C# 中没有看到任何直接执行此操作的文档,但我发现许多帖子(在 Google 搜索时)建议使用 ToOADate

ToOADate 上的文档并不建议将此作为有效的儒略日期的转换方法。

任何人都可以澄清此函数是否能够准确地执行转换,或者可能是将 DateTime 转换为 Julian 格式字符串的更合适的方法。


为我提供了针对 维基百科的儒略日页面

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;
}

然而,这并没有理解所使用的幻数。

谢谢


参考文献:

I need to convert from a standard Gregorian date to a Julian day number.

I've seen nothing documented in C# to do this directly, but I have found many posts (while Googling) suggesting the use of ToOADate.

The documentation on ToOADate does not suggest this as a valid conversion method for Julian dates.

Can anyone clarify if this function will perform conversion accurately, or perhaps a more appropriate method to convert DateTime to a Julian formatted string.


This provides me with the expected number when validated against Wikipedia's Julian Day page

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;
}

However, this is without an understanding of the magic numbers being used.

Thanks


References:

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

爱*していゐ 2024-10-27 23:12:23

如果有人需要将儒略日期转换为 DateTime ,请参见下文:

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

If someone need to convert from Julian date to DateTime , see below :

public static DateTime FromJulianDate(double julianDate)
{
    return DateTime.FromOADate(julianDate - 2415018.5);
}
梦里寻她 2024-10-27 23:12:23

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)
    };

并且计算的前三行将变为:

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

对于范围内的值,表达式

(153*month - 457)/5)

虽然会生成与上面的数组完全相同的序列相同的整数:3至 14;包容性,并且没有存储要求。缺乏存储要求只是以这种模糊的方式计算累积天数的优点。

The explanation by David Yaw is spot on, but calculation of the cumulative number of days of the year for the months prior to the given month is anti-intuitive. If you prefer an array of integers to make the algorithm more clear then this will do:

    /*
     * 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)
    };

and the first thre lines of the calculation then become:

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

The expression

(153*month - 457)/5)

though produces the exact same sequence same integers as the array above for values in the range: 3 to 14; inclusive and does so with no storage requirements. The lack of storage requirements is only virtue in calculating the cumulative number of days in such and obfuscated way.

萌︼了一个春 2024-10-27 23:12:23

我的修改儒略日期代码使用相同的算法,但末尾使用不同的幻数,以便结果值与 维基百科。我已经使用同样的算法作为每日时间序列的密钥(最初使用 Java)至少 10 年了。

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);
    }

My code for modified Julian Date uses the same algorithm but a different magic number on the end so that the resulting value matches the Modified Julian Date shown on Wikipedia. I have been using this same algorithm for at least 10 years as the key for daily time series (originally in 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;
    }

The reverse calculation has more magic numbers for your amusement:

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);
    }
微凉 2024-10-27 23:12:23

以下方法为您提供从 1995/1/1, 00:00:00 开始的儒略日

    /// <summary>
    /// "GetJulianDays" will return a Julian Days starting from date 1 Jan 1995
    /// </summary>
    /// <param name="YYYYMMddHHmmss"></param>
    /// <returns>Julian Day for given date</returns>
    public string GetJulianDays(DateTime YYYYMMddHHmmss)
    {
        string DateTimeInJulianFormat = string.Empty;
        DateTime julianStartDate = new DateTime(1995, 1, 1, 00, 00, 00); //YYYY,MM,dd,HH,mm,ss

        DateTime DateTimeNow = YYYYMMddHHmmss;

        double difference = (DateTimeNow - julianStartDate).TotalDays;

        int totalDays = int.Parse(difference.ToString());

        DateTimeInJulianFormat = string.Format("{0:X}", totalDays);

        return DateTimeInJulianFormat;
    }

The below method gives you the julian days starting from 1995/1/1, 00:00:00

    /// <summary>
    /// "GetJulianDays" will return a Julian Days starting from date 1 Jan 1995
    /// </summary>
    /// <param name="YYYYMMddHHmmss"></param>
    /// <returns>Julian Day for given date</returns>
    public string GetJulianDays(DateTime YYYYMMddHHmmss)
    {
        string DateTimeInJulianFormat = string.Empty;
        DateTime julianStartDate = new DateTime(1995, 1, 1, 00, 00, 00); //YYYY,MM,dd,HH,mm,ss

        DateTime DateTimeNow = YYYYMMddHHmmss;

        double difference = (DateTimeNow - julianStartDate).TotalDays;

        int totalDays = int.Parse(difference.ToString());

        DateTimeInJulianFormat = string.Format("{0:X}", totalDays);

        return DateTimeInJulianFormat;
    }
他不在意 2024-10-27 23:12:23

在 razo 页面中:

代码:

ViewData["jul"] = DateTime.Now.ToOADate() + 2415018.5;

视图:

@ViewData["jul"]

仅在视图中:

@{Double jday= DateTime.Now.ToOADate() + 2415018.5;}
@jday

in razo pages:

code:

ViewData["jul"] = DateTime.Now.ToOADate() + 2415018.5;

view:

@ViewData["jul"]

in view only:

@{Double jday= DateTime.Now.ToOADate() + 2415018.5;}
@jday
烟织青萝梦 2024-10-27 23:12:23

我在微控制器中使用了一些计算,但只需要 2000 年到 2255 年之间的年份。
这是我的代码:

typedef struct {
    unsigned int8   seconds;    // 0 to 59
    unsigned int8   minutes;    // 0 to 59
    unsigned int8   hours;      // 0 to 23  (24-hour time)
    unsigned int8   day;        // 1 to 31
    unsigned int8   weekday;    // 0 = Sunday, 1 = Monday, etc.
    unsigned int8   month;      // 1 to 12
    unsigned int8   year;       // (2)000 to (2)255
    unsigned int32  julian;     // Julian date
} date_time_t;

    

// Convert from DD-MM-YY HH:MM:SS to JulianTime

void JulianTime(date_time_t * dt)
{
    unsigned int8   m, y;

    y = dt->year;
    m = dt->month;
    if (m > 2) m -= 3;
    else {
        m +=  9;
        y --;
    }
    dt->julian  = ((1461 * y) / 4) + ((153 * m + 2) / 5) + dt->day;
    dt->weekday = ( dt->julian + 2 ) % 7;
    dt->julian  = (dt->julian * 24) + (dt->hours   ); 
    dt->julian  = (dt->julian * 60) + (dt->minutes );     
    dt->julian  = (dt->julian * 60) + (dt->seconds );     
}

// Reverse from JulianTime to DD-MM-YY HH:MM:SS

void GregorianTime(date_time_t *dt) 
{
    unsigned int32  j = dt->julian;

    dt->seconds = j % 60;
    j /= 60;
    dt->minutes = j % 60;
    j /= 60;
    dt->hours   = j % 24;
    j /= 24;
    dt->weekday = ( j + 2 ) % 7; // Get day of week
    dt->year = (4 * j) / 1461;
    j = j - ((1461 * dt->year) / 4);
    dt->month = (5 * j - 3) / 153;
    dt->day  = j - (((dt->month * 153) + 3) / 5);
    if ( dt->month < 10 )
    {
        dt->month += 3;
    }
    else
    {
        dt->month -= 9;
        dt->year ++;
    }
}

希望这有帮助:D

I use some calculations in microcontrollers but require years only between 2000 and 2255.
Here is my code:

typedef struct {
    unsigned int8   seconds;    // 0 to 59
    unsigned int8   minutes;    // 0 to 59
    unsigned int8   hours;      // 0 to 23  (24-hour time)
    unsigned int8   day;        // 1 to 31
    unsigned int8   weekday;    // 0 = Sunday, 1 = Monday, etc.
    unsigned int8   month;      // 1 to 12
    unsigned int8   year;       // (2)000 to (2)255
    unsigned int32  julian;     // Julian date
} date_time_t;

    

// Convert from DD-MM-YY HH:MM:SS to JulianTime

void JulianTime(date_time_t * dt)
{
    unsigned int8   m, y;

    y = dt->year;
    m = dt->month;
    if (m > 2) m -= 3;
    else {
        m +=  9;
        y --;
    }
    dt->julian  = ((1461 * y) / 4) + ((153 * m + 2) / 5) + dt->day;
    dt->weekday = ( dt->julian + 2 ) % 7;
    dt->julian  = (dt->julian * 24) + (dt->hours   ); 
    dt->julian  = (dt->julian * 60) + (dt->minutes );     
    dt->julian  = (dt->julian * 60) + (dt->seconds );     
}

// Reverse from JulianTime to DD-MM-YY HH:MM:SS

void GregorianTime(date_time_t *dt) 
{
    unsigned int32  j = dt->julian;

    dt->seconds = j % 60;
    j /= 60;
    dt->minutes = j % 60;
    j /= 60;
    dt->hours   = j % 24;
    j /= 24;
    dt->weekday = ( j + 2 ) % 7; // Get day of week
    dt->year = (4 * j) / 1461;
    j = j - ((1461 * dt->year) / 4);
    dt->month = (5 * j - 3) / 153;
    dt->day  = j - (((dt->month * 153) + 3) / 5);
    if ( dt->month < 10 )
    {
        dt->month += 3;
    }
    else
    {
        dt->month -= 9;
        dt->year ++;
    }
}

Hope this helps :D

稚气少女 2024-10-27 23:12:23

您链接的维基百科页面包含用于从儒略历或公历转换的代码。例如,您可以选择转换公历时代之前的日期,这称为“预推公历”。

根据所选的“转换”日历,输出会有所不同。这是因为日历本身是不同的构造,并以不同的方式处理各种类型的对齐/更正。

public enum ConversionCalendar
{
  GregorianCalendar,
  JulianCalendar,
}

public static int ConvertDatePartsToJdn(int year, int month, int day, ConversionCalendar conversionCalendar)
{
  switch (conversionCalendar)
  {
    case ConversionCalendar.GregorianCalendar:
      return ((1461 * (year + 4800 + (month - 14) / 12)) / 4 + (367 * (month - 2 - 12 * ((month - 14) / 12))) / 12 - (3 * ((year + 4900 + (month - 14) / 12) / 100)) / 4 + day - 32075);
    case ConversionCalendar.JulianCalendar:
      return (367 * year - (7 * (year + 5001 + (month - 9) / 7)) / 4 + (275 * month) / 9 + day + 1729777);
    default:
      throw new System.ArgumentOutOfRangeException(nameof(calendar));
  }
}

人们还可以从 JDN 转换回日期组件:

public static void ConvertJdnToDateParts(int julianDayNumber, ConversionCalendar conversionCalendar, out int year, out int month, out int day)
{
  var f = julianDayNumber + 1401;

  if (conversionCalendar == ConversionCalendar.GregorianCalendar)
    f += (4 * julianDayNumber + 274277) / 146097 * 3 / 4 + -38;

  var eq = System.Math.DivRem(4 * f + 3, 1461, out var er);
  var hq = System.Math.DivRem(5 * (er / 4) + 2, 153, out var hr);

  day = hr / 5 + 1;
  month = ((hq + 2) % 12) + 1;
  year = eq - 4716 + (14 - month) / 12;
}

这些方法是根据维基百科上的代码创建的,所以它们应该可以工作,除非我搞砸了一些事情。

The wikipedia page you linked contain the code for conversion from either the Julian or the Gregorian calendars. E.g. you can choose to convert a date prior to the Gregorian calendar era, which is called 'the proleptic Gregorian calendar'.

Depending on the chosen 'conversion' calendar the output will vary. This is because the calendars themselves are different constructs and deals with alignments/corrections of various sorts in different ways.

public enum ConversionCalendar
{
  GregorianCalendar,
  JulianCalendar,
}

public static int ConvertDatePartsToJdn(int year, int month, int day, ConversionCalendar conversionCalendar)
{
  switch (conversionCalendar)
  {
    case ConversionCalendar.GregorianCalendar:
      return ((1461 * (year + 4800 + (month - 14) / 12)) / 4 + (367 * (month - 2 - 12 * ((month - 14) / 12))) / 12 - (3 * ((year + 4900 + (month - 14) / 12) / 100)) / 4 + day - 32075);
    case ConversionCalendar.JulianCalendar:
      return (367 * year - (7 * (year + 5001 + (month - 9) / 7)) / 4 + (275 * month) / 9 + day + 1729777);
    default:
      throw new System.ArgumentOutOfRangeException(nameof(calendar));
  }
}

One can also convert back from JDN to date components:

public static void ConvertJdnToDateParts(int julianDayNumber, ConversionCalendar conversionCalendar, out int year, out int month, out int day)
{
  var f = julianDayNumber + 1401;

  if (conversionCalendar == ConversionCalendar.GregorianCalendar)
    f += (4 * julianDayNumber + 274277) / 146097 * 3 / 4 + -38;

  var eq = System.Math.DivRem(4 * f + 3, 1461, out var er);
  var hq = System.Math.DivRem(5 * (er / 4) + 2, 153, out var hr);

  day = hr / 5 + 1;
  month = ((hq + 2) % 12) + 1;
  year = eq - 4716 + (14 - month) / 12;
}

These methods were created from the code on wikipedia, so they should work, unless I fumbled something up.

好听的两个字的网名 2024-10-27 23:12:23

根据 2000 年 1 月 1 日 11:58:55,800 UTC (J2000.0) 的定义,

自第一天起已经过去了 2451545 JD(儒略日)。

const long J2000UtcTicks = 630823247358000000L; // (new DateTime(2000,1,1,11,58,55,800)).Ticks
const double TicksPerDay = 24 * 60 * 60 * 1E7; // 100ns is equal to 1 tick

// to convert any
DateTime dt;

// you need to convert to timezone GMT and calc the ticks ...
double ticks = dt.ToUniversalTime().Ticks - J2000UtcTicks;
return 2451545d + ticks / TicksPerDay;

Per definition on 1.1.2000 at 11:58:55,800 UTC (J2000.0)

exactly 2451545 JD (julian days) had passed since the very first day.

const long J2000UtcTicks = 630823247358000000L; // (new DateTime(2000,1,1,11,58,55,800)).Ticks
const double TicksPerDay = 24 * 60 * 60 * 1E7; // 100ns is equal to 1 tick

// to convert any
DateTime dt;

// you need to convert to timezone GMT and calc the ticks ...
double ticks = dt.ToUniversalTime().Ticks - J2000UtcTicks;
return 2451545d + ticks / TicksPerDay;
这个俗人 2024-10-27 23:12:23

好的,所以我使用儒略日期将它们存储在 SQLite 数据库中。好消息是该库内置了对此类日期的支持。您无需编写任何额外代码即可使用它们。
因此,要启动并运行该程序,请执行以下操作:

第 1 步:下载正确的 DLL:System.Data.Sqlite.DLL
您可以在 www.dll-files.com 上找到此内容。我选择的是 64 位版本。

步骤 2:在您的项目中引用此 DLL。

第 3 步:启动时,您可能会收到一条恼人的消息,表明该库是混合模式并且是针对早期框架构建的。要解决这个问题,请打开 app.config 并将这一行修改

  <startup>

  <startup useLegacyV2RuntimeActivationPolicy="true"> 

“现在你应该可以开始了”。
示例代码(当然可以使用“using”指令来缩短):

  var d = new DateTime(2023, 5, 25, 16, 11, 30);
  Debug.Print(d.ToString());
  var j = System.Data.SQLite.SQLiteConvert.ToJulianDay(d);
  Debug.Print(j.ToString());
  var d2 = System.Data.SQLite.SQLiteConvert.ToDateTime(j, DateTimeKind.Local);
  Debug.Print(d2.ToString());

输出是:

 2023-05-25 16:11:30
 2460090.17465278
 2023-05-25 16:11:30

OK, so I used Julian dates to store them in SQLite databases. The good news is that support for such dates is built into that library. You can use those without writing any extra code.
So here's to get that up and running:

Step 1: Download the right DLL: System.Data.Sqlite.DLL.
You can find this on the web on www.dll-files.com. I took the 64-bits version.

Step 2: Make a reference to this DLL in your project.

Step 3: On startup you may get an annoying message that the library is mixed mode and built against an earlier framework. To solve that, open app.config and modify this line

  <startup>

into

  <startup useLegacyV2RuntimeActivationPolicy="true"> 

Now you should be good to go.
Example code (can be shortened with a 'using' directive of course):

  var d = new DateTime(2023, 5, 25, 16, 11, 30);
  Debug.Print(d.ToString());
  var j = System.Data.SQLite.SQLiteConvert.ToJulianDay(d);
  Debug.Print(j.ToString());
  var d2 = System.Data.SQLite.SQLiteConvert.ToDateTime(j, DateTimeKind.Local);
  Debug.Print(d2.ToString());

And the output from this is:

 2023-05-25 16:11:30
 2460090.17465278
 2023-05-25 16:11:30
乖乖哒 2024-10-27 23:12:23

以下函数将日期转换为与 Tradestation Easylanguage 相匹配的儒略日期:
public double ToJulianDate(DateTime date) { return date.ToOADate(); }

The following function converts a date to Julian date that matches that of Tradestation Easylanguage:
public double ToJulianDate(DateTime date) { return date.ToOADate(); }

守不住的情 2024-10-27 23:12:22

OADate 与儒略日期类似,但使用不同的起点(1899 年 12 月 30 日与公元前 4713 年 1 月 1 日)和不同的“新一天”点。 Julian Dates 认为中午是新一天的开始,OADates 使用现代定义“午夜”。

1899 年 12 月 30 日午夜的儒略日期是 2415018.5。此方法应该为您提供正确的值:

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

至于算法:

  • if (Month < 3) ...:为了使幻数发挥作用,他们将二月放在“末尾” ” 今年。
  • (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 是每月的平均天数,不包括二月(准确地说是 30.63 重复)。 91.4 几乎是 3 个非 2 月份的平均天数。 (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 将补偿前三个月的天数,这些天数被移至一年的“年底”,并调整它将连续差值 1(上表中的 0.6 * 4 和 0.6 * 5)移动 0.4,以与 31 天的相邻月份对齐。
    • 由于二月现在是一年的“年底”,我们不需要处理它的长度。它可能有 45 天(闰年为 46 天),唯一需要改变的是一年中的天数常数 365。
    • 请注意,这依赖于 30 个月和 31 个月的天数模式。如果我们连续两个月都是 30 天,这是不可能的。
  • 365 * Year:每年的天数
  • (Year / 4) - (Year / 100) + (Year / 400):每 4 年加 1 个闰日,每 4 年减去 1 个闰日100,每 400 加一。
  • + 1721119:这是儒略日期,公元前 1 年 3 月 2 日。由于我们将日历的“开始”从 1 月移至 3 月,因此我们使用它作为偏移量,而不是 1 月 1 日。由于没有零年,所以 1 BC 得到的整数值为 0。至于为什么是 3 月 2 日而不是 3 月 1 日,我猜那是因为整个月份的计算在最后仍然有点偏差。如果原始作者在浮点中使用 - 462 而不是 - 457- 92.4 而不是 - 91.4 math),那么偏移量将是 3 月 1 日。

OADate is similar to Julian Dates, but uses a different starting point (December 30, 1899 vs. January 1, 4713 BC), and a different 'new day' point. Julian Dates consider noon to be the beginning of a new day, OADates use the modern definition, midnight.

The Julian Date of midnight, December 30, 1899 is 2415018.5. This method should give you the proper values:

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

As for the algorithm:

  • if (Month < 3) ...: To make the magic numbers work our right, they're putting February at the 'end' of the year.
  • (153 * Month - 457) / 5: Wow, that's some serious magic numbers.
    • Normally, the number of days in each month is 31 28 31 30 31 30 31 31 30 31 30 31, but after that adjustment in the if statement, it becomes 31 30 31 30 31 31 30 31 30 31 31 28. Or, subtract 30 and you end up with 1 0 1 0 1 1 0 1 0 1 1 -2. They're creating that pattern of 1s and 0s by doing that division in integer space.
    • Re-written to floating point, it would be (int)(30.6 * Month - 91.4). 30.6 is the average number of days per month, excluding February (30.63 repeating, to be exact). 91.4 is almost the number of days in 3 average non-February months. (30.6 * 3 is 91.8).
    • So, let's remove the 30, and just focus on that 0.6 days. If we multiply it by the number of months, and then truncate to an integer, we'll get a pattern of 0s and 1s.
      • 0.6 * 0 = 0.0 -> 0
      • 0.6 * 1 = 0.6 -> 0 (difference of 0)
      • 0.6 * 2 = 1.2 -> 1 (difference of 1)
      • 0.6 * 3 = 1.8 -> 1 (difference of 0)
      • 0.6 * 4 = 2.4 -> 2 (difference of 1)
      • 0.6 * 5 = 3.0 -> 3 (difference of 1)
      • 0.6 * 6 = 3.6 -> 3 (difference of 0)
      • 0.6 * 7 = 4.2 -> 4 (difference of 1)
      • 0.6 * 8 = 4.8 -> 4 (difference of 0)
    • See that pattern of differences in the right? That's the same pattern in the list above, the number of days in each month minus 30. The subtraction of 91.8 would compensate for the number of days in the first three months, that were moved to the 'end' of the year, and adjusting it by 0.4 moves the successive differences of 1 (0.6 * 4 and 0.6 * 5 in the above table) to align with the adjacent months that are 31 days.
    • Since February is now at the 'end' of the year, we don't need to deal with its length. It could be 45 days long (46 on a leap year), and the only thing that would have to change is the constant for the number of days in a year, 365.
    • Note that this relies on the pattern of 30 and 31 month days. If we had two months in a row that were 30 days, this would not be possible.
  • 365 * Year: Days per year
  • (Year / 4) - (Year / 100) + (Year / 400): Plus one leap day every 4 years, minus one every 100, plus one every 400.
  • + 1721119: This is the Julian Date of March 2nd, 1 BC. Since we moved the 'start' of the calendar from January to March, we use this as our offset, rather than January 1st. Since there is no year zero, 1 BC gets the integer value 0. As for why March 2nd instead of March 1st, I'm guessing that's because that whole month calculation was still a little off at the end. If the original writer had used - 462 instead of - 457 (- 92.4 instead of - 91.4 in floating point math), then the offset would have been to March 1st.
东风软 2024-10-27 23:12:22

虽然该方法

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 的“天文算法” ,返回从 1 月 1 日中午 -4712(儒略历时间零)开始的所有日期的正确结果。如果传递了无效日期,它们还会抛出 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);
    }
}

While the method

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

works for modern dates, it has significant shortcomings.

The Julian date is defined for negative dates - i.e, BCE (before common era) dates and is common in astronomical calculations. You cannot construct a DateTime object with the year less than 0, and so the Julian Date cannot be computed for BCE dates using the above method.

The Gregorian calendar reform of 1582 put an 11 day hole in the calendar between October 4th and the 15th. Those dates are not defined in either the Julian calendar or the Gregorian calendar, but DateTime accepts them as arguments. Furthermore, using the above method does not return the correct value for any Julian date. Experiments with using the System.Globalization.JulianCalendar.ToDateTime(), or passing the JulianCalendar era into the DateTime constructor still produce incorrect results for all dates prior to October 5, 1582.

The following routines, adapted from Jean Meeus' "Astronomical Algorithms", returns correct results for all dates starting from noon on January 1st, -4712, time zero on the Julian calendar. They also throw an ArgumentOutOfRangeException if an invalid date is passed.

 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);
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文