比较Java日期并强制将日期强制转换为特定时区

时间:2017-07-11 11:11:29

标签: java date groovy datetime-parsing

我在进行日期比较时遇到了麻烦。

我正在使用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似乎没有过载。

我尝试使用我理解的更现代的类 - 例如我的模型中的LocalDateTimeZonedDateTime而不是Date,但是Gson不能很好地使用反序列化。

2 个答案:

答案 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();

您可以使用ZonedDateTimeOffsetDateTimeInstant,因为所有内容都包含UTC中的等效日期和时间。要获得它们,您可以使用反序列化程序as linked in the comments

要查看此日期与当前日期之间的分钟数,您可以使用java.time.temporal.ChronoUnit

ChronoUnit.MINUTES.between(instant, Instant.now());

这将返回instant与当前日期/时间之间的分钟数。您也可以将其与ZonedDateTimeOffsetDateTime

一起使用
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,则还可以使用OffsetDateTimeZonedDateTime也有效,但如果您不需要时区规则(跟踪DST规则等),那么OffsetDateTime是更好的选择。

另一个区别是Instant只有纪元1970-01-01T00:00Z)以来的纳秒数。如果您需要日,月,年,小时,分钟,秒等字段,最好使用ZonedDateTimeOffsetDateTime

您还可以查看API tutorialgood 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中实现的),因此不会在我的测试之外使用这些对象。