Как мне использовать юлианские числа дня с API Календаря Java?

Юлианские числа дней - это средство представления временных меток в виде непрерывного количества дней (и дробных дней) с полудня UTC, 1 января, 4713 г. до н.э. Java 7 SE API не поддерживает этот формат. Разработчики, которые использовали базу данных SQLite, могли использовать встроенную поддержку Julian Day, предоставляемую функциями strftime().

Преимущества представления временных меток в виде юлианских чисел дня включают в себя:

  • Дата и время могут быть представлены с точностью до миллисекунды в примитивном типе данных (double)
  • Дни в году несколько конкретнее, чем секунды в дне
  • Обходит проблему "високосных секунд", если эта степень точности не важна
  • Дни между датами арифметика тривиальна; приоритет сортировки легко определить
  • Очень легкий

Недостатки

  • Java Date/Time API не имеет встроенной поддержки JDN
  • Не подходит для очень точных измерений времени
  • Определено только для UTC и должно быть сопоставлено с UTC по местному времени
  • Не подходит для отображения конечным пользователям; должны быть преобразованы / отформатированы перед отображением

Числа юлианского дня обычно используются в астрономических расчетах, и их определение очень стандартизировано и принято. Аналогичным образом, модифицированные юлианские дневные номера (отсчитываемые с полуночи UTC 17 ноября 1858 года) стандартно определены и используются в аэрокосмических приложениях (см. http://tycho.usno.navy.mil/mjd.html).

Для приложений, которые широко используют арифметическую или хронологическую сортировку по дате / времени (или если постоянные облегченные примитивы более привлекательны, чем постоянные временные метки), внутреннее представление даты и времени в виде JDN или MJD может иметь смысл для вас.

В следующем коде определяются функции, которые облегчают использование либо номеров дня Джулиана, либо модифицированных номеров дня Джулиана с помощью API даты / времени / календаря Java. Код основан на алгоритмах, опубликованных в "Астрономических алгоритмах" Жана Миуса, 1-е изд., 1991.

public class JulianDay {

    private static final int YEAR = 0;
    private static final int MONTH = 1;
    private static final int DAY = 2;
    private static final int HOURS = 3;
    private static final int MINUTES = 4;
    private static final int SECONDS = 5;
    private static final int MILLIS = 6;

    :
    :

    // Converts a timestamp presented as an array of integers in the following
    // order (from index 0 to 6): year,month,day,hours,minutes,seconds,millis
    // month (1-12), day (1-28 or 29), hours (0-23), min/sec (0-59) to a
    // Modified Julian Day Number.
    // For clarity and simplicity, the input values are assumed to be well-formed;
    // error checking is not implemented in the snippet.

    public static double toMJD(int[] ymd_hms) {

        int y = ymd_hms[YEAR];
        int m = ymd_hms[MONTH];
        double d = (double) ymd_hms[DAY];

        d = d + ((ymd_hms[HOURS] / 24.0) +
                 (ymd_hms[MINUTES] / 1440.0) +
                 (ymd_hms[SECONDS] / 86400.0) +
                 (ymd_hms[MILLIS] / 86400000.0));

        if (m == 1 || m == 2) {
            y--;
            m = m + 12;
        }

        double a = Math.floor(y / 100);
        double b = 2 - a + Math.floor(a / 4);

        return (Math.floor(365.25 * (y + 4716.0)) +
               Math.floor(30.6001 * (m + 1)) +
               d + b - 1524.5) - 2400000.5;  // for Julian Day omit the 2400000.5 term
    }

    // Converts an Modified Julian Day Number (double) to an integer array representing
    // a timestamp (year,month,day,hours,mins,secs,millis). Works for all positive JDN

    public static int[] toTimestamp(double mjd) {

        int ymd_hms[] = { -1, -1, -1, -1, -1, -1, -1 };
        int a, b, c, d, e, z;

        double jd = mjd + 2400000.5 + 0.5;  // if a JDN is passed as argument,
                                            // omit the 2400000.5 term
        double f, x;

        z = (int) Math.floor(jd);
        f = jd - z;

        if (z >= 2299161) {
            int alpha = (int) Math.floor((z - 1867216.25) / 36524.25);
            a = z + 1 + alpha - (int) Math.floor(alpha / 4);
        } else {
            a = z;
        }

        b = a + 1524;
        c = (int) Math.floor((b - 122.1) / 365.25);
        d = (int) Math.floor(365.25 * c);
        e = (int) Math.floor((b - d) / 30.6001);

        ymd_hms[DAY] = b - d - (int) Math.floor(30.6001 * e);
        ymd_hms[MONTH] = (e < 14)
                ? (e - 1)
                : (e - 13);
        ymd_hms[YEAR] = (ymd_hms[MONTH] > 2)
                ? (c - 4716)
                : (c - 4715);

        for (int i = HOURS; i <= MILLIS; i++) {
            switch(i) {
                case HOURS:
                    f = f * 24.0;
                    break;
                case MINUTES: case SECONDS:
                    f = f * 60.0;
                    break;
                case MILLIS:
                    f = f * 1000.0;
                    break;  
            }
            x = Math.floor(f);
            ymd_hms[i] = (int) x;
            f = f - x;
        }   

        return ymd_hms;
    }
}

Этот ответ также был предоставлен здесь: Как я могу конвертировать между датой Java и номером юлианского дня?, В текущем посте приведены ссылки на алгоритм, а также некоторые дополнительные обсуждения. Реализация вышеприведенных алгоритмов также не содержит зависимостей API Java (кроме функций Math).

4 ответа

java.time

Инфраструктура java.time, встроенная в Java 8 и более поздние версии, заменяет старые классы даты и времени, связанные с ранними версиями Java. Смотрите Oracle Tutorial. Большая часть функциональности была перенесена в Java 6 & 7 в ThreeTen-Backport и дополнительно адаптирована для Android в ThreeTenABP.

Классы java.time включают java.time.temporal.JulianFields, Этот класс предоставляет три реализации TemporalField предоставить ограниченную поддержку юлианских значений только для даты (без времени суток). Таким образом, вы можете получить целое количество дней, а не double просил в вопросе. Внимательно прочитайте этот учебный документ, чтобы убедиться, что он соответствует вашим ожиданиям. Обратите внимание, что в отличие от большинства других классов java.time, эти классы Julian игнорируют любую информацию о смещении от UTC или информацию о часовом поясе (всегда обрабатываются как локальная дата).

  • JULIAN_DAY → Количество целых дней со дня 0, то есть 1 января 4713 г. до н.э. в юлианском календаре ( -4713-11-24 григорианский).
  • MODIFIED_JULIAN_DAY → Как и JULIAN_DAY, но вычитать 2_400_000.5 (в основном отбрасывание первых двух цифр юлианского номера даты). Обратите внимание, что результаты здесь на единицу меньше (-1), чем число в юлианской дате вышеупомянутого элемента.
  • RATA_DIE → Аналогично двум пунктам выше в том смысле, что это количество дней с начала эпохи. Но здесь эпохой является дата ISO 8601 0001-01-01,

В этом примере мы начинаем с даты ISO 8601 1970-01-01,

LocalDate localDate = LocalDate.of ( 1970 , 1 , 1 );
long julianDate = JulianFields.JULIAN_DAY.getFrom ( localDate );
long modifiedJulianDate = JulianFields.MODIFIED_JULIAN_DAY.getFrom ( localDate );
long rataDie = JulianFields.RATA_DIE.getFrom ( localDate );

localDate: 1970-01-01 | julianDate: 2440588 | updatedJulianDate: 40587 | rataDie: 719163

ThreeTen-Extra

Проект ThreeTen-Extra является экспериментальным полигоном для возможных будущих дополнений к java.time. Название происходит от JSR 310, который определяет java.time.

Эта библиотека включает дополнительную поддержку юлианских дат в своей системе юлианского календаря ( хронология). Как и поддержка Java 8, эта библиотека ограничена значениями только для даты (без частичных дней или времени суток).

С помощью этой библиотеки вы можете создать экземпляр JulianDate объекты.

Множество методов и возможностей для вас там есть.

Я знаю, что это не API Java Calendar, но, возможно, вам стоит попробовать инструмент Jodd.

JulianDateStamp julianStamp = new JulianDateStamp(julianDays);
JDateTime jdate = new JDateTime(julianStamp);
Date date = new Date(jdate.getTimeInMillis());

Это прекрасно работает для:

  • 2113488,2746855323 -> 1074.06.01 18:35
  • 2453479,5866961805 -> 2005.04.19 02:04

Читать дальше

Для этого я написал небольшую утилиту. Это открытый исходный код, вы можете использовать его по своему усмотрению.

Java Date не идеальна, поскольку часовой пояс действительно имеет значение для юлианских дней, поэтому я предпочитаю ZonedDateTime. Код предлагает преобразование ZonedDateTime <-> JulianDay, Modified Julian Day и еще несколько полезных вещей. Также код тестируется.

Код находится на github и опубликован на maven Central .

Вот пример кода, который вы можете написать с помощью этой утилиты:

      import xyz.wirklich.astro.time.JulianDay;
import java.time.ZonedDateTime;
import java.time.ZonedId;

public void jdFromDate(Date d) {
  ZonedDateTime zdt = ZonedDateTime.ofInstant(d.toInstant(),
                                              ZoneId.systemDefault());
  JulianDay jd = new JulianDay(yzt);
  double jd_value = jd.getJd();
  double mjd_value = jd.getMjd();
  ZonedDateTime date_value = jd.getDate();
  // ...
}

ZonedDateTime позаботится обо всех раздражающих вещах, таких как часовые пояса, летнее время и т. д. Это отлично подходит для практических приложений, и в противном случае может быть очень неприятно.

Если вы готовы выйти за рамки базовых классов JDK, то Joda может быть решением. Йода поддерживает систему юлианского календаря. Со страницы их документа:

Chronology julianChrono = JulianChronology.getInstance();
DateTime dt = new DateTime(1066, 10, 14, 0, 0, 0, julianChrono);

Это будет битва при Гастингсе 1066 по системе юлианского календаря.

Другие вопросы по тегам