Конвертировать DateTime в юлианскую дату в C# (ToOADate Safe?)
Мне нужно преобразовать стандартную григорианскую дату в число юлианских дней.
Я не видел ничего задокументированного в 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;
}
Тем не менее, это без понимания используемых магических чисел.
Спасибо
Рекомендации:
12 ответов
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 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 - это почти число дней в трех средних не февральских месяцах. (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 марта.
Пока метод
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 года.
Следующие процедуры, адаптированные из "Астрономических алгоритмов" Жана Миуса, возвращают правильные результаты для всех дат, начиная с полудня 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);
}
}
Если кому-то нужно преобразовать юлианскую дату в DateTime, см. Ниже:
public static DateTime FromJulianDate(double julianDate)
{
return DateTime.FromOADate(julianDate - 2415018.5);
}
Объяснения Дэвида Йоу точны, но расчет кумулятивного числа дней в году за месяцы, предшествующие данному месяцу, не интуитивен. Если вы предпочитаете массив целых чисел, чтобы сделать алгоритм более понятным, то это будет делать:
/*
* 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; включительно и делает это без требований к хранению. Отсутствие требований к хранению является лишь достоинством при расчете совокупного количества дней таким и запутанным способом.
Приведенный ниже метод дает вам юлианские дни, начиная с 1 января 1995 г., 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;
}
Мой код для модифицированной юлианской даты использует тот же алгоритм, но с другим магическим числом на конце, так что полученное значение соответствует модифицированной юлианской дате, показанной в Википедии. Я использую этот же алгоритм в течение как минимум 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);
}
на разных страницах:
код:
ViewData["jul"] = DateTime.Now.ToOADate() + 2415018.5;
вид:
@ViewData["jul"]
только на виду:
@{Double jday= DateTime.Now.ToOADate() + 2415018.5;}
@jday
Я использую некоторые вычисления в микроконтроллерах, но мне нужны годы между 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;
// Преобразование из DD-MM-YY HH:MM:SS в 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 );
}
// Обратный переход от JulianTime к 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
По определению от 1 января 2000 г. в 11:58:55800 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;
Страница Википедии, на которую вы ссылаетесь, содержит код для преобразования из юлианского или григорианского календарей. Например, вы можете преобразовать дату, предшествующую эпохе григорианского календаря, которая называется «пролептическим григорианским календарем».
В зависимости от выбранного календаря «конверсии» результат будет отличаться. Это связано с тем, что сами календари представляют собой разные конструкции и по-разному относятся к выравниванию / исправлению различного рода.
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;
}
Эти методы были созданы из кода в Википедии, поэтому они должны работать, если я что-то не наткнулся.
Хорошо, поэтому я использовал юлианские даты для хранения их в базах данных 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
Следующая функция преобразует дату в юлианскую дату, соответствующую дате Tradestation Easylanguage:
public double ToJulianDate(DateTime date) { return date.ToOADate(); }