package org.openhab.persistence.dynamodb.internal;
import java.math.BigDecimal;
-import java.text.DateFormat;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter;
+
/**
* Base class for all DynamoDBItem. Represents openHAB Item serialized in a suitable format for the database
*
*/
public abstract class AbstractDynamoDBItem<T> implements DynamoDBItem<T> {
- public static final DateTimeFormatter DATEFORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT)
- .withZone(ZoneId.of("UTC"));
+ private static final ZoneId UTC = ZoneId.of("UTC");
+ public static final DateTimeFormatter DATEFORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT).withZone(UTC);
private static final String UNDEFINED_PLACEHOLDER = "<org.openhab.core.types.UnDefType.UNDEF>";
return dtoclass;
}
+ /**
+ * Custom converter for serialization/deserialization of ZonedDateTime.
+ *
+ * Serialization: ZonedDateTime is first converted to UTC and then stored with format yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
+ * This allows easy sorting of values since all timestamps are in UTC and string ordering can be used.
+ *
+ * @author Sami Salonen - Initial contribution
+ *
+ */
+ public static final class ZonedDateTimeConverter implements DynamoDBTypeConverter<String, ZonedDateTime> {
+
+ @Override
+ public String convert(ZonedDateTime time) {
+ return DATEFORMATTER.format(time.withZoneSameInstant(UTC));
+ }
+
+ @Override
+ public ZonedDateTime unconvert(String serialized) {
+ return ZonedDateTime.parse(serialized, DATEFORMATTER);
+ }
+ }
+
+ private static final ZonedDateTimeConverter zonedDateTimeConverter = new ZonedDateTimeConverter();
private final Logger logger = LoggerFactory.getLogger(AbstractDynamoDBItem.class);
protected String name;
return new DynamoDBBigDecimalItem(name,
((UpDownType) state) == UpDownType.UP ? BigDecimal.ONE : BigDecimal.ZERO, time);
} else if (state instanceof DateTimeType) {
- return new DynamoDBStringItem(name, ((DateTimeType) state).getZonedDateTime().format(DATEFORMATTER), time);
+ return new DynamoDBStringItem(name,
+ zonedDateTimeConverter.convert(((DateTimeType) state).getZonedDateTime()), time);
} else if (state instanceof UnDefType) {
return new DynamoDBStringItem(name, UNDEFINED_PLACEHOLDER, time);
} else if (state instanceof StringListType) {
// Parse ZoneDateTime from string. DATEFORMATTER assumes UTC in case it is not clear
// from the string (should be).
// We convert to default/local timezone for user convenience (e.g. display)
- state[0] = new DateTimeType(ZonedDateTime.parse(dynamoStringItem.getState(), DATEFORMATTER)
+ state[0] = new DateTimeType(zonedDateTimeConverter.unconvert(dynamoStringItem.getState())
.withZoneSameInstant(ZoneId.systemDefault()));
} catch (DateTimeParseException e) {
logger.warn("Failed to parse {} as date. Outputting UNDEF instead",
@Override
public String toString() {
- return DateFormat.getDateTimeInstance().format(time) + ": " + name + " -> " + state.toString();
+ return DATEFORMATTER.format(time) + ": " + name + " -> " + state.toString();
}
}
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBDocument;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverted;
/**
* DynamoDBItem for items that can be serialized as DynamoDB number
@Override
@DynamoDBRangeKey(attributeName = ATTRIBUTE_NAME_TIMEUTC)
+ @DynamoDBTypeConverted(converter = ZonedDateTimeConverter.class)
public ZonedDateTime getTime() {
return time;
}
*/
package org.openhab.persistence.dynamodb.internal;
-import java.text.DateFormat;
+import java.time.ZoneId;
import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.persistence.HistoricItem;
*/
@NonNullByDefault
public class DynamoDBHistoricItem implements HistoricItem {
+ private static final ZoneId UTC = ZoneId.of("UTC");
+ private static final DateTimeFormatter DATEFORMATTER = DateTimeFormatter.ofPattern(DynamoDBItem.DATE_FORMAT)
+ .withZone(UTC);
+
private final String name;
private final State state;
private final ZonedDateTime timestamp;
@Override
public String toString() {
- return DateFormat.getDateTimeInstance().format(timestamp) + ": " + name + " -> " + state.toString();
+ return name + ": " + DATEFORMATTER.format(timestamp) + ": " + state.toString();
}
}
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBDocument;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey;
+import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverted;
/**
* DynamoDBItem for items that can be serialized as DynamoDB string
@Override
@DynamoDBRangeKey(attributeName = ATTRIBUTE_NAME_TIMEUTC)
+ @DynamoDBTypeConverted(converter = ZonedDateTimeConverter.class)
public ZonedDateTime getTime() {
return time;
}
*/
package org.openhab.persistence.dynamodb.internal;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
private static final ZonedDateTime ZDT2 = ZonedDateTime.parse("2016-06-15T16:00:00.123Z");
private static final ZonedDateTime ZDT_BETWEEN = ZonedDateTime.parse("2016-06-15T14:00:00Z");
+ // State1 stored as DateTimeType wrapping ZonedDateTime specified in UTC
private static final DateTimeType STATE1 = new DateTimeType(ZDT1);
- private static final DateTimeType STATE2 = new DateTimeType(ZDT2);
+ // State2 stored as DateTimeType wrapping ZonedDateTime specified in UTC+5
+ private static final DateTimeType STATE2 = new DateTimeType(ZDT2.withZoneSameInstant(ZoneOffset.ofHours(5)));
private static final DateTimeType STATE_BETWEEN = new DateTimeType(ZDT_BETWEEN);
@BeforeAll
@Override
protected State getFirstItemState() {
- return STATE1;
+ // The persistence converts to system default timezone
+ // Thus we need to convert here as well for comparison
+ // In the logs:
+ // [main] TRACE org.openhab.persistence.dynamodb.internal.DynamoDBPersistenceService - Dynamo item datetime
+ // (Type=DateTimeItem, State=2016-06-15T16:00:00.123+0000, Label=null, Category=null) converted to historic
+ // item: datetime: 2020-11-28T11:29:54.326Z: 2016-06-15T19:00:00.123+0300
+ return STATE1.toZone(ZoneId.systemDefault());
}
@Override
protected State getSecondItemState() {
- return STATE2;
+ // The persistence converts to system default timezone
+ // Thus we need to convert here as well for comparison
+ // In the logs:
+ // [main] TRACE org.openhab.persistence.dynamodb.internal.DynamoDBPersistenceService - Dynamo item datetime
+ // (Type=DateTimeItem, State=2016-06-15T16:00:00.123+0000, Label=null, Category=null) converted to historic
+ // item: datetime: 2020-11-28T11:29:54.326Z: 2016-06-15T19:00:00.123+0300
+ return STATE2.toZone(ZoneId.systemDefault());
}
@Override