我正在使用Java 8中的新java.time包。我有一个遗留数据库,它提供java.util.Date
,我将其转换为Instant
。
我要做的是添加一段基于另一个数据库标志的时间。我可以添加几天,几周,几个月或几年。我不想关心我要添加的内容,我希望将来能够添加更多选项。
我的第一个想法是Instant.plus()
,但是对于大于一天的值,这给了我一个UnsupportedTemporalTypeException
。 Instant显然不支持大单位时间的操作。好的,无论如何,LocalDateTime
都可以。
所以这给了我这段代码:
private Date adjustDate(Date myDate, TemporalUnit unit){
Instant instant = myDate.toInstant();
LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
dateTime = dateTime.plus(1, unit);
Instant updatedInstant = dateTime.atZone(ZoneId.systemDefault()).toInstant();
return new Date(dueInstant.toEpochMilli());
}
现在,这是我第一次使用新的时间API,所以我可能在这里遗漏了一些东西。但是我必须离开看起来很笨拙:
Date --> Instant --> LocalDateTime --> do stuff--> Instant --> Date.
即使我不必使用Date部分,我仍然认为它有点尴尬。所以我的问题是,我这样做完全错了,最好的办法是什么?
修改:扩展评论中的讨论。
我想我现在更好地了解了LocalDateTime和Instant如何使用java.util.Date和java.sql.Timestamp。谢谢大家。
现在,更实际的考虑。假设用户向我发送了他们在世界任何地方的日期,任意时区。他们发送给我2014-04-16T13:00:00
我可以解析为LocalDateTime。然后我将它直接转换为java.sql.Timestamp并在我的数据库中保留。
现在,我没有做任何其他事情,我从我的数据库中提取java.sql.timestamp,使用LocalDateTime
转换为timestamp.toLocalDateTime()
。都好。然后我使用ISO_DATE_TIME格式将此值返回给我的用户。结果是2014-04-16T09:00:00
。
我认为这种差异是因为某些类型的隐式转换到/来自UTC。我认为我的默认时区可能会应用于该值(EDT,UTC-4),这可以解释为什么数字会在4小时后关闭。
新问题。从本地时间到UTC的隐式转换在哪里?保留时区的更好方法是什么。我不能直接从当地时间作为字符串(2014-04-16T13:00:00)到LocalDateTime
吗?我应该期待用户输入的时区吗?
答案 0 :(得分:20)
我会继续根据我的最终解决方案发布一个答案,并对这个很长的评论链进行总结。
首先,整个转换链:
Date --> Instant --> LocalDateTime --> Do stuff --> Instant --> Date
必须保留时区信息,并且仍然在日期上执行操作,例如知道日历及其中所有上下文的对象。否则我们冒着隐式转换到本地时区的风险,如果我们试图将其置于人类可读的日期格式中,那么时间可能因此而改变。
例如,toLocalDateTime()
类上的java.sql.Timestamp
方法隐式转换为默认时区。这对我的目的来说是不可取的,但不一定是坏行为。但是,重要的是要意识到这一点。这是将遗留Java日期对象直接转换为LocalDateTime
对象的问题。由于传统对象通常被假定为UTC,因此转换使用本地时区偏移量。
现在,假设我们的程序接受2014-04-16T13:00:00
的输入并以java.sql.Timestamp
保存到数据库。
//Parse string into local date. LocalDateTime has no timezone component
LocalDateTime time = LocalDateTime.parse("2014-04-16T13:00:00");
//Convert to Instant with no time zone offset
Instant instant = time.atZone(ZoneOffset.ofHours(0)).toInstant();
//Easy conversion from Instant to the java.sql.Timestamp object
Timestamp timestamp = Timestamp.from(instant);
现在我们采用时间戳并为其添加一些天数:
Timestamp timestamp = ...
//Convert to LocalDateTime. Use no offset for timezone
LocalDateTime time = LocalDateTime.ofInstant(timestamp.toInstant(), ZoneOffset.ofHours(0));
//Add time. In this case, add one day.
time = time.plus(1, ChronoUnit.DAYS);
//Convert back to instant, again, no time zone offset.
Instant output = time.atZone(ZoneOffset.ofHours(0)).toInstant();
Timestamp savedTimestamp = Timestamp.from(output);
现在我们只需要以ISO_LOCAL_DATE_TIME
。
Timestamp timestamp = ....
LocalDateTime time = LocalDateTime.ofInstant(timestamp.toInstant(), ZoneOffset.ofHours(0));
String formatted = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(time);