web-dev-qa-db-fra.com

Convertir DateTime en date julienne en C # (ToOADate Safe?)

Je dois convertir une date grégorienne standard en un numéro de jour julien julien .

Je n'ai vu rien de documenté en C # pour le faire directement, mais j'ai trouvé de nombreux messages (pendant la recherche sur Google) suggérant l'utilisation de ToOADate .

La documentation sur ToOADate ne suggère pas cela comme une méthode de conversion valide pour les dates juliennes.

Quelqu'un peut-il préciser si cette fonction effectuera la conversion avec précision, ou peut-être une méthode plus appropriée pour convertir DateTime en une chaîne au format Julian.


Ce me fournit le nombre attendu lorsqu'il est validé par rapport à page 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;
}

Cependant, cela ne comprend pas les nombres magiques utilisés.

Merci


Références:

28
cweston

OADate est similaire aux dates juliennes, mais utilise un point de départ différent (30 décembre 1899 par rapport au 1er janvier 4713 avant JC) et un point de "nouveau jour" différent. Julian Dates considère que midi est le début d'une nouvelle journée, OADates utilise la définition moderne de minuit.

La date julienne de minuit, le 30 décembre 1899 est 2415018,5. Cette méthode devrait vous donner les valeurs appropriées:

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

Quant à l'algorithme:

  • if (Month < 3) ...: Pour que les nombres magiques fonctionnent à notre droite, ils mettent février à la "fin" de l'année.
  • (153 * Month - 457) / 5: Wow, ce sont des chiffres magiques sérieux.
    • Normalement, le nombre de jours de chaque mois est de 31 28 31 30 31 30 31 31 30 31 30 31, mais après cet ajustement dans le relevé if, il devient 31 30 31 30 31 31 30 31 30 31 31 28. Ou, soustrayez 30 et vous vous retrouvez avec 1 0 1 0 1 1 0 1 0 1 1 -2. Ils créent ce modèle de 1 et de 0 en faisant cette division dans l'espace entier.
    • Réécrit en virgule flottante, ce serait (int)(30.6 * Month - 91.4). 30,6 est le nombre moyen de jours par mois, hors février (30,63 répétition, pour être exact). 91,4 est presque le nombre de jours en 3 mois non février moyens. (30,6 * 3 est 91,8).
    • Supprimons donc les 30 et concentrons-nous sur ces 0,6 jours. Si nous le multiplions par le nombre de mois, puis que nous le tronquons en un entier, nous obtiendrons un modèle de 0 et de 1.
      • 0,6 * 0 = 0,0 -> 0
      • 0,6 * 1 = 0,6 -> 0 (différence de 0)
      • 0,6 * 2 = 1,2 -> 1 (différence de 1)
      • 0,6 * 3 = 1,8 -> 1 (différence de 0)
      • 0,6 * 4 = 2,4 -> 2 (différence de 1)
      • 0,6 * 5 = 3,0 -> 3 (différence de 1)
      • 0,6 * 6 = 3,6 -> 3 (différence de 0)
      • 0,6 * 7 = 4,2 -> 4 (différence de 1)
      • 0,6 * 8 = 4,8 -> 4 (différence de 0)
    • Voir ce modèle de différences à droite? C'est le même schéma dans la liste ci-dessus, le nombre de jours dans chaque mois moins 30. La soustraction de 91,8 compenserait le nombre de jours dans les trois premiers mois, qui ont été déplacés vers la "fin" de l'année, et en ajustant il de 0,4 déplace les différences successives de 1 (0,6 * 4 et 0,6 * 5 dans le tableau ci-dessus) pour s'aligner sur les mois adjacents qui sont de 31 jours.
    • Depuis février est maintenant à la "fin" de l'année, nous n'avons pas besoin de faire face à sa longueur. Elle pourrait durer 45 jours (46 sur une année bissextile), et la seule chose qui devrait changer est la constante du nombre de jours dans une année, 365.
    • Notez que cela repose sur le modèle des jours de 30 et 31 mois. Si nous avions deux mois consécutifs de 30 jours, ce ne serait pas possible.
  • 365 * Year: Jours par an
  • (Year / 4) - (Year / 100) + (Year / 400): Plus un jour bissextile tous les 4 ans, moins un tous les 100, plus un tous les 400.
  • + 1721119: Il s'agit de la date julienne du 2 mars 1 BC. Puisque nous avons déplacé le "début" du calendrier de janvier à mars, nous l'utilisons comme décalage, plutôt que le 1er janvier. Puisqu'il n'y a pas d'année zéro, 1 BC obtient la valeur entière 0. Quant à savoir pourquoi le 2 mars au lieu du 1er mars, je suppose que c'est parce que le calcul du mois entier était encore un peu décalé à la fin. Si l'auteur d'origine avait utilisé - 462 Au lieu de - 457 (- 92.4 Au lieu de - 91.4 En mathématiques à virgule flottante), alors l'offset aurait été au 1er mars .
82
David Yaw

Alors que la méthode

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

fonctionne pour les dates modernes, il présente des lacunes importantes.

La date julienne est définie pour les dates négatives - c'est-à-dire les dates BCE (avant l'ère commune) et est courante dans les calculs astronomiques. Vous ne pouvez pas construire un objet DateTime avec l'année inférieure à 0, et donc la date julienne ne peut pas être calculée pour les dates BCE à l'aide de la méthode ci-dessus.

La réforme du calendrier grégorien de 1582 a mis un trou de 11 jours dans le calendrier entre le 4 et le 15 octobre. Ces dates ne sont définies ni dans le calendrier julien ni dans le calendrier grégorien, mais DateTime les accepte comme arguments. En outre, l'utilisation de la méthode ci-dessus ne renvoie la valeur correcte pour aucune date julienne. Les expériences d'utilisation de System.Globalization.JulianCalendar.ToDateTime () ou de passage de l'ère JulianCalendar dans le constructeur DateTime produisent toujours des résultats incorrects pour toutes les dates antérieures au 5 octobre 1582.

Les routines suivantes, adaptées des "algorithmes astronomiques" de Jean Meeus, renvoient des résultats corrects pour toutes les dates à partir de midi le 1er janvier -4712, heure zéro sur le calendrier julien. Ils lèvent également une ArgumentOutOfRangeException si une date non valide est passée.

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

L'explication de David Yaw est exacte, mais le calcul du nombre cumulé de jours de l'année pour les mois précédant le mois donné est anti-intuitif. Si vous préférez un tableau d'entiers pour rendre l'algorithme plus clair, cela fera:

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

et les trois premières lignes du calcul deviennent alors:

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

L'expression

(153*month - 457)/5)

produit cependant la même séquence exacte, les mêmes nombres entiers que le tableau ci-dessus pour les valeurs dans la plage: 3 à 14; inclus et le fait sans aucune exigence de stockage. L'absence d'exigences de stockage n'est que vertu dans le calcul du nombre cumulé de jours de manière telle et obscurcie.

4
John Washburn

Si quelqu'un a besoin de convertir de date julienne en DateTime, voir ci-dessous:

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

Mon code pour la date julienne modifiée utilise le même algorithme mais un nombre magique différent à la fin afin que la valeur résultante corresponde à la date julienne modifiée indiquée sur Wikipedia . J'utilise ce même algorithme depuis au moins 10 ans comme clé pour les séries chronologiques quotidiennes (à l'origine 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;
    }

Le calcul inverse a plus de nombres magiques pour votre 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);
    }
0
user3902302