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

