2011-03-09 32 views
24

Necesito convertir de un estándar fecha gregoriana a un número día Julian.Convertir DateTime a Julian Fecha en C# (ToOADate seguro?)

No he visto nada documentado en C# para hacer esto directamente, pero he encontrado muchas publicaciones (mientras busca en Google) que sugieren 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 quizás un método más apropiado para convertir DateTime en una cadena con formato juliano?


This me proporciona el número esperado cuando validado contra 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; 
} 

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

Gracias


Referencias:

Respuesta

74

OADate es similar a Julian fechas, pero utiliza un punto de partida diferente (30 de diciembre, 1,899 respecto a enero 1, 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, 30 de diciembre de 1899 es 2415018.5. Este método debe darle los valores adecuados:

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

En cuanto al algoritmo:

  • if (Month < 3) ...: hacer que los números mágicos funcionan nuestro derecho, que están poniendo de febrero en el 'fin' de la 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 declaración if, se convierte en 31 30 31 30 31 31 30 31 30 31 31 28 O bien, reste 30 y termina 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 coma 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 el 'final' del año, no es necesario que tratemos 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 el número de días en un año, 365.
    • Tenga en cuenta que esto depende del 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 de 2 de marzo, 1 aC . 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 había usado - 462 en lugar de - 457 (- 92.4 en lugar de - 91.4 en matemáticas de coma flotante), entonces el desplazamiento habría sido al 1 de marzo.
+0

Gracias por esta explicación detallada. – cweston

+0

+1. Impresionante publicación. – BSalita

+1

Lo siento, no me gustan las matemáticas, pero lo hiciste genial. Gracias – fiberOptics

7

Mientras que el método

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

obras para las 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); 
    } 
} 
+0

Si bien esto generalmente funciona, la fórmula para D necesita leer '... (segundo + milisegundo/1000.0) ...', de lo contrario, el resultado está fuera de los valores de DateTime que contienen milisegundos. – JBartlau

4

La explicación de David guiñada es el clavo, pero el cálculo del número acumulado de días del año durante los meses anteriores a la mes dado es anti-intuitivo. Si prefiere una matriz de enteros para hacer el algoritmo más claro, entonces esto va a hacer:

/* 
    * 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 los primeros thre líneas del cálculo convertirá entonces en:

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

La expresión

(153*month - 457)/5) 

aunque produce exactamente la misma secuencia que los enteros de 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.

0

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

Si alguien necesita convertir de fecha juliana a DateTime, ver más abajo:

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