将儒略日期转换为即时日期

Convert a Julian Date to an Instant

我 运行 遇到这样一种情况,我想从 Julian 日期转换为 java.time.Instant (if that makes sense), or some Java time that can be more easily understood. My understanding of what a Julian date is comes from reading the Wikipedia page. There are bunch of different variants,而我尝试读取的日期使用的纪元与这些日期中的任何一个都不同.

例如,假设纪元是 Calendar (New Style) Act 1750 的开始,儒略日期是 95906.27600694445,在这种情况下我认为是 CE 2015 April 15 06:37:26.9 UT,我如何从中获得瞬间?稍后我需要调整时区。

我注意到有一个名为 JulianFields 的 class,但我不知道 where/how 使用它。此外,我在包中看到的大多数方法都使用了 intlong,对 double.

没有任何意义

那么,有没有一种简单的方法可以将使用不同纪元的 Julian 日期转换为 Java 8 Instant(如果我的想法是错误的,或者其他时间)。

我假设您有一个数字时间戳,它是一种修改后的儒略日数,即自定义纪元以来的连续天数。

比如a的定义"Modified Julian Day Number"是从1858年11月17日午夜开始的连续天数。我相信你问的是:

How do I convert a continuous count of days in England since the Gregorian Calendar was officially adopted to an Instant?

我不确定在新式日历法案之后公历纪元正式开始的时间。我假设它是 1752 年 1 月 1 日,即数字 95906.276 是从那时起的连续天数。

METHOD1:这是一个算法,用于将天数处理为以年、月(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);
}

这是一个使用新 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

1752 年 9 月 14 日纪元

我在 Calendar (New Style) Act 1750 上阅读维基百科页面表明纪元参考日期为 1752-09-14。

如果我们将您输入的数字 95906.2760069444595_906L 的整数部分相加,我们确实会得到您的目标日期 2015 年 4 月 15 日(在现代日历系统中)。

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 days from epoch of: 1752-09-14 is 2015-04-15

关于小数,我认为它是通用 24 小时制中秒数的分数。

虽然 LocalDate 仅适用于没有时间的日期,但我们现在需要用小数表示的时间。所以代替 LocalDate,我们切换到 OffsetDateTime

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

我们使用 BigDecimal as double and Double are floating-point technology 以牺牲准确性来换取执行速度。

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类包括OffsetDateTimeDuration.

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

您可以从 OffsetDateTime 中提取一个 Instant 对象。

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

这段代码似乎工作正常。这里的结果与问题中所述的预期相符,尽管我们不同意秒的分数。

看到这个 code run live at IdeOne.com

bd: 95906.27600694445

days: 95906

fractionOfADay.toString(): 0.27600694445

secondsFractional: 23847.00000048000

secondsWhole: 23847

nanos: 480

duration.toString(): PT2301750H37M27.00000048S

duration.toDays(): 95906

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

当然这个数学可以更简单。但我认为展示这些作品可能会很有趣。一种更简单的方法是将 95906.27600694445 BigDecimal 乘以通用 24 小时制中的秒数。然后将生成的整数与其小数部分分开,并将每个整数馈送到 Duration.ofSecondsDuration::plusNanos,因为这符合 Duration 的内部数据模型,总秒数和总纳米数在几分之一秒内。我们将跳过调用 Duration.ofDays.

的部分

关于java.time

java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

Joda-Time project, now in maintenance mode, advises migration to the java.time 类.

要了解更多信息,请参阅 Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310

在哪里获取 java.time 类?

  • Java SE 8 and SE 9 及更高版本
  • 内置。
  • 标准 Java API 的一部分,带有捆绑实施。
  • Java 9 添加了一些小功能和修复。
  • Java SE 6 and SE 7
  • 许多 java.time 功能被反向移植到 ThreeTen-Backport 中的 Java 6 和 7。
  • Android
  • ThreeTenABP 项目专门为 Android 改编 ThreeTen-Backport(如上所述)。
  • 参见

ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

我的图书馆获得了最完整也是最短的方法Time4J, see this snippet using the class 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

不过需要注意的是,儒略日最主要的应用领域是天文学,另见官方recommendation of the IAU。在那个领域,更常见的是包含一个 delta-T 校正,即在时间尺度 TT(地球时间)上定义儒略日。为此,Time4J 提供了方法JulianDay.ofEphemerisTime(...)。如果您认真考虑处理时间尺度,例如 TT,那么您应该使用 class Moment 而不是 Instant,因为最后一个无法理解 TT、UTC、UT,包括闰秒处理等.