Skip to content

Commit 7df4b06

Browse files
committed
Use Instant internally
Signed-off-by: Jacob Laursen <[email protected]>
1 parent 591d8d9 commit 7df4b06

File tree

15 files changed

+201
-147
lines changed

15 files changed

+201
-147
lines changed

bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/internal/item/ItemResource.java

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
package org.openhab.core.io.rest.core.internal.item;
1414

1515
import java.time.Instant;
16+
import java.time.ZoneId;
1617
import java.time.temporal.ChronoUnit;
1718
import java.util.ArrayList;
1819
import java.util.Arrays;
@@ -57,6 +58,7 @@
5758
import org.openhab.core.auth.Role;
5859
import org.openhab.core.common.registry.RegistryChangedRunnableListener;
5960
import org.openhab.core.events.EventPublisher;
61+
import org.openhab.core.i18n.TimeZoneProvider;
6062
import org.openhab.core.io.rest.DTOMapper;
6163
import org.openhab.core.io.rest.JSONResponse;
6264
import org.openhab.core.io.rest.LocaleService;
@@ -180,6 +182,7 @@ private static void respectForwarded(final UriBuilder uriBuilder, final @Context
180182
private final MetadataRegistry metadataRegistry;
181183
private final MetadataSelectorMatcher metadataSelectorMatcher;
182184
private final SemanticTagRegistry semanticTagRegistry;
185+
private final TimeZoneProvider timeZoneProvider;
183186

184187
private final RegistryChangedRunnableListener<Item> resetLastModifiedItemChangeListener = new RegistryChangedRunnableListener<>(
185188
() -> lastModified = null);
@@ -198,7 +201,8 @@ public ItemResource(//
198201
final @Reference ManagedItemProvider managedItemProvider,
199202
final @Reference MetadataRegistry metadataRegistry,
200203
final @Reference MetadataSelectorMatcher metadataSelectorMatcher,
201-
final @Reference SemanticTagRegistry semanticTagRegistry) {
204+
final @Reference SemanticTagRegistry semanticTagRegistry,
205+
final @Reference TimeZoneProvider timeZoneProvider) {
202206
this.dtoMapper = dtoMapper;
203207
this.eventPublisher = eventPublisher;
204208
this.itemBuilderFactory = itemBuilderFactory;
@@ -208,6 +212,7 @@ public ItemResource(//
208212
this.metadataRegistry = metadataRegistry;
209213
this.metadataSelectorMatcher = metadataSelectorMatcher;
210214
this.semanticTagRegistry = semanticTagRegistry;
215+
this.timeZoneProvider = timeZoneProvider;
211216

212217
this.itemRegistry.addRegistryChangeListener(resetLastModifiedItemChangeListener);
213218
this.metadataRegistry.addRegistryChangeListener(resetLastModifiedMetadataChangeListener);
@@ -240,6 +245,7 @@ public Response getItems(final @Context UriInfo uriInfo, final @Context HttpHead
240245
@QueryParam("fields") @Parameter(description = "limit output to the given fields (comma separated)") @Nullable String fields,
241246
@DefaultValue("false") @QueryParam("staticDataOnly") @Parameter(description = "provides a cacheable list of values not expected to change regularly and checks the If-Modified-Since header, all other parameters are ignored except \"metadata\"") boolean staticDataOnly) {
242247
final Locale locale = localeService.getLocale(language);
248+
final ZoneId zoneId = timeZoneProvider.getTimeZone();
243249
final Set<String> namespaces = splitAndFilterNamespaces(namespaceSelector, locale);
244250

245251
final UriBuilder uriBuilder = uriBuilder(uriInfo, httpHeaders);
@@ -256,7 +262,7 @@ public Response getItems(final @Context UriInfo uriInfo, final @Context HttpHead
256262
}
257263

258264
Stream<EnrichedItemDTO> itemStream = getItems(type, tags).stream() //
259-
.map(item -> EnrichedItemDTOMapper.map(item, false, null, uriBuilder, locale)) //
265+
.map(item -> EnrichedItemDTOMapper.map(item, false, null, uriBuilder, locale, zoneId)) //
260266
.peek(dto -> addMetadata(dto, namespaces, null)) //
261267
.peek(dto -> dto.editable = isEditable(dto.name));
262268
itemStream = dtoMapper.limitToFields(itemStream,
@@ -267,7 +273,7 @@ public Response getItems(final @Context UriInfo uriInfo, final @Context HttpHead
267273
}
268274

269275
Stream<EnrichedItemDTO> itemStream = getItems(type, tags).stream() //
270-
.map(item -> EnrichedItemDTOMapper.map(item, recursive, null, uriBuilder, locale)) //
276+
.map(item -> EnrichedItemDTOMapper.map(item, recursive, null, uriBuilder, locale, zoneId)) //
271277
.peek(dto -> addMetadata(dto, namespaces, null)) //
272278
.peek(dto -> dto.editable = isEditable(dto.name)) //
273279
.peek(dto -> {
@@ -318,6 +324,7 @@ public Response getItemByName(final @Context UriInfo uriInfo, final @Context Htt
318324
@DefaultValue("true") @QueryParam("recursive") @Parameter(description = "get member items if the item is a group item") boolean recursive,
319325
@PathParam("itemname") @Parameter(description = "item name") String itemname) {
320326
final Locale locale = localeService.getLocale(language);
327+
final ZoneId zoneId = timeZoneProvider.getTimeZone();
321328
final Set<String> namespaces = splitAndFilterNamespaces(namespaceSelector, locale);
322329

323330
// get item
@@ -326,7 +333,7 @@ public Response getItemByName(final @Context UriInfo uriInfo, final @Context Htt
326333
// if it exists
327334
if (item != null) {
328335
EnrichedItemDTO dto = EnrichedItemDTOMapper.map(item, recursive, null, uriBuilder(uriInfo, httpHeaders),
329-
locale);
336+
locale, zoneId);
330337
addMetadata(dto, namespaces, null);
331338
dto.editable = isEditable(dto.name);
332339
if (dto instanceof EnrichedGroupItemDTO enrichedGroupItemDTO) {
@@ -424,6 +431,7 @@ public Response putItemState(
424431
@PathParam("itemname") @Parameter(description = "item name") String itemname,
425432
@Parameter(description = "valid item state (e.g. ON, OFF)", required = true) String value) {
426433
final Locale locale = localeService.getLocale(language);
434+
final ZoneId zoneId = timeZoneProvider.getTimeZone();
427435

428436
// get Item
429437
Item item = getItem(itemname);
@@ -436,7 +444,7 @@ public Response putItemState(
436444
if (state != null) {
437445
// set State and report OK
438446
eventPublisher.post(ItemEventFactory.createStateEvent(itemname, state));
439-
return getItemResponse(null, Status.ACCEPTED, null, locale, null);
447+
return getItemResponse(null, Status.ACCEPTED, null, locale, zoneId, null);
440448
} else {
441449
// State could not be parsed
442450
return JSONResponse.createErrorResponse(Status.BAD_REQUEST, "State could not be parsed: " + value);
@@ -739,6 +747,7 @@ public Response createOrUpdateItem(final @Context UriInfo uriInfo, final @Contex
739747
@PathParam("itemname") @Parameter(description = "item name") String itemname,
740748
@Parameter(description = "item data", required = true) @Nullable GroupItemDTO item) {
741749
final Locale locale = localeService.getLocale(language);
750+
final ZoneId zoneId = timeZoneProvider.getTimeZone();
742751

743752
// If we didn't get an item bean, then return!
744753
if (item == null) {
@@ -763,12 +772,12 @@ public Response createOrUpdateItem(final @Context UriInfo uriInfo, final @Contex
763772
// item does not yet exist, create it
764773
managedItemProvider.add(newItem);
765774
return getItemResponse(uriBuilder(uriInfo, httpHeaders), Status.CREATED, itemRegistry.get(itemname),
766-
locale, null);
775+
locale, zoneId, null);
767776
} else if (managedItemProvider.get(itemname) != null) {
768777
// item already exists as a managed item, update it
769778
managedItemProvider.update(newItem);
770779
return getItemResponse(uriBuilder(uriInfo, httpHeaders), Status.OK, itemRegistry.get(itemname), locale,
771-
null);
780+
zoneId, null);
772781
} else {
773782
// Item exists but cannot be updated
774783
logger.warn("Cannot update existing item '{}', because is not managed.", itemname);
@@ -872,7 +881,8 @@ public Response getSemanticItem(final @Context UriInfo uriInfo, final @Context H
872881
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language,
873882
@PathParam("itemName") @Parameter(description = "item name") String itemName,
874883
@PathParam("semanticClass") @Parameter(description = "semantic class") String semanticClassName) {
875-
Locale locale = localeService.getLocale(language);
884+
final Locale locale = localeService.getLocale(language);
885+
final ZoneId zoneId = timeZoneProvider.getTimeZone();
876886

877887
Class<? extends org.openhab.core.semantics.Tag> semanticClass = semanticTagRegistry
878888
.getTagClassById(semanticClassName);
@@ -886,7 +896,7 @@ public Response getSemanticItem(final @Context UriInfo uriInfo, final @Context H
886896
}
887897

888898
EnrichedItemDTO dto = EnrichedItemDTOMapper.map(foundItem, false, null, uriBuilder(uriInfo, httpHeaders),
889-
locale);
899+
locale, zoneId);
890900
dto.editable = isEditable(dto.name);
891901
return JSONResponse.createResponse(Status.OK, dto, null);
892902
}
@@ -935,8 +945,8 @@ private static Response getItemNotFoundResponse(String itemname) {
935945
* @return Response configured to represent the Item in depending on the status
936946
*/
937947
private Response getItemResponse(final @Nullable UriBuilder uriBuilder, Status status, @Nullable Item item,
938-
Locale locale, @Nullable String errormessage) {
939-
Object entity = null != item ? EnrichedItemDTOMapper.map(item, true, null, uriBuilder, locale) : null;
948+
Locale locale, ZoneId zoneId, @Nullable String errormessage) {
949+
Object entity = null != item ? EnrichedItemDTOMapper.map(item, true, null, uriBuilder, locale, zoneId) : null;
940950
return JSONResponse.createResponse(status, entity, errormessage);
941951
}
942952

bundles/org.openhab.core.io.rest.core/src/main/java/org/openhab/core/io/rest/core/item/EnrichedItemDTOMapper.java

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
*/
1313
package org.openhab.core.io.rest.core.item;
1414

15+
import java.time.Instant;
16+
import java.time.ZoneId;
17+
import java.time.format.DateTimeFormatter;
1518
import java.util.ArrayList;
1619
import java.util.Collection;
1720
import java.util.LinkedHashSet;
@@ -29,7 +32,9 @@
2932
import org.openhab.core.items.Item;
3033
import org.openhab.core.items.dto.ItemDTO;
3134
import org.openhab.core.items.dto.ItemDTOMapper;
35+
import org.openhab.core.library.items.DateTimeItem;
3236
import org.openhab.core.library.items.NumberItem;
37+
import org.openhab.core.library.types.DateTimeType;
3338
import org.openhab.core.transform.TransformationException;
3439
import org.openhab.core.transform.TransformationHelper;
3540
import org.openhab.core.transform.TransformationService;
@@ -51,6 +56,10 @@ public class EnrichedItemDTOMapper {
5156

5257
private static final Pattern EXTRACT_TRANSFORM_FUNCTION_PATTERN = Pattern.compile("(.*?)\\((.*)\\):(.*)");
5358

59+
private static final String DATE_FORMAT_PATTERN_WITH_TZ_RFC = "yyyy-MM-dd'T'HH:mm[:ss[.SSSSSSSSS]]Z";
60+
private static final DateTimeFormatter FORMATTER_TZ_RFC = DateTimeFormatter
61+
.ofPattern(DATE_FORMAT_PATTERN_WITH_TZ_RFC);
62+
5463
private static final Logger LOGGER = LoggerFactory.getLogger(EnrichedItemDTOMapper.class);
5564

5665
/**
@@ -63,28 +72,39 @@ public class EnrichedItemDTOMapper {
6372
* @param uriBuilder if present the URI builder contains one template that will be replaced by the specific item
6473
* name
6574
* @param locale locale (can be null)
75+
* @param zoneId time-zone id (can be null)
6676
* @return item DTO object
6777
*/
6878
public static EnrichedItemDTO map(Item item, boolean drillDown, @Nullable Predicate<Item> itemFilter,
69-
@Nullable UriBuilder uriBuilder, @Nullable Locale locale) {
79+
@Nullable UriBuilder uriBuilder, @Nullable Locale locale, @Nullable ZoneId zoneId) {
7080
ItemDTO itemDTO = ItemDTOMapper.map(item);
71-
return map(item, itemDTO, drillDown, itemFilter, uriBuilder, locale, new ArrayList<>());
81+
return map(item, itemDTO, drillDown, itemFilter, uriBuilder, locale, zoneId, new ArrayList<>());
7282
}
7383

7484
private static EnrichedItemDTO mapRecursive(Item item, @Nullable Predicate<Item> itemFilter,
75-
@Nullable UriBuilder uriBuilder, @Nullable Locale locale, List<Item> parents) {
85+
@Nullable UriBuilder uriBuilder, @Nullable Locale locale, @Nullable ZoneId zoneId, List<Item> parents) {
7686
ItemDTO itemDTO = ItemDTOMapper.map(item);
77-
return map(item, itemDTO, true, itemFilter, uriBuilder, locale, parents);
87+
return map(item, itemDTO, true, itemFilter, uriBuilder, locale, zoneId, parents);
7888
}
7989

8090
private static EnrichedItemDTO map(Item item, ItemDTO itemDTO, boolean drillDown,
8191
@Nullable Predicate<Item> itemFilter, @Nullable UriBuilder uriBuilder, @Nullable Locale locale,
82-
List<Item> parents) {
92+
@Nullable ZoneId zoneId, List<Item> parents) {
8393
if (item instanceof GroupItem) {
8494
// only add as parent item if it is a group, otherwise duplicate memberships trigger false warnings
8595
parents.add(item);
8696
}
87-
String state = item.getState().toFullString();
97+
String state;
98+
if (item instanceof DateTimeItem dateTimeItem && zoneId != null) {
99+
DateTimeType dateTime = dateTimeItem.getStateAs(DateTimeType.class);
100+
if (dateTime == null) {
101+
state = item.getState().toFullString();
102+
} else {
103+
state = formatDateTime(dateTime.getInstant(), zoneId);
104+
}
105+
} else {
106+
state = item.getState().toFullString();
107+
}
88108
String transformedState = considerTransformation(item, locale);
89109
if (state.equals(transformedState)) {
90110
transformedState = null;
@@ -117,7 +137,8 @@ private static EnrichedItemDTO map(Item item, ItemDTO itemDTO, boolean drillDown
117137
"Recursive group membership found: {} is a member of {}, but it is also one of its ancestors.",
118138
member.getName(), groupItem.getName());
119139
} else if (itemFilter == null || itemFilter.test(member)) {
120-
members.add(mapRecursive(member, itemFilter, uriBuilder, locale, new ArrayList<>(parents)));
140+
members.add(
141+
mapRecursive(member, itemFilter, uriBuilder, locale, zoneId, new ArrayList<>(parents)));
121142
}
122143
}
123144
memberDTOs = members.toArray(new EnrichedItemDTO[0]);
@@ -134,6 +155,24 @@ private static EnrichedItemDTO map(Item item, ItemDTO itemDTO, boolean drillDown
134155
return enrichedItemDTO;
135156
}
136157

158+
private static String formatDateTime(Instant instant, ZoneId zoneId) {
159+
String formatted = instant.atZone(zoneId).format(FORMATTER_TZ_RFC);
160+
if (formatted.contains(".")) {
161+
String sign = "";
162+
if (formatted.contains("+")) {
163+
sign = "+";
164+
} else if (formatted.contains("-")) {
165+
sign = "-";
166+
}
167+
if (!sign.isEmpty()) {
168+
// the formatted string contains 9 fraction-of-second digits
169+
// truncate at most 2 trailing groups of 000s
170+
return formatted.replace("000" + sign, sign).replace("000" + sign, sign);
171+
}
172+
}
173+
return formatted;
174+
}
175+
137176
private static @Nullable StateDescription considerTransformation(@Nullable StateDescription stateDescription) {
138177
if (stateDescription != null) {
139178
String pattern = stateDescription.getPattern();

bundles/org.openhab.core.io.rest.core/src/test/java/org/openhab/core/io/rest/core/item/EnrichedItemDTOMapperTest.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,31 +55,32 @@ public void testFiltering() {
5555
subGroup.addMember(stringItem);
5656
}
5757

58-
EnrichedGroupItemDTO dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, false, null, null, null);
58+
EnrichedGroupItemDTO dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, false, null, null, null,
59+
null);
5960
assertThat(dto.members.length, is(0));
6061

61-
dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, true, null, null, null);
62+
dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, true, null, null, null, null);
6263
assertThat(dto.members.length, is(3));
6364
assertThat(((EnrichedGroupItemDTO) dto.members[0]).members.length, is(1));
6465

6566
dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, true,
66-
i -> CoreItemFactory.NUMBER.equals(i.getType()), null, null);
67+
i -> CoreItemFactory.NUMBER.equals(i.getType()), null, null, null);
6768
assertThat(dto.members.length, is(1));
6869

6970
dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, true,
70-
i -> CoreItemFactory.NUMBER.equals(i.getType()) || i instanceof GroupItem, null, null);
71+
i -> CoreItemFactory.NUMBER.equals(i.getType()) || i instanceof GroupItem, null, null, null);
7172
assertThat(dto.members.length, is(2));
7273
assertThat(((EnrichedGroupItemDTO) dto.members[0]).members.length, is(0));
7374

7475
dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, true,
75-
i -> CoreItemFactory.NUMBER.equals(i.getType()) || i instanceof GroupItem, null, null);
76+
i -> CoreItemFactory.NUMBER.equals(i.getType()) || i instanceof GroupItem, null, null, null);
7677
assertThat(dto.members.length, is(2));
7778
assertThat(((EnrichedGroupItemDTO) dto.members[0]).members.length, is(0));
7879

7980
dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, true,
8081
i -> CoreItemFactory.NUMBER.equals(i.getType()) || i.getType().equals(CoreItemFactory.STRING)
8182
|| i instanceof GroupItem,
82-
null, null);
83+
null, null, null);
8384
assertThat(dto.members.length, is(2));
8485
assertThat(((EnrichedGroupItemDTO) dto.members[0]).members.length, is(1));
8586
}
@@ -92,7 +93,7 @@ public void testDirectRecursiveMembershipDoesNotThrowStackOverflowException() {
9293
groupItem1.addMember(groupItem2);
9394
groupItem2.addMember(groupItem1);
9495

95-
assertDoesNotThrow(() -> EnrichedItemDTOMapper.map(groupItem1, true, null, null, null));
96+
assertDoesNotThrow(() -> EnrichedItemDTOMapper.map(groupItem1, true, null, null, null, null));
9697

9798
assertLogMessage(EnrichedItemDTOMapper.class, LogLevel.ERROR,
9899
"Recursive group membership found: group1 is a member of group2, but it is also one of its ancestors.");
@@ -108,7 +109,7 @@ public void testIndirectRecursiveMembershipDoesNotThrowStackOverflowException()
108109
groupItem2.addMember(groupItem3);
109110
groupItem3.addMember(groupItem1);
110111

111-
assertDoesNotThrow(() -> EnrichedItemDTOMapper.map(groupItem1, true, null, null, null));
112+
assertDoesNotThrow(() -> EnrichedItemDTOMapper.map(groupItem1, true, null, null, null, null));
112113

113114
assertLogMessage(EnrichedItemDTOMapper.class, LogLevel.ERROR,
114115
"Recursive group membership found: group1 is a member of group3, but it is also one of its ancestors.");
@@ -124,7 +125,7 @@ public void testDuplicateMembershipOfPlainItemsDoesNotTriggerWarning() {
124125
groupItem1.addMember(numberItem);
125126
groupItem2.addMember(numberItem);
126127

127-
EnrichedItemDTOMapper.map(groupItem1, true, null, null, null);
128+
EnrichedItemDTOMapper.map(groupItem1, true, null, null, null, null);
128129

129130
assertNoLogMessage(EnrichedItemDTOMapper.class);
130131
}
@@ -139,7 +140,7 @@ public void testDuplicateMembershipOfGroupItemsDoesNotTriggerWarning() {
139140
groupItem1.addMember(groupItem3);
140141
groupItem2.addMember(groupItem3);
141142

142-
EnrichedItemDTOMapper.map(groupItem1, true, null, null, null);
143+
EnrichedItemDTOMapper.map(groupItem1, true, null, null, null, null);
143144

144145
assertNoLogMessage(EnrichedItemDTOMapper.class);
145146
}

0 commit comments

Comments
 (0)