Конвертировать DateTime в юлианскую дату в C # (ToOADate Safe?) - PullRequest
27 голосов
/ 09 марта 2011

Мне нужно преобразовать стандартную григорианскую дату в юлианское число дня.

Я не видел ничего задокументированного в C #, чтобы сделать это напрямую, но я нашел много постов (в то время как поиск в Google), предлагающих использовать ToOADate .

Документация по ToOADate не предлагает это в качестве допустимого метода преобразования для юлианских дат.

Может кто-нибудь уточнить, будет ли эта функция выполнять преобразование точно, или, возможно, более подходящий метод для преобразования DateTime в строку в формате Юлиана.


Этот дает мне ожидаемое число при проверке по Страница Википедии по юлианскому дню

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

Однако, это без понимания используемых магических чисел.

Спасибо


Ссылка:

Ответы [ 5 ]

80 голосов
/ 10 марта 2011

OADate похож на юлианские даты, но использует другую отправную точку (30 декабря 1899 г. против 1 января 4713 г. до н.э.) и другую точку «нового дня».Джулиан Дэйтс считает, что полдень является началом нового дня, а OADates используют современное определение - полночь.

Юлианская дата полуночи 30 декабря 1899 года - 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 3131 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 - это почти число дней в трех средних не февральских месяцах.(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 компенсирует количество дней в первых трех месяцах, которые были перенесены на «конец» года, и корректировкуон на 0,4 сдвигает последовательные разности 1 (0,6 * 4 и 0,6 * 5 в приведенной выше таблице) для выравнивания с соседними месяцами, которые составляют 31 день.
    • Так как февраль сейчас в «конце» года, нам не нужно иметь дело с его продолжительностью.Это может длиться 45 дней (46 в високосный год), и единственное, что должно измениться, - это константа количества дней в году, 365.
    • Обратите внимание, что это зависит от модели30 и 31 месяц дней.Если бы у нас было два месяца подряд по 30 дней, это было бы невозможно.
  • 365 * Year: дней в году
  • (Year / 4) - (Year / 100) + (Year / 400): плюс одинвисокосный день каждые 4 года, минус один каждые 100, плюс один каждые 400.
  • + 1721119: это юлианская дата 2 марта 1 года до нашей эры.Поскольку мы перенесли «начало» календаря с января на март, мы используем это в качестве нашего смещения, а не 1 января.Поскольку нулевого года нет, 1 до н.э. получает целочисленное значение 0. Что касается того, что 2 марта вместо 1 марта, я предполагаю, что это потому, что вычисление целого месяца все еще было немного в конце.Если бы оригинальный писатель использовал - 462 вместо - 457 (- 92.4 вместо - 91.4 в математике с плавающей запятой), то смещение было бы до 1 марта.
7 голосов
/ 28 января 2013

Хотя метод

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

работает для современных дат, он имеет существенные недостатки.

Юлианская дата определена для отрицательных дат - то есть дат до нашей эры (до общей эры) и является обычной в астрономических вычислениях.Вы не можете построить объект DateTime с годом меньше 0, и поэтому юлианская дата не может быть рассчитана для дат BCE с использованием вышеуказанного метода.

Реформа григорианского календаря 1582 года создала 11-дневную дыру в календаре между4 и 15 октября.Эти даты не определены ни в юлианском календаре, ни в григорианском календаре, но DateTime принимает их в качестве аргументов.Кроме того, использование вышеуказанного метода не возвращает правильное значение для любой юлианской даты.Эксперименты с использованием System.Globalization.JulianCalendar.ToDateTime () или передачей эры JulianCalendar в конструктор DateTime по-прежнему дают неверные результаты для всех дат до 5 октября 1582 года.

Следующие процедуры, адаптированные из Jean«Астрономические алгоритмы» Мееуса возвращают правильные результаты для всех дат, начиная с полудня 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);
    }
}
4 голосов
/ 22 июня 2014

Объяснения Дэвида Йоу точны, но расчет кумулятивного числа дней в году за месяцы, предшествующие данному месяцу, антиинтуитивен. Если вы предпочитаете массив целых чисел, чтобы сделать алгоритм более понятным, то это будет делать:

    /*
     * 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; включительно и делает это без требований к хранению. Отсутствие требований к хранению является лишь достоинством при расчете совокупного количества дней таким и запутанным способом.

2 голосов
/ 28 декабря 2016

Если кому-то нужно преобразовать из юлианской даты в DateTime, см. Ниже:

public static DateTime FromJulianDate(double julianDate)
{
    return DateTime.FromOADate(julianDate - 2415018.5);
}
0 голосов
/ 11 ноября 2016

Мой код для модифицированной юлианской даты использует тот же алгоритм, но с другим магическим числом на конце, так что полученное значение соответствует модифицированной юлианской дате, показанной в Википедии . Я использовал этот же алгоритм в течение как минимум 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);
    }
...