mirror of
https://github.com/danieldemus/openhab-core.git
synced 2025-01-10 13:21:53 +01:00
[core] Switch DateTimeType
to Instant
internally for consistent time-zone handling (#3583)
Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
parent
a35041ac7b
commit
b31ff66bba
@ -25,6 +25,7 @@ import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.annotation.security.RolesAllowed;
|
||||
@ -76,6 +77,7 @@ import org.openhab.core.common.registry.RegistryChangedRunnableListener;
|
||||
import org.openhab.core.config.core.ConfigUtil;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.events.Event;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.io.rest.DTOMapper;
|
||||
import org.openhab.core.io.rest.JSONResponse;
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
@ -133,6 +135,7 @@ public class RuleResource implements RESTResource {
|
||||
private final RuleManager ruleManager;
|
||||
private final RuleRegistry ruleRegistry;
|
||||
private final ManagedRuleProvider managedRuleProvider;
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
private final RegistryChangedRunnableListener<Rule> resetLastModifiedChangeListener = new RegistryChangedRunnableListener<>(
|
||||
() -> lastModified = null);
|
||||
|
||||
@ -144,11 +147,13 @@ public class RuleResource implements RESTResource {
|
||||
final @Reference DTOMapper dtoMapper, //
|
||||
final @Reference RuleManager ruleManager, //
|
||||
final @Reference RuleRegistry ruleRegistry, //
|
||||
final @Reference ManagedRuleProvider managedRuleProvider) {
|
||||
final @Reference ManagedRuleProvider managedRuleProvider, //
|
||||
final @Reference TimeZoneProvider timeZoneProvider) {
|
||||
this.dtoMapper = dtoMapper;
|
||||
this.ruleManager = ruleManager;
|
||||
this.ruleRegistry = ruleRegistry;
|
||||
this.managedRuleProvider = managedRuleProvider;
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
|
||||
this.ruleRegistry.addRegistryChangeListener(resetLastModifiedChangeListener);
|
||||
}
|
||||
@ -419,10 +424,10 @@ public class RuleResource implements RESTResource {
|
||||
+ DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS + "]") @QueryParam("from") @Nullable String from,
|
||||
@Parameter(description = "End time of the simulated rule executions. Will default to 30 days after the start time. Must be less than 180 days after the given start time. ["
|
||||
+ DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS + "]") @QueryParam("until") @Nullable String until) {
|
||||
final ZonedDateTime fromDate = from == null || from.isEmpty() ? ZonedDateTime.now() : parseTime(from);
|
||||
final ZonedDateTime untilDate = until == null || until.isEmpty() ? fromDate.plusDays(31) : parseTime(until);
|
||||
final ZonedDateTime fromDate = parseTime(from, ZonedDateTime::now);
|
||||
final ZonedDateTime untilDate = parseTime(until, () -> fromDate.plusDays(31));
|
||||
|
||||
if (daysBetween(fromDate, untilDate) >= 180) {
|
||||
if (ChronoUnit.DAYS.between(fromDate, untilDate) >= 180) {
|
||||
return JSONResponse.createErrorResponse(Status.BAD_REQUEST,
|
||||
"Simulated time span must be smaller than 180 days.");
|
||||
}
|
||||
@ -431,13 +436,12 @@ public class RuleResource implements RESTResource {
|
||||
return Response.ok(ruleExecutions.toList()).build();
|
||||
}
|
||||
|
||||
private static ZonedDateTime parseTime(String sTime) {
|
||||
private ZonedDateTime parseTime(@Nullable String sTime, Supplier<ZonedDateTime> defaultSupplier) {
|
||||
if (sTime == null || sTime.isEmpty()) {
|
||||
return defaultSupplier.get();
|
||||
}
|
||||
final DateTimeType dateTime = new DateTimeType(sTime);
|
||||
return dateTime.getZonedDateTime();
|
||||
}
|
||||
|
||||
private static long daysBetween(ZonedDateTime d1, ZonedDateTime d2) {
|
||||
return ChronoUnit.DAYS.between(d1, d2);
|
||||
return dateTime.getZonedDateTime(timeZoneProvider.getTimeZone());
|
||||
}
|
||||
|
||||
@GET
|
||||
|
@ -178,8 +178,7 @@ public class DateTimeTriggerHandler extends BaseTriggerModuleHandler
|
||||
cronExpression = CronAdjuster.REBOOT;
|
||||
} else if (value instanceof DateTimeType dateTimeType) {
|
||||
boolean itemIsTimeOnly = dateTimeType.toString().startsWith("1970-01-01T");
|
||||
cronExpression = dateTimeType.getZonedDateTime().withZoneSameInstant(ZoneId.systemDefault())
|
||||
.plusSeconds(offset.longValue())
|
||||
cronExpression = dateTimeType.getZonedDateTime(ZoneId.systemDefault()).plusSeconds(offset.longValue())
|
||||
.format(timeOnly || itemIsTimeOnly ? CRON_TIMEONLY_FORMATTER : CRON_FORMATTER);
|
||||
startScheduler();
|
||||
} else {
|
||||
|
@ -13,6 +13,7 @@
|
||||
package org.openhab.core.automation.internal.module.handler;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
@ -158,9 +159,9 @@ public class ItemStateConditionHandler extends BaseConditionModuleHandler implem
|
||||
Item item = itemRegistry.getItem(itemName);
|
||||
State compareState = TypeParser.parseState(item.getAcceptedDataTypes(), state);
|
||||
State itemState = item.getState();
|
||||
if (itemState instanceof DateTimeType type) {
|
||||
ZonedDateTime itemTime = type.getZonedDateTime();
|
||||
ZonedDateTime compareTime = getCompareTime(state);
|
||||
if (itemState instanceof DateTimeType dateTimeState) {
|
||||
Instant itemTime = dateTimeState.getInstant();
|
||||
Instant compareTime = getCompareTime(state);
|
||||
return itemTime.compareTo(compareTime) <= 0;
|
||||
} else if (itemState instanceof QuantityType qtState) {
|
||||
if (compareState instanceof DecimalType type) {
|
||||
@ -195,9 +196,9 @@ public class ItemStateConditionHandler extends BaseConditionModuleHandler implem
|
||||
Item item = itemRegistry.getItem(itemName);
|
||||
State compareState = TypeParser.parseState(item.getAcceptedDataTypes(), state);
|
||||
State itemState = item.getState();
|
||||
if (itemState instanceof DateTimeType type) {
|
||||
ZonedDateTime itemTime = type.getZonedDateTime();
|
||||
ZonedDateTime compareTime = getCompareTime(state);
|
||||
if (itemState instanceof DateTimeType dateTimeState) {
|
||||
Instant itemTime = dateTimeState.getInstant();
|
||||
Instant compareTime = getCompareTime(state);
|
||||
return itemTime.compareTo(compareTime) >= 0;
|
||||
} else if (itemState instanceof QuantityType qtState) {
|
||||
if (compareState instanceof DecimalType type) {
|
||||
@ -252,36 +253,36 @@ public class ItemStateConditionHandler extends BaseConditionModuleHandler implem
|
||||
eventSubscriberRegistration.unregister();
|
||||
}
|
||||
|
||||
private ZonedDateTime getCompareTime(String input) {
|
||||
private Instant getCompareTime(String input) {
|
||||
if (input.isBlank()) {
|
||||
// no parameter given, use now
|
||||
return ZonedDateTime.now();
|
||||
return Instant.now();
|
||||
}
|
||||
try {
|
||||
return ZonedDateTime.parse(input);
|
||||
return ZonedDateTime.parse(input).toInstant();
|
||||
} catch (DateTimeParseException ignored) {
|
||||
}
|
||||
try {
|
||||
return LocalDateTime.parse(input, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
||||
.atZone(timeZoneProvider.getTimeZone());
|
||||
.atZone(timeZoneProvider.getTimeZone()).toInstant();
|
||||
} catch (DateTimeParseException ignored) {
|
||||
}
|
||||
try {
|
||||
int dayPosition = input.indexOf("D");
|
||||
if (dayPosition == -1) {
|
||||
// no date in string, add period symbol and time separator
|
||||
return ZonedDateTime.now().plus(Duration.parse("PT" + input));
|
||||
return Instant.now().plus(Duration.parse("PT" + input));
|
||||
} else if (dayPosition == input.length() - 1) {
|
||||
// day is the last symbol, only add the period symbol
|
||||
return ZonedDateTime.now().plus(Duration.parse("P" + input));
|
||||
return Instant.now().plus(Duration.parse("P" + input));
|
||||
} else {
|
||||
// add period symbol and time separator
|
||||
return ZonedDateTime.now().plus(Duration
|
||||
return Instant.now().plus(Duration
|
||||
.parse("P" + input.substring(0, dayPosition + 1) + "T" + input.substring(dayPosition + 1)));
|
||||
}
|
||||
} catch (DateTimeParseException e) {
|
||||
logger.warn("Couldn't get a comparable time from '{}', using now", input);
|
||||
}
|
||||
return ZonedDateTime.now();
|
||||
return Instant.now();
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
package org.openhab.core.io.rest.core.internal.item;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -57,6 +58,7 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.auth.Role;
|
||||
import org.openhab.core.common.registry.RegistryChangedRunnableListener;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.io.rest.DTOMapper;
|
||||
import org.openhab.core.io.rest.JSONResponse;
|
||||
import org.openhab.core.io.rest.LocaleService;
|
||||
@ -180,6 +182,7 @@ public class ItemResource implements RESTResource {
|
||||
private final MetadataRegistry metadataRegistry;
|
||||
private final MetadataSelectorMatcher metadataSelectorMatcher;
|
||||
private final SemanticTagRegistry semanticTagRegistry;
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
|
||||
private final RegistryChangedRunnableListener<Item> resetLastModifiedItemChangeListener = new RegistryChangedRunnableListener<>(
|
||||
() -> lastModified = null);
|
||||
@ -198,7 +201,8 @@ public class ItemResource implements RESTResource {
|
||||
final @Reference ManagedItemProvider managedItemProvider,
|
||||
final @Reference MetadataRegistry metadataRegistry,
|
||||
final @Reference MetadataSelectorMatcher metadataSelectorMatcher,
|
||||
final @Reference SemanticTagRegistry semanticTagRegistry) {
|
||||
final @Reference SemanticTagRegistry semanticTagRegistry,
|
||||
final @Reference TimeZoneProvider timeZoneProvider) {
|
||||
this.dtoMapper = dtoMapper;
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.itemBuilderFactory = itemBuilderFactory;
|
||||
@ -208,6 +212,7 @@ public class ItemResource implements RESTResource {
|
||||
this.metadataRegistry = metadataRegistry;
|
||||
this.metadataSelectorMatcher = metadataSelectorMatcher;
|
||||
this.semanticTagRegistry = semanticTagRegistry;
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
|
||||
this.itemRegistry.addRegistryChangeListener(resetLastModifiedItemChangeListener);
|
||||
this.metadataRegistry.addRegistryChangeListener(resetLastModifiedMetadataChangeListener);
|
||||
@ -240,6 +245,7 @@ public class ItemResource implements RESTResource {
|
||||
@QueryParam("fields") @Parameter(description = "limit output to the given fields (comma separated)") @Nullable String fields,
|
||||
@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) {
|
||||
final Locale locale = localeService.getLocale(language);
|
||||
final ZoneId zoneId = timeZoneProvider.getTimeZone();
|
||||
final Set<String> namespaces = splitAndFilterNamespaces(namespaceSelector, locale);
|
||||
|
||||
final UriBuilder uriBuilder = uriBuilder(uriInfo, httpHeaders);
|
||||
@ -256,7 +262,7 @@ public class ItemResource implements RESTResource {
|
||||
}
|
||||
|
||||
Stream<EnrichedItemDTO> itemStream = getItems(type, tags).stream() //
|
||||
.map(item -> EnrichedItemDTOMapper.map(item, false, null, uriBuilder, locale)) //
|
||||
.map(item -> EnrichedItemDTOMapper.map(item, false, null, uriBuilder, locale, zoneId)) //
|
||||
.peek(dto -> addMetadata(dto, namespaces, null)) //
|
||||
.peek(dto -> dto.editable = isEditable(dto.name));
|
||||
itemStream = dtoMapper.limitToFields(itemStream,
|
||||
@ -267,7 +273,7 @@ public class ItemResource implements RESTResource {
|
||||
}
|
||||
|
||||
Stream<EnrichedItemDTO> itemStream = getItems(type, tags).stream() //
|
||||
.map(item -> EnrichedItemDTOMapper.map(item, recursive, null, uriBuilder, locale)) //
|
||||
.map(item -> EnrichedItemDTOMapper.map(item, recursive, null, uriBuilder, locale, zoneId)) //
|
||||
.peek(dto -> addMetadata(dto, namespaces, null)) //
|
||||
.peek(dto -> dto.editable = isEditable(dto.name)) //
|
||||
.peek(dto -> {
|
||||
@ -318,6 +324,7 @@ public class ItemResource implements RESTResource {
|
||||
@DefaultValue("true") @QueryParam("recursive") @Parameter(description = "get member items if the item is a group item") boolean recursive,
|
||||
@PathParam("itemname") @Parameter(description = "item name") String itemname) {
|
||||
final Locale locale = localeService.getLocale(language);
|
||||
final ZoneId zoneId = timeZoneProvider.getTimeZone();
|
||||
final Set<String> namespaces = splitAndFilterNamespaces(namespaceSelector, locale);
|
||||
|
||||
// get item
|
||||
@ -326,7 +333,7 @@ public class ItemResource implements RESTResource {
|
||||
// if it exists
|
||||
if (item != null) {
|
||||
EnrichedItemDTO dto = EnrichedItemDTOMapper.map(item, recursive, null, uriBuilder(uriInfo, httpHeaders),
|
||||
locale);
|
||||
locale, zoneId);
|
||||
addMetadata(dto, namespaces, null);
|
||||
dto.editable = isEditable(dto.name);
|
||||
if (dto instanceof EnrichedGroupItemDTO enrichedGroupItemDTO) {
|
||||
@ -424,6 +431,7 @@ public class ItemResource implements RESTResource {
|
||||
@PathParam("itemname") @Parameter(description = "item name") String itemname,
|
||||
@Parameter(description = "valid item state (e.g. ON, OFF)", required = true) String value) {
|
||||
final Locale locale = localeService.getLocale(language);
|
||||
final ZoneId zoneId = timeZoneProvider.getTimeZone();
|
||||
|
||||
// get Item
|
||||
Item item = getItem(itemname);
|
||||
@ -436,7 +444,7 @@ public class ItemResource implements RESTResource {
|
||||
if (state != null) {
|
||||
// set State and report OK
|
||||
eventPublisher.post(ItemEventFactory.createStateEvent(itemname, state));
|
||||
return getItemResponse(null, Status.ACCEPTED, null, locale, null);
|
||||
return getItemResponse(null, Status.ACCEPTED, null, locale, zoneId, null);
|
||||
} else {
|
||||
// State could not be parsed
|
||||
return JSONResponse.createErrorResponse(Status.BAD_REQUEST, "State could not be parsed: " + value);
|
||||
@ -739,6 +747,7 @@ public class ItemResource implements RESTResource {
|
||||
@PathParam("itemname") @Parameter(description = "item name") String itemname,
|
||||
@Parameter(description = "item data", required = true) @Nullable GroupItemDTO item) {
|
||||
final Locale locale = localeService.getLocale(language);
|
||||
final ZoneId zoneId = timeZoneProvider.getTimeZone();
|
||||
|
||||
// If we didn't get an item bean, then return!
|
||||
if (item == null) {
|
||||
@ -763,12 +772,12 @@ public class ItemResource implements RESTResource {
|
||||
// item does not yet exist, create it
|
||||
managedItemProvider.add(newItem);
|
||||
return getItemResponse(uriBuilder(uriInfo, httpHeaders), Status.CREATED, itemRegistry.get(itemname),
|
||||
locale, null);
|
||||
locale, zoneId, null);
|
||||
} else if (managedItemProvider.get(itemname) != null) {
|
||||
// item already exists as a managed item, update it
|
||||
managedItemProvider.update(newItem);
|
||||
return getItemResponse(uriBuilder(uriInfo, httpHeaders), Status.OK, itemRegistry.get(itemname), locale,
|
||||
null);
|
||||
zoneId, null);
|
||||
} else {
|
||||
// Item exists but cannot be updated
|
||||
logger.warn("Cannot update existing item '{}', because is not managed.", itemname);
|
||||
@ -872,7 +881,8 @@ public class ItemResource implements RESTResource {
|
||||
@HeaderParam(HttpHeaders.ACCEPT_LANGUAGE) @Parameter(description = "language") @Nullable String language,
|
||||
@PathParam("itemName") @Parameter(description = "item name") String itemName,
|
||||
@PathParam("semanticClass") @Parameter(description = "semantic class") String semanticClassName) {
|
||||
Locale locale = localeService.getLocale(language);
|
||||
final Locale locale = localeService.getLocale(language);
|
||||
final ZoneId zoneId = timeZoneProvider.getTimeZone();
|
||||
|
||||
Class<? extends org.openhab.core.semantics.Tag> semanticClass = semanticTagRegistry
|
||||
.getTagClassById(semanticClassName);
|
||||
@ -886,7 +896,7 @@ public class ItemResource implements RESTResource {
|
||||
}
|
||||
|
||||
EnrichedItemDTO dto = EnrichedItemDTOMapper.map(foundItem, false, null, uriBuilder(uriInfo, httpHeaders),
|
||||
locale);
|
||||
locale, zoneId);
|
||||
dto.editable = isEditable(dto.name);
|
||||
return JSONResponse.createResponse(Status.OK, dto, null);
|
||||
}
|
||||
@ -935,8 +945,8 @@ public class ItemResource implements RESTResource {
|
||||
* @return Response configured to represent the Item in depending on the status
|
||||
*/
|
||||
private Response getItemResponse(final @Nullable UriBuilder uriBuilder, Status status, @Nullable Item item,
|
||||
Locale locale, @Nullable String errormessage) {
|
||||
Object entity = null != item ? EnrichedItemDTOMapper.map(item, true, null, uriBuilder, locale) : null;
|
||||
Locale locale, ZoneId zoneId, @Nullable String errormessage) {
|
||||
Object entity = null != item ? EnrichedItemDTOMapper.map(item, true, null, uriBuilder, locale, zoneId) : null;
|
||||
return JSONResponse.createResponse(status, entity, errormessage);
|
||||
}
|
||||
|
||||
|
@ -340,7 +340,7 @@ public class PersistenceResource implements RESTResource {
|
||||
|
||||
private ZonedDateTime convertTime(String sTime) {
|
||||
DateTimeType dateTime = new DateTimeType(sTime);
|
||||
return dateTime.getZonedDateTime();
|
||||
return dateTime.getZonedDateTime(timeZoneProvider.getTimeZone());
|
||||
}
|
||||
|
||||
private Response getItemHistoryDTO(@Nullable String serviceId, String itemName, @Nullable String timeBegin,
|
||||
|
@ -12,6 +12,7 @@
|
||||
*/
|
||||
package org.openhab.core.io.rest.core.item;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
@ -29,7 +30,9 @@ import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.dto.ItemDTO;
|
||||
import org.openhab.core.items.dto.ItemDTOMapper;
|
||||
import org.openhab.core.library.items.DateTimeItem;
|
||||
import org.openhab.core.library.items.NumberItem;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.transform.TransformationException;
|
||||
import org.openhab.core.transform.TransformationHelper;
|
||||
import org.openhab.core.transform.TransformationService;
|
||||
@ -63,28 +66,39 @@ public class EnrichedItemDTOMapper {
|
||||
* @param uriBuilder if present the URI builder contains one template that will be replaced by the specific item
|
||||
* name
|
||||
* @param locale locale (can be null)
|
||||
* @param zoneId time-zone id (can be null)
|
||||
* @return item DTO object
|
||||
*/
|
||||
public static EnrichedItemDTO map(Item item, boolean drillDown, @Nullable Predicate<Item> itemFilter,
|
||||
@Nullable UriBuilder uriBuilder, @Nullable Locale locale) {
|
||||
@Nullable UriBuilder uriBuilder, @Nullable Locale locale, @Nullable ZoneId zoneId) {
|
||||
ItemDTO itemDTO = ItemDTOMapper.map(item);
|
||||
return map(item, itemDTO, drillDown, itemFilter, uriBuilder, locale, new ArrayList<>());
|
||||
return map(item, itemDTO, drillDown, itemFilter, uriBuilder, locale, zoneId, new ArrayList<>());
|
||||
}
|
||||
|
||||
private static EnrichedItemDTO mapRecursive(Item item, @Nullable Predicate<Item> itemFilter,
|
||||
@Nullable UriBuilder uriBuilder, @Nullable Locale locale, List<Item> parents) {
|
||||
@Nullable UriBuilder uriBuilder, @Nullable Locale locale, @Nullable ZoneId zoneId, List<Item> parents) {
|
||||
ItemDTO itemDTO = ItemDTOMapper.map(item);
|
||||
return map(item, itemDTO, true, itemFilter, uriBuilder, locale, parents);
|
||||
return map(item, itemDTO, true, itemFilter, uriBuilder, locale, zoneId, parents);
|
||||
}
|
||||
|
||||
private static EnrichedItemDTO map(Item item, ItemDTO itemDTO, boolean drillDown,
|
||||
@Nullable Predicate<Item> itemFilter, @Nullable UriBuilder uriBuilder, @Nullable Locale locale,
|
||||
List<Item> parents) {
|
||||
@Nullable ZoneId zoneId, List<Item> parents) {
|
||||
if (item instanceof GroupItem) {
|
||||
// only add as parent item if it is a group, otherwise duplicate memberships trigger false warnings
|
||||
parents.add(item);
|
||||
}
|
||||
String state = item.getState().toFullString();
|
||||
String state;
|
||||
if (item instanceof DateTimeItem dateTimeItem && zoneId != null) {
|
||||
DateTimeType dateTime = dateTimeItem.getStateAs(DateTimeType.class);
|
||||
if (dateTime == null) {
|
||||
state = item.getState().toFullString();
|
||||
} else {
|
||||
state = dateTime.toFullString(zoneId);
|
||||
}
|
||||
} else {
|
||||
state = item.getState().toFullString();
|
||||
}
|
||||
String transformedState = considerTransformation(item, locale);
|
||||
if (state.equals(transformedState)) {
|
||||
transformedState = null;
|
||||
@ -117,7 +131,8 @@ public class EnrichedItemDTOMapper {
|
||||
"Recursive group membership found: {} is a member of {}, but it is also one of its ancestors.",
|
||||
member.getName(), groupItem.getName());
|
||||
} else if (itemFilter == null || itemFilter.test(member)) {
|
||||
members.add(mapRecursive(member, itemFilter, uriBuilder, locale, new ArrayList<>(parents)));
|
||||
members.add(
|
||||
mapRecursive(member, itemFilter, uriBuilder, locale, zoneId, new ArrayList<>(parents)));
|
||||
}
|
||||
}
|
||||
memberDTOs = members.toArray(new EnrichedItemDTO[0]);
|
||||
|
@ -55,31 +55,32 @@ public class EnrichedItemDTOMapperTest extends JavaTest {
|
||||
subGroup.addMember(stringItem);
|
||||
}
|
||||
|
||||
EnrichedGroupItemDTO dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, false, null, null, null);
|
||||
EnrichedGroupItemDTO dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, false, null, null, null,
|
||||
null);
|
||||
assertThat(dto.members.length, is(0));
|
||||
|
||||
dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, true, null, null, null);
|
||||
dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, true, null, null, null, null);
|
||||
assertThat(dto.members.length, is(3));
|
||||
assertThat(((EnrichedGroupItemDTO) dto.members[0]).members.length, is(1));
|
||||
|
||||
dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, true,
|
||||
i -> CoreItemFactory.NUMBER.equals(i.getType()), null, null);
|
||||
i -> CoreItemFactory.NUMBER.equals(i.getType()), null, null, null);
|
||||
assertThat(dto.members.length, is(1));
|
||||
|
||||
dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, true,
|
||||
i -> CoreItemFactory.NUMBER.equals(i.getType()) || i instanceof GroupItem, null, null);
|
||||
i -> CoreItemFactory.NUMBER.equals(i.getType()) || i instanceof GroupItem, null, null, null);
|
||||
assertThat(dto.members.length, is(2));
|
||||
assertThat(((EnrichedGroupItemDTO) dto.members[0]).members.length, is(0));
|
||||
|
||||
dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, true,
|
||||
i -> CoreItemFactory.NUMBER.equals(i.getType()) || i instanceof GroupItem, null, null);
|
||||
i -> CoreItemFactory.NUMBER.equals(i.getType()) || i instanceof GroupItem, null, null, null);
|
||||
assertThat(dto.members.length, is(2));
|
||||
assertThat(((EnrichedGroupItemDTO) dto.members[0]).members.length, is(0));
|
||||
|
||||
dto = (EnrichedGroupItemDTO) EnrichedItemDTOMapper.map(group, true,
|
||||
i -> CoreItemFactory.NUMBER.equals(i.getType()) || i.getType().equals(CoreItemFactory.STRING)
|
||||
|| i instanceof GroupItem,
|
||||
null, null);
|
||||
null, null, null);
|
||||
assertThat(dto.members.length, is(2));
|
||||
assertThat(((EnrichedGroupItemDTO) dto.members[0]).members.length, is(1));
|
||||
}
|
||||
@ -92,7 +93,7 @@ public class EnrichedItemDTOMapperTest extends JavaTest {
|
||||
groupItem1.addMember(groupItem2);
|
||||
groupItem2.addMember(groupItem1);
|
||||
|
||||
assertDoesNotThrow(() -> EnrichedItemDTOMapper.map(groupItem1, true, null, null, null));
|
||||
assertDoesNotThrow(() -> EnrichedItemDTOMapper.map(groupItem1, true, null, null, null, null));
|
||||
|
||||
assertLogMessage(EnrichedItemDTOMapper.class, LogLevel.ERROR,
|
||||
"Recursive group membership found: group1 is a member of group2, but it is also one of its ancestors.");
|
||||
@ -108,7 +109,7 @@ public class EnrichedItemDTOMapperTest extends JavaTest {
|
||||
groupItem2.addMember(groupItem3);
|
||||
groupItem3.addMember(groupItem1);
|
||||
|
||||
assertDoesNotThrow(() -> EnrichedItemDTOMapper.map(groupItem1, true, null, null, null));
|
||||
assertDoesNotThrow(() -> EnrichedItemDTOMapper.map(groupItem1, true, null, null, null, null));
|
||||
|
||||
assertLogMessage(EnrichedItemDTOMapper.class, LogLevel.ERROR,
|
||||
"Recursive group membership found: group1 is a member of group3, but it is also one of its ancestors.");
|
||||
@ -124,7 +125,7 @@ public class EnrichedItemDTOMapperTest extends JavaTest {
|
||||
groupItem1.addMember(numberItem);
|
||||
groupItem2.addMember(numberItem);
|
||||
|
||||
EnrichedItemDTOMapper.map(groupItem1, true, null, null, null);
|
||||
EnrichedItemDTOMapper.map(groupItem1, true, null, null, null, null);
|
||||
|
||||
assertNoLogMessage(EnrichedItemDTOMapper.class);
|
||||
}
|
||||
@ -139,7 +140,7 @@ public class EnrichedItemDTOMapperTest extends JavaTest {
|
||||
groupItem1.addMember(groupItem3);
|
||||
groupItem2.addMember(groupItem3);
|
||||
|
||||
EnrichedItemDTOMapper.map(groupItem1, true, null, null, null);
|
||||
EnrichedItemDTOMapper.map(groupItem1, true, null, null, null, null);
|
||||
|
||||
assertNoLogMessage(EnrichedItemDTOMapper.class);
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.events.Event;
|
||||
import org.openhab.core.events.EventSubscriber;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.io.rest.sitemap.internal.SitemapEvent;
|
||||
import org.openhab.core.io.rest.sitemap.internal.WidgetsChangeListener;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
@ -87,6 +88,7 @@ public class SitemapSubscriptionService implements ModelRepositoryChangeListener
|
||||
}
|
||||
|
||||
private final ItemUIRegistry itemUIRegistry;
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
|
||||
private final List<SitemapProvider> sitemapProviders = new ArrayList<>();
|
||||
|
||||
@ -107,8 +109,9 @@ public class SitemapSubscriptionService implements ModelRepositoryChangeListener
|
||||
|
||||
@Activate
|
||||
public SitemapSubscriptionService(Map<String, Object> config, final @Reference ItemUIRegistry itemUIRegistry,
|
||||
BundleContext bundleContext) {
|
||||
final @Reference TimeZoneProvider timeZoneProvider, BundleContext bundleContext) {
|
||||
this.itemUIRegistry = itemUIRegistry;
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
this.bundleContext = bundleContext;
|
||||
applyConfig(config);
|
||||
}
|
||||
@ -264,7 +267,7 @@ public class SitemapSubscriptionService implements ModelRepositoryChangeListener
|
||||
String sitemapWithPageId = getScopeIdentifier(sitemapName, pageId);
|
||||
ListenerRecord listener = pageChangeListeners.computeIfAbsent(sitemapWithPageId, v -> {
|
||||
WidgetsChangeListener newListener = new WidgetsChangeListener(sitemapName, pageId, itemUIRegistry,
|
||||
collectWidgets(sitemapName, pageId));
|
||||
timeZoneProvider, collectWidgets(sitemapName, pageId));
|
||||
ServiceRegistration<?> registration = bundleContext.registerService(EventSubscriber.class.getName(),
|
||||
newListener, null);
|
||||
return new ListenerRecord(newListener, registration);
|
||||
|
@ -62,6 +62,7 @@ import org.openhab.core.auth.Role;
|
||||
import org.openhab.core.common.ThreadPoolManager;
|
||||
import org.openhab.core.events.Event;
|
||||
import org.openhab.core.events.EventSubscriber;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.io.rest.JSONResponse;
|
||||
import org.openhab.core.io.rest.LocaleService;
|
||||
import org.openhab.core.io.rest.RESTConstants;
|
||||
@ -189,6 +190,7 @@ public class SitemapResource
|
||||
private final ItemUIRegistry itemUIRegistry;
|
||||
private final SitemapSubscriptionService subscriptions;
|
||||
private final LocaleService localeService;
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
|
||||
private final java.util.List<SitemapProvider> sitemapProviders = new ArrayList<>();
|
||||
|
||||
@ -204,9 +206,11 @@ public class SitemapResource
|
||||
public SitemapResource( //
|
||||
final @Reference ItemUIRegistry itemUIRegistry, //
|
||||
final @Reference LocaleService localeService, //
|
||||
final @Reference TimeZoneProvider timeZoneProvider, //
|
||||
final @Reference SitemapSubscriptionService subscriptions) {
|
||||
this.itemUIRegistry = itemUIRegistry;
|
||||
this.localeService = localeService;
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
this.subscriptions = subscriptions;
|
||||
|
||||
broadcaster = new SseBroadcaster<>();
|
||||
@ -596,7 +600,7 @@ public class SitemapResource
|
||||
boolean isMapview = "mapview".equalsIgnoreCase(widgetTypeName);
|
||||
Predicate<Item> itemFilter = (i -> CoreItemFactory.LOCATION.equals(i.getType()));
|
||||
bean.item = EnrichedItemDTOMapper.map(item, isMapview, itemFilter,
|
||||
UriBuilder.fromUri(uri).path("items/{itemName}"), locale);
|
||||
UriBuilder.fromUri(uri).path("items/{itemName}"), locale, timeZoneProvider.getTimeZone());
|
||||
bean.state = itemUIRegistry.getState(widget).toFullString();
|
||||
// In case the widget state is identical to the item state, its value is set to null.
|
||||
if (bean.state != null && bean.state.equals(bean.item.state)) {
|
||||
|
@ -27,6 +27,7 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.common.ThreadPoolManager;
|
||||
import org.openhab.core.events.Event;
|
||||
import org.openhab.core.events.EventSubscriber;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.io.rest.core.item.EnrichedItemDTOMapper;
|
||||
import org.openhab.core.io.rest.sitemap.SitemapSubscriptionService.SitemapSubscriptionCallback;
|
||||
import org.openhab.core.items.Item;
|
||||
@ -65,6 +66,7 @@ public class WidgetsChangeListener implements EventSubscriber {
|
||||
private final String sitemapName;
|
||||
private final String pageId;
|
||||
private final ItemUIRegistry itemUIRegistry;
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
private EList<Widget> widgets;
|
||||
private Set<Item> items;
|
||||
private final HashSet<String> filterItems = new HashSet<>();
|
||||
@ -79,11 +81,12 @@ public class WidgetsChangeListener implements EventSubscriber {
|
||||
* @param itemUIRegistry the ItemUIRegistry which is needed for the functionality
|
||||
* @param widgets the list of widgets that are part of the page.
|
||||
*/
|
||||
public WidgetsChangeListener(String sitemapName, String pageId, ItemUIRegistry itemUIRegistry,
|
||||
EList<Widget> widgets) {
|
||||
public WidgetsChangeListener(String sitemapName, String pageId, final ItemUIRegistry itemUIRegistry,
|
||||
final TimeZoneProvider timeZoneProvider, EList<Widget> widgets) {
|
||||
this.sitemapName = sitemapName;
|
||||
this.pageId = pageId;
|
||||
this.itemUIRegistry = itemUIRegistry;
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
|
||||
updateItemsAndWidgets(widgets);
|
||||
}
|
||||
@ -248,7 +251,8 @@ public class WidgetsChangeListener implements EventSubscriber {
|
||||
.substring(widget.eClass().getInstanceTypeName().lastIndexOf(".") + 1);
|
||||
boolean drillDown = "mapview".equalsIgnoreCase(widgetTypeName);
|
||||
Predicate<Item> itemFilter = (i -> CoreItemFactory.LOCATION.equals(i.getType()));
|
||||
event.item = EnrichedItemDTOMapper.map(itemToBeSent, drillDown, itemFilter, null, null);
|
||||
event.item = EnrichedItemDTOMapper.map(itemToBeSent, drillDown, itemFilter, null, null,
|
||||
timeZoneProvider.getTimeZone());
|
||||
|
||||
// event.state is an adjustment of the item state to the widget type.
|
||||
stateToBeSent = itemBelongsToWidget ? state : itemToBeSent.getState();
|
||||
|
@ -42,6 +42,7 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.io.rest.LocaleService;
|
||||
import org.openhab.core.io.rest.sitemap.SitemapSubscriptionService;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
@ -119,6 +120,7 @@ public class SitemapResourceTest extends JavaTest {
|
||||
private @Mock @NonNullByDefault({}) HttpHeaders headersMock;
|
||||
private @Mock @NonNullByDefault({}) Sitemap defaultSitemapMock;
|
||||
private @Mock @NonNullByDefault({}) ItemUIRegistry itemUIRegistryMock;
|
||||
private @Mock @NonNullByDefault({}) TimeZoneProvider timeZoneProviderMock;
|
||||
private @Mock @NonNullByDefault({}) LocaleService localeServiceMock;
|
||||
private @Mock @NonNullByDefault({}) HttpServletRequest requestMock;
|
||||
private @Mock @NonNullByDefault({}) SitemapProvider sitemapProviderMock;
|
||||
@ -129,10 +131,12 @@ public class SitemapResourceTest extends JavaTest {
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
subscriptions = new SitemapSubscriptionService(Collections.emptyMap(), itemUIRegistryMock, bundleContextMock);
|
||||
subscriptions = new SitemapSubscriptionService(Collections.emptyMap(), itemUIRegistryMock, timeZoneProviderMock,
|
||||
bundleContextMock);
|
||||
subscriptions.addSitemapProvider(sitemapProviderMock);
|
||||
|
||||
sitemapResource = new SitemapResource(itemUIRegistryMock, localeServiceMock, subscriptions);
|
||||
sitemapResource = new SitemapResource(itemUIRegistryMock, localeServiceMock, timeZoneProviderMock,
|
||||
subscriptions);
|
||||
|
||||
when(uriInfoMock.getAbsolutePathBuilder()).thenReturn(UriBuilder.fromPath(SITEMAP_PATH));
|
||||
when(uriInfoMock.getBaseUriBuilder()).thenReturn(UriBuilder.fromPath(SITEMAP_PATH));
|
||||
|
@ -12,7 +12,6 @@
|
||||
*/
|
||||
package org.openhab.core.io.rest.sse.internal;
|
||||
|
||||
import java.time.DateTimeException;
|
||||
import java.util.HashMap;
|
||||
import java.util.IllegalFormatException;
|
||||
import java.util.Locale;
|
||||
@ -28,6 +27,7 @@ import javax.ws.rs.sse.OutboundSseEvent.Builder;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.io.rest.LocaleService;
|
||||
import org.openhab.core.io.rest.sse.internal.dto.StateDTO;
|
||||
import org.openhab.core.items.Item;
|
||||
@ -68,13 +68,16 @@ public class SseItemStatesEventBuilder {
|
||||
|
||||
private final ItemRegistry itemRegistry;
|
||||
private final LocaleService localeService;
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
private final StartLevelService startLevelService;
|
||||
|
||||
@Activate
|
||||
public SseItemStatesEventBuilder(final @Reference ItemRegistry itemRegistry,
|
||||
final @Reference LocaleService localeService, final @Reference StartLevelService startLevelService) {
|
||||
final @Reference LocaleService localeService, final @Reference TimeZoneProvider timeZoneProvider,
|
||||
final @Reference StartLevelService startLevelService) {
|
||||
this.itemRegistry = itemRegistry;
|
||||
this.localeService = localeService;
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
this.startLevelService = startLevelService;
|
||||
}
|
||||
|
||||
@ -187,12 +190,6 @@ public class SseItemStatesEventBuilder {
|
||||
if (quantityState != null) {
|
||||
state = quantityState;
|
||||
}
|
||||
} else if (state instanceof DateTimeType type) {
|
||||
// Translate a DateTimeType state to the local time zone
|
||||
try {
|
||||
state = type.toLocaleZone();
|
||||
} catch (DateTimeException e) {
|
||||
}
|
||||
}
|
||||
|
||||
// The following exception handling has been added to work around a Java bug with formatting
|
||||
@ -200,7 +197,11 @@ public class SseItemStatesEventBuilder {
|
||||
// This also handles IllegalFormatConversionException, which is a subclass of
|
||||
// IllegalArgument.
|
||||
try {
|
||||
displayState = state.format(pattern);
|
||||
if (state instanceof DateTimeType dateTimeState) {
|
||||
displayState = dateTimeState.format(pattern, timeZoneProvider.getTimeZone());
|
||||
} else {
|
||||
displayState = state.format(pattern);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.debug(
|
||||
"Unable to format value '{}' of item {} using format pattern '{}': {}, displaying raw state",
|
||||
|
@ -27,6 +27,7 @@ import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.io.rest.LocaleService;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
@ -76,6 +77,7 @@ public class SseItemStatesEventBuilderTest {
|
||||
|
||||
private @Mock @NonNullByDefault({}) ItemRegistry itemRegistryMock;
|
||||
private @Mock @NonNullByDefault({}) LocaleService localeServiceMock;
|
||||
private @Mock @NonNullByDefault({}) TimeZoneProvider timeZoneProviderMock;
|
||||
private @Mock @NonNullByDefault({}) StartLevelService startLevelServiceMock;
|
||||
|
||||
private @Mock @NonNullByDefault({}) Item itemMock;
|
||||
@ -112,7 +114,7 @@ public class SseItemStatesEventBuilderTest {
|
||||
Mockito.when(itemMock.getName()).thenReturn(ITEM_NAME);
|
||||
|
||||
sseItemStatesEventBuilder = new SseItemStatesEventBuilder(itemRegistryMock, localeServiceMock,
|
||||
startLevelServiceMock);
|
||||
timeZoneProviderMock, startLevelServiceMock);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
|
@ -12,14 +12,11 @@
|
||||
*/
|
||||
package org.openhab.core.thing.internal.profiles;
|
||||
|
||||
import java.time.DateTimeException;
|
||||
import java.time.Duration;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.internal.i18n.I18nProviderImpl;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.thing.profiles.ProfileCallback;
|
||||
import org.openhab.core.thing.profiles.ProfileContext;
|
||||
@ -36,22 +33,18 @@ import org.slf4j.LoggerFactory;
|
||||
/**
|
||||
* Applies the given parameter "offset" to a {@link DateTimeType} state.
|
||||
*
|
||||
* Options for the "timezone" parameter are provided by the {@link I18nProviderImpl}.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TimestampOffsetProfile implements StateProfile {
|
||||
|
||||
static final String OFFSET_PARAM = "offset";
|
||||
static final String TIMEZONE_PARAM = "timezone";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TimestampOffsetProfile.class);
|
||||
|
||||
private final ProfileCallback callback;
|
||||
|
||||
private final Duration offset;
|
||||
private @Nullable ZoneId timeZone;
|
||||
|
||||
public TimestampOffsetProfile(ProfileCallback callback, ProfileContext context) {
|
||||
this.callback = callback;
|
||||
@ -68,19 +61,6 @@ public class TimestampOffsetProfile implements StateProfile {
|
||||
OFFSET_PARAM);
|
||||
offset = Duration.ZERO;
|
||||
}
|
||||
|
||||
String timeZoneParam = toStringOrNull(context.getConfiguration().get(TIMEZONE_PARAM));
|
||||
logger.debug("Configuring profile with {} parameter '{}'", TIMEZONE_PARAM, timeZoneParam);
|
||||
if (timeZoneParam == null || timeZoneParam.isBlank()) {
|
||||
timeZone = null;
|
||||
} else {
|
||||
try {
|
||||
timeZone = ZoneId.of(timeZoneParam);
|
||||
} catch (DateTimeException e) {
|
||||
logger.debug("Error setting time zone '{}': {}", timeZoneParam, e.getMessage());
|
||||
timeZone = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable String toStringOrNull(@Nullable Object value) {
|
||||
@ -98,20 +78,20 @@ public class TimestampOffsetProfile implements StateProfile {
|
||||
|
||||
@Override
|
||||
public void onCommandFromItem(Command command) {
|
||||
callback.handleCommand((Command) applyOffsetAndTimezone(command, false));
|
||||
callback.handleCommand((Command) applyOffset(command, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommandFromHandler(Command command) {
|
||||
callback.sendCommand((Command) applyOffsetAndTimezone(command, true));
|
||||
callback.sendCommand((Command) applyOffset(command, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateUpdateFromHandler(State state) {
|
||||
callback.sendUpdate((State) applyOffsetAndTimezone(state, true));
|
||||
callback.sendUpdate((State) applyOffset(state, true));
|
||||
}
|
||||
|
||||
private Type applyOffsetAndTimezone(Type type, boolean towardsItem) {
|
||||
private Type applyOffset(Type type, boolean towardsItem) {
|
||||
if (type instanceof UnDefType) {
|
||||
// we cannot adjust UNDEF or NULL values, thus we simply return them without reporting an error or warning
|
||||
return type;
|
||||
@ -120,20 +100,15 @@ public class TimestampOffsetProfile implements StateProfile {
|
||||
Duration finalOffset = towardsItem ? offset : offset.negated();
|
||||
Type result;
|
||||
if (type instanceof DateTimeType timeType) {
|
||||
ZonedDateTime zdt = timeType.getZonedDateTime();
|
||||
Instant instant = timeType.getInstant();
|
||||
|
||||
// apply offset
|
||||
if (!Duration.ZERO.equals(offset)) {
|
||||
// we do not need apply an offset equals to 0
|
||||
zdt = zdt.plus(finalOffset);
|
||||
instant = instant.plus(finalOffset);
|
||||
}
|
||||
|
||||
// apply time zone
|
||||
ZoneId localTimeZone = timeZone;
|
||||
if (localTimeZone != null && !zdt.getZone().equals(localTimeZone) && towardsItem) {
|
||||
zdt = zdt.withZoneSameInstant(localTimeZone);
|
||||
}
|
||||
result = new DateTimeType(zdt);
|
||||
result = new DateTimeType(instant);
|
||||
} else {
|
||||
logger.warn(
|
||||
"Offset '{}' cannot be applied to the incompatible state '{}' sent from the binding. Returning original state.",
|
||||
|
@ -12,10 +12,5 @@
|
||||
in the reverse
|
||||
direction.</description>
|
||||
</parameter>
|
||||
<parameter name="timezone" type="text">
|
||||
<label>Time Zone</label>
|
||||
<description>A time zone to be applied on the state towards the item.</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
||||
|
@ -21,7 +21,5 @@ profile-type.system.timestamp-change.label = Timestamp on Change
|
||||
profile-type.system.timestamp-offset.label = Timestamp Offset
|
||||
profile.config.system.timestamp-offset.offset.label = Offset
|
||||
profile.config.system.timestamp-offset.offset.description = Offset to be applied on the state towards the item. The negative offset will be applied in the reverse direction.
|
||||
profile.config.system.timestamp-offset.timezone.label = Time Zone
|
||||
profile.config.system.timestamp-offset.timezone.description = A time zone to be applied on the state.
|
||||
profile-type.system.timestamp-trigger.label = Timestamp on Trigger
|
||||
profile-type.system.timestamp-update.label = Timestamp on Update
|
||||
|
@ -15,14 +15,12 @@ package org.openhab.core.thing.internal.profiles;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
@ -45,28 +43,23 @@ public class TimestampOffsetProfileTest {
|
||||
|
||||
public static class ParameterSet {
|
||||
public final long seconds;
|
||||
public final @Nullable String timeZone;
|
||||
|
||||
public ParameterSet(long seconds, @Nullable String timeZone) {
|
||||
public ParameterSet(long seconds) {
|
||||
this.seconds = seconds;
|
||||
this.timeZone = timeZone;
|
||||
}
|
||||
}
|
||||
|
||||
public static Collection<Object[]> parameters() {
|
||||
return List.of(new Object[][] { //
|
||||
{ new ParameterSet(0, null) }, //
|
||||
{ new ParameterSet(30, null) }, //
|
||||
{ new ParameterSet(-30, null) }, //
|
||||
{ new ParameterSet(0, "Europe/Berlin") }, //
|
||||
{ new ParameterSet(30, "Europe/Berlin") }, //
|
||||
{ new ParameterSet(-30, "Europe/Berlin") } });
|
||||
{ new ParameterSet(0) }, //
|
||||
{ new ParameterSet(30) }, //
|
||||
{ new ParameterSet(-30) } });
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUNDEFOnStateUpdateFromHandler() {
|
||||
ProfileCallback callback = mock(ProfileCallback.class);
|
||||
TimestampOffsetProfile offsetProfile = createProfile(callback, Long.toString(60), null);
|
||||
TimestampOffsetProfile offsetProfile = createProfile(callback, Long.toString(60));
|
||||
|
||||
State state = UnDefType.UNDEF;
|
||||
offsetProfile.onStateUpdateFromHandler(state);
|
||||
@ -82,8 +75,7 @@ public class TimestampOffsetProfileTest {
|
||||
@MethodSource("parameters")
|
||||
public void testOnCommandFromItem(ParameterSet parameterSet) {
|
||||
ProfileCallback callback = mock(ProfileCallback.class);
|
||||
TimestampOffsetProfile offsetProfile = createProfile(callback, Long.toString(parameterSet.seconds),
|
||||
parameterSet.timeZone);
|
||||
TimestampOffsetProfile offsetProfile = createProfile(callback, Long.toString(parameterSet.seconds));
|
||||
|
||||
Command cmd = DateTimeType.valueOf("2021-03-30T10:58:47.033+0000");
|
||||
offsetProfile.onCommandFromItem(cmd);
|
||||
@ -94,17 +86,15 @@ public class TimestampOffsetProfileTest {
|
||||
Command result = capture.getValue();
|
||||
DateTimeType updateResult = (DateTimeType) result;
|
||||
DateTimeType expectedResult = new DateTimeType(
|
||||
((DateTimeType) cmd).getZonedDateTime().minusSeconds(parameterSet.seconds));
|
||||
assertEquals(ZoneOffset.UTC, updateResult.getZonedDateTime().getOffset());
|
||||
assertEquals(expectedResult.getZonedDateTime(), updateResult.getZonedDateTime());
|
||||
((DateTimeType) cmd).getInstant().minusSeconds(parameterSet.seconds));
|
||||
assertEquals(expectedResult.getInstant(), updateResult.getInstant());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("parameters")
|
||||
public void testOnCommandFromHandler(ParameterSet parameterSet) {
|
||||
ProfileCallback callback = mock(ProfileCallback.class);
|
||||
TimestampOffsetProfile offsetProfile = createProfile(callback, Long.toString(parameterSet.seconds),
|
||||
parameterSet.timeZone);
|
||||
TimestampOffsetProfile offsetProfile = createProfile(callback, Long.toString(parameterSet.seconds));
|
||||
|
||||
Command cmd = new DateTimeType("2021-03-30T10:58:47.033+0000");
|
||||
offsetProfile.onCommandFromHandler(cmd);
|
||||
@ -115,20 +105,15 @@ public class TimestampOffsetProfileTest {
|
||||
Command result = capture.getValue();
|
||||
DateTimeType updateResult = (DateTimeType) result;
|
||||
DateTimeType expectedResult = new DateTimeType(
|
||||
((DateTimeType) cmd).getZonedDateTime().plusSeconds(parameterSet.seconds));
|
||||
String timeZone = parameterSet.timeZone;
|
||||
if (timeZone != null) {
|
||||
expectedResult = expectedResult.toZone(timeZone);
|
||||
}
|
||||
assertEquals(expectedResult.getZonedDateTime(), updateResult.getZonedDateTime());
|
||||
((DateTimeType) cmd).getInstant().plusSeconds(parameterSet.seconds));
|
||||
assertEquals(expectedResult.getInstant(), updateResult.getInstant());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("parameters")
|
||||
public void testOnStateUpdateFromHandler(ParameterSet parameterSet) {
|
||||
ProfileCallback callback = mock(ProfileCallback.class);
|
||||
TimestampOffsetProfile offsetProfile = createProfile(callback, Long.toString(parameterSet.seconds),
|
||||
parameterSet.timeZone);
|
||||
TimestampOffsetProfile offsetProfile = createProfile(callback, Long.toString(parameterSet.seconds));
|
||||
|
||||
State state = new DateTimeType("2021-03-30T10:58:47.033+0000");
|
||||
offsetProfile.onStateUpdateFromHandler(state);
|
||||
@ -139,21 +124,14 @@ public class TimestampOffsetProfileTest {
|
||||
State result = capture.getValue();
|
||||
DateTimeType updateResult = (DateTimeType) result;
|
||||
DateTimeType expectedResult = new DateTimeType(
|
||||
((DateTimeType) state).getZonedDateTime().plusSeconds(parameterSet.seconds));
|
||||
String timeZone = parameterSet.timeZone;
|
||||
if (timeZone != null) {
|
||||
expectedResult = expectedResult.toZone(timeZone);
|
||||
}
|
||||
assertEquals(expectedResult.getZonedDateTime(), updateResult.getZonedDateTime());
|
||||
((DateTimeType) state).getInstant().plusSeconds(parameterSet.seconds));
|
||||
assertEquals(expectedResult.getInstant(), updateResult.getInstant());
|
||||
}
|
||||
|
||||
private TimestampOffsetProfile createProfile(ProfileCallback callback, String offset, @Nullable String timeZone) {
|
||||
private TimestampOffsetProfile createProfile(ProfileCallback callback, String offset) {
|
||||
ProfileContext context = mock(ProfileContext.class);
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put(TimestampOffsetProfile.OFFSET_PARAM, offset);
|
||||
if (timeZone != null) {
|
||||
properties.put(TimestampOffsetProfile.TIMEZONE_PARAM, timeZone);
|
||||
}
|
||||
when(context.getConfiguration()).thenReturn(new Configuration(properties));
|
||||
return new TimestampOffsetProfile(callback, context);
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ package org.openhab.core.thing.internal.profiles;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
@ -40,7 +40,7 @@ public class TimestampProfileTest extends JavaTest {
|
||||
ProfileCallback callback = mock(ProfileCallback.class);
|
||||
TimestampUpdateProfile timestampProfile = new TimestampUpdateProfile(callback);
|
||||
|
||||
ZonedDateTime now = ZonedDateTime.now();
|
||||
Instant now = Instant.now();
|
||||
timestampProfile.onStateUpdateFromHandler(new DecimalType(23));
|
||||
|
||||
ArgumentCaptor<State> capture = ArgumentCaptor.forClass(State.class);
|
||||
@ -48,7 +48,7 @@ public class TimestampProfileTest extends JavaTest {
|
||||
|
||||
State result = capture.getValue();
|
||||
DateTimeType updateResult = (DateTimeType) result;
|
||||
ZonedDateTime timestamp = updateResult.getZonedDateTime();
|
||||
Instant timestamp = updateResult.getInstant();
|
||||
long difference = ChronoUnit.MINUTES.between(now, timestamp);
|
||||
assertTrue(difference < 1);
|
||||
}
|
||||
@ -66,7 +66,7 @@ public class TimestampProfileTest extends JavaTest {
|
||||
State result = capture.getValue();
|
||||
DateTimeType changeResult = (DateTimeType) result;
|
||||
|
||||
waitForAssert(() -> assertTrue(ZonedDateTime.now().isAfter(changeResult.getZonedDateTime())));
|
||||
waitForAssert(() -> assertTrue(Instant.now().isAfter(changeResult.getInstant())));
|
||||
|
||||
// The state is unchanged, no additional call to the callback
|
||||
timestampProfile.onStateUpdateFromHandler(new DecimalType(23));
|
||||
@ -77,6 +77,6 @@ public class TimestampProfileTest extends JavaTest {
|
||||
verify(callback, times(2)).sendUpdate(capture.capture());
|
||||
result = capture.getValue();
|
||||
DateTimeType updatedResult = (DateTimeType) result;
|
||||
assertTrue(updatedResult.getZonedDateTime().isAfter(changeResult.getZonedDateTime()));
|
||||
assertTrue(updatedResult.getInstant().isAfter(changeResult.getInstant()));
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ package org.openhab.core.thing.internal.profiles;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
@ -38,14 +38,14 @@ public class TimestampTriggerProfileTest {
|
||||
ProfileCallback callback = mock(ProfileCallback.class);
|
||||
TriggerProfile profile = new TimestampTriggerProfile(callback);
|
||||
|
||||
ZonedDateTime now = ZonedDateTime.now();
|
||||
Instant now = Instant.now();
|
||||
profile.onTriggerFromHandler(CommonTriggerEvents.PRESSED);
|
||||
ArgumentCaptor<State> capture = ArgumentCaptor.forClass(State.class);
|
||||
verify(callback, times(1)).sendUpdate(capture.capture());
|
||||
|
||||
State result = capture.getValue();
|
||||
DateTimeType updateResult = (DateTimeType) result;
|
||||
ZonedDateTime timestamp = updateResult.getZonedDateTime();
|
||||
Instant timestamp = updateResult.getInstant();
|
||||
long difference = ChronoUnit.MINUTES.between(now, timestamp);
|
||||
assertTrue(difference < 1);
|
||||
}
|
||||
|
@ -12,8 +12,7 @@
|
||||
*/
|
||||
package org.openhab.core.ui.internal.items;
|
||||
|
||||
import java.time.DateTimeException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@ -40,6 +39,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.common.registry.RegistryChangeListener;
|
||||
import org.openhab.core.config.core.ConfigurableService;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemNotFoundException;
|
||||
@ -138,6 +138,7 @@ public class ItemUIRegistryImpl implements ItemUIRegistry {
|
||||
protected final Set<ItemUIProvider> itemUIProviders = new HashSet<>();
|
||||
|
||||
private final ItemRegistry itemRegistry;
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
|
||||
private final Map<Widget, Widget> defaultWidgets = Collections.synchronizedMap(new WeakHashMap<>());
|
||||
|
||||
@ -154,8 +155,10 @@ public class ItemUIRegistryImpl implements ItemUIRegistry {
|
||||
}
|
||||
|
||||
@Activate
|
||||
public ItemUIRegistryImpl(@Reference ItemRegistry itemRegistry) {
|
||||
public ItemUIRegistryImpl(final @Reference ItemRegistry itemRegistry,
|
||||
final @Reference TimeZoneProvider timeZoneProvider) {
|
||||
this.itemRegistry = itemRegistry;
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
}
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
|
||||
@ -455,12 +458,6 @@ public class ItemUIRegistryImpl implements ItemUIRegistry {
|
||||
quantityState = convertStateToWidgetUnit(quantityState, w);
|
||||
state = quantityState;
|
||||
}
|
||||
} else if (state instanceof DateTimeType type) {
|
||||
// Translate a DateTimeType state to the local time zone
|
||||
try {
|
||||
state = type.toLocaleZone();
|
||||
} catch (DateTimeException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
// The following exception handling has been added to work around a Java bug with formatting
|
||||
@ -474,10 +471,20 @@ public class ItemUIRegistryImpl implements ItemUIRegistry {
|
||||
String type = matcher.group(1);
|
||||
String function = matcher.group(2);
|
||||
String value = matcher.group(3);
|
||||
formatPattern = type + "(" + function + "):" + state.format(value);
|
||||
transformFailbackValue = state.toString();
|
||||
formatPattern = type + "(" + function + "):";
|
||||
if (state instanceof DateTimeType dateTimeState) {
|
||||
formatPattern += dateTimeState.format(value, timeZoneProvider.getTimeZone());
|
||||
transformFailbackValue = dateTimeState.toFullString(timeZoneProvider.getTimeZone());
|
||||
} else {
|
||||
formatPattern += state.format(value);
|
||||
transformFailbackValue = state.toString();
|
||||
}
|
||||
} else {
|
||||
formatPattern = state.format(formatPattern);
|
||||
if (state instanceof DateTimeType dateTimeState) {
|
||||
formatPattern = dateTimeState.format(formatPattern, timeZoneProvider.getTimeZone());
|
||||
} else {
|
||||
formatPattern = state.format(formatPattern);
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warn("Exception while formatting value '{}' of item {} with format '{}': {}", state,
|
||||
@ -1138,9 +1145,9 @@ public class ItemUIRegistryImpl implements ItemUIRegistry {
|
||||
} catch (NumberFormatException e) {
|
||||
logger.debug("matchStateToValue: Decimal format exception: ", e);
|
||||
}
|
||||
} else if (state instanceof DateTimeType type) {
|
||||
ZonedDateTime val = type.getZonedDateTime();
|
||||
ZonedDateTime now = ZonedDateTime.now();
|
||||
} else if (state instanceof DateTimeType dateTimeState) {
|
||||
Instant val = dateTimeState.getInstant();
|
||||
Instant now = Instant.now();
|
||||
long secsDif = ChronoUnit.SECONDS.between(val, now);
|
||||
|
||||
try {
|
||||
|
@ -20,6 +20,7 @@ import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
@ -36,6 +37,7 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemNotFoundException;
|
||||
@ -99,22 +101,24 @@ public class ItemUIRegistryImplTest {
|
||||
// we need to get the decimal separator of the default locale for our tests
|
||||
private static final char SEP = (new DecimalFormatSymbols().getDecimalSeparator());
|
||||
private static final String ITEM_NAME = "Item";
|
||||
private static final String DEFAULT_TIME_ZONE = "GMT-6";
|
||||
|
||||
private @NonNullByDefault({}) ItemUIRegistryImpl uiRegistry;
|
||||
|
||||
private @Mock @NonNullByDefault({}) ItemRegistry registryMock;
|
||||
private @Mock @NonNullByDefault({}) TimeZoneProvider timeZoneProviderMock;
|
||||
private @Mock @NonNullByDefault({}) Widget widgetMock;
|
||||
private @Mock @NonNullByDefault({}) Item itemMock;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
uiRegistry = new ItemUIRegistryImpl(registryMock);
|
||||
uiRegistry = new ItemUIRegistryImpl(registryMock, timeZoneProviderMock);
|
||||
|
||||
when(widgetMock.getItem()).thenReturn(ITEM_NAME);
|
||||
when(registryMock.getItem(ITEM_NAME)).thenReturn(itemMock);
|
||||
when(timeZoneProviderMock.getTimeZone()).thenReturn(ZoneId.of(DEFAULT_TIME_ZONE));
|
||||
|
||||
// Set default time zone to GMT-6
|
||||
TimeZone.setDefault(TimeZone.getTimeZone("GMT-6"));
|
||||
TimeZone.setDefault(TimeZone.getTimeZone(DEFAULT_TIME_ZONE));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -12,7 +12,7 @@
|
||||
*/
|
||||
package org.openhab.core.library.types;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.Instant;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
@ -41,12 +41,12 @@ public interface DateTimeGroupFunction extends GroupFunction {
|
||||
@Override
|
||||
public State calculate(@Nullable Set<Item> items) {
|
||||
if (items != null && !items.isEmpty()) {
|
||||
ZonedDateTime max = null;
|
||||
Instant max = null;
|
||||
for (Item item : items) {
|
||||
DateTimeType itemState = item.getStateAs(DateTimeType.class);
|
||||
if (itemState != null) {
|
||||
if (max == null || max.isBefore(itemState.getZonedDateTime())) {
|
||||
max = itemState.getZonedDateTime();
|
||||
if (max == null || max.isBefore(itemState.getInstant())) {
|
||||
max = itemState.getInstant();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -84,12 +84,12 @@ public interface DateTimeGroupFunction extends GroupFunction {
|
||||
@Override
|
||||
public State calculate(@Nullable Set<Item> items) {
|
||||
if (items != null && !items.isEmpty()) {
|
||||
ZonedDateTime max = null;
|
||||
Instant max = null;
|
||||
for (Item item : items) {
|
||||
DateTimeType itemState = item.getStateAs(DateTimeType.class);
|
||||
if (itemState != null) {
|
||||
if (max == null || max.isAfter(itemState.getZonedDateTime())) {
|
||||
max = itemState.getZonedDateTime();
|
||||
if (max == null || max.isAfter(itemState.getInstant())) {
|
||||
max = itemState.getInstant();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ import java.time.DateTimeException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
@ -39,6 +38,7 @@ import org.openhab.core.types.State;
|
||||
* @author Laurent Garnier - added methods toLocaleZone and toZone
|
||||
* @author Gaël L'hopital - added ability to use second and milliseconds unix time
|
||||
* @author Jimmy Tanagra - implement Comparable
|
||||
* @author Jacob Laursen - Refactored to use {@link Instant} internally
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DateTimeType implements PrimitiveType, State, Command, Comparable<DateTimeType> {
|
||||
@ -73,48 +73,65 @@ public class DateTimeType implements PrimitiveType, State, Command, Comparable<D
|
||||
private static final DateTimeFormatter FORMATTER_TZ_RFC = DateTimeFormatter
|
||||
.ofPattern(DATE_FORMAT_PATTERN_WITH_TZ_RFC);
|
||||
|
||||
private ZonedDateTime zonedDateTime;
|
||||
private Instant instant;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DateTimeType} representing the current
|
||||
* instant from the system clock.
|
||||
*/
|
||||
public DateTimeType() {
|
||||
this(ZonedDateTime.now());
|
||||
this(Instant.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link DateTimeType} with the given value.
|
||||
*
|
||||
* @param instant
|
||||
*/
|
||||
public DateTimeType(Instant instant) {
|
||||
this.instant = instant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link DateTimeType} with the given value.
|
||||
* The time-zone information will be discarded, only the
|
||||
* resulting {@link Instant} is preserved.
|
||||
*
|
||||
* @param zoned
|
||||
*/
|
||||
public DateTimeType(ZonedDateTime zoned) {
|
||||
this.zonedDateTime = ZonedDateTime.from(zoned).withFixedOffsetZone();
|
||||
instant = zoned.toInstant();
|
||||
}
|
||||
|
||||
public DateTimeType(String zonedValue) {
|
||||
ZonedDateTime date;
|
||||
try {
|
||||
// direct parsing (date and time)
|
||||
try {
|
||||
if (DATE_PARSE_PATTERN_WITH_SPACE.matcher(zonedValue).matches()) {
|
||||
date = parse(zonedValue.substring(0, 10) + "T" + zonedValue.substring(11));
|
||||
instant = parse(zonedValue.substring(0, 10) + "T" + zonedValue.substring(11));
|
||||
} else {
|
||||
date = parse(zonedValue);
|
||||
instant = parse(zonedValue);
|
||||
}
|
||||
} catch (DateTimeParseException fullDtException) {
|
||||
// time only
|
||||
try {
|
||||
date = parse("1970-01-01T" + zonedValue);
|
||||
instant = parse("1970-01-01T" + zonedValue);
|
||||
} catch (DateTimeParseException timeOnlyException) {
|
||||
try {
|
||||
long epoch = Double.valueOf(zonedValue).longValue();
|
||||
int length = (int) (Math.log10(epoch >= 0 ? epoch : epoch * -1) + 1);
|
||||
Instant i;
|
||||
// Assume that below 12 digits we're in seconds
|
||||
if (length < 12) {
|
||||
i = Instant.ofEpochSecond(epoch);
|
||||
instant = Instant.ofEpochSecond(epoch);
|
||||
} else {
|
||||
i = Instant.ofEpochMilli(epoch);
|
||||
instant = Instant.ofEpochMilli(epoch);
|
||||
}
|
||||
date = ZonedDateTime.ofInstant(i, ZoneOffset.UTC);
|
||||
} catch (NumberFormatException notANumberException) {
|
||||
// date only
|
||||
if (zonedValue.length() == 10) {
|
||||
date = parse(zonedValue + "T00:00:00");
|
||||
instant = parse(zonedValue + "T00:00:00");
|
||||
} else {
|
||||
date = parse(zonedValue.substring(0, 10) + "T00:00:00" + zonedValue.substring(10));
|
||||
instant = parse(zonedValue.substring(0, 10) + "T00:00:00" + zonedValue.substring(10));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -122,21 +139,37 @@ public class DateTimeType implements PrimitiveType, State, Command, Comparable<D
|
||||
} catch (DateTimeParseException invalidFormatException) {
|
||||
throw new IllegalArgumentException(zonedValue + " is not in a valid format.", invalidFormatException);
|
||||
}
|
||||
|
||||
zonedDateTime = date.withFixedOffsetZone();
|
||||
}
|
||||
|
||||
public ZonedDateTime getZonedDateTime() {
|
||||
return zonedDateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get curent object represented as an {@link Instant}
|
||||
* @deprecated
|
||||
* Get object represented as a {@link ZonedDateTime} with system
|
||||
* default time-zone applied
|
||||
*
|
||||
* @return an {@link Instant} representation of the current object
|
||||
* @return a {@link ZonedDateTime} representation of the object
|
||||
*/
|
||||
@Deprecated
|
||||
public ZonedDateTime getZonedDateTime() {
|
||||
return getZonedDateTime(ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object represented as a {@link ZonedDateTime} with the
|
||||
* the provided time-zone applied
|
||||
*
|
||||
* @return a {@link ZonedDateTime} representation of the object
|
||||
*/
|
||||
public ZonedDateTime getZonedDateTime(ZoneId zoneId) {
|
||||
return instant.atZone(zoneId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Instant} value of the object
|
||||
*
|
||||
* @return the {@link Instant} value of the object
|
||||
*/
|
||||
public Instant getInstant() {
|
||||
return zonedDateTime.toInstant();
|
||||
return instant;
|
||||
}
|
||||
|
||||
public static DateTimeType valueOf(String value) {
|
||||
@ -145,6 +178,11 @@ public class DateTimeType implements PrimitiveType, State, Command, Comparable<D
|
||||
|
||||
@Override
|
||||
public String format(@Nullable String pattern) {
|
||||
return format(pattern, ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
public String format(@Nullable String pattern, ZoneId zoneId) {
|
||||
ZonedDateTime zonedDateTime = instant.atZone(zoneId);
|
||||
if (pattern == null) {
|
||||
return DateTimeFormatter.ofPattern(DATE_PATTERN).format(zonedDateTime);
|
||||
}
|
||||
@ -153,42 +191,48 @@ public class DateTimeType implements PrimitiveType, State, Command, Comparable<D
|
||||
}
|
||||
|
||||
public String format(Locale locale, String pattern) {
|
||||
return String.format(locale, pattern, zonedDateTime);
|
||||
return String.format(locale, pattern, getZonedDateTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link DateTimeType} being the translation of the current object to the locale time zone
|
||||
* @deprecated
|
||||
* Create a {@link DateTimeType} being the translation of the current object to the locale time zone
|
||||
*
|
||||
* @return a {@link DateTimeType} translated to the locale time zone
|
||||
* @throws DateTimeException if the converted zone ID has an invalid format or the result exceeds the supported date
|
||||
* range
|
||||
* @throws ZoneRulesException if the converted zone region ID cannot be found
|
||||
*/
|
||||
@Deprecated
|
||||
public DateTimeType toLocaleZone() throws DateTimeException, ZoneRulesException {
|
||||
return toZone(ZoneId.systemDefault());
|
||||
return new DateTimeType(instant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link DateTimeType} being the translation of the current object to a given zone
|
||||
* @deprecated
|
||||
* Create a {@link DateTimeType} being the translation of the current object to a given zone
|
||||
*
|
||||
* @param zone the target zone as a string
|
||||
* @return a {@link DateTimeType} translated to the given zone
|
||||
* @throws DateTimeException if the zone has an invalid format or the result exceeds the supported date range
|
||||
* @throws ZoneRulesException if the zone is a region ID that cannot be found
|
||||
*/
|
||||
@Deprecated
|
||||
public DateTimeType toZone(String zone) throws DateTimeException, ZoneRulesException {
|
||||
return toZone(ZoneId.of(zone));
|
||||
return new DateTimeType(instant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link DateTimeType} being the translation of the current object to a given zone
|
||||
* @deprecated
|
||||
* Create a {@link DateTimeType} being the translation of the current object to a given zone
|
||||
*
|
||||
* @param zoneId the target {@link ZoneId}
|
||||
* @return a {@link DateTimeType} translated to the given zone
|
||||
* @throws DateTimeException if the result exceeds the supported date range
|
||||
*/
|
||||
@Deprecated
|
||||
public DateTimeType toZone(ZoneId zoneId) throws DateTimeException {
|
||||
return new DateTimeType(zonedDateTime.withZoneSameInstant(zoneId));
|
||||
return new DateTimeType(instant);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -198,7 +242,11 @@ public class DateTimeType implements PrimitiveType, State, Command, Comparable<D
|
||||
|
||||
@Override
|
||||
public String toFullString() {
|
||||
String formatted = zonedDateTime.format(FORMATTER_TZ_RFC);
|
||||
return toFullString(ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
public String toFullString(ZoneId zoneId) {
|
||||
String formatted = instant.atZone(zoneId).format(FORMATTER_TZ_RFC);
|
||||
if (formatted.contains(".")) {
|
||||
String sign = "";
|
||||
if (formatted.contains("+")) {
|
||||
@ -219,7 +267,7 @@ public class DateTimeType implements PrimitiveType, State, Command, Comparable<D
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + getZonedDateTime().hashCode();
|
||||
result = prime * result + instant.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -235,15 +283,15 @@ public class DateTimeType implements PrimitiveType, State, Command, Comparable<D
|
||||
return false;
|
||||
}
|
||||
DateTimeType other = (DateTimeType) obj;
|
||||
return zonedDateTime.compareTo(other.zonedDateTime) == 0;
|
||||
return instant.compareTo(other.instant) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(DateTimeType o) {
|
||||
return zonedDateTime.compareTo(o.getZonedDateTime());
|
||||
return instant.compareTo(o.getInstant());
|
||||
}
|
||||
|
||||
private ZonedDateTime parse(String value) throws DateTimeParseException {
|
||||
private Instant parse(String value) throws DateTimeParseException {
|
||||
ZonedDateTime date;
|
||||
try {
|
||||
date = ZonedDateTime.parse(value, PARSER_TZ_RFC);
|
||||
@ -260,6 +308,6 @@ public class DateTimeType implements PrimitiveType, State, Command, Comparable<D
|
||||
}
|
||||
}
|
||||
|
||||
return date;
|
||||
return date.toInstant();
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,8 @@ package org.openhab.core.library.types;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@ -36,36 +37,36 @@ public class DateTimeGroupFunctionTest {
|
||||
|
||||
@Test
|
||||
public void testLatestFunction() {
|
||||
ZonedDateTime expectedDateTime = ZonedDateTime.now();
|
||||
Instant expectedDateTime = Instant.now();
|
||||
Set<Item> items = new HashSet<>();
|
||||
items.add(new TestItem("TestItem1", new DateTimeType(expectedDateTime)));
|
||||
items.add(new TestItem("TestItem2", UnDefType.UNDEF));
|
||||
items.add(new TestItem("TestItem3", new DateTimeType(expectedDateTime.minusDays(10))));
|
||||
items.add(new TestItem("TestItem4", new DateTimeType(expectedDateTime.minusYears(1))));
|
||||
items.add(new TestItem("TestItem3", new DateTimeType(expectedDateTime.minus(10, ChronoUnit.DAYS))));
|
||||
items.add(new TestItem("TestItem4", new DateTimeType(expectedDateTime.minus(366, ChronoUnit.DAYS))));
|
||||
items.add(new TestItem("TestItem5", UnDefType.UNDEF));
|
||||
items.add(new TestItem("TestItem6", new DateTimeType(expectedDateTime.minusSeconds(1))));
|
||||
|
||||
GroupFunction function = new DateTimeGroupFunction.Latest();
|
||||
State state = function.calculate(items);
|
||||
|
||||
assertTrue(expectedDateTime.isEqual(((DateTimeType) state).getZonedDateTime()));
|
||||
assertTrue(expectedDateTime.equals(((DateTimeType) state).getInstant()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEarliestFunction() {
|
||||
ZonedDateTime expectedDateTime = ZonedDateTime.now();
|
||||
Instant expectedDateTime = Instant.now();
|
||||
Set<Item> items = new HashSet<>();
|
||||
items.add(new TestItem("TestItem1", new DateTimeType(expectedDateTime)));
|
||||
items.add(new TestItem("TestItem2", UnDefType.UNDEF));
|
||||
items.add(new TestItem("TestItem3", new DateTimeType(expectedDateTime.plusDays(10))));
|
||||
items.add(new TestItem("TestItem4", new DateTimeType(expectedDateTime.plusYears(1))));
|
||||
items.add(new TestItem("TestItem3", new DateTimeType(expectedDateTime.plus(10, ChronoUnit.DAYS))));
|
||||
items.add(new TestItem("TestItem4", new DateTimeType(expectedDateTime.plus(366, ChronoUnit.DAYS))));
|
||||
items.add(new TestItem("TestItem5", UnDefType.UNDEF));
|
||||
items.add(new TestItem("TestItem6", new DateTimeType(expectedDateTime.plusSeconds(1))));
|
||||
|
||||
GroupFunction function = new DateTimeGroupFunction.Earliest();
|
||||
State state = function.calculate(items);
|
||||
|
||||
assertTrue(expectedDateTime.isEqual(((DateTimeType) state).getZonedDateTime()));
|
||||
assertTrue(expectedDateTime.equals(((DateTimeType) state).getInstant()));
|
||||
}
|
||||
|
||||
private static class TestItem extends GenericItem {
|
||||
|
@ -13,6 +13,7 @@
|
||||
package org.openhab.core.library.types;
|
||||
|
||||
import static java.util.Map.entry;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
@ -30,11 +31,13 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
@ -183,19 +186,19 @@ public class DateTimeTypeTest {
|
||||
{ new ParameterSet(TimeZone.getTimeZone("UTC"), initTimeMap(), TimeZone.getTimeZone("UTC"),
|
||||
"2014-03-30T10:58:47.033+0000", "2014-03-30T10:58:47.033+0000") },
|
||||
{ new ParameterSet(TimeZone.getTimeZone("UTC"), initTimeMap(), TimeZone.getTimeZone("CET"),
|
||||
"2014-03-30T10:58:47.033+0200", "2014-03-30T08:58:47.033+0000") },
|
||||
"2014-03-30T08:58:47.033+0000", "2014-03-30T08:58:47.033+0000") },
|
||||
{ new ParameterSet(TimeZone.getTimeZone("UTC"), "2014-03-30T10:58:47.23",
|
||||
"2014-03-30T10:58:47.230+0000", "2014-03-30T10:58:47.230+0000") },
|
||||
{ new ParameterSet(TimeZone.getTimeZone("UTC"), "2014-03-30T10:58:47UTC",
|
||||
"2014-03-30T10:58:47.000+0000", "2014-03-30T10:58:47.000+0000") },
|
||||
{ new ParameterSet(TimeZone.getTimeZone("CET"), initTimeMap(), TimeZone.getTimeZone("UTC"),
|
||||
"2014-03-30T10:58:47.033+0000", "2014-03-30T12:58:47.033+0200") },
|
||||
"2014-03-30T12:58:47.033+0200", "2014-03-30T12:58:47.033+0200") },
|
||||
{ new ParameterSet(TimeZone.getTimeZone("CET"), initTimeMap(), TimeZone.getTimeZone("CET"),
|
||||
"2014-03-30T10:58:47.033+0200", "2014-03-30T10:58:47.033+0200") },
|
||||
{ new ParameterSet(TimeZone.getTimeZone("CET"), "2014-03-30T10:58:47CET",
|
||||
"2014-03-30T10:58:47.000+0200", "2014-03-30T10:58:47.000+0200") },
|
||||
{ new ParameterSet(TimeZone.getTimeZone("GMT+5"), "2014-03-30T10:58:47.000Z",
|
||||
"2014-03-30T10:58:47.000+0000", "2014-03-30T15:58:47.000+0500") },
|
||||
"2014-03-30T15:58:47.000+0500", "2014-03-30T15:58:47.000+0500") },
|
||||
{ new ParameterSet(TimeZone.getTimeZone("GMT+2"), null, null, "2014-03-30T10:58:47",
|
||||
"2014-03-30T10:58:47.000+0200", "2014-03-30T10:58:47.000+0200", null,
|
||||
"%1$td.%1$tm.%1$tY %1$tH:%1$tM", "30.03.2014 10:58") },
|
||||
@ -203,15 +206,15 @@ public class DateTimeTypeTest {
|
||||
"2014-03-30T10:58:47.033+0000", "2014-03-30T10:58:47.033+0000") },
|
||||
// Parameter set with an invalid time zone id as input, leading to GMT being considered
|
||||
{ new ParameterSet(TimeZone.getTimeZone("CET"), initTimeMap(), TimeZone.getTimeZone("+02:00"),
|
||||
"2014-03-30T10:58:47.033+0000", "2014-03-30T12:58:47.033+0200") },
|
||||
"2014-03-30T12:58:47.033+0200", "2014-03-30T12:58:47.033+0200") },
|
||||
// Parameter set with an invalid time zone id as input, leading to GMT being considered
|
||||
{ new ParameterSet(TimeZone.getTimeZone("GMT+2"), initTimeMap(), TimeZone.getTimeZone("GML"),
|
||||
"2014-03-30T10:58:47.033+0000", "2014-03-30T12:58:47.033+0200") },
|
||||
"2014-03-30T12:58:47.033+0200", "2014-03-30T12:58:47.033+0200") },
|
||||
{ new ParameterSet(TimeZone.getTimeZone("GMT-2"), initTimeMap(), TimeZone.getTimeZone("GMT+3"), null,
|
||||
"2014-03-30T10:58:47.033+0300", "2014-03-30T05:58:47.033-0200", Locale.GERMAN,
|
||||
"%1$tA %1$td.%1$tm.%1$tY %1$tH:%1$tM", "Sonntag 30.03.2014 10:58") },
|
||||
"2014-03-30T05:58:47.033-0200", "2014-03-30T05:58:47.033-0200", Locale.GERMAN,
|
||||
"%1$tA %1$td.%1$tm.%1$tY %1$tH:%1$tM", "Sonntag 30.03.2014 05:58") },
|
||||
{ new ParameterSet(TimeZone.getTimeZone("GMT-2"), initTimeMap(), TimeZone.getTimeZone("GMT-4"),
|
||||
"2014-03-30T10:58:47.033-0400", "2014-03-30T12:58:47.033-0200") },
|
||||
"2014-03-30T12:58:47.033-0200", "2014-03-30T12:58:47.033-0200") },
|
||||
{ new ParameterSet(TimeZone.getTimeZone("UTC"), "10:58:47", "1970-01-01T10:58:47.000+0000",
|
||||
"1970-01-01T10:58:47.000+0000") },
|
||||
{ new ParameterSet(TimeZone.getTimeZone("UTC"), "10:58", "1970-01-01T10:58:00.000+0000",
|
||||
@ -282,29 +285,37 @@ public class DateTimeTypeTest {
|
||||
DateTimeType dt1 = new DateTimeType("2019-06-12T17:30:00Z");
|
||||
DateTimeType dt2 = new DateTimeType("2019-06-12T17:30:00+0000");
|
||||
DateTimeType dt3 = new DateTimeType("2019-06-12T19:30:00+0200");
|
||||
DateTimeType dt4 = new DateTimeType("2019-06-12T19:30:00+0200");
|
||||
assertThat(dt1, is(dt2));
|
||||
|
||||
ZonedDateTime zdt1 = dt1.getZonedDateTime();
|
||||
ZonedDateTime zdt2 = dt2.getZonedDateTime();
|
||||
ZonedDateTime zdt3 = dt3.getZonedDateTime();
|
||||
ZonedDateTime zdt4 = dt4.getZonedDateTime(ZoneId.of("UTC"));
|
||||
assertThat(zdt1.getZone(), is(zdt2.getZone()));
|
||||
assertThat(zdt1, is(zdt2));
|
||||
assertThat(zdt1, is(zdt3.withZoneSameInstant(zdt1.getZone())));
|
||||
assertThat(zdt2, is(zdt3.withZoneSameInstant(zdt2.getZone())));
|
||||
assertThat(zdt1, is(zdt4));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void instantParsingTest() {
|
||||
DateTimeType dt1 = new DateTimeType("2019-06-12T17:30:00Z");
|
||||
DateTimeType dt2 = new DateTimeType("2019-06-12T17:30:00+0000");
|
||||
DateTimeType dt3 = new DateTimeType("2019-06-12T19:30:00+0200");
|
||||
DateTimeType dt1 = new DateTimeType(Instant.parse("2019-06-12T17:30:00Z"));
|
||||
DateTimeType dt2 = new DateTimeType("2019-06-12T17:30:00Z");
|
||||
DateTimeType dt3 = new DateTimeType("2019-06-12T17:30:00+0000");
|
||||
DateTimeType dt4 = new DateTimeType("2019-06-12T19:30:00+0200");
|
||||
assertThat(dt1, is(dt2));
|
||||
assertThat(dt2, is(dt3));
|
||||
assertThat(dt3, is(dt4));
|
||||
|
||||
Instant i1 = dt1.getInstant();
|
||||
Instant i2 = dt2.getInstant();
|
||||
Instant i3 = dt3.getInstant();
|
||||
Instant i4 = dt4.getInstant();
|
||||
assertThat(i1, is(i2));
|
||||
assertThat(i1, is(i3));
|
||||
assertThat(i2, is(i3));
|
||||
assertThat(i3, is(i4));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -370,22 +381,21 @@ public class DateTimeTypeTest {
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("parameters")
|
||||
public void changingZoneTest(ParameterSet parameterSet) {
|
||||
TimeZone.setDefault(parameterSet.defaultTimeZone);
|
||||
DateTimeType dt = createDateTimeType(parameterSet);
|
||||
DateTimeType dt2 = dt.toLocaleZone();
|
||||
assertEquals(parameterSet.expectedResultLocalTZ, dt2.toFullString());
|
||||
dt2 = dt.toZone(parameterSet.defaultTimeZone.toZoneId());
|
||||
assertEquals(parameterSet.expectedResultLocalTZ, dt2.toFullString());
|
||||
@MethodSource("provideTestCasesForFormatWithZone")
|
||||
void formatWithZone(String instant, @Nullable String pattern, ZoneId zoneId, String expected) {
|
||||
DateTimeType dt = new DateTimeType(Instant.parse(instant));
|
||||
String actual = dt.format(pattern, zoneId);
|
||||
assertThat(actual, is(equalTo(expected)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("parameters")
|
||||
public void changingZoneThrowsExceptionTest(ParameterSet parameterSet) {
|
||||
TimeZone.setDefault(parameterSet.defaultTimeZone);
|
||||
DateTimeType dt = createDateTimeType(parameterSet);
|
||||
assertThrows(DateTimeException.class, () -> dt.toZone("XXX"));
|
||||
private static Stream<Arguments> provideTestCasesForFormatWithZone() {
|
||||
return Stream.of( //
|
||||
Arguments.of("2024-11-11T20:39:01Z", null, ZoneId.of("UTC"), "2024-11-11T20:39:01"), //
|
||||
Arguments.of("2024-11-11T20:39:01Z", "%1$td.%1$tm.%1$tY %1$tH:%1$tM", ZoneId.of("Europe/Paris"),
|
||||
"11.11.2024 21:39"), //
|
||||
Arguments.of("2024-11-11T20:39:01Z", "%1$td.%1$tm.%1$tY %1$tH:%1$tM", ZoneId.of("US/Alaska"),
|
||||
"11.11.2024 11:39") //
|
||||
);
|
||||
}
|
||||
|
||||
private DateTimeType createDateTimeType(ParameterSet parameterSet) throws DateTimeException {
|
||||
@ -414,4 +424,25 @@ public class DateTimeTypeTest {
|
||||
|
||||
return LocalDateTime.of(year, month + 1, dayOfMonth, hourOfDay, minute, second, durationInNano);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestCasesForToFullStringWithZone")
|
||||
void toFullStringWithZone(String instant, ZoneId zoneId, String expected) {
|
||||
DateTimeType dt = new DateTimeType(Instant.parse(instant));
|
||||
String actual = dt.toFullString(zoneId);
|
||||
assertThat(actual, is(equalTo(expected)));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> provideTestCasesForToFullStringWithZone() {
|
||||
return Stream.of( //
|
||||
Arguments.of("2024-11-11T20:39:00Z", ZoneId.of("UTC"), "2024-11-11T20:39:00.000+0000"), //
|
||||
Arguments.of("2024-11-11T20:39:00.000000000Z", ZoneId.of("UTC"), "2024-11-11T20:39:00.000+0000"), //
|
||||
Arguments.of("2024-11-11T20:39:00.000000001Z", ZoneId.of("UTC"), "2024-11-11T20:39:00.000000001+0000"), //
|
||||
Arguments.of("2024-11-11T20:39:00.123000000Z", ZoneId.of("UTC"), "2024-11-11T20:39:00.123+0000"), //
|
||||
Arguments.of("2024-11-11T20:39:00.123456000Z", ZoneId.of("UTC"), "2024-11-11T20:39:00.123456+0000"), //
|
||||
Arguments.of("2024-11-11T20:39:00.123456789Z", ZoneId.of("UTC"), "2024-11-11T20:39:00.123456789+0000"), //
|
||||
Arguments.of("2024-11-11T20:39:00.123Z", ZoneId.of("Europe/Paris"), "2024-11-11T21:39:00.123+0100"), //
|
||||
Arguments.of("2024-11-11T04:59:59.999Z", ZoneId.of("America/New_York"), "2024-11-10T23:59:59.999-0500") //
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ public class EnrichedItemDTOMapperWithTransformOSGiTest extends JavaOSGiTest {
|
||||
item1.setState(new DecimalType("12.34"));
|
||||
item1.setStateDescriptionService(stateDescriptionServiceMock);
|
||||
|
||||
EnrichedItemDTO enrichedDTO = EnrichedItemDTOMapper.map(item1, false, null, null, null);
|
||||
EnrichedItemDTO enrichedDTO = EnrichedItemDTOMapper.map(item1, false, null, null, null, null);
|
||||
assertThat(enrichedDTO, is(notNullValue()));
|
||||
assertThat(enrichedDTO.name, is("Item1"));
|
||||
assertThat(enrichedDTO.state, is("12.34"));
|
||||
|
Loading…
Reference in New Issue
Block a user