将Julian日期转换为Instant

时间:2015-08-14 18:16:27

标签: java time java-8 julian-date

我遇到了一种情况,我希望将Julian日期转换为java.time.Instant(如果这有意义),或者某些Java时间可以更容易理解。我对朱利安日期的理解来自阅读Wikipedia page。有许多不同的variants,我试图阅读的日期使用的时期与其中任何一个不同。

例如,假设时代是Calendar (New Style) Act 1750的开头,朱利安日期是95906.27600694445,在这种情况下我认为是 CE 2015年4月15日06:37:26.9 UT ,我如何从中得到一个瞬间?我需要稍后调整时区。

我注意到有一个名为JulianFields的类,但我不知道在哪里/如何使用它。此外,我在包中看到的大部分方法都使用intlong,而不是double的任何内容。

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

4 个答案:

答案 0 :(得分:5)

以下是使用新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

答案 1 :(得分:3)

我将假设您有一个数字时间戳,这是一种类型的修改后的朱利安日数,即自定义时期以来的连续天数。

例如,“改良朱利安日数”的定义是自1858年11月17日午夜起的连续天数。我相信你所问的是:

  

我如何在英格兰转换连续的天数   格里高利历被正式采用为Instant?

我不确定格列高利时代在新风格日历行为之后正式开始的地方。我将假设它是1752年1月1日,即数字95906.276是自那时起的连续天数。

METHOD1:这是一个算法,用于将年数处理为年,月(1-12),日(1-31),小时(0-23)中的整数数组表示, min(0-59),sec(0-59),millis:

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

答案 2 :(得分:2)

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天:1752-09-14是2015-04-15

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

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

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

我们使用BigDecimal作为doubleDoublefloating-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 );

您可以从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 );

此代码似乎正常运行。这里的结果与第二个问题中所述的期望相符,尽管我们不同意一秒的分数。

这里不保证;这段代码是我的头脑中新鲜的,并且是未经测试和未经证实的。

请参阅此code run live at IdeOne.com

  

bd:95906.27600694445

     

天: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的内部数据模型,总秒数和总数为nanos在几分之一秒内。我们将跳过我们称之为Duration.ofDays的部分。

关于java.time

java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendar和&amp; SimpleDateFormat

现在位于Joda-Timemaintenance mode项目建议迁移到java.time类。

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310

从哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuartermore

答案 3 :(得分:0)

我的图书馆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

然而,应该指出的是,朱利安时代最主要的应用领域是天文学,另见官方recommendation of the IAU。在该领域,更常见的是包括Δ-T校正,即在时间尺度TT(地面时间)上定义朱利安天数。 Time4J为此目的提供了方法JulianDay.ofEphemerisTime(...)。如果您认真考虑处理TT等时间尺度,那么您应该使用班级Moment而不是Instant,因为最后一个人无法理解TT,UTC,UT,包括闰秒处理等。