Convert DateTime to Julian Date in C# (ToOADate Safe?)
Asked Answered
S

12

35

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:

Susette answered 9/3, 2011 at 16:27 Comment(0)
L
94

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.
Leopard answered 10/3, 2011 at 2:33 Comment(5)
Sorry I don't like math, but you did it great. ThanksBeaver
This doesn't consider that DateTime values are in the Gregorian calendar, and JD are based on the Julian calendar. Wikipedia provides a proleptic Gregorian starting date of November 24, 4714 BC for JD values. I think your values need adjusting.Eyesight
This worked for me. Automation date 0 begins at 1899-12-30 midnight. The example counts the number of Julian days after this date. Input Dates before this date (when value is less than 2415018.5) will have a negative automation value. Example: Input Julian date 2459886.5 (2022-11-03) minus Julian days 2415018.5 (1899-12-30) = 44868 Automation Date Days. In summary; A typical conversion uses 2415018.5 = 1899-12-30, Modified date uses: 2400000.5 = 1858-11-17, Truncated date uses: 2440000.5 = 1968-05-24Cenogenesis
If the input date is a Modified date; The truncated date is the julian modified date - 40000Cenogenesis
Add validation for the output Automation Date. e.g. Minimum negative automation date value supported: -657434 // 0100-01-01 12:00:00 AM and Maximum automation date value supported: 2958465 // 9999-12-31 12:00:00 AMCenogenesis
S
12

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);
    }
}
Sow answered 28/1, 2013 at 2:5 Comment(1)
While this generally works, the formula for D needs to read ... (second + millisecond / 1000.0)..., otherwise the result is way off for DateTime values containing milliseconds.Essen
V
6

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

public static DateTime FromJulianDate(double julianDate)
{
    return DateTime.FromOADate(julianDate - 2415018.5);
}
Veliger answered 28/12, 2016 at 20:39 Comment(2)
You didn't answer the question, but you gave me exactly what I was looking for, exactly when I needed it! Thanks :)Peeve
I don't see how this caters for the difference in the way the Julian calendar handles leap years, but it seemed to work when compared with an online calculator. Thanks.Sneaking
E
4

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.

Extravehicular answered 22/6, 2014 at 16:54 Comment(0)
P
1

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);
    }
Prunelle answered 11/11, 2016 at 14:45 Comment(0)
D
1

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;
    }
Drouin answered 30/11, 2021 at 10:8 Comment(0)
D
1

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
Derwin answered 16/11, 2022 at 8:29 Comment(0)
S
0

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

Selfrighteous answered 24/9, 2020 at 18:33 Comment(0)
W
0

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.

Wildon answered 21/10, 2021 at 15:40 Comment(0)
C
0

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;
Crone answered 7/10, 2022 at 21:3 Comment(2)
This code is exact for JD from 1999 to 2005. Writing in 2022 there were 5 leap seconds added since J2000.0. The first one on 31.12.2005 the last one on 31.12.2016 - meaning the last minute of the day was 61 seconds long or in other words: a second was not counted in DateTimeTicks but happened in julian days. en.wikipedia.org/wiki/Leap_second So - writing this in 2022 - you need to add 5E7 ticks (5 seconds) to calculate the amount of ticks since J2000.0 for a DateTime.Now correctly.Crone
Concluding if someone wants to calculate some fancy astronomical stuff like sunset: if you forget to convert the timezone your predicted sunset is wrong by hours, if you do convert but use ToOADate() like emphasized in other comments here you are wrong by minutes. If you do not care about leap seconds since 2000 you are wrong by some seconds (which is acceptable for human observations) - five seconds in 2022… but if you want do be exactly correct there’s no way but adding and mainaining a table containing these manually added leap seconds! en.wikipedia.org/wiki/Leap_secondCrone
S
-1

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
Spracklen answered 7/8, 2023 at 1:22 Comment(0)
A
-3

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

Auckland answered 22/6, 2021 at 23:52 Comment(1)
This answer is exactly not what the question is asking for. This might answer some completely different question, but certainly not the question here.Weevily

© 2022 - 2024 — McMap. All rights reserved.