diff --git a/sdk/servicebus/azure-messaging-servicebus/CHANGELOG.md b/sdk/servicebus/azure-messaging-servicebus/CHANGELOG.md index 651ac883d084..46e7c33e4b40 100644 --- a/sdk/servicebus/azure-messaging-servicebus/CHANGELOG.md +++ b/sdk/servicebus/azure-messaging-servicebus/CHANGELOG.md @@ -8,6 +8,8 @@ ### Bugs Fixed +* Fixed issue where OffsetDateTimeDescribedType would be translated to user's system time instead of UTC. ([42995](https://github.com/Azure/azure-sdk-for-java/pull/42995)) + ### Other Changes ## 7.17.8 (2025-01-09) diff --git a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/implementation/MessageUtils.java b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/implementation/MessageUtils.java index 65baa23d918c..8f179be036e9 100644 --- a/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/implementation/MessageUtils.java +++ b/sdk/servicebus/azure-messaging-servicebus/src/main/java/com/azure/messaging/servicebus/implementation/MessageUtils.java @@ -40,7 +40,6 @@ import java.time.Duration; import java.time.Instant; import java.time.OffsetDateTime; -import java.time.ZoneId; import java.time.ZoneOffset; import java.util.HashMap; import java.util.Iterator; @@ -391,7 +390,7 @@ public static T describedToOrigin(DescribedType describedType) { long tickTime = (long) described - EPOCH_TICKS; int nano = (int) ((tickTime % TICK_PER_SECOND) * TIME_LENGTH_DELTA); long seconds = tickTime / TICK_PER_SECOND; - return (T) OffsetDateTime.ofInstant(Instant.ofEpochSecond(seconds, nano), ZoneId.systemDefault()); + return (T) OffsetDateTime.ofInstant(Instant.ofEpochSecond(seconds, nano), ZoneOffset.UTC); } else if (ServiceBusConstants.DURATION_SYMBOL.equals(descriptor)) { return (T) Duration.ofNanos(((long) described) * TIME_LENGTH_DELTA); } diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/implementation/DurationDescribedTypeTests.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/implementation/DurationDescribedTypeTests.java new file mode 100644 index 000000000000..c51557325ddf --- /dev/null +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/implementation/DurationDescribedTypeTests.java @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.messaging.servicebus.implementation; + +import org.apache.qpid.proton.amqp.DescribedType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.time.Duration; + +import static com.azure.messaging.servicebus.implementation.ServiceBusConstants.DURATION_SYMBOL; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link DurationDescribedType} + */ +public class DurationDescribedTypeTests { + + @Test + public void fromJavaClassToAmqp() { + // Arrange + // var timeSpan = new TimeSpan(0, 25, 10, 12, 450); + final Duration duration = Duration.ofHours(25).plusMinutes(10).plusSeconds(12).plusMillis(450); + final Long expectedTicks = 906124500000L; + final int expectedLength = DURATION_SYMBOL.length() + Long.BYTES; + + // Act + final DurationDescribedType actual = new DurationDescribedType(duration); + + // Assert + Assertions.assertEquals(DURATION_SYMBOL, actual.getDescriptor()); + + final Long actualTick = (Long) actual.getDescribed(); + Assertions.assertEquals(expectedTicks, actualTick); + + Assertions.assertEquals(expectedLength, actual.size()); + } + + @Test + public void fromAmqpToJavaClass() { + // Arrange + // var timeSpan = new TimeSpan(0, 3, 20, 35, 600); + // 3 hours, 20 minutes, 35 seconds, 600 milliseconds. + final long amqpValueTicks = 120356000000L; + final Duration expectedDuration = Duration.ofHours(3).plusMinutes(20).plusSeconds(35).plusMillis(600); + final DescribedType describedType = mock(DescribedType.class); + when(describedType.getDescriptor()).thenReturn(DURATION_SYMBOL); + when(describedType.getDescribed()).thenReturn(amqpValueTicks); + + // Act + final Duration actual = MessageUtils.describedToOrigin(describedType); + + // Assert + Assertions.assertEquals(expectedDuration, actual); + } +} diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/implementation/OffsetDateTimeDescribedTypeTests.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/implementation/OffsetDateTimeDescribedTypeTests.java new file mode 100644 index 000000000000..7c9b21bbcbaf --- /dev/null +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/implementation/OffsetDateTimeDescribedTypeTests.java @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.messaging.servicebus.implementation; + +import org.apache.qpid.proton.amqp.DescribedType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.time.OffsetDateTime; +import java.time.ZoneOffset; + +import static com.azure.messaging.servicebus.implementation.ServiceBusConstants.OFFSETDATETIME_SYMBOL; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Tests for OffsetDateTimeDescribedType + */ +public class OffsetDateTimeDescribedTypeTests { + @Test + public void fromJavaClassToAmqp() { + // Arrange + // DateTimeOffset represented 2024-11-15T23:26:02.5931825+00:00 + final long expectedTicks = 638673099625931825L; + final OffsetDateTime input = OffsetDateTime.of(2024, 11, 15, 23, 26, 2, 593182500, ZoneOffset.UTC); + final int expectedLength = OFFSETDATETIME_SYMBOL.length() + Long.BYTES; + + // Act + final OffsetDateTimeDescribedType actualType = new OffsetDateTimeDescribedType(input); + + // Assert + Assertions.assertEquals(OFFSETDATETIME_SYMBOL, actualType.getDescriptor()); + + Assertions.assertInstanceOf(Long.class, actualType.getDescribed()); + + final Long actual = (Long) actualType.getDescribed(); + Assertions.assertEquals(expectedTicks, actual); + + Assertions.assertEquals(expectedLength, actualType.size()); + } + + @Test + public void fromAmqpToJavaClass() { + // Arrange + // DateTimeOffset represented is: 2024-11-16T00:06:39.6292674+00:00 + // Number of ticks for this .NET object is: 638673123996292674 + final Long amqpValue = 638673123996292674L; + final OffsetDateTime expectedValue = OffsetDateTime.of(2024, 11, 16, 0, 6, 39, 629267400, ZoneOffset.UTC); + + final DescribedType describedType = mock(DescribedType.class); + when(describedType.getDescriptor()).thenReturn(OFFSETDATETIME_SYMBOL); + when(describedType.getDescribed()).thenReturn(amqpValue); + + // Act + final OffsetDateTime actual = MessageUtils.describedToOrigin(describedType); + + // Assert + Assertions.assertEquals(expectedValue, actual); + } +} diff --git a/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/implementation/UriDescribedTypeTests.java b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/implementation/UriDescribedTypeTests.java new file mode 100644 index 000000000000..37c50c9a456e --- /dev/null +++ b/sdk/servicebus/azure-messaging-servicebus/src/test/java/com/azure/messaging/servicebus/implementation/UriDescribedTypeTests.java @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.messaging.servicebus.implementation; + +import org.apache.qpid.proton.amqp.DescribedType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.net.URI; +import java.nio.charset.StandardCharsets; + +import static com.azure.messaging.servicebus.implementation.ServiceBusConstants.URI_SYMBOL; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link UriDescribedType}. + */ +public class UriDescribedTypeTests { + + @Test + public void fromJavaClassToAmqp() { + // Arrange + final String uriString = "https://bing.com"; + final int symbolLength = URI_SYMBOL.length(); + final int uriLength = uriString.getBytes(StandardCharsets.UTF_8).length; + final int expectedLength = symbolLength + uriLength; + + // Act + final UriDescribedType actual = new UriDescribedType(URI.create(uriString)); + + // Assert + Assertions.assertEquals(URI_SYMBOL, actual.getDescriptor()); + Assertions.assertEquals(expectedLength, actual.size()); + } + + @Test + public void fromAmqpToJavaClass() { + // Arrange + final String uriString = "https://bing.com"; + final URI uri = URI.create(uriString); + final DescribedType describedType = mock(DescribedType.class); + when(describedType.getDescriptor()).thenReturn(URI_SYMBOL); + when(describedType.getDescribed()).thenReturn(uriString); + + // Act + final URI actual = MessageUtils.describedToOrigin(describedType); + + // Assert + Assertions.assertEquals(uri, actual); + } +}