Преобразование юлианской даты в мгновение

Я сталкиваюсь с ситуацией, когда я хотел бы преобразовать юлианскую дату в java.time.Instant (если это имеет смысл), или некоторое время Java, которое может быть более легко понято. Мое понимание того, что такое юлианская дата, исходит от чтения страницы Википедии. Есть куча разных вариантов, и дата, которую я пытаюсь прочитать, использует другую эпоху, чем любая из них.

Например, предположим, что эпоха - это начало действия календаря (нового стиля) 1750 года, а юлианская дата - это 95906.27600694445 что в данном случае я считаю CE 2015 15 апреля 06: 37: 26.9 UT, как я могу получить от этого мгновение? Мне нужно будет настроить часовой пояс позже.

Я заметил, что есть класс под названием JulianFields, но я не знаю, где и как его использовать. Кроме того, большинство методов, которые я вижу в пакете, используют int или же long на самом деле ничего double,

Итак, есть ли простой способ конвертировать юлианскую дату с использованием другой эпохи в Java 8? Instant (или как-нибудь в другой раз, если я ошибаюсь).

4 ответа

Решение

Вот решение с использованием новых классов Java 8:

public class JulianDay {
    private static final double NANOS_PER_DAY = 24.0 * 60.0 * 60.0 * 1000000000.0;

    // Calculate Instants for some epochs as defined in Wikipedia.
    public static final Instant REDUCED_JD =
            ZonedDateTime.of(1858, 11, 16, 12, 0, 0, 0, ZoneOffset.UTC).toInstant();
    public static final Instant MODIFIED_JD =
            ZonedDateTime.of(1858, 11, 17, 0, 0, 0, 0, ZoneOffset.UTC).toInstant();
    public static final Instant JULIAN_DATE =
            REDUCED_JD.minus(2400000, ChronoUnit.DAYS);

    private final Instant epoch;

    public JulianDay(Instant epoch) {
        super();
        this.epoch = epoch;
    }

    public Instant toInstant(double day) {
        long l = (long) day;
        return epoch
                .plus(l, ChronoUnit.DAYS)
                .plusNanos(Math.round((day - l) * NANOS_PER_DAY));
    }

    public static void main(String[] args) {
        // Use the example values from Wikipedia for 2015-09-07 13:21 UTC.
        System.out.println(new JulianDay(REDUCED_JD).toInstant(57273.05625));
        // Output: 2015-09-07T13:21:00.000000126Z
        System.out.println(new JulianDay(MODIFIED_JD).toInstant(57272.55625));
        // Output: 2015-09-07T13:21:00.000000126Z
        System.out.println(new JulianDay(JULIAN_DATE).toInstant(2457273.05625));
        // Output: 2015-09-07T13:20:59.999991953Z
    }
}

Учитывая JulianFields Вы спросили о, вы можете определить пользовательский форматер, как это:

DateTimeFormatter formatter = new DateTimeFormatterBuilder()
    .appendValue(JulianFields.MODIFIED_JULIAN_DAY)
    .toFormatter().withZone(ZoneOffset.UTC);

К сожалению, он не поддерживает доли дней:

System.out.println(formatter.format(Instant.now())); // Output: 57249
System.out.println(LocalDate.from(formatter.parse("57249"))); // Output: 2015-08-15

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

Например, определение "измененного числа юлианского дня" представляет собой непрерывный подсчет дней с полуночи 17 ноября 1858 года. Я полагаю, что вы спрашиваете:

Как я могу преобразовать непрерывное количество дней в Англии с момента официального принятия григорианского календаря в мгновенное?

Я не уверен, где Грегорианская Эпоха официально началась после акта Календаря Нового Стиля. Я буду считать, что это 1 января 1752 года, то есть число 95906.276 с тех пор непрерывное количество дней

МЕТОД 1: Здесь приведен алгоритм обработки номера дня в представлении целочисленного массива в год, месяц (1-12), день (1-31), часы (0-23), мин (0-59), сек (0 -59), миллис:

    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;

    public static int[] toTimeStampArray(double yourEpochDayNumber) {

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

        // convert from your epoch (1/1/1752) to Julian Day Number
        double jd = yourEpochDayNumber + 2360965.5 + 0.5;
        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;
    }

Алгоритм адаптирован из Meeus J., Astronomical Algorithms, 2nd Ed.

Из этих данных вы можете создать LocalDateTime пример. Вы можете объединить это с ZoneId экземпляр для создания ZonedDateTime и получить Instant,

СПОСОБ 2. Если ваш номер дня уже считается в GMT/UTC и не требует каких-либо смещений для часового пояса или перехода на летнее время, то вы можете напрямую преобразовать номер дня (в вашей эпохе) в Instant следующее:

public Instant dayNumberToInstant(double dayNumber) {
    long millisFromPosixEpoch;

    final double POSIX_EPOCH_AS_DAYNUM = 79622.0

    millisFromPosixEpoch = (long) ((dayNumber - POSIX_EPOCH_AS_DAYNUM) *
        (86400.0 * 1000.0));
    return Instant.ofEpochMillis(millisFromPosixEpoch);
}

Эпоха 14 сентября 1752 г.

Мое чтение страницы Википедии по Акту о календаре (новый стиль) 1750 года указывает на эпохальную ссылку 1752-09-14.

Если мы добавим целую часть вашего входного числа 95906.27600694445, 95_906L, мы действительно получаем вашу целевую дату 15 апреля 2015 года (в современной календарной системе).

long input = 95_906L;
LocalDate epochCalendarNewStyleActOf1750 = LocalDate.of ( 1752 , Month.SEPTEMBER , 14 );
LocalDate localDate = epochCalendarNewStyleActOf1750.plusDays ( input );
System.out.println ( input + " days from epoch of: " + epochCalendarNewStyleActOf1750 + " is " + localDate );

95906 дней с эпохи: 1752-09-14 - 2015-04-15

Что касается дробного числа, которое я предполагаю, является долей числа секунд в общем 24-часовом дне.

В то время как LocalDate только для даты без времени суток, теперь нам нужно время суток, которое представлено нашим дробным числом. Так что вместо LocalDate мы переключаемся на OffsetDateTime,

OffsetDateTime epochCalendarNewStyleActOf1750 = LocalDate.of ( 1752 , Month.SEPTEMBER , 14 ).atStartOfDay ().atOffset ( ZoneOffset.UTC );

Мы используем BigDecimal как double а также Double являются технологией с плавающей точкой, которая меняет точность на скорость выполнения.

String input = "95906.27600694445";
BigDecimal bd = new BigDecimal ( input );

Вытащите из этого количество целых дней.

long days = bd.toBigInteger ().longValue ();

Работа на долю дня. Извлеките дробное число, вычитая целую часть.

BigDecimal fractionOfADay = bd.subtract ( new BigDecimal ( days ) ); // Extract the fractional number, separate from the integer number.

Мы предполагаем, что эта десятичная дробь является частью количества секунд в дне. Таким образом, мы можем умножить на количество секунд это день.

BigDecimal secondsFractional = new BigDecimal ( TimeUnit.DAYS.toSeconds ( 1 ) ).multiply ( fractionOfADay );

Из этого извлеките количество целых секунд. Из остатка выведите целое число наносекунд, разрешение классов java.time, включая OffsetDateTime а также Duration,

long secondsWhole = secondsFractional.longValue ();
long nanos = secondsFractional.subtract ( new BigDecimal ( secondsWhole ) ).multiply ( new BigDecimal ( 1_000_000_000L ) ).longValue ();

Создать Duration представлять количество времени, которое мы хотим добавить к нашей эпохе.

Duration duration = Duration.ofDays ( days ).plusSeconds ( secondsWhole ).plusNanos ( nanos );

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

OffsetDateTime odt = epochCalendarNewStyleActOf1750.plus ( duration );

Вы можете извлечь Instant объект из OffsetDateTime,

Instant instant = odt.toInstant();

Дамп на консоль.

System.out.println ( "bd: " + bd );
System.out.println ( "days: " + days );
System.out.println ( "fractionOfADay.toString(): " + fractionOfADay );
System.out.println ( "secondsFractional: " + secondsFractional );
System.out.println ( "secondsWhole: " + secondsWhole );
System.out.println ( "nanos: " + nanos );
System.out.println ( "duration.toString(): " + duration );
System.out.println ( "duration.toDays(): " + duration.toDays () );
System.out.println ( "odt.toString(): " + odt );

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

Здесь нет никаких гарантий; этот код свеж у меня в голове, и он довольно непроверен и не проверен.

Смотрите этот код в прямом эфире на IdeOne.com.

бд: 95906.27600694445

дней: 95906

FractionOfADay.toString(): 0.27600694445

секунд Fractional: 23847.00000048000

секунд: 23847

Нанос: 480

duration.toString (): PT2301750H37M27.00000048S

duration.toDays (): 95906

odt.toString (): 2015-04-15T06: 37: 27.000000480Z

Конечно, эта математика может быть проще. Но я подумал, что было бы интересно показать кусочки. Одним из более простых способов является умножение 95906.27600694445 BigDecimal по количеству секунд в обычном 24-часовом дне. Затем отделите полученное целое число от его десятичной дроби и передайте каждому Duration.ofSeconds а также Duration::plusNanos так как это соответствует внутренней модели данных Duration, общее количество секунд и общее количество наночастиц в доли секунды. Мы будем пропускать ту часть, где мы позвонили Duration.ofDays,


О java.time

Инфраструктура java.time встроена в Java 8 и более поздние версии. Эти классы вытесняют проблемные старые классы даты и времени, такие как java.util.Date, Calendar & SimpleDateFormat,

Проект Joda-Time, находящийся сейчас в режиме обслуживания, рекомендует перейти на классы java.time.

Чтобы узнать больше, смотрите Oracle Tutorial. И поиск переполнения стека для многих примеров и объяснений. Спецификация JSR 310.

Где взять классы java.time?

  • Java SE 8 и SE 9 и позже
    • Встроенный.
    • Часть стандартного Java API со встроенной реализацией.
    • Java 9 добавляет некоторые незначительные функции и исправления.
  • Java SE 6 и SE 7
    • Большая часть функциональности java.time перенесена на Java 6 и 7 в ThreeTen-Backport.
  • Android

Проект ThreeTen-Extra расширяет java.time дополнительными классами. Этот проект является полигоном для возможных будущих дополнений к java.time. Вы можете найти некоторые полезные классы здесь, такие как Interval, YearWeek, YearQuarter и многое другое.

Наиболее полный и кратчайший подход получен моей библиотекой Time4J, посмотрите этот фрагмент с использованием класса JulianDay:

double customJD = 95906.27600694445; 

// my comment: I have never seen Julian days with that epoch until now    
HistoricCalendar hcal = // date when the new style calendar act took effect
    HistoricCalendar.of(ChronoHistory.of(Locale.UK), HistoricEra.AD, 1752, 9, 14);

// last term 0.5 necessary because julian days start at noon
double value = customJD + hcal.get(EpochDays.JULIAN_DAY_NUMBER) - 0.5;
JulianDay jd = JulianDay.ofSimplifiedTime(value);
Instant instant = jd.toMoment().toTemporalAccessor();

System.out.println(instant); // 2015-04-15T06:37:27Z

Однако следует отметить, что наиболее доминирующей областью применения юлианских дней является астрономия, см. Также официальную рекомендацию МАС. И в этом поле гораздо чаще включать дельта-Т-коррекцию, то есть определять юлианские дни по шкале времени TT (земное время). Time4J предлагает свои методы JulianDay.ofEphemerisTime(...) для этого. Если вы серьезно думаете о том, чтобы обрабатывать шкалы времени, такие как TT, то вам лучше работать с классом Moment вместо Instant потому что последний не может понять TT, UTC, UT, включая обработку високосных секунд и т. д.

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