Преобразование юлианской даты в мгновение
Я сталкиваюсь с ситуацией, когда я хотел бы преобразовать юлианскую дату в 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
- Проект ThreeTenABP адаптирует ThreeTen-Backport (упомянутый выше) специально для Android.
- Смотрите Как использовать ThreeTenABP….
Проект 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, включая обработку високосных секунд и т. д.