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

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

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

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

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


person mkobit    schedule 14.08.2015    source источник
comment
stackoverflow.com/questions/16586806/   -  person RisingSun    schedule 14.08.2015
comment
@khudren Я не уверен, что имеет в виду Морт. В моем случае я конвертирую юлианскую дату, которая содержит часть другого дня, и она также начинается с другой эпохи. Кроме того, исходя из моего понимания даты по юлианскому календарю, строка, которую пользователь форматирует в этом вопросе (по крайней мере, так, как ее форматирует пользователь), не является датой по юлианскому календарю.   -  person mkobit    schedule 14.08.2015
comment
В этой терминологии вообще путаница. Существует тенденция использовать юлианскую дату и юлианское число дня как синонимы. Однако юлианская дата рассчитывается либо в соответствии с юлианским календарем (который предшествовал григорианской реформации в 16 веке), либо в соответствии с юлианской эпохой из числа юлианских дней. Число дней по юлианскому календарю — это просто непрерывный подсчет дней, прошедших с 1 января 4713 года до нашей эры. Ваш вопрос создает впечатление, что вы используете последний.   -  person scottb    schedule 14.08.2015
comment
С помощью подходящей веб-страницы найти MJD известной даты/времени и вычесть Двойная дата ОП из рассчитанного MJD, можно найти эпоху двойника ОП - 14 сентября 1752 года по григорианскому календарю, что действительно является первым днем, когда григорианский календарь наблюдался в Великобритании.   -  person Gerard Ashton    schedule 15.08.2015


Ответы (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
person Sleafar    schedule 14.08.2015
comment
Если его номера дней отсчитывать от эпохи календаря Нового Стиля, то ваши Инстанты будут отключены, верно? Эпоха модифицированных чисел дня по юлианскому календарю наступила примерно 75 лет спустя. - person scottb; 14.08.2015
comment
@scottb Вы имеете в виду пропавшие дни в 1751 и 1752 годах, верно? Если использовались даты до 14 сентября 1752 года из этого календаря, то вы правы. В зависимости от точной даты потребуется смещение. - person Sleafar; 15.08.2015
comment
Ну ... я не уверен, какую дату вы используете в качестве своей эпохи, тбх. 16 ноября 1858 г. близко к дате эпохи для измененного номера дня по юлианскому календарю ... но ОП не работает с MJD. Ваше решение также работает с определенным номером дня, а не с произвольным номером, предоставленным, скажем, в качестве параметра функции. - person scottb; 15.08.2015
comment
@scottb Это сокращенный JD, как определено в статье Википедии. Он начинается на 0,5 дня раньше, чем Modified JD. Я только что выбрал первый вариант в таблице, потому что ОП сказал, что использует вариант, которого нет в списке. - person Sleafar; 15.08.2015
comment
@scottb Я изменил свой ответ на полное решение, как вы и хотели. :) - person Sleafar; 15.08.2015
comment
Ваш метод toInstant() верен только в том случае, если номер дня уже рассчитан по Гринвичу/UTC. Если номер дня был отсчитан в другой зоне, Мгновение будет неверным. - person scottb; 17.08.2015
comment
@scottb Цитата из статьи в Википедии: Юлианская дата (JD) любого момента времени – это номер дня по юлианскому календарю для предшествующего полудня по среднему времени по Гринвичу плюс доля суток, прошедших с этого момента. - person Sleafar; 17.08.2015
comment
Совершенно верно. Числа дней по юлианскому календарю официально считаются в формате UTC, чтобы соответствовать определению JDN. Однако точно так же, как LocalTime и LocalDateTime можно считать независимо от часового пояса, можно вести непрерывный подсчет дней в любом часовом поясе, отражающем местное время. Эти местные или региональные номера дней не могут быть преобразованы в JDN (или вариант) или Instant без предварительной корректировки смещения для GMT/UTC и летнего времени. - person scottb; 17.08.2015
comment
Это ответ, который я решил использовать, и, похоже, он работает хорошо, поскольку даты, с которыми я имею дело, уже указаны в UTC. Однако несколько замечаний по этому поводу. 1) При использовании далекой прошлой эпохи ваш расчет нано станет Long.MAX_VALUE и будет неверным. Это происходит, если вы используете эпоху 12h Jan 1, 4713 BC. 2) Не так много о вашем ответе, но вы должны быть осторожны с преобразованием дат юлианского календаря в григорианский календарь< /а> даты. У меня были некоторые недостающие знания, прежде чем я занялся этим вопросом, который я узнал. - person mkobit; 08.09.2015
comment
@mkobit: FWIW, оба метода, показанные в моем ответе, не подвержены переполнению наносекунд и верны для номеров дней по юлианскому календарю до 4713 г. до н.э. (на самом деле оба метода используют арифметические выражения и в значительной степени независимы от Календаря Java и DateTime Java 8 API). Однако они имеют точность только в миллисекундах. Что касается вашего второго пункта, преобразование подсчета дней в правильные локальные отметки даты/времени всегда связано с самыми большими проблемами (смещения, летнее время, юлианский → григорианский календарь). К счастью, API-интерфейсы Java решают наиболее распространенные из этих проблем. - person scottb; 09.09.2015
comment
@mkobit Я адаптировал код из своего первого ответа, чтобы предотвратить проблемы с переполнением. Даты вне григорианского календаря все равно потребуют дополнительной ручной обработки. - person Sleafar; 09.09.2015
comment
кажется, работает нормально, насколько я могу судить. я читал некоторые из этих комментариев, но они идут выше моей головы. на первый взгляд для первых нескольких использований это нормально для меня. - person Nicholas DiPiazza; 09.01.2021

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

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

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

Я не уверен, где официально началась григорианская эпоха после акта календаря нового стиля. Я буду считать, что это 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. Если ваш номер дня уже рассчитан по Гринвичу/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);
}
person scottb    schedule 14.08.2015
comment
Смотрите мой ответ для более короткого и полного решения. - person Meno Hochschild; 24.10.2017

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

Мое прочтение страницы Википедии на Календарь (новый стиль) Act 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.

bd: 95906.27600694445

дней: 95906

фракцияDay.toString(): 0,27600694445

секундыДробная часть: 23847.00000048000

секундВсего: 23847

нано: 480

продолжительность.toString(): PT2301750H37M27.00000048S

продолжительность.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. И поищите множество примеров и пояснений в Stack Overflow. Спецификация: JSR 310.

Где получить классы java.time?

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

person Basil Bourque    schedule 01.03.2017

Наиболее полный, а также кратчайший подход дает моя библиотека 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

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

person Meno Hochschild    schedule 24.10.2017