我正在使用Java 8中的新java.time包。我有一个遗留数据库java.util.Date,可以将其转换为Instant。
java.util.Date
Instant
我想做的是添加一个基于另一个数据库标志的时间段。我可能要加上几天,几周,几个月或几年。我不想关心我要添加的内容,并且希望将来能够添加更多选项。
我的第一个想法是Instant.plus(),但这给了我UnsupportedTemporalTypeException大于一天的价值。即时显然不支持大时间单位上的操作。好吧,随便吧LocalDateTime。
Instant.plus()
UnsupportedTemporalTypeException
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()); }
现在,这是我第一次使用新的time 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并保存在我的数据库中。
2014-04-16T13:00:00
现在,无需执行任何其他操作,就从数据库中提取java.sql.timestamp,转换为LocalDateTimeusing timestamp.toLocalDateTime()。都好。然后,我使用ISO_DATE_TIME格式将此值返回给用户。结果是2014-04-16T09:00:00。
timestamp.toLocalDateTime()
2014-04-16T09:00:00
我认为这种差异是由于某种类型的往返于UTC的隐式转换。我认为我的默认时区可能已应用于该值(EDT,UTC-4),这可以解释为什么该数字减少4小时。
新问题。从本地时间到UTC的隐式转换在哪里发生?保存时区的更好方法是什么。我是否应该直接从本地时间(2014-04-16T13:00:00)转到字符串LocalDateTime?我应该从用户输入中期待一个时区吗?
我将继续根据最终解决方案以及很长的评论链的摘要发布答案。
首先,整个转换链如下:
Date --> Instant --> LocalDateTime --> Do stuff --> Instant --> Date
必须保留时区信息,并且仍要对类似Date的对象执行操作,该对象知道Calendar及其中的所有上下文。否则,我们将存在隐式转换为本地时区的风险,如果尝试将其转换为人类可读的日期格式,则时间可能因此而发生了变化。
例如,该类toLocalDateTime()上的方法java.sql.Timestamp隐式转换为默认时区。就我的目的而言,这是不可取的,但不一定是不良行为。但是,重要的是要意识到这一点。这就是直接从旧版Java日期对象转换为LocalDateTime对象的问题。由于传统对象通常被假定为UTC,因此转换使用本地时区偏移量。
toLocalDateTime()
java.sql.Timestamp
现在,假设我们的程序接受的输入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。
ISO_LOCAL_DATE_TIME
Timestamp timestamp = .... LocalDateTime time = LocalDateTime.ofInstant(timestamp.toInstant(), ZoneOffset.ofHours(0)); String formatted = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(time);