Java将“ Excel日期序列号”转换为“ DateTime”

时间:2018-10-26 16:46:55

标签: java excel datetime

如何使用Java将“ excel日期序列号”(即33257.415972222225)转换为DateTime(即1991年1月19日09:59:00)?

3 个答案:

答案 0 :(得分:2)

计划参考日期

根据this documentation,来自Microsoft Excel的值是自epoch reference中1900-01-01的UTC以来的天数。在内部,实际参考日期为this Wikipedia page中记录的1899年12月30日

请注意,Excel use a different epoch in 1904的某些版本(macOS的旧版本?)。

在代码中的某个地方建立纪元引用。

final static public LocalDate EXCEL_EPOCH_REFERENCE = 
    LocalDate.of( 1899 , Month.DECEMBER , 30 )
; // Beware: Some versions of Excel use a 1904 epoch reference.

算一下。我们分两个步骤进行。

首先获取仅包含日期的部分,而不包含日期。从输入的数字中提取整个天数,然后添加到纪元参考日期。

第二,我们提取分数,并用它来计算一个普通的24小时工作日的一部分。我们得到一天中的纳秒总数,然后乘以分数。我认为Excel可能基于毫秒,但是我认为这没什么大不了的,特别是如果您希望在结果中舍入小数秒,如下所示。

最后,我们用UTC获得日期的第一时刻,并添加一天中该部分的纳秒数。这将返回一个OffsetDateTime对象。

在此代码中,您可能也可以使用double / Double,但是我使用了BigDecimalBigDecimal类缓慢但准确,而floating-point类型则快速但不准确。

BigDecimal countFromEpoch = new BigDecimal( "33257.415972222225" );

// Date portion
long days = countFromEpoch.longValue();
LocalDate localDate = EXCEL_EPOCH_REFERENCE.plusDays( days );

// Time-of-day portion
BigDecimal fraction = countFromEpoch.subtract( new BigDecimal( days ) );
long nanos = fraction.multiply( new BigDecimal( TimeUnit.DAYS.toNanos( 1 ) ) ).longValue(); // Get the number of nanoseconds in a day, and multiply our decimal fraction.

OffsetDateTime odt = OffsetDateTime.of( localDate , LocalTime.MIN , ZoneOffset.UTC ).plusNanos( nanos ) ;
  

odt.toString():1991-01-19T09:59:00.000000240Z

您可能希望截断小数秒。

OffsetDateTime odt = 
    OffsetDateTime
    .of( localDate , LocalTime.MIN , ZoneOffset.UTC )
    .plusNanos( nanos )
    .truncatedTo( ChronoUnit.SECONDS ) 
;
  

odt.toString():1991-01-19T09:59Z


关于 java.time

java.time框架已内置在Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendarSimpleDateFormat

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

要了解更多信息,请参见Oracle Tutorial。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310

您可以直接与数据库交换 java.time 对象。使用符合JDBC driver或更高版本的JDBC 4.2。不需要字符串,不需要java.sql.*类。

在哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展了java.time。该项目为将来可能在java.time中添加内容提供了一个试验场。您可能会在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

答案 1 :(得分:1)

您还可以查看方法DateUtils#getLocalDateTime(double date, boolean use1904windowing, boolean roundSeconds)的{​​{3}}。该实用工具类提供了更多方便的方法来处理Excel日期。

答案 2 :(得分:1)

Excel 将日期和时间存储为一个数字,表示自 January 0, 1900 以来的天数,再加上一天 24 小时的小数部分:ddddd.tttttt
这称为序列日期序列日期-时间

微软提供的链接文档似乎很清楚。

MS DATEVALUE function 声明:

<块引用>

Excel 将日期存储为连续的序列号,以便它们可以用于计算。 默认情况下,1900 年 1 月 1 日是序号 1,而 2008 年 1 月 1 日是序号 39448,因为它是 1900 年 1 月 1 日之后的 39,447 天

好吧,我要检查一下这个声明:

LocalDate testDate = LocalDate.of(1900, Month.JANUARY, 1).plusDays(39447);
System.out.println(testDate);// prints 2008-01-02

1900 年 1 月 1 日之后的 39,447 天确实是……2008 年 1 月 2 日

这是为什么?

Excel 中的日期由从 纪元(1899 年 12 月 30 日或 1900 年 1 月 1 日或 1904 年...)开始的天数表示的事实只是故事的一部分.

我在这里找到了最终答案:Dates And Times In Excel(上帝或谁可以祝福这些人)。

在 Excel 中实现日期例程的开发人员故意引入了一个错误,以便与 Lotus 1-2-3 中的同一已知问题兼容。

他们将 1900 年视为闰年,但事实并非如此, 所以任何超过 1900 年 1 月 28 日的日期都比实际日期多一天。

这就是为什么 Excel 认为 2008 年 1 月 1 日 用数字 39448 表示:因为 1900 年 1 月 0 日 之后是 39,448 个单位 (是的,Excel 认为 ) - 即 39,447 天加上 1900 年 1 月 29 日

Excel 还可以将序列日期的日期部分视为自 1904 年 1 月 0 日以来的天数;这种模式称为1904-mode1904-system,用于兼容Macintosh系统。

由于 Excel 日期不携带任何时区信息 - 它只是一个数字 - 最好使用像 LocalDate/LocalDateTime 这样的 Java 类来表示没有时区信息的此类值。

嗯,实际上 - 对于现在的日期 - 人们可能认为 Excel 纪元从 1900 年 12 月 30 日开始,但事实并非如此。


Excel 演示 - 日期格式为 dd/mm/yyyy hh:MM:ss

7
日期作为左边的数字插入


适合所需转换的类:

public class SerialDate {

    //days from 1899-12-31 to Instant.EPOCH (1970-01-01T00:00:00Z)
    public static final long EPOCH = -25568L;
    
    private long serialDays;
    private double serialTime;
    private long epochDays;
    private long daySeconds;
    
    /**
     * @param date number of Excel-days since <i>January 0, 1899</i>
     */
    public SerialDate(long date) {
        serialDays = date;
        if (date > 59)//Lotus123 bug
            --date;
        epochDays = EPOCH + date;
    }
    
    /**
     * @param date number of days since <i>January 0, 1899</i> with a time fraction
     */
    public SerialDate(double date) {
        this((long)date);
        serialTime = date - serialDays;
        daySeconds = Math.round(serialTime * 24 * 60 * 60);
    }
    
    /**
     * @return days since 1970-01-01
     */
    public long toEpochDays() {
        return epochDays;
    }
    
    /**
     * @return seconds of the day for this SerialDate
     */
    public long toDaySeconds() {
        return daySeconds;
    }
    
    /**
     * @return a value suitable for an Excel date
     */
    public double getSerialDate() {
        return serialTime + serialDays;
    }
}

使用示例:

SerialDate sd = new SerialDate(33257.415972222225);
LocalDateTime dt = LocalDateTime.of(
        LocalDate.ofEpochDay(sd.toEpochDays()),
        LocalTime.ofSecondOfDay(sd.toDaySeconds()));
System.out.println(dt);//prints 1991-01-19T09:59