You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

java.sql.Date通过毫秒实例化时1870年前日期偏差1天问题

问题:java.sql.Date处理1870年前日期时偏差1天

场景复现

通过Joda Time解析1854-01-01(Asia/Kolkata时区)得到毫秒数,用该毫秒数创建java.sql.Date时,结果偏差1天;但处理1870-01-01及之后的日期时无此问题:

出错代码

package org.example;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;

import java.sql.Date;

public class App 
{
    static final DateTimeFormatter DATE_FORMATTER = ISODateTimeFormat.date();
    public static void main( String[] args )
    {
        DateTimeZone localTimeZone = DateTimeZone.forID("Asia/Kolkata");
        long millis = DATE_FORMATTER.withZone(localTimeZone).parseMillis("1854-01-01");
        Date date = new Date(millis);
        DateTime jodaDate = new DateTime(millis,localTimeZone);
        System.out.println("millis " + millis + ",date " + date + ",jodadate " + jodaDate);
    }
}

出错输出

millis -3660616408000,date 1853-12-31,jodadate 1854-01-01T00:00:00.000+05:53:28

无偏差代码(1870-01-01)

package org.example;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;

import java.sql.Date;

public class App 
{
    static final DateTimeFormatter DATE_FORMATTER = ISODateTimeFormat.date();
    public static void main( String[] args )
    {
        DateTimeZone localTimeZone = DateTimeZone.forID("Asia/Kolkata");
        long millis = DATE_FORMATTER.withZone(localTimeZone).parseMillis("1870-01-01");
        Date date = new Date(millis);
        DateTime jodaDate = new DateTime(millis,localTimeZone);
        System.out.println("millis " + millis + ",date " + date + ",jodadate " + jodaDate);
    }
}

无偏差输出

millis -3155692870000,date 1870-01-01,jodadate 1870-01-01T00:00:00.000+05:21:10

原因分析

  1. 时区数据库支持差异:java.sql.Date底层依赖的java.util.Date使用的是早期JDK的时区规则库,对1900年前的历史时区支持不完善。而Joda Time采用了更完整的IANA时区数据库,能正确识别Asia/Kolkata在1854年的非规整时区偏移(+05:53:28),但java.sql.Date无法正确解析这种偏移,导致日期计算跨天。
  2. UTC转换逻辑缺陷:java.sql.Date基于UTC毫秒数构造,但toString()会用系统时区格式化日期。当历史时区偏移导致UTC时间对应前一天的本地时间时,旧时区规则会错误映射日期,出现1天偏差。1870年之后的时区偏移更规整,java.sql.Date的规则能正确处理。

解决方案

  1. 替换为Java 8+的java.time API:这是官方推荐的日期处理方案,对历史时区支持更完善:
import java.sql.Date;
import java.time.LocalDate;

public class App {
    public static void main(String[] args) {
        LocalDate localDate = LocalDate.parse("1854-01-01");
        Date sqlDate = Date.valueOf(localDate);
        System.out.println("sqlDate: " + sqlDate);
    }
}
  1. 用Joda Time的LocalDate中转:如果必须保留Joda和java.sql.Date,避免直接用毫秒数构造,通过LocalDate做转换:
import org.joda.time.LocalDate;
import org.joda.time.DateTimeZone;
import java.sql.Date;

public class App {
    public static void main(String[] args) {
        DateTimeZone localTimeZone = DateTimeZone.forID("Asia/Kolkata");
        LocalDate jodaLocalDate = new LocalDate("1854-01-01", localTimeZone);
        Date sqlDate = Date.valueOf(jodaLocalDate.toDateTimeAtStartOfDay(localTimeZone).toLocalDate());
        System.out.println("sqlDate: " + sqlDate);
    }
}

内容的提问来源于stack exchange,提问作者Elbin Pallimalil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 22:25:03