我在进行日期比较时遇到了麻烦。
我正在使用Groovy和Spock编写针对Web服务的集成测试。
测试是首先使用网络服务创建Thing
,然后立即拨打另一个电话,通过其ID获取Thing
的详细信息。然后,我想验证该事物的CreatedDate
是否超过一分钟。
以下是调用的一些JSON
{
"Id":"696fbd5f-5a0c-4209-8b21-ea7f77e3e09d",
"CreatedDate":"2017-07-11T10:53:52"
}
因此,请注意日期字符串中没有时区信息;但我知道它是UTC。
我是Java(来自.NET)的新手,并且有点不同的日期类型。
这是我的类的Groovy模型,我使用Gson反序列化:
class Thing {
public UUID Id
public Date CreatedDate
}
反序列化工作正常。但是,在非UTC时区运行的代码认为日期实际上是在本地时区。
我可以使用Instant
类创建一个代表“1分钟前”的变量:
def aMinuteAgo = Instant.now().plusSeconds(-60)
这就是我尝试进行比较的方式:
rule.CreatedDate.toInstant().compareTo(aMinuteAgo) < 0
麻烦的是,运行时认为日期是本地时间。我强迫.toInstant()
加入UTC似乎没有过载。
我尝试使用我理解的更现代的类 - 例如我的模型中的LocalDateTime
和ZonedDateTime
而不是Date
,但是Gson不能很好地使用反序列化。
答案 0 :(得分:3)
输入String
只有日期和时间,没有时区信息,因此您可以将其解析为LocalDateTime
,然后将其转换为UTC:
// parse date and time
LocalDateTime d = LocalDateTime.parse("2017-07-11T10:53:52");
// convert to UTC
ZonedDateTime z = d.atZone(ZoneOffset.UTC);
// or
OffsetDateTime odt = d.atOffset(ZoneOffset.UTC);
// convert to Instant
Instant instant = z.toInstant();
您可以使用ZonedDateTime
,OffsetDateTime
或Instant
,因为所有内容都包含UTC中的等效日期和时间。要获得它们,您可以使用反序列化程序as linked in the comments。
要查看此日期与当前日期之间的分钟数,您可以使用java.time.temporal.ChronoUnit
:
ChronoUnit.MINUTES.between(instant, Instant.now());
这将返回instant
与当前日期/时间之间的分钟数。您也可以将其与ZonedDateTime
或OffsetDateTime
:
ChronoUnit.MINUTES.between(z, ZonedDateTime.now());
ChronoUnit.MINUTES.between(odt, OffsetDateTime.now());
但是当您使用UTC时,Instant
更好(因为它&#34;在UTC&#34;根据定义 - 实际上,Instant
代表一个时间点,自纪元(1970-01-01T00:00Z
)以来的纳秒数,并且没有时区/偏移,因此您也可以认为&#34;它总是在UTC& #34;。)
如果您确定日期始终为UTC,则还可以使用OffsetDateTime
。 ZonedDateTime
也有效,但如果您不需要时区规则(跟踪DST规则等),那么OffsetDateTime
是更好的选择。
另一个区别是Instant
只有纪元(1970-01-01T00:00Z
)以来的纳秒数。如果您需要日,月,年,小时,分钟,秒等字段,最好使用ZonedDateTime
或OffsetDateTime
。
您还可以查看API tutorial,good explanation about the different types。
答案 1 :(得分:1)
非常感谢你的评论,他们让我走上了一条好路。
为了其他任何遇到此问题的人的利益,这是我使用的代码。
修改后的模型类:
import java.time.LocalDateTime
class Thing {
public UUID Id
public LocalDateTime CreatedDate
}
Utils类(此外还包括ZonedDateTime
的方法,就像我获得原始代码一样。但事实证明我可以让LocalDateTime
为我工作。({{{ 1}}是支持其他模型类中使用的setDateFormat
个对象,我不需要进行比较,尽管我可以看到自己不久就会弃用这些对象。
Date
以下是进行比较的代码(它的Spock / Groovy):
class Utils {
static Gson UtilGson = new GsonBuilder()
.registerTypeAdapter(ZonedDateTime.class, GsonHelper.ZDT_DESERIALIZER)
.registerTypeAdapter(LocalDateTime.class, GsonHelper.LDT_DESERIALIZER)
.registerTypeAdapter(OffsetDateTime.class, GsonHelper.ODT_DESERIALIZER)
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
.create();
// From https://stackoverflow.com/a/36418842/276036
static class GsonHelper {
public static final JsonDeserializer<ZonedDateTime> ZDT_DESERIALIZER = new JsonDeserializer<ZonedDateTime>() {
@Override
public ZonedDateTime deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException {
JsonPrimitive jsonPrimitive = json.getAsJsonPrimitive();
try {
// if provided as String - '2011-12-03T10:15:30+01:00[Europe/Paris]'
if(jsonPrimitive.isString()){
return ZonedDateTime.parse(jsonPrimitive.getAsString(), DateTimeFormatter.ISO_ZONED_DATE_TIME);
}
// if provided as Long
if(jsonPrimitive.isNumber()){
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(jsonPrimitive.getAsLong()), ZoneId.systemDefault());
}
} catch(RuntimeException e){
throw new JsonParseException("Unable to parse ZonedDateTime", e);
}
throw new JsonParseException("Unable to parse ZonedDateTime");
}
};
public static final JsonDeserializer<LocalDateTime> LDT_DESERIALIZER = new JsonDeserializer<LocalDateTime>() {
@Override
public LocalDateTime deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException {
JsonPrimitive jsonPrimitive = json.getAsJsonPrimitive();
try {
// if provided as String - '2011-12-03T10:15:30'
if(jsonPrimitive.isString()){
return LocalDateTime.parse(jsonPrimitive.getAsString(), DateTimeFormatter.ISO_DATE_TIME);
}
// if provided as Long
if(jsonPrimitive.isNumber()){
return LocalDateTime.ofInstant(Instant.ofEpochMilli(jsonPrimitive.getAsLong()), ZoneId.systemDefault());
}
} catch(RuntimeException e){
throw new JsonParseException("Unable to parse LocalDateTime", e);
}
throw new JsonParseException("Unable to parse LocalDateTime");
}
public static final JsonDeserializer<OffsetDateTime> ODT_DESERIALIZER = new JsonDeserializer<OffsetDateTime>() {
@Override
public OffsetDateTime deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException {
JsonPrimitive jsonPrimitive = json.getAsJsonPrimitive()
try {
// if provided as String - '2011-12-03T10:15:30' (i.e. no timezone information e.g. '2011-12-03T10:15:30+01:00[Europe/Paris]')
// We know our services return UTC dates without specific timezone information so can do this.
// But if, in future we have a different requirement, we'll have to review.
if(jsonPrimitive.isString()){
LocalDateTime localDateTime = LocalDateTime.parse(jsonPrimitive.getAsString());
return localDateTime.atOffset(ZoneOffset.UTC)
}
} catch(RuntimeException e){
throw new JsonParseException("Unable to parse OffsetDateTime", e)
}
throw new JsonParseException("Unable to parse OffsetDateTime")
}
}
};
}
与运营商进行比较似乎更自然。当我明确地将它用于UTC时,OffsetDateTime运行良好。我只使用这个模型对服务进行集成测试(它们本身实际上是在.NET中实现的),因此不会在我的测试之外使用这些对象。