Convierta DateTime a Julian Date en C # (¿ToOADate Safe?)

Necesito convertir de una fecha gregoriana estándar a un número de día juliano .

No he visto nada documentado en C # para hacer esto directamente, pero he encontrado muchas publicaciones (mientras buscaba en Google) que sugerían el uso de ToOADate .

La documentación en ToOADate no sugiere esto como un método de conversión válido para fechas julianas.

¿Alguien puede aclarar si esta función realizará la conversión con precisión, o tal vez un método más apropiado para convertir DateTime en una cadena con formato juliano?


Esto me proporciona el número esperado cuando se valida contra la página Julian Day de 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; } 

Sin embargo, esto es sin una comprensión de los números mágicos que se utilizan.

Gracias


Referencias

  • Método DateTime.ToOADate

OADate es similar a Julian Dates, pero utiliza un punto de partida diferente (30 de diciembre de 1899 frente a 1 de enero de 4713 aC) y un punto diferente de “nuevo día”. Julian Dates considera que el mediodía es el comienzo de un nuevo día. OADates usa la definición moderna, medianoche.

La fecha juliana de la medianoche del 30 de diciembre de 1899 es 2415018.5. Este método debería darle los valores adecuados:

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

En cuanto al algoritmo:

  • if (Month < 3) ... : para hacer que los números mágicos funcionen correctamente, están poniendo febrero al final del año.
  • (153 * Month - 457) / 5 : Guau, esos son algunos números mágicos serios.
    • Normalmente, el número de días en cada mes es 31 28 31 30 31 30 31 31 30 31 30 31, pero después de ese ajuste en la statement if, se convierte en 31 30 31 30 31 31 30 31 30 31 31 28. O bien, reste 30 y terminas con 1 0 1 0 1 1 0 1 0 1 1 -2. Están creando ese patrón de 1s y 0s haciendo esa división en un espacio entero.
    • Reescrito en punto flotante, sería (int)(30.6 * Month - 91.4) . 30.6 es el número promedio de días por mes, excluyendo febrero (30.63 repitiendo, para ser exactos). 91.4 es casi la cantidad de días en 3 meses promedio que no son de febrero. (30.6 * 3 es 91.8).
    • Entonces, eliminemos el 30, y solo nos concentremos en esos 0.6 días. Si lo multiplicamos por el número de meses y luego lo truncamos a un número entero, obtendremos un patrón de 0 y 1.
      • 0.6 * 0 = 0.0 -> 0
      • 0.6 * 1 = 0.6 -> 0 (diferencia de 0)
      • 0.6 * 2 = 1.2 -> 1 (diferencia de 1)
      • 0.6 * 3 = 1.8 -> 1 (diferencia de 0)
      • 0.6 * 4 = 2.4 -> 2 (diferencia de 1)
      • 0.6 * 5 = 3.0 -> 3 (diferencia de 1)
      • 0.6 * 6 = 3.6 -> 3 (diferencia de 0)
      • 0.6 * 7 = 4.2 -> 4 (diferencia de 1)
      • 0.6 * 8 = 4.8 -> 4 (diferencia de 0)
    • ¿Ves ese patrón de diferencias en el derecho? Ese es el mismo patrón en la lista anterior, el número de días en cada mes menos 30. La resta de 91.8 compensaría la cantidad de días en los primeros tres meses, que se movieron al 'final' del año, y el ajuste por 0.4 mueve las diferencias sucesivas de 1 (0.6 * 4 y 0.6 * 5 en la tabla anterior) para alinearse con los meses adyacentes que son 31 días.
    • Dado que febrero es ahora al final del año, no necesitamos lidiar con su longitud. Podría durar 45 días (46 en un año bisiesto), y lo único que tendría que cambiar es la constante para la cantidad de días en un año, 365.
    • Tenga en cuenta que esto se basa en el patrón de 30 y 31 días de mes. Si tuviéramos dos meses seguidos que fueran 30 días, esto no sería posible.
  • 365 * Year : días por año
  • (Year / 4) - (Year / 100) + (Year / 400) : más un día bisiesto cada 4 años, menos uno cada 100, más uno cada 400.
  • + 1721119 : Esta es la fecha juliana del 2 de marzo, 1 a. Dado que movimos el 'inicio' del calendario de enero a marzo, usamos esto como nuestro desplazamiento, en lugar del 1 de enero. Como no hay año cero, 1 BC obtiene el valor entero 0. En cuanto a por qué el 2 de marzo en lugar del 1 de marzo, supongo que es porque el cálculo de todo el mes todavía estaba un poco apagado al final. Si el escritor original usó - 462 lugar de - 457 ( - 92.4 lugar de - 91.4 en matemáticas de punto flotante), entonces el desplazamiento habría sido al 1 de marzo.

Mientras que el método

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

funciona para fechas modernas, tiene importantes deficiencias.

La fecha juliana se define para fechas negativas, es decir, fechas BCE (antes de la era común) y es común en cálculos astronómicos. No puede construir un objeto DateTime con un año inferior a 0, por lo que la fecha juliana no se puede calcular para las fechas BCE utilizando el método anterior.

La reforma del calendario gregoriano de 1582 puso un agujero de 11 días en el calendario entre el 4 y el 15 de octubre. Esas fechas no están definidas ni en el calendario juliano ni en el calendario gregoriano, pero DateTime las acepta como argumentos. Además, el uso del método anterior no devuelve el valor correcto para cualquier fecha juliana. Los experimentos con el uso de System.Globalization.JulianCalendar.ToDateTime () o al pasar la era JulianCalendar al constructor DateTime aún producen resultados incorrectos para todas las fechas anteriores al 5 de octubre de 1582.

Las siguientes rutinas, adaptadas de los “Astronomical Algorithms” de Jean Meeus, arrojan resultados correctos para todas las fechas a partir del mediodía del 1 de enero, -4712, hora cero en el calendario juliano. También lanzan una ArgumentOutOfRangeException si se pasa una fecha no válida.

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

La explicación de David Yaw es acertada, pero el cálculo del número acumulativo de días del año para los meses previos al mes dado es anti-intuitivo. Si prefieres una matriz de enteros para que el algoritmo sea más claro, entonces:

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

y las primeras tres líneas del cálculo se convierten en:

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

La expresion

 (153*month - 457)/5) 

aunque produce la misma secuencia exacta de enteros que la matriz anterior para valores en el rango: 3 a 14; inclusive y lo hace sin requisitos de almacenamiento. La falta de requisitos de almacenamiento es solo una virtud al calcular el número acumulado de días de una manera tan ofuscada.

Si alguien necesita convertir una fecha juliana a DateTime, consulte a continuación:

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

Mi código para Julian Date modificado utiliza el mismo algoritmo pero un número mágico diferente en el extremo para que el valor resultante coincida con la fecha juliana modificada que se muestra en Wikipedia . He estado utilizando este mismo algoritmo durante al menos 10 años como la clave para series de tiempo diarias (originalmente en 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; } 

El cálculo inverso tiene más números mágicos para su diversión:

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