diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java index 90976449b..17180d344 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultStringRedisConnection.java @@ -3353,6 +3353,15 @@ public class DefaultStringRedisConnection implements StringRedisConnection, Deco return convertAndReturn(this.delegate.time(), Converters.identityConverter()); } + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisServerCommands#time(TimeUnit) + */ + @Override + public Long time(TimeUnit timeUnit) { + return convertAndReturn(this.delegate.time(timeUnit), Converters.identityConverter()); + } + /* * (non-Javadoc) * @see org.springframework.data.redis.connection.StringRedisConnection#getClientList() diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisClusterConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisClusterConnection.java index b41126eee..7678ad483 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisClusterConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisClusterConnection.java @@ -18,6 +18,7 @@ package org.springframework.data.redis.connection; import java.util.Collection; import java.util.List; import java.util.Properties; +import java.util.concurrent.TimeUnit; import org.springframework.data.redis.core.types.RedisClientInfo; import org.springframework.lang.Nullable; @@ -128,6 +129,13 @@ public interface DefaultedRedisClusterConnection extends RedisClusterConnection, return serverCommands().time(node); } + /** @deprecated in favor of {@link RedisConnection#serverCommands()}. */ + @Override + @Deprecated + default Long time(RedisClusterNode node, TimeUnit timeUnit) { + return serverCommands().time(node, timeUnit); + } + /** @deprecated in favor of {@link RedisConnection#serverCommands()}. */ @Override @Deprecated diff --git a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java index 41c50d7f1..b6deb5cbe 100644 --- a/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java @@ -1437,6 +1437,13 @@ public interface DefaultedRedisConnection extends RedisConnection { return serverCommands().time(); } + /** @deprecated in favor of {@link RedisConnection#serverCommands()}. */ + @Override + @Deprecated + default Long time(TimeUnit timeUnit) { + return serverCommands().time(timeUnit); + } + /** @deprecated in favor of {@link RedisConnection#serverCommands()}. */ @Override @Deprecated diff --git a/src/main/java/org/springframework/data/redis/connection/ReactiveServerCommands.java b/src/main/java/org/springframework/data/redis/connection/ReactiveServerCommands.java index 1dea7a627..5051c0de0 100644 --- a/src/main/java/org/springframework/data/redis/connection/ReactiveServerCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/ReactiveServerCommands.java @@ -19,6 +19,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.Properties; +import java.util.concurrent.TimeUnit; import org.springframework.data.redis.core.types.RedisClientInfo; @@ -142,12 +143,24 @@ public interface ReactiveServerCommands { Mono resetConfigStats(); /** - * Request server timestamp using {@code TIME} command. + * Request server timestamp using {@code TIME} command in {@link TimeUnit#MILLISECONDS}. * * @return {@link Mono} wrapping current server time in milliseconds. * @see Redis Documentation: TIME */ - Mono time(); + default Mono time() { + return time(TimeUnit.MILLISECONDS); + } + + /** + * Request server timestamp using {@code TIME} command. + * + * @param timeUnit target unit. + * @return {@link Mono} wrapping current server time in {@link TimeUnit}. + * @since 2.5 + * @see Redis Documentation: TIME + */ + Mono time(TimeUnit timeUnit); /** * Closes a given client connection identified by {@literal host:port}. diff --git a/src/main/java/org/springframework/data/redis/connection/RedisClusterServerCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisClusterServerCommands.java index 442ccd044..8e790ed64 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisClusterServerCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisClusterServerCommands.java @@ -17,6 +17,7 @@ package org.springframework.data.redis.connection; import java.util.List; import java.util.Properties; +import java.util.concurrent.TimeUnit; import org.springframework.data.redis.core.types.RedisClientInfo; @@ -118,7 +119,18 @@ public interface RedisClusterServerCommands extends RedisServerCommands { * @return * @see RedisServerCommands#time() */ - Long time(RedisClusterNode node); + default Long time(RedisClusterNode node) { + return time(node, TimeUnit.MILLISECONDS); + } + + /** + * @param node must not be {@literal null}. + * @param timeUnit must not be {@literal null}. + * @return + * @since 2.5 + * @see RedisServerCommands#time(TimeUnit) + */ + Long time(RedisClusterNode node, TimeUnit timeUnit); /** * @param node must not be {@literal null}. diff --git a/src/main/java/org/springframework/data/redis/connection/RedisServerCommands.java b/src/main/java/org/springframework/data/redis/connection/RedisServerCommands.java index aa5aa1b2e..c9b7041be 100644 --- a/src/main/java/org/springframework/data/redis/connection/RedisServerCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/RedisServerCommands.java @@ -17,6 +17,7 @@ package org.springframework.data.redis.connection; import java.util.List; import java.util.Properties; +import java.util.concurrent.TimeUnit; import org.springframework.data.redis.core.types.RedisClientInfo; import org.springframework.lang.Nullable; @@ -174,14 +175,27 @@ public interface RedisServerCommands { void resetConfigStats(); /** - * Request server timestamp using {@code TIME} command. + * Request server timestamp using {@code TIME} command in {@link TimeUnit#MILLISECONDS}. * * @return current server time in milliseconds or {@literal null} when used in pipeline / transaction. * @since 1.1 * @see Redis Documentation: TIME */ @Nullable - Long time(); + default Long time() { + return time(TimeUnit.MILLISECONDS); + } + + /** + * Request server timestamp using {@code TIME} command. + * + * @param timeUnit target unit. + * @return current server time in {@link TimeUnit} or {@literal null} when used in pipeline / transaction. + * @since 2.5 + * @see Redis Documentation: TIME + */ + @Nullable + Long time(TimeUnit timeUnit); /** * Closes a given client connection identified by {@literal host:port}. diff --git a/src/main/java/org/springframework/data/redis/connection/convert/Converters.java b/src/main/java/org/springframework/data/redis/connection/convert/Converters.java index daf11373d..065bba6e1 100644 --- a/src/main/java/org/springframework/data/redis/connection/convert/Converters.java +++ b/src/main/java/org/springframework/data/redis/connection/convert/Converters.java @@ -207,6 +207,23 @@ abstract public class Converters { + NumberUtils.parseNumber(microseconds, Long.class) / 1000L; } + /** + * Returns the timestamp constructed from the given {@code seconds} and {@code microseconds}. + * + * @param seconds server time in seconds. + * @param microseconds elapsed microseconds in current second. + * @param unit target unit. + * @return + * @since 2.5 + */ + public static Long toTimeMillis(String seconds, String microseconds, TimeUnit unit) { + + long secondValue = TimeUnit.SECONDS.toMicros(NumberUtils.parseNumber(seconds, Long.class)); + long microValue = NumberUtils.parseNumber(microseconds, Long.class); + + return unit.convert(secondValue + microValue, TimeUnit.MICROSECONDS); + } + /** * Converts {@code seconds} to the given {@link TimeUnit}. * diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterServerCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterServerCommands.java index d9b76b7f7..14c5c25c1 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterServerCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisClusterServerCommands.java @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.List; import java.util.Map.Entry; import java.util.Properties; +import java.util.concurrent.TimeUnit; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.redis.connection.ClusterCommandExecutor.MultiNodeResult; @@ -391,24 +392,26 @@ class JedisClusterServerCommands implements RedisClusterServerCommands { /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.RedisServerCommands#time() + * @see org.springframework.data.redis.connection.RedisServerCommands#time(TimeUnit) */ @Override - public Long time() { + public Long time(TimeUnit timeUnit) { return convertListOfStringToTime(connection.getClusterCommandExecutor() - .executeCommandOnArbitraryNode((JedisClusterCommandCallback>) BinaryJedis::time).getValue()); + .executeCommandOnArbitraryNode((JedisClusterCommandCallback>) BinaryJedis::time).getValue(), + timeUnit); } /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.RedisClusterServerCommands#time(org.springframework.data.redis.connection.RedisClusterNode) + * @see org.springframework.data.redis.connection.RedisClusterServerCommands#time(org.springframework.data.redis.connection.RedisClusterNode, TimeUnit) */ @Override - public Long time(RedisClusterNode node) { + public Long time(RedisClusterNode node, TimeUnit timeUnit) { return convertListOfStringToTime(connection.getClusterCommandExecutor() - .executeCommandOnSingleNode((JedisClusterCommandCallback>) BinaryJedis::time, node).getValue()); + .executeCommandOnSingleNode((JedisClusterCommandCallback>) BinaryJedis::time, node).getValue(), + timeUnit); } /* @@ -517,13 +520,13 @@ class JedisClusterServerCommands implements RedisClusterServerCommands { node); } - private Long convertListOfStringToTime(List serverTimeInformation) { + private Long convertListOfStringToTime(List serverTimeInformation, TimeUnit timeUnit) { Assert.notEmpty(serverTimeInformation, "Received invalid result from server. Expected 2 items in collection."); Assert.isTrue(serverTimeInformation.size() == 2, "Received invalid number of arguments from redis server. Expected 2 received " + serverTimeInformation.size()); - return Converters.toTimeMillis(serverTimeInformation.get(0), serverTimeInformation.get(1)); + return Converters.toTimeMillis(serverTimeInformation.get(0), serverTimeInformation.get(1), timeUnit); } private NodeResult executeCommandOnSingleNode(JedisClusterCommandCallback cmd, RedisClusterNode node) { diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java index 5e34e143e..2cd697236 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConverters.java @@ -547,13 +547,14 @@ public abstract class JedisConverters extends Converters { return sp; } - static Long toTime(List source) { + static Long toTime(List source, TimeUnit timeUnit) { Assert.notEmpty(source, "Received invalid result from server. Expected 2 items in collection."); Assert.isTrue(source.size() == 2, "Received invalid nr of arguments from redis server. Expected 2 received " + source.size()); + Assert.notNull(timeUnit, "TimeUnit must not be null."); - return toTimeMillis(source.get(0), source.get(1)); + return toTimeMillis(source.get(0), source.get(1), timeUnit); } /** diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisServerCommands.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisServerCommands.java index 05a410ef1..a5db0aa56 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisServerCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisServerCommands.java @@ -21,6 +21,7 @@ import redis.clients.jedis.MultiKeyPipelineBase; import java.util.List; import java.util.Properties; +import java.util.concurrent.TimeUnit; import org.springframework.data.redis.connection.RedisNode; import org.springframework.data.redis.connection.RedisServerCommands; @@ -190,12 +191,15 @@ class JedisServerCommands implements RedisServerCommands { /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.RedisServerCommands#time() + * @see org.springframework.data.redis.connection.RedisServerCommands#time(TimeUnit) */ @Override - public Long time() { + public Long time(TimeUnit timeUnit) { + + Assert.notNull(timeUnit, "TimeUnit must not be null."); + return connection.invoke().from(BinaryJedis::time, MultiKeyPipelineBase::time) - .get(JedisConverters::toTime); + .get((List source) -> JedisConverters.toTime(source, timeUnit)); } /* diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterServerCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterServerCommands.java index 04e5f7f62..1b1a36013 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterServerCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterServerCommands.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; +import java.util.concurrent.TimeUnit; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.redis.connection.ClusterCommandExecutor.MultiNodeResult; @@ -306,25 +307,13 @@ class LettuceClusterServerCommands extends LettuceServerCommands implements Redi executeCommandOnSingleNode(RedisServerCommands::configResetstat, node); } - /* - * (non-Javadoc) - * @see org.springframework.data.redis.connection.lettuce.LettuceServerCommands#time() - */ - @Override - public Long time() { - - return convertListOfStringToTime(connection.getClusterCommandExecutor() - .executeCommandOnArbitraryNode((LettuceClusterCommandCallback>) RedisServerCommands::time) - .getValue()); - } - /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterServerCommands#time(org.springframework.data.redis.connection.RedisClusterNode) */ @Override - public Long time(RedisClusterNode node) { - return convertListOfStringToTime(executeCommandOnSingleNode(RedisServerCommands::time, node).getValue()); + public Long time(RedisClusterNode node, TimeUnit timeUnit) { + return convertListOfStringToTime(executeCommandOnSingleNode(RedisServerCommands::time, node).getValue(), timeUnit); } /* @@ -383,13 +372,13 @@ class LettuceClusterServerCommands extends LettuceServerCommands implements Redi return connection.getClusterCommandExecutor().executeCommandOnAllNodes(cmd); } - private static Long convertListOfStringToTime(List serverTimeInformation) { + private static Long convertListOfStringToTime(List serverTimeInformation, TimeUnit timeUnit) { Assert.notEmpty(serverTimeInformation, "Received invalid result from server. Expected 2 items in collection."); Assert.isTrue(serverTimeInformation.size() == 2, "Received invalid number of arguments from redis server. Expected 2 received " + serverTimeInformation.size()); return Converters.toTimeMillis(LettuceConverters.toString(serverTimeInformation.get(0)), - LettuceConverters.toString(serverTimeInformation.get(1))); + LettuceConverters.toString(serverTimeInformation.get(1)), timeUnit); } } diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java index 60177e2ac..9d9879805 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConverters.java @@ -725,7 +725,7 @@ public abstract class LettuceConverters extends Converters { return args; } - static Converter, Long> toTimeConverter() { + static Converter, Long> toTimeConverter(TimeUnit timeUnit) { return source -> { @@ -733,7 +733,7 @@ public abstract class LettuceConverters extends Converters { Assert.isTrue(source.size() == 2, "Received invalid nr of arguments from redis server. Expected 2 received " + source.size()); - return toTimeMillis(toString(source.get(0)), toString(source.get(1))); + return toTimeMillis(toString(source.get(0)), toString(source.get(1)), timeUnit); }; } diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterServerCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterServerCommands.java index c686ec1c4..5c459943a 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterServerCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterServerCommands.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; @@ -260,7 +261,7 @@ class LettuceReactiveClusterServerCommands extends LettuceReactiveServerCommands return connection.execute(node, RedisServerReactiveCommands::time) // .map(ByteUtils::getBytes) // .collectList() // - .map(LettuceConverters.toTimeConverter()::convert); + .map(LettuceConverters.toTimeConverter(TimeUnit.MILLISECONDS)::convert); } /* diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveServerCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveServerCommands.java index 460637f66..24058d217 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveServerCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveServerCommands.java @@ -22,6 +22,7 @@ import reactor.core.publisher.Mono; import java.nio.ByteBuffer; import java.util.Date; import java.util.Properties; +import java.util.concurrent.TimeUnit; import org.springframework.data.redis.connection.ReactiveServerCommands; import org.springframework.data.redis.core.types.RedisClientInfo; @@ -177,15 +178,15 @@ class LettuceReactiveServerCommands implements ReactiveServerCommands { /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.ReactiveServerCommands#time() + * @see org.springframework.data.redis.connection.ReactiveServerCommands#time(TimeUnit) */ @Override - public Mono time() { + public Mono time(TimeUnit timeUnit) { return connection.execute(RedisServerReactiveCommands::time) // .map(ByteUtils::getBytes) // .collectList() // - .map(LettuceConverters.toTimeConverter()::convert); + .map(LettuceConverters.toTimeConverter(timeUnit)::convert); } /* diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceServerCommands.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceServerCommands.java index d597078e6..67555c4c4 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceServerCommands.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceServerCommands.java @@ -201,11 +201,14 @@ class LettuceServerCommands implements RedisServerCommands { /* * (non-Javadoc) - * @see org.springframework.data.redis.connection.RedisServerCommands#time() + * @see org.springframework.data.redis.connection.RedisServerCommands#time(TimeUnit) */ @Override - public Long time() { - return connection.invoke().from(RedisServerAsyncCommands::time).get(LettuceConverters.toTimeConverter()); + public Long time(TimeUnit timeUnit) { + + Assert.notNull(timeUnit, "TimeUnit must not be null."); + + return connection.invoke().from(RedisServerAsyncCommands::time).get(LettuceConverters.toTimeConverter(timeUnit)); } /* diff --git a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java index 5899c6636..9a0cacdeb 100644 --- a/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/AbstractConnectionIntegrationTests.java @@ -26,6 +26,8 @@ import static org.springframework.data.redis.connection.RedisGeoCommands.GeoRadi import static org.springframework.data.redis.core.ScanOptions.*; import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.*; import java.util.concurrent.BlockingDeque; import java.util.concurrent.LinkedBlockingDeque; @@ -2117,6 +2119,31 @@ public abstract class AbstractConnectionIntegrationTests { assertThat((Long) results.get(0) > 0).isTrue(); } + @Test // GH-526 + void testGetTimeShouldRequestServerTimeAsMicros() { + + actual.add(connection.time(TimeUnit.MICROSECONDS)); + actual.add(connection.time(TimeUnit.SECONDS)); + actual.add(connection.time(TimeUnit.HOURS)); + + List results = getResults(); + assertThat(results).isNotEmpty(); + assertThat(results.get(0)).isNotNull(); + + Instant now = Instant.now(); + Instant reference = Instant.parse("1970-01-01T00:00:00.0Z"); + + Instant micros = reference.plus(Duration.ofNanos(TimeUnit.MICROSECONDS.toNanos((Long) results.get(0)))); + + Instant seconds = reference.plus(Duration.ofNanos(TimeUnit.SECONDS.toNanos((Long) results.get(1)))); + + Instant hours = reference.plus(Duration.ofNanos(TimeUnit.HOURS.toNanos((Long) results.get(2)))); + + assertThat(micros).isCloseTo(now, within(1, ChronoUnit.MINUTES)); + assertThat(seconds).isCloseTo(now, within(1, ChronoUnit.MINUTES)); + assertThat(hours).isCloseTo(now, within(1, ChronoUnit.HOURS)); + } + @Test // DATAREDIS-269 public void clientSetNameWorksCorrectly() { connection.setClientName("foo".getBytes()); diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionUnitTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionUnitTests.java index eb5f5e97e..ee10e7ddc 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionUnitTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnectionUnitTests.java @@ -319,13 +319,11 @@ class LettuceClusterConnectionUnitTests { void timeShouldBeExecutedOnArbitraryNode() { List values = Arrays.asList("1449655759".getBytes(), "92217".getBytes()); - when(clusterConnection1Mock.time()).thenReturn(values); - when(clusterConnection2Mock.time()).thenReturn(values); - when(clusterConnection3Mock.time()).thenReturn(values); + when(dedicatedConnectionMock.time()).thenReturn(new LettuceServerCommands.CompletedRedisFuture<>(values)); connection.time(); - verifyInvocationsAcross("time", times(1), clusterConnection1Mock, clusterConnection2Mock, clusterConnection3Mock); + verify(dedicatedConnectionMock).time(); } @Test // DATAREDIS-315