{
+
+ private static final DateTimeFormatter LOCAL_DATE_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE;
+ private static final DateTimeFormatter ZONED_DATETIME_FORMATTER = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
+
+ /**
+ * Gson invokes this call-back method during serialization when it encounters a field of the
+ * specified type.
+ *
+ *
+ * In the implementation of this call-back method, you should consider invoking
+ * {@link JsonSerializationContext#serialize(Object, Type)} method to create JsonElements for any
+ * non-trivial field of the {@code src} object. However, you should never invoke it on the
+ * {@code src} object itself since that will cause an infinite loop (Gson will call your
+ * call-back method again).
+ *
+ * @param src the object that needs to be converted to Json.
+ * @param typeOfSrc the actual type (fully genericized version) of the source object.
+ * @return a JsonElement corresponding to the specified object.
+ */
+ @Override
+ public JsonElement serialize(LocalDate src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive(LOCAL_DATE_FORMATTER.format(src));
+ }
+
+ /**
+ * Gson invokes this call-back method during deserialization when it encounters a field of the
+ * specified type.
+ *
+ *
+ * In the implementation of this call-back method, you should consider invoking
+ * {@link JsonDeserializationContext#deserialize(JsonElement, Type)} method to create objects
+ * for any non-trivial field of the returned object. However, you should never invoke it on the
+ * the same type passing {@code json} since that will cause an infinite loop (Gson will call your
+ * call-back method again).
+ *
+ * @param json The Json data being deserialized
+ * @param typeOfT The type of the Object to deserialize to
+ * @return a deserialized object of the specified type typeOfT which is a subclass of {@code T}
+ * @throws JsonParseException if json is not in the expected format of {@code typeOfT}
+ */
+ @Override
+ public @Nullable LocalDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ String el = json.getAsString();
+ // we allow dates to be represented as dates only or with time and timezone offset
+ if (el.length() > 10) {
+ return ZONED_DATETIME_FORMATTER.parse(el, LocalDate::from);
+ } else {
+ return LOCAL_DATE_FORMATTER.parse(el, LocalDate::from);
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.surepetcare/src/main/java/org/openhab/binding/surepetcare/internal/utils/GsonLocalTimeTypeAdapter.java b/bundles/org.openhab.binding.surepetcare/src/main/java/org/openhab/binding/surepetcare/internal/utils/GsonLocalTimeTypeAdapter.java
new file mode 100644
index 0000000000000..edbaf348af3b4
--- /dev/null
+++ b/bundles/org.openhab.binding.surepetcare/src/main/java/org/openhab/binding/surepetcare/internal/utils/GsonLocalTimeTypeAdapter.java
@@ -0,0 +1,81 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.surepetcare.internal.utils;
+
+import java.lang.reflect.Type;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+/**
+ * GSON serialiser/deserialiser for converting {@link LocalTime} objects.
+ *
+ * @author Rene Scherer - Initial Contribution
+ */
+@NonNullByDefault
+public class GsonLocalTimeTypeAdapter implements JsonSerializer, JsonDeserializer {
+
+ private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
+
+ /**
+ * Gson invokes this call-back method during serialization when it encounters a field of the
+ * specified type.
+ *
+ *
+ * In the implementation of this call-back method, you should consider invoking
+ * {@link JsonSerializationContext#serialize(Object, Type)} method to create JsonElements for any
+ * non-trivial field of the {@code src} object. However, you should never invoke it on the
+ * {@code src} object itself since that will cause an infinite loop (Gson will call your
+ * call-back method again).
+ *
+ * @param src the object that needs to be converted to Json.
+ * @param typeOfSrc the actual type (fully genericized version) of the source object.
+ * @return a JsonElement corresponding to the specified object.
+ */
+ @Override
+ public JsonElement serialize(LocalTime src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive(TIME_FORMATTER.format(src));
+ }
+
+ /**
+ * Gson invokes this call-back method during deserialization when it encounters a field of the
+ * specified type.
+ *
+ *
+ * In the implementation of this call-back method, you should consider invoking
+ * {@link JsonDeserializationContext#deserialize(JsonElement, Type)} method to create objects
+ * for any non-trivial field of the returned object. However, you should never invoke it on the
+ * the same type passing {@code json} since that will cause an infinite loop (Gson will call your
+ * call-back method again).
+ *
+ * @param json The Json data being deserialized
+ * @param typeOfT The type of the Object to deserialize to
+ * @return a deserialized object of the specified type typeOfT which is a subclass of {@code T}
+ * @throws JsonParseException if json is not in the expected format of {@code typeOfT}
+ */
+ @Override
+ public @Nullable LocalTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ return TIME_FORMATTER.parse(json.getAsString(), LocalTime::from);
+ }
+}
diff --git a/bundles/org.openhab.binding.surepetcare/src/main/java/org/openhab/binding/surepetcare/internal/utils/GsonZonedDateTimeTypeAdapter.java b/bundles/org.openhab.binding.surepetcare/src/main/java/org/openhab/binding/surepetcare/internal/utils/GsonZonedDateTimeTypeAdapter.java
new file mode 100644
index 0000000000000..a2b64c0bb8dde
--- /dev/null
+++ b/bundles/org.openhab.binding.surepetcare/src/main/java/org/openhab/binding/surepetcare/internal/utils/GsonZonedDateTimeTypeAdapter.java
@@ -0,0 +1,81 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.surepetcare.internal.utils;
+
+import java.lang.reflect.Type;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+/**
+ * GSON serialiser/deserialiser for converting {@link ZonedDateTime} objects.
+ *
+ * @author Rene Scherer - Initial Contribution
+ */
+@NonNullByDefault
+public class GsonZonedDateTimeTypeAdapter implements JsonSerializer, JsonDeserializer {
+
+ private static final DateTimeFormatter ZONED_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssxxx");
+
+ /**
+ * Gson invokes this call-back method during serialization when it encounters a field of the
+ * specified type.
+ *
+ *
+ * In the implementation of this call-back method, you should consider invoking
+ * {@link JsonSerializationContext#serialize(Object, Type)} method to create JsonElements for any
+ * non-trivial field of the {@code src} object. However, you should never invoke it on the
+ * {@code src} object itself since that will cause an infinite loop (Gson will call your
+ * call-back method again).
+ *
+ * @param src the object that needs to be converted to Json.
+ * @param typeOfSrc the actual type (fully genericized version) of the source object.
+ * @return a JsonElement corresponding to the specified object.
+ */
+ @Override
+ public JsonElement serialize(ZonedDateTime src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive(ZONED_FORMATTER.format(src));
+ }
+
+ /**
+ * Gson invokes this call-back method during deserialization when it encounters a field of the
+ * specified type.
+ *
+ *
+ * In the implementation of this call-back method, you should consider invoking
+ * {@link JsonDeserializationContext#deserialize(JsonElement, Type)} method to create objects
+ * for any non-trivial field of the returned object. However, you should never invoke it on the
+ * the same type passing {@code json} since that will cause an infinite loop (Gson will call your
+ * call-back method again).
+ *
+ * @param json The Json data being deserialized
+ * @param typeOfT The type of the Object to deserialize to
+ * @return a deserialized object of the specified type typeOfT which is a subclass of {@code T}
+ * @throws JsonParseException if json is not in the expected format of {@code typeOfT}
+ */
+ @Override
+ public @Nullable ZonedDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ return ZONED_FORMATTER.parse(json.getAsString(), ZonedDateTime::from);
+ }
+}
diff --git a/bundles/org.openhab.binding.surepetcare/src/main/java/org/openhab/binding/surepetcare/internal/utils/SurePetcareDeviceCurfewListTypeAdapterFactory.java b/bundles/org.openhab.binding.surepetcare/src/main/java/org/openhab/binding/surepetcare/internal/utils/SurePetcareDeviceCurfewListTypeAdapterFactory.java
new file mode 100644
index 0000000000000..c2dd47a2e04e3
--- /dev/null
+++ b/bundles/org.openhab.binding.surepetcare/src/main/java/org/openhab/binding/surepetcare/internal/utils/SurePetcareDeviceCurfewListTypeAdapterFactory.java
@@ -0,0 +1,123 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.surepetcare.internal.utils;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.surepetcare.internal.dto.SurePetcareDeviceCurfew;
+import org.openhab.binding.surepetcare.internal.dto.SurePetcareDeviceCurfewList;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import com.google.gson.stream.MalformedJsonException;
+
+/**
+ * The {@link GsonColonDateTypeAdapter} class is a custom TypeAdapter factory to ensure deserialization always returns a
+ * list even if the Json document contains only a single curfew object and not an array.
+ *
+ * See https://stackoverflow.com/questions/43412261/make-gson-accept-single-objects-where-it-expects-arrays
+ *
+ * @author Rene Scherer - Initial contribution
+ */
+@NonNullByDefault
+public final class SurePetcareDeviceCurfewListTypeAdapterFactory implements TypeAdapterFactory {
+
+ // Gson can instantiate it itself
+ private SurePetcareDeviceCurfewListTypeAdapterFactory() {
+ }
+
+ @Override
+ public @Nullable TypeAdapter create(final @Nullable Gson gson, final @Nullable TypeToken