将Excel中表示日期的数字转换为Java Date对象

18

我在Excel中有一个日期列,但是当我在我的Java应用程序中读取它时,得到的值是数字。

例子

Excel日期

1/1/2013

我正在理解它

41275.00

如何在我的Java应用程序中将数字转换为日期?


你是如何读取它的? - Sotirios Delimanolis
@SotiriosDelimanolis 使用apache.poi。 - aizaz
6个回答

27

以下是将Excel日期转换为Java日期的最小工作示例:

        Date javaDate= DateUtil.getJavaDate((double) 41275.00);
        System.out.println(new SimpleDateFormat("MM/dd/yyyy").format(javaDate));

返回

01/01/2013

您还需要导入以下包:

java.text.SimpleDateFormat
java.util.Date

2
请注意,像java.util.Datejava.util.Calendarjava.text.SimpleDateFormat这样的旧日期时间类现在已经成为遗留系统,被内置于Java 8及更高版本中的java.time类所取代。请参阅Oracle的教程 - Basil Bourque

18

简短概述

使用现代的java.time类。

LocalDate                                    // Represent a date-only vaule, without time-of-day and without time zone.
.of( 1899 , Month.DECEMBER , 30 )            // Specify epoch reference date used by *some* versions of Excel. Beware: Some versions use a 1904 epoch reference. Returns a `LocalDate` object.
.plusDays(                                   // Add a number of days. 
    (long) Double.parseDouble( "41275.00" )  // Get a number of whole days from your input string.
)                                            // Returns another instance of a `LocalDate`, per Immutable Objects pattern, rather than altering the original.        
.toString()                                  // Generate text representing the value of this `LocalDate` in standard ISO 8601 format.

{{2013-01-01}}

java.time

现代解决方案使用了替代最早版本Java中可怕的日期时间类的java.time类。
纪元参考日期: 1899-12-30
根据此文档,来自Microsoft Excel的该值是自UTC的1900-01-01纪元参考以来的天数。内部上,实际参考日期为1899年12月30日,如此维基百科页面所述。
请注意,某些版本(macOS的旧版本?)Excel 在1904年使用不同的纪元
在代码中的某处建立纪元参考。
final static public LocalDate EXCEL_EPOCH_REFERENCE = 
    LocalDate.of( 1899 , Month.DECEMBER , 30 )
; // Beware: Some versions of Excel use a 1904 epoch reference.

算一下

将输入字符串解析为BigDecimal以获取更高的精度(相对于浮点类型,后者为了更快的执行而牺牲了精度)。

BigDecimal countFromEpoch = new BigDecimal( "41275.00" );

将整天数添加到纪元参考日期。

long days = countFromEpoch.longValue();  // Extract the number of whole days, dropping the fraction.
LocalDate localDate = EXCEL_EPOCH_REFERENCE.plusDays( days );
localDate.toString(): 2013-01-01 本地日期.toString():2013-01-01

关于java.time

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

欲了解更多信息,请参阅Oracle教程。同时在Stack Overflow上搜索许多示例和解释。规范为JSR 310

Joda-Time项目现在处于维护模式,建议迁移到java.time类。

您可以直接与数据库交换java.time对象。使用符合JDBC 4.2或更高版本的JDBC驱动程序。不需要字符串,也不需要java.sql.*类。Hibernate 5和JPA 2.2支持java.time

如何获取java.time类?


16

4
正如许多用户已经指出的那样,Excel将日期和时间存储为一个数字,表示自1900年1月0日以来的天数,加上一天中24小时的小数部分:ddddd.tttttt。这被称为序列日期序列日期时间
但这里的答案只代表了故事的一部分 - 除了指出现有库中可用的方法之外。
微软的链接文档并没有使它更加清晰。
MS DATEVALUE函数声明:
Excel把日期存储为连续的序列号,以便进行计算。默认情况下,1900年1月1日是序列号1,而2008年1月1日是序列号39448,因为它是自1900年1月1日以来的第39447天。
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年...)开始计数的天数,这只是故事的一部分。

我在这里找到了一个终极答案:Excel中的日期和时间(某个神祇或任何人都可以保佑这些家伙)。

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

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

这就是Excel认为2008年1月1日代表数字39448的原因:因为它距离1900年1月0日(是的,Excel认为)已经过去了39447天再加上1900年1月29日,总共39448个单位。Excel也可以将序列日期的日期部分视为自1904年1月0日以来的天数;这种模式称为1904模式1904系统,用于与Macintosh系统兼容。由于Excel日期不携带任何时区信息——只是一个数字——因此最好使用Java类如LocalDate/LocalDateTime来表示这些值而不包含时区信息。

实际上,对于现在的日期来说,人们可以从1900年12月30日开始计算Excel时代,但事实并非如此。


Excel演示 - 日期格式为dd/mm/yyyy

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

使用示例:

LocalDate dt = LocalDate.ofEpochDay(new SerialDate(41275).toEpochDays());
System.out.println(dt);//prints 2013-01-01

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

0

Excel 序列化日期是自 1900 年 1 月 1 日以来的天数。为了再次确定日期,我们必须添加相应数量的连续编号天数。

对于 Java 8 而言,不需要任何依赖项。

```

  /*

    1900-1-0            0
    1900-1-1            1
    1900-1-2            2
    1900-1-3            3


     */


    int days = 43323;
    LocalDate start = LocalDate.of(1900, 1, 1);
    LocalDate today = LocalDate.of(2018, 8, 11);


    // days to date
    LocalDate date = start.plusDays(days).minusDays(2);

    System.out.println(date);

    // date to days
    long days1 = ChronoUnit.DAYS.between(start, today) + 2;
    System.out.println(days1);

```


0
我根据SerialDate类的LocalDateTime示例使用了以下代码:
// somewhere as double static 
LocalDateTime EXCEL_START = LocalDateTime.of(1899, 12, 30, 0, 0);
Double doubleValue = 44705.416666666664; // input

long days = doubleValue.longValue();
long seconds = Math.round((doubleValue - days) * 24 * 60 * 60);
LocalDateTime converted = EXCEL_START
            .plusDays(days)
            .plusSeconds(seconds); //2022-05-24T10:00

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接