diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/DiagnosisKeysStructureProvider.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/DiagnosisKeysStructureProvider.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/DiagnosisKeysStructureProvider.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/component/DiagnosisKeysStructureProvider.java
@@ -27,6 +27,8 @@
 import app.coronawarn.server.services.distribution.assembly.structure.WritableOnDisk;
 import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
 import app.coronawarn.server.services.distribution.config.DistributionServiceConfig;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
 import java.util.Collection;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -65,7 +67,7 @@ public class DiagnosisKeysStructureProvider {
   public Directory<WritableOnDisk> getDiagnosisKeys() {
     logger.debug("Querying diagnosis keys from the database...");
     Collection<DiagnosisKey> diagnosisKeys = diagnosisKeyService.getDiagnosisKeys();
-    diagnosisKeyBundler.setDiagnosisKeys(diagnosisKeys);
+    diagnosisKeyBundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.now().truncatedTo(ChronoUnit.HOURS));
     return new DiagnosisKeysDirectory(diagnosisKeyBundler, cryptoProvider, distributionServiceConfig);
   }
 }
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/DemoDiagnosisKeyBundler.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/DemoDiagnosisKeyBundler.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/DemoDiagnosisKeyBundler.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/DemoDiagnosisKeyBundler.java
@@ -24,15 +24,13 @@

 import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
 import app.coronawarn.server.services.distribution.config.DistributionServiceConfig;
-import java.time.LocalDateTime;
 import java.util.Collection;
-import java.util.List;
 import org.springframework.context.annotation.Profile;
 import org.springframework.stereotype.Component;

 /**
- * An instance of this class contains a collection of {@link DiagnosisKey DiagnosisKeys}, that will be distributed
- * in the same hour they have been submitted.
+ * An instance of this class contains a collection of {@link DiagnosisKey DiagnosisKeys}, that will be distributed in
+ * the same hour they have been submitted.
  */
 @Profile("demo")
 @Component
@@ -44,20 +42,11 @@ public DemoDiagnosisKeyBundler(DistributionServiceConfig distributionServiceConf

   /**
    * Initializes the internal {@code distributableDiagnosisKeys} map, grouping the diagnosis keys by the submission
-   * timestamp, thus ignoring the expiry policy.
+   * timestamp, thus ignoring the expiry and shifting policies.
    */
   @Override
   protected void createDiagnosisKeyDistributionMap(Collection<DiagnosisKey> diagnosisKeys) {
     this.distributableDiagnosisKeys.clear();
     this.distributableDiagnosisKeys.putAll(diagnosisKeys.stream().collect(groupingBy(this::getSubmissionDateTime)));
   }
-
-  /**
-   * Returns all diagnosis keys that should be distributed in a specific hour, without respecting the shifting and
-   * expiry policies.
-   */
-  @Override
-  public List<DiagnosisKey> getDiagnosisKeysDistributableAt(LocalDateTime hour) {
-    return this.getDiagnosisKeysForHour(hour);
-  }
 }
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/DiagnosisKeyBundler.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/DiagnosisKeyBundler.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/DiagnosisKeyBundler.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/DiagnosisKeyBundler.java
@@ -20,18 +20,20 @@

 package app.coronawarn.server.services.distribution.assembly.diagnosiskeys;

-import static app.coronawarn.server.services.distribution.assembly.diagnosiskeys.util.DateTime.ONE_HOUR_INTERVAL_SECONDS;
 import static java.time.ZoneOffset.UTC;
 import static java.util.Collections.emptyList;

 import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
 import app.coronawarn.server.services.distribution.config.DistributionServiceConfig;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;

 /**
@@ -39,9 +41,23 @@
  */
 public abstract class DiagnosisKeyBundler {

+  /**
+   * The submission timestamp is counted in 1 hour intervals since epoch.
+   */
+  public static final long ONE_HOUR_INTERVAL_SECONDS = TimeUnit.HOURS.toSeconds(1);
+
+  /**
+   * The rolling start interval number is counted in 10 minute intervals since epoch.
+   */
+  public static final long TEN_MINUTES_INTERVAL_SECONDS = TimeUnit.MINUTES.toSeconds(10);
+
   protected final int minNumberOfKeysPerBundle;
   protected final long expiryPolicyMinutes;

+  // The hour at which the distribution runs. This field is needed to prevent the run from distributing any keys that
+  // have already been submitted but may only be distributed in the future (e.g. because they are not expired yet).
+  protected LocalDateTime distributionTime;
+
   // A map containing diagnosis keys, grouped by the LocalDateTime on which they may be distributed
   protected final Map<LocalDateTime, List<DiagnosisKey>> distributableDiagnosisKeys = new HashMap<>();

@@ -51,11 +67,22 @@ public DiagnosisKeyBundler(DistributionServiceConfig distributionServiceConfig)
   }

   /**
-   * Sets the {@link DiagnosisKey DiagnosisKeys} contained by this {@link DiagnosisKeyBundler} and calls {@link
-   * DiagnosisKeyBundler#createDiagnosisKeyDistributionMap}.
+   * Creates a {@link LocalDateTime} based on the specified epoch timestamp.
+   */
+  public static LocalDateTime getLocalDateTimeFromHoursSinceEpoch(long timestamp) {
+    return LocalDateTime.ofEpochSecond(TimeUnit.HOURS.toSeconds(timestamp), 0, UTC);
+  }
+
+  /**
+   * Sets the {@link DiagnosisKey DiagnosisKeys} contained by this {@link DiagnosisKeyBundler} and the time at which the
+   * distribution runs and calls {@link DiagnosisKeyBundler#createDiagnosisKeyDistributionMap}.
+   *
+   * @param diagnosisKeys    The {@link DiagnosisKey DiagnosisKeys} contained by this {@link DiagnosisKeyBundler}.
+   * @param distributionTime The {@link LocalDateTime} at which the distribution runs.
    */
-  public void setDiagnosisKeys(Collection<DiagnosisKey> diagnosisKeys) {
-    createDiagnosisKeyDistributionMap(diagnosisKeys);
+  public void setDiagnosisKeys(Collection<DiagnosisKey> diagnosisKeys, LocalDateTime distributionTime) {
+    this.distributionTime = distributionTime;
+    this.createDiagnosisKeyDistributionMap(diagnosisKeys);
   }

   /**
@@ -74,9 +101,23 @@ public List<DiagnosisKey> getAllDiagnosisKeys() {
   protected abstract void createDiagnosisKeyDistributionMap(Collection<DiagnosisKey> diagnosisKeys);

   /**
-   * Returns all diagnosis keys that should be distributed in a specific hour.
+   * Returns a set of all {@link LocalDate dates} on which {@link DiagnosisKey diagnosis keys} shall be distributed.
    */
-  public abstract List<DiagnosisKey> getDiagnosisKeysDistributableAt(LocalDateTime hour);
+  public Set<LocalDate> getDatesWithDistributableDiagnosisKeys() {
+    return this.distributableDiagnosisKeys.keySet().stream()
+        .map(LocalDateTime::toLocalDate)
+        .collect(Collectors.toSet());
+  }
+
+  /**
+   * Returns a set of all {@link LocalDateTime hours} of a specified {@link LocalDate date} during which {@link
+   * DiagnosisKey diagnosis keys} shall be distributed.
+   */
+  public Set<LocalDateTime> getHoursWithDistributableDiagnosisKeys(LocalDate currentDate) {
+    return this.distributableDiagnosisKeys.keySet().stream()
+        .filter(dateTime -> dateTime.toLocalDate().equals(currentDate))
+        .collect(Collectors.toSet());
+  }

   /**
    * Returns the submission timestamp of a {@link DiagnosisKey} as a {@link LocalDateTime}.
@@ -85,10 +126,21 @@ protected LocalDateTime getSubmissionDateTime(DiagnosisKey diagnosisKey) {
     return LocalDateTime.ofEpochSecond(diagnosisKey.getSubmissionTimestamp() * ONE_HOUR_INTERVAL_SECONDS, 0, UTC);
   }

+  /**
+   * Returns all diagnosis keys that should be distributed on a specific date.
+   */
+  public List<DiagnosisKey> getDiagnosisKeysForDate(LocalDate date) {
+    return this.distributableDiagnosisKeys.keySet().stream()
+        .filter(dateTime -> dateTime.toLocalDate().equals(date))
+        .map(this::getDiagnosisKeysForHour)
+        .flatMap(List::stream)
+        .collect(Collectors.toList());
+  }
+
   /**
    * Returns all diagnosis keys that should be distributed in a specific hour.
    */
-  protected List<DiagnosisKey> getDiagnosisKeysForHour(LocalDateTime hour) {
+  public List<DiagnosisKey> getDiagnosisKeysForHour(LocalDateTime hour) {
     return Optional
         .ofNullable(this.distributableDiagnosisKeys.get(hour))
         .orElse(emptyList());
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundler.java
@@ -20,7 +20,6 @@

 package app.coronawarn.server.services.distribution.assembly.diagnosiskeys;

-import static app.coronawarn.server.services.distribution.assembly.diagnosiskeys.util.DateTime.TEN_MINUTES_INTERVAL_SECONDS;
 import static java.time.ZoneOffset.UTC;
 import static java.util.Collections.emptyList;
 import static java.util.stream.Collectors.groupingBy;
@@ -30,11 +29,13 @@
 import java.time.Duration;
 import java.time.LocalDateTime;
 import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
+import java.util.stream.LongStream;
 import org.springframework.context.annotation.Profile;
 import org.springframework.stereotype.Component;

@@ -57,55 +58,38 @@ public ProdDiagnosisKeyBundler(DistributionServiceConfig distributionServiceConf

   /**
    * Initializes the internal {@code distributableDiagnosisKeys} map, grouping the diagnosis keys by the date on which
-   * they may be distributed, while respecting the expiry policy.
+   * they may be distributed, while respecting the expiry and shifting policies.
    */
   @Override
   protected void createDiagnosisKeyDistributionMap(Collection<DiagnosisKey> diagnosisKeys) {
     this.distributableDiagnosisKeys.clear();
-    this.distributableDiagnosisKeys.putAll(diagnosisKeys.stream().collect(groupingBy(this::getDistributionDateTime)));
-  }
-
-  /**
-   * Returns all diagnosis keys that should be distributed in a specific hour, while respecting the shifting and expiry
-   * policies.
-   */
-  @Override
-  public List<DiagnosisKey> getDiagnosisKeysDistributableAt(LocalDateTime hour) {
-    List<DiagnosisKey> keysSinceLastDistribution = getKeysSinceLastDistribution(hour);
-    if (keysSinceLastDistribution.size() >= minNumberOfKeysPerBundle) {
-      return keysSinceLastDistribution;
-    } else {
-      return emptyList();
+    if (diagnosisKeys.isEmpty()) {
+      return;
     }
-  }
+    Map<LocalDateTime, List<DiagnosisKey>> distributableDiagnosisKeysGroupedByExpiryPolicy = new HashMap<>(
+        diagnosisKeys.stream().collect(groupingBy(this::getDistributionDateTimeByExpiryPolicy)));
+    LocalDateTime earliestDistributableTimestamp =
+        getEarliestDistributableTimestamp(distributableDiagnosisKeysGroupedByExpiryPolicy).orElseThrow();
+    LocalDateTime latestDistributableTimestamp = this.distributionTime;

-  /**
-   * Returns a all distributable keys between a specific hour and the last distribution (bundle that was above the
-   * shifting threshold) or the earliest distributable key.
-   */
-  private List<DiagnosisKey> getKeysSinceLastDistribution(LocalDateTime hour) {
-    Optional<LocalDateTime> earliestDistributableTimestamp = getEarliestDistributableTimestamp();
-    if (earliestDistributableTimestamp.isEmpty() || hour.isBefore(earliestDistributableTimestamp.get())) {
-      return emptyList();
-    }
-    List<DiagnosisKey> distributableInCurrentHour = getDiagnosisKeysForHour(hour);
-    if (distributableInCurrentHour.size() >= minNumberOfKeysPerBundle) {
-      return distributableInCurrentHour;
-    }
-    LocalDateTime previousHour = hour.minusHours(1);
-    Collection<DiagnosisKey> distributableInPreviousHour = getDiagnosisKeysDistributableAt(previousHour);
-    if (distributableInPreviousHour.size() >= minNumberOfKeysPerBundle) {
-      // Last hour was distributed, so we can not combine the current hour with the last hour
-      return distributableInCurrentHour;
-    } else {
-      // Last hour was not distributed, so we can combine the current hour with the last hour
-      return Stream.concat(distributableInCurrentHour.stream(), getKeysSinceLastDistribution(previousHour).stream())
-          .collect(Collectors.toList());
-    }
+    List<DiagnosisKey> diagnosisKeyAccumulator = new ArrayList<>();
+    LongStream.range(0, earliestDistributableTimestamp.until(latestDistributableTimestamp, ChronoUnit.HOURS))
+        .forEach(hourCounter -> {
+          LocalDateTime currentHour = earliestDistributableTimestamp.plusHours(hourCounter);
+          Collection<DiagnosisKey> currentHourDiagnosisKeys = Optional
+              .ofNullable(distributableDiagnosisKeysGroupedByExpiryPolicy.get(currentHour))
+              .orElse(emptyList());
+          diagnosisKeyAccumulator.addAll(currentHourDiagnosisKeys);
+          if (diagnosisKeyAccumulator.size() >= minNumberOfKeysPerBundle) {
+            this.distributableDiagnosisKeys.put(currentHour, new ArrayList<>(diagnosisKeyAccumulator));
+            diagnosisKeyAccumulator.clear();
+          }
+        });
   }

-  private Optional<LocalDateTime> getEarliestDistributableTimestamp() {
-    return this.distributableDiagnosisKeys.keySet().stream().min(LocalDateTime::compareTo);
+  private static Optional<LocalDateTime> getEarliestDistributableTimestamp(
+      Map<LocalDateTime, List<DiagnosisKey>> distributableDiagnosisKeys) {
+    return distributableDiagnosisKeys.keySet().stream().min(LocalDateTime::compareTo);
   }

   /**
@@ -118,12 +102,13 @@ private LocalDateTime getExpiryDateTime(DiagnosisKey diagnosisKey) {
   }

   /**
-   * Calculates the earliest point in time at which the specified {@link DiagnosisKey} can be distributed. Before keys
-   * are allowed to be distributed, they must be expired for a configured amount of time.
+   * Calculates the earliest point in time at which the specified {@link DiagnosisKey} can be distributed, while
+   * respecting the expiry policy and the submission timestamp. Before keys are allowed to be distributed, they must be
+   * expired for a configured amount of time.
    *
    * @return {@link LocalDateTime} at which the specified {@link DiagnosisKey} can be distributed.
    */
-  private LocalDateTime getDistributionDateTime(DiagnosisKey diagnosisKey) {
+  private LocalDateTime getDistributionDateTimeByExpiryPolicy(DiagnosisKey diagnosisKey) {
     LocalDateTime submissionDateTime = getSubmissionDateTime(diagnosisKey);
     LocalDateTime expiryDateTime = getExpiryDateTime(diagnosisKey);
     long minutesBetweenExpiryAndSubmission = Duration.between(expiryDateTime, submissionDateTime).toMinutes();
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDateDirectory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDateDirectory.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDateDirectory.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDateDirectory.java
@@ -24,7 +24,6 @@
 import app.coronawarn.server.services.distribution.assembly.component.CryptoProvider;
 import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.DiagnosisKeyBundler;
 import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.directory.decorator.HourIndexingDecorator;
-import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.util.DateTime;
 import app.coronawarn.server.services.distribution.assembly.structure.WritableOnDisk;
 import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
 import app.coronawarn.server.services.distribution.assembly.structure.directory.IndexDirectoryOnDisk;
@@ -46,12 +45,12 @@ public class DiagnosisKeysDateDirectory extends IndexDirectoryOnDisk<LocalDate>
    * collection. Payload signing is be performed according to the specified {@link CryptoProvider}.
    *
    * @param diagnosisKeyBundler A {@link DiagnosisKeyBundler} containing the {@link DiagnosisKey DiagnosisKeys}.
-   * @param cryptoProvider The {@link CryptoProvider} used for payload signing.
+   * @param cryptoProvider      The {@link CryptoProvider} used for payload signing.
    */
   public DiagnosisKeysDateDirectory(DiagnosisKeyBundler diagnosisKeyBundler,
       CryptoProvider cryptoProvider, DistributionServiceConfig distributionServiceConfig) {
     super(distributionServiceConfig.getApi().getDatePath(),
-        __ -> DateTime.getDates(diagnosisKeyBundler.getAllDiagnosisKeys()), ISO8601::format);
+        __ -> diagnosisKeyBundler.getDatesWithDistributableDiagnosisKeys(), ISO8601::format);
     this.cryptoProvider = cryptoProvider;
     this.diagnosisKeyBundler = diagnosisKeyBundler;
     this.distributionServiceConfig = distributionServiceConfig;
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDirectory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDirectory.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDirectory.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDirectory.java
@@ -23,7 +23,6 @@
 import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
 import app.coronawarn.server.services.distribution.assembly.component.CryptoProvider;
 import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.DiagnosisKeyBundler;
-import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.directory.decorator.CountryIndexingDecorator;
 import app.coronawarn.server.services.distribution.assembly.structure.WritableOnDisk;
 import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
 import app.coronawarn.server.services.distribution.assembly.structure.directory.DirectoryOnDisk;
@@ -70,8 +69,6 @@ public void prepare(ImmutableStack<Object> indices) {

   private IndexDirectory<String, WritableOnDisk> decorateCountryDirectory(
       IndexDirectoryOnDisk<String> countryDirectory) {
-    return new CountryIndexingDecorator<>(
-        new IndexingDecoratorOnDisk<>(countryDirectory, distributionServiceConfig.getOutputFileName()),
-        distributionServiceConfig);
+    return new IndexingDecoratorOnDisk<>(countryDirectory, distributionServiceConfig.getOutputFileName());
   }
 }
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysHourDirectory.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysHourDirectory.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysHourDirectory.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysHourDirectory.java
@@ -25,7 +25,6 @@
 import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.DiagnosisKeyBundler;
 import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.archive.decorator.singing.DiagnosisKeySigningDecorator;
 import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.file.TemporaryExposureKeyExportFile;
-import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.util.DateTime;
 import app.coronawarn.server.services.distribution.assembly.structure.WritableOnDisk;
 import app.coronawarn.server.services.distribution.assembly.structure.archive.Archive;
 import app.coronawarn.server.services.distribution.assembly.structure.archive.ArchiveOnDisk;
@@ -53,8 +52,9 @@ public class DiagnosisKeysHourDirectory extends IndexDirectoryOnDisk<LocalDateTi
    */
   public DiagnosisKeysHourDirectory(DiagnosisKeyBundler diagnosisKeyBundler, CryptoProvider cryptoProvider,
       DistributionServiceConfig distributionServiceConfig) {
-    super(distributionServiceConfig.getApi().getHourPath(), indices -> DateTime.getHours(((LocalDate) indices.peek()),
-        diagnosisKeyBundler.getAllDiagnosisKeys()), LocalDateTime::getHour);
+    super(distributionServiceConfig.getApi().getHourPath(),
+        indices -> diagnosisKeyBundler.getHoursWithDistributableDiagnosisKeys(((LocalDate) indices.peek())),
+        LocalDateTime::getHour);
     this.diagnosisKeyBundler = diagnosisKeyBundler;
     this.cryptoProvider = cryptoProvider;
     this.distributionServiceConfig = distributionServiceConfig;
@@ -70,7 +70,7 @@ public void prepare(ImmutableStack<Object> indices) {
       String region = (String) currentIndices.pop().pop().peek();

       List<DiagnosisKey> diagnosisKeysForCurrentHour =
-          this.diagnosisKeyBundler.getDiagnosisKeysDistributableAt(currentHour);
+          this.diagnosisKeyBundler.getDiagnosisKeysForHour(currentHour);

       long startTimestamp = currentHour.toEpochSecond(ZoneOffset.UTC);
       long endTimestamp = currentHour.plusHours(1).toEpochSecond(ZoneOffset.UTC);
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/decorator/CountryIndexingDecorator.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/decorator/CountryIndexingDecorator.java
deleted file mode 100644
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/decorator/CountryIndexingDecorator.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*-
- * ---license-start
- * Corona-Warn-App
- * ---
- * Copyright (C) 2020 SAP SE and all other contributors
- * ---
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ---license-end
- */
-
-package app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.directory.decorator;
-
-import app.coronawarn.server.services.distribution.assembly.structure.Writable;
-import app.coronawarn.server.services.distribution.assembly.structure.WritableOnDisk;
-import app.coronawarn.server.services.distribution.assembly.structure.archive.Archive;
-import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
-import app.coronawarn.server.services.distribution.assembly.structure.directory.DirectoryOnDisk;
-import app.coronawarn.server.services.distribution.assembly.structure.directory.IndexDirectory;
-import app.coronawarn.server.services.distribution.assembly.structure.directory.decorator.IndexDirectoryDecorator;
-import app.coronawarn.server.services.distribution.assembly.structure.file.FileOnDisk;
-import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
-import app.coronawarn.server.services.distribution.config.DistributionServiceConfig;
-import java.nio.charset.StandardCharsets;
-import java.util.Collection;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * This decorator creates the central index file for a country, e.g. DE.
- */
-public class CountryIndexingDecorator<T> extends IndexDirectoryDecorator<T, WritableOnDisk> {
-
-  /**
-   * Separate each entry in the index file with new line.
-   */
-  private static final String NEW_LINE_SEPARATOR = "\r\n";
-
-  /**
-   * the name of this index file.
-   */
-  private final String fileName;
-
-  /**
-   * Creates a new decorator instance for the given directory.
-   *
-   * @param directory                 The target country directory.
-   * @param distributionServiceConfig The config.
-   */
-  public CountryIndexingDecorator(IndexDirectory<T, WritableOnDisk> directory,
-      DistributionServiceConfig distributionServiceConfig) {
-    super(directory);
-    this.fileName = distributionServiceConfig.getOutputFileName();
-  }
-
-  @Override
-  public void prepare(ImmutableStack<Object> indices) {
-    super.prepare(indices);
-
-    Collection<DirectoryOnDisk> countryDirectories = this.getWritables().stream()
-        .filter(Writable::isDirectory)
-        .map(directory -> (DirectoryOnDisk) directory)
-        .collect(Collectors.toSet());
-
-    countryDirectories.forEach(this::writeIndexFileForCountry);
-  }
-
-  private void writeIndexFileForCountry(Directory<WritableOnDisk> directory) {
-    var dateDirectory = (Directory<WritableOnDisk>) directory.getWritables()
-        .stream()
-        .filter(Writable::isDirectory)
-        .findFirst()
-        .orElseThrow();
-
-    String resourcePaths = CountryIndexingDecorator.getExposureKeyExportPaths(dateDirectory)
-        .stream()
-        .sorted()
-        .collect(Collectors.joining(NEW_LINE_SEPARATOR));
-
-    directory.addWritable(new FileOnDisk(fileName, resourcePaths.getBytes(StandardCharsets.UTF_8)));
-  }
-
-  private static Set<String> getExposureKeyExportPaths(Directory<WritableOnDisk> rootDirectory) {
-    Collection<Directory<WritableOnDisk>> directories = rootDirectory.getWritables()
-        .stream()
-        .filter(Writable::isDirectory)
-        .filter(directory -> !(directory instanceof Archive))
-        .map(directory -> (Directory<WritableOnDisk>) directory)
-        .collect(Collectors.toSet());
-
-    if (directories.isEmpty()) {
-      return Set.of(rootDirectory.getName());
-    } else {
-      return directories.stream()
-          .map(CountryIndexingDecorator::getExposureKeyExportPaths)
-          .flatMap(Set::stream)
-          .map(childName -> rootDirectory.getName() + "/" + childName)
-          .collect(Collectors.toSet());
-    }
-  }
-}
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/DateTime.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/DateTime.java
deleted file mode 100644
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/DateTime.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*-
- * ---license-start
- * Corona-Warn-App
- * ---
- * Copyright (C) 2020 SAP SE and all other contributors
- * ---
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ---license-end
- */
-
-package app.coronawarn.server.services.distribution.assembly.diagnosiskeys.util;
-
-import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.ZoneOffset;
-import java.util.Collection;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
-
-/**
- * Methods for conversions of time/date data.
- */
-public class DateTime {
-
-  /**
-   * The submission timestamp is counted in 1 hour intervals since epoch.
-   */
-  public static final long ONE_HOUR_INTERVAL_SECONDS = TimeUnit.HOURS.toSeconds(1);
-
-  /**
-   * The rolling start interval number is counted in 10 minute intervals since epoch.
-   */
-  public static final long TEN_MINUTES_INTERVAL_SECONDS = TimeUnit.MINUTES.toSeconds(10);
-
-  private DateTime() {
-  }
-
-  /**
-   * Returns a set of all {@link LocalDate dates} that are associated with the submission timestamps of the specified
-   * {@link DiagnosisKey diagnosis keys}.
-   */
-  public static Set<LocalDate> getDates(Collection<DiagnosisKey> diagnosisKeys) {
-    return diagnosisKeys.stream()
-        .map(DiagnosisKey::getSubmissionTimestamp)
-        .map(timestamp -> LocalDate.ofEpochDay(timestamp / 24))
-        .collect(Collectors.toSet());
-  }
-
-  /**
-   * Returns a set of all {@link LocalDateTime hours} that are associated with the submission timestamps of the
-   * specified {@link DiagnosisKey diagnosis keys} and the specified {@link LocalDate date}.
-   */
-  public static Set<LocalDateTime> getHours(LocalDate currentDate, Collection<DiagnosisKey> diagnosisKeys) {
-    return diagnosisKeys.stream()
-        .map(DiagnosisKey::getSubmissionTimestamp)
-        .map(DateTime::getLocalDateTimeFromHoursSinceEpoch)
-        .filter(currentDateTime -> currentDateTime.toLocalDate().equals(currentDate))
-        .collect(Collectors.toSet());
-  }
-
-  /**
-   * Creates a {@link LocalDateTime} based on the specified epoch timestamp.
-   */
-  public static LocalDateTime getLocalDateTimeFromHoursSinceEpoch(long timestamp) {
-    return LocalDateTime.ofEpochSecond(TimeUnit.HOURS.toSeconds(timestamp), 0, ZoneOffset.UTC);
-  }
-}
diff --git a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/TestDataGeneration.java b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/TestDataGeneration.java
--- a/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/TestDataGeneration.java
+++ b/services/distribution/src/main/java/app/coronawarn/server/services/distribution/runner/TestDataGeneration.java
@@ -20,8 +20,8 @@

 package app.coronawarn.server.services.distribution.runner;

-import static app.coronawarn.server.services.distribution.assembly.diagnosiskeys.util.DateTime.ONE_HOUR_INTERVAL_SECONDS;
-import static app.coronawarn.server.services.distribution.assembly.diagnosiskeys.util.DateTime.TEN_MINUTES_INTERVAL_SECONDS;
+import static app.coronawarn.server.services.distribution.assembly.diagnosiskeys.DiagnosisKeyBundler.ONE_HOUR_INTERVAL_SECONDS;
+import static app.coronawarn.server.services.distribution.assembly.diagnosiskeys.DiagnosisKeyBundler.TEN_MINUTES_INTERVAL_SECONDS;

 import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
 import app.coronawarn.server.common.persistence.service.DiagnosisKeyService;

diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundlerExpiryPolicyTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundlerExpiryPolicyTest.java
new file mode 100644
--- /dev/null
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundlerExpiryPolicyTest.java
@@ -0,0 +1,88 @@
+/*-
+ * ---license-start
+ * Corona-Warn-App
+ * ---
+ * Copyright (C) 2020 SAP SE and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package app.coronawarn.server.services.distribution.assembly.diagnosiskeys;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
+import app.coronawarn.server.services.distribution.common.Helpers;
+import app.coronawarn.server.services.distribution.config.DistributionServiceConfig;
+import java.time.LocalDateTime;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.test.context.ConfigFileApplicationContextInitializer;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+@EnableConfigurationProperties(value = DistributionServiceConfig.class)
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = {DistributionServiceConfig.class, ProdDiagnosisKeyBundler.class},
+    initializers = ConfigFileApplicationContextInitializer.class)
+class ProdDiagnosisKeyBundlerExpiryPolicyTest {
+
+  @Autowired
+  DistributionServiceConfig distributionServiceConfig;
+
+  @Autowired
+  DiagnosisKeyBundler bundler;
+
+  @ParameterizedTest
+  @ValueSource(longs = {0L, 24L, 24L + 2L})
+  void testLastPeriodOfHourAndSubmissionLessThanDistributionDateTime(long submissionTimestamp) {
+    List<DiagnosisKey> diagnosisKeys = Helpers.buildDiagnosisKeys(5, submissionTimestamp, 10);
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 3, 0, 0))).hasSize(10);
+  }
+
+  @Test
+  void testLastPeriodOfHourAndSubmissionEqualsDistributionDateTime() {
+    List<DiagnosisKey> diagnosisKeys = Helpers.buildDiagnosisKeys(5, 24L + 3L, 10);
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 3, 0, 0))).hasSize(10);
+  }
+
+  @ParameterizedTest
+  @ValueSource(longs = {0L, 24L, 24L + 2L, 24L + 3L})
+  void testFirstPeriodOfHourAndSubmissionLessThanDistributionDateTime(long submissionTimestamp) {
+    List<DiagnosisKey> diagnosisKeys = Helpers.buildDiagnosisKeys(6, submissionTimestamp, 10);
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 4, 0, 0))).hasSize(10);
+  }
+
+  @Test
+  void testFirstPeriodOfHourAndSubmissionEqualsDistributionDateTime() {
+    List<DiagnosisKey> diagnosisKeys = Helpers.buildDiagnosisKeys(6, 24L + 4L, 10);
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 4, 0, 0))).hasSize(10);
+  }
+
+  @Test
+  void testLastPeriodOfHourAndSubmissionGreaterDistributionDateTime() {
+    List<DiagnosisKey> diagnosisKeys = Helpers.buildDiagnosisKeys(5, 24L + 4L, 10);
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 4, 0, 0))).hasSize(10);
+  }
+}
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundlerKeyRetrievalTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundlerKeyRetrievalTest.java
new file mode 100644
--- /dev/null
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundlerKeyRetrievalTest.java
@@ -0,0 +1,185 @@
+/*-
+ * ---license-start
+ * Corona-Warn-App
+ * ---
+ * Copyright (C) 2020 SAP SE and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package app.coronawarn.server.services.distribution.assembly.diagnosiskeys;
+
+import static app.coronawarn.server.services.distribution.common.Helpers.buildDiagnosisKeys;
+import static java.util.Collections.emptySet;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
+import app.coronawarn.server.services.distribution.config.DistributionServiceConfig;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.test.context.ConfigFileApplicationContextInitializer;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+@EnableConfigurationProperties(value = DistributionServiceConfig.class)
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = {DistributionServiceConfig.class, ProdDiagnosisKeyBundler.class},
+    initializers = ConfigFileApplicationContextInitializer.class)
+class ProdDiagnosisKeyBundlerKeyRetrievalTest {
+
+  @Autowired
+  DistributionServiceConfig distributionServiceConfig;
+
+  @Autowired
+  DiagnosisKeyBundler bundler;
+
+  @Test
+  void testGetsAllDiagnosisKeys() {
+    List<DiagnosisKey> diagnosisKeys = Stream
+        .of(buildDiagnosisKeys(6, 50L, 5), buildDiagnosisKeys(6, 51L, 5), buildDiagnosisKeys(6, 52L, 5))
+        .flatMap(List::stream)
+        .collect(Collectors.toList());
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    assertThat(bundler.getAllDiagnosisKeys()).hasSize(15);
+  }
+
+  @Test
+  void testGetDatesForEmptyList() {
+    bundler.setDiagnosisKeys(emptySet(), LocalDateTime.of(1970, 1, 5, 0, 0));
+    assertThat(bundler.getDatesWithDistributableDiagnosisKeys()).isEmpty();
+  }
+
+  @Test
+  void testGetsDatesWithDistributableDiagnosisKeys() {
+    List<DiagnosisKey> diagnosisKeys = Stream
+        .of(buildDiagnosisKeys(6, 26L, 5), buildDiagnosisKeys(6, 50L, 1), buildDiagnosisKeys(6, 74L, 5))
+        .flatMap(List::stream)
+        .collect(Collectors.toList());
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    assertThat(bundler.getDatesWithDistributableDiagnosisKeys()).containsAll(List.of(
+        LocalDate.of(1970, 1, 2),
+        LocalDate.of(1970, 1, 4)
+    ));
+  }
+
+  @ParameterizedTest
+  @MethodSource("createDiagnosisKeysForEpochDay0")
+  void testGetDatesForEpochDay0(Collection<DiagnosisKey> diagnosisKeys) {
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    var expDates = Set.of(LocalDate.ofEpochDay(2L));
+    var actDates = bundler.getDatesWithDistributableDiagnosisKeys();
+    assertThat(actDates).isEqualTo(expDates);
+  }
+
+  private static Stream<Arguments> createDiagnosisKeysForEpochDay0() {
+    return Stream.of(
+        buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 3, 0, 0), 5),
+        buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 3, 1, 0), 5),
+        buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 3, 23, 59, 59), 5)
+    ).map(Arguments::of);
+  }
+
+  @Test
+  void testGetDatesFor2Days() {
+    List<DiagnosisKey> diagnosisKeys = Stream
+        .of(buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 3, 1, 0), 5),
+            buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 4, 1, 0), 5))
+        .flatMap(List::stream)
+        .collect(Collectors.toList());
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    var expDates = Set.of(LocalDate.ofEpochDay(2L), LocalDate.ofEpochDay(3L));
+    assertThat(bundler.getDatesWithDistributableDiagnosisKeys()).isEqualTo(expDates);
+  }
+
+  @Test
+  void testGetHoursForEmptyList() {
+    bundler.setDiagnosisKeys(emptySet(), LocalDateTime.of(1970, 1, 5, 0, 0));
+    assertThat(bundler.getHoursWithDistributableDiagnosisKeys(LocalDate.of(1970, 1, 3))).isEmpty();
+  }
+
+  @Test
+  void testGetsHoursWithDistributableDiagnosisKeys() {
+    List<DiagnosisKey> diagnosisKeys = Stream
+        .of(buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 2, 4, 0), 5),
+            buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 2, 5, 0), 1),
+            buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 2, 6, 0), 5))
+        .flatMap(List::stream)
+        .collect(Collectors.toList());
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    assertThat(bundler.getHoursWithDistributableDiagnosisKeys(LocalDate.of(1970, 1, 2))).containsAll(List.of(
+        LocalDateTime.of(1970, 1, 2, 4, 0, 0),
+        LocalDateTime.of(1970, 1, 2, 6, 0, 0)
+    ));
+  }
+
+  @Test
+  void testGetsDiagnosisKeysForDate() {
+    List<DiagnosisKey> diagnosisKeys = Stream
+        .of(buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 2, 2, 0), 5),
+            buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 3, 2, 0), 1),
+            buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 4, 2, 0), 5))
+        .flatMap(List::stream)
+        .collect(Collectors.toList());
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 20, 0));
+    assertThat(bundler.getDiagnosisKeysForDate(LocalDate.of(1970, 1, 1))).hasSize(0);
+    assertThat(bundler.getDiagnosisKeysForDate(LocalDate.of(1970, 1, 2))).hasSize(5);
+    assertThat(bundler.getDiagnosisKeysForDate(LocalDate.of(1970, 1, 3))).hasSize(0);
+    assertThat(bundler.getDiagnosisKeysForDate(LocalDate.of(1970, 1, 4))).hasSize(6);
+    assertThat(bundler.getDiagnosisKeysForDate(LocalDate.of(1970, 1, 5))).hasSize(0);
+  }
+
+  @Test
+  void testEmptyListWhenGettingDiagnosisKeysForDateBeforeEarliestDiagnosisKey() {
+    List<DiagnosisKey> diagnosisKeys = buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 2, 4, 0), 5);
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    assertThat(bundler.getDiagnosisKeysForDate(LocalDate.of(1970, 1, 1))).hasSize(0);
+  }
+
+  @Test
+  void testGetsDiagnosisKeysForHour() {
+    List<DiagnosisKey> diagnosisKeys = Stream
+        .of(buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 2, 4, 0), 5),
+            buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 2, 5, 0), 1),
+            buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 2, 6, 0), 5))
+        .flatMap(List::stream)
+        .collect(Collectors.toList());
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 3, 0))).hasSize(0);
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 4, 0))).hasSize(5);
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 5, 0))).hasSize(0);
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 6, 0))).hasSize(6);
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 2, 7, 0))).hasSize(0);
+  }
+
+  @Test
+  void testEmptyListWhenGettingDiagnosisKeysForHourBeforeEarliestDiagnosisKey() {
+    List<DiagnosisKey> diagnosisKeys = buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 2, 4, 0), 5);
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 1, 0, 0, 0))).hasSize(0);
+  }
+}
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundlerShiftingPolicyTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundlerShiftingPolicyTest.java
new file mode 100644
--- /dev/null
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundlerShiftingPolicyTest.java
@@ -0,0 +1,121 @@
+/*-
+ * ---license-start
+ * Corona-Warn-App
+ * ---
+ * Copyright (C) 2020 SAP SE and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package app.coronawarn.server.services.distribution.assembly.diagnosiskeys;
+
+import static app.coronawarn.server.services.distribution.common.Helpers.buildDiagnosisKeys;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
+import app.coronawarn.server.services.distribution.config.DistributionServiceConfig;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.test.context.ConfigFileApplicationContextInitializer;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+@EnableConfigurationProperties(value = DistributionServiceConfig.class)
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = {DistributionServiceConfig.class, ProdDiagnosisKeyBundler.class},
+    initializers = ConfigFileApplicationContextInitializer.class)
+class ProdDiagnosisKeyBundlerShiftingPolicyTest {
+
+  @Autowired
+  DistributionServiceConfig distributionServiceConfig;
+
+  @Autowired
+  DiagnosisKeyBundler bundler;
+
+  @Test
+  void testDoesNotShiftIfPackageSizeGreaterThanThreshold() {
+    List<DiagnosisKey> diagnosisKeys = buildDiagnosisKeys(6, 50L, 6);
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 3, 2, 0, 0))).hasSize(6);
+  }
+
+  @Test
+  void testDoesNotShiftIfPackageSizeEqualsThreshold() {
+    List<DiagnosisKey> diagnosisKeys = buildDiagnosisKeys(6, 50L, 5);
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 3, 2, 0, 0))).hasSize(5);
+  }
+
+  @Test
+  void testShiftsIfPackageSizeLessThanThreshold() {
+    List<DiagnosisKey> diagnosisKeys = Stream
+        .concat(buildDiagnosisKeys(6, 50L, 4).stream(), buildDiagnosisKeys(6, 51L, 1).stream())
+        .collect(Collectors.toList());
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 3, 2, 0, 0))).hasSize(0);
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 3, 3, 0, 0))).hasSize(5);
+  }
+
+  @Test
+  void testShiftsSinceLastDistribution() {
+    List<DiagnosisKey> diagnosisKeys = Stream
+        .of(buildDiagnosisKeys(6, 50L, 5), buildDiagnosisKeys(6, 51L, 2), buildDiagnosisKeys(6, 52L, 4))
+        .flatMap(List::stream)
+        .collect(Collectors.toList());
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 3, 2, 0, 0))).hasSize(5);
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 3, 3, 0, 0))).hasSize(0);
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 3, 4, 0, 0))).hasSize(6);
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 3, 5, 0, 0))).hasSize(0);
+  }
+
+  @Test
+  void testShiftIncludesPreviouslyUndistributedKeys() {
+    List<DiagnosisKey> diagnosisKeys = Stream
+        .concat(buildDiagnosisKeys(6, 50L, 1).stream(), buildDiagnosisKeys(6, 51L, 5).stream())
+        .collect(Collectors.toList());
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 3, 2, 0, 0))).hasSize(0);
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 3, 3, 0, 0))).hasSize(6);
+  }
+
+  @Test
+  void testShiftsSparseDistributions() {
+    List<DiagnosisKey> diagnosisKeys = Stream
+        .of(buildDiagnosisKeys(6, 50L, 1), buildDiagnosisKeys(6, 51L, 1), buildDiagnosisKeys(6, 52L, 1),
+            buildDiagnosisKeys(6, 53L, 0), buildDiagnosisKeys(6, 54L, 0), buildDiagnosisKeys(6, 55L, 1),
+            buildDiagnosisKeys(6, 56L, 1))
+        .flatMap(List::stream)
+        .collect(Collectors.toList());
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 3, 2, 0, 0))).hasSize(0);
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 3, 3, 0, 0))).hasSize(0);
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 3, 4, 0, 0))).hasSize(0);
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 3, 5, 0, 0))).hasSize(0);
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 3, 6, 0, 0))).hasSize(0);
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 3, 7, 0, 0))).hasSize(0);
+    assertThat(bundler.getDiagnosisKeysForHour(LocalDateTime.of(1970, 1, 3, 8, 0, 0))).hasSize(5);
+  }
+}
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundlerTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundlerTest.java
deleted file mode 100644
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/ProdDiagnosisKeyBundlerTest.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*-
- * ---license-start
- * Corona-Warn-App
- * ---
- * Copyright (C) 2020 SAP SE and all other contributors
- * ---
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ---license-end
- */
-
-package app.coronawarn.server.services.distribution.assembly.diagnosiskeys;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
-import app.coronawarn.server.services.distribution.config.DistributionServiceConfig;
-import java.time.LocalDateTime;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import java.util.stream.Stream;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.ValueSource;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.boot.test.context.ConfigFileApplicationContextInitializer;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit.jupiter.SpringExtension;
-
-@EnableConfigurationProperties(value = DistributionServiceConfig.class)
-@ExtendWith(SpringExtension.class)
-@ContextConfiguration(classes = {DistributionServiceConfig.class, ProdDiagnosisKeyBundler.class},
-    initializers = ConfigFileApplicationContextInitializer.class)
-class ProdDiagnosisKeyBundlerTest {
-
-  @Autowired
-  DistributionServiceConfig distributionServiceConfig;
-
-  @Autowired
-  DiagnosisKeyBundler bundler;
-
-  @Test
-  void testEmptyListWhenNoDiagnosisKeys() {
-    bundler.setDiagnosisKeys(List.of());
-    assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 1, 0, 0, 0))).hasSize(0);
-  }
-
-  @Test
-  void testEmptyListWhenGettingDistributableKeysBeforeEarliestDiagnosisKey() {
-    List<DiagnosisKey> diagnosisKeys = buildDiagnosisKeys(6, 50L, 5);
-    bundler.setDiagnosisKeys(diagnosisKeys);
-    assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 1, 0, 0, 0))).hasSize(0);
-  }
-
-  @Nested
-  @DisplayName("Expiry policy")
-  class ProdDiagnosisKeyBundlerExpiryPolicyTest {
-
-    @ParameterizedTest
-    @ValueSource(longs = {0L, 24L, 24L + 2L})
-    void testLastPeriodOfHourAndSubmissionLessThanDistributionDateTime(long submissionTimestamp) {
-      List<DiagnosisKey> diagnosisKeys = buildDiagnosisKeys(5, submissionTimestamp, 10);
-      bundler.setDiagnosisKeys(diagnosisKeys);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 2, 3, 0, 0))).hasSize(10);
-    }
-
-    @Test
-    void testLastPeriodOfHourAndSubmissionEqualsDistributionDateTime() {
-      List<DiagnosisKey> diagnosisKeys = buildDiagnosisKeys(5, 24L + 3L, 10);
-      bundler.setDiagnosisKeys(diagnosisKeys);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 2, 3, 0, 0))).hasSize(10);
-    }
-
-    @ParameterizedTest
-    @ValueSource(longs = {0L, 24L, 24L + 2L, 24L + 3L})
-    void testFirstPeriodOfHourAndSubmissionLessThanDistributionDateTime(long submissionTimestamp) {
-      List<DiagnosisKey> diagnosisKeys = buildDiagnosisKeys(6, submissionTimestamp, 10);
-      bundler.setDiagnosisKeys(diagnosisKeys);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 2, 4, 0, 0))).hasSize(10);
-    }
-
-    @Test
-    void testFirstPeriodOfHourAndSubmissionEqualsDistributionDateTime() {
-      List<DiagnosisKey> diagnosisKeys = buildDiagnosisKeys(6, 24L + 4L, 10);
-      bundler.setDiagnosisKeys(diagnosisKeys);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 2, 4, 0, 0))).hasSize(10);
-    }
-
-    @Test
-    void testLastPeriodOfHourAndSubmissionGreaterDistributionDateTime() {
-      List<DiagnosisKey> diagnosisKeys = buildDiagnosisKeys(5, 24L + 4L, 10);
-      bundler.setDiagnosisKeys(diagnosisKeys);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 2, 4, 0, 0))).hasSize(10);
-    }
-  }
-
-  @Nested
-  @DisplayName("Shifting policy")
-  class ProdDiagnosisKeyBundlerShiftingPolicyTest {
-
-    @Test
-    void testDoesNotShiftIfPackageSizeGreaterThanThreshold() {
-      List<DiagnosisKey> diagnosisKeys = buildDiagnosisKeys(6, 50L, 6);
-      bundler.setDiagnosisKeys(diagnosisKeys);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 3, 2, 0, 0))).hasSize(6);
-    }
-
-    @Test
-    void testDoesNotShiftIfPackageSizeEqualsThreshold() {
-      List<DiagnosisKey> diagnosisKeys = buildDiagnosisKeys(6, 50L, 5);
-      bundler.setDiagnosisKeys(diagnosisKeys);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 3, 2, 0, 0))).hasSize(5);
-    }
-
-    @Test
-    void testShiftsIfPackageSizeLessThanThreshold() {
-      List<DiagnosisKey> diagnosisKeys = Stream
-          .concat(buildDiagnosisKeys(6, 50L, 4).stream(), buildDiagnosisKeys(6, 51L, 1).stream())
-          .collect(Collectors.toList());
-      bundler.setDiagnosisKeys(diagnosisKeys);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 3, 2, 0, 0))).hasSize(0);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 3, 3, 0, 0))).hasSize(5);
-    }
-
-    @Test
-    void testShiftsSinceLastDistribution() {
-      List<DiagnosisKey> diagnosisKeys = Stream
-          .of(buildDiagnosisKeys(6, 50L, 5), buildDiagnosisKeys(6, 51L, 2), buildDiagnosisKeys(6, 52L, 4))
-          .flatMap(List::stream)
-          .collect(Collectors.toList());
-      bundler.setDiagnosisKeys(diagnosisKeys);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 3, 2, 0, 0))).hasSize(5);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 3, 3, 0, 0))).hasSize(0);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 3, 4, 0, 0))).hasSize(6);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 3, 5, 0, 0))).hasSize(0);
-    }
-
-    @Test
-    void testShiftIncludesPreviouslyUndistributedKeys() {
-      List<DiagnosisKey> diagnosisKeys = Stream
-          .concat(buildDiagnosisKeys(6, 50L, 1).stream(), buildDiagnosisKeys(6, 51L, 5).stream())
-          .collect(Collectors.toList());
-      bundler.setDiagnosisKeys(diagnosisKeys);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 3, 2, 0, 0))).hasSize(0);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 3, 3, 0, 0))).hasSize(5);
-    }
-
-    @Test
-    void testShiftsSparseDistributions() {
-      List<DiagnosisKey> diagnosisKeys = Stream
-          .of(buildDiagnosisKeys(6, 50L, 1), buildDiagnosisKeys(6, 51L, 1), buildDiagnosisKeys(6, 52L, 1),
-              buildDiagnosisKeys(6, 53L, 0), buildDiagnosisKeys(6, 54L, 0), buildDiagnosisKeys(6, 55L, 1),
-              buildDiagnosisKeys(6, 56L, 1))
-          .flatMap(List::stream)
-          .collect(Collectors.toList());
-      bundler.setDiagnosisKeys(diagnosisKeys);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 3, 2, 0, 0))).hasSize(0);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 3, 3, 0, 0))).hasSize(0);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 3, 4, 0, 0))).hasSize(0);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 3, 5, 0, 0))).hasSize(0);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 3, 6, 0, 0))).hasSize(0);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 3, 7, 0, 0))).hasSize(0);
-      assertThat(bundler.getDiagnosisKeysDistributableAt(LocalDateTime.of(1970, 1, 3, 8, 0, 0))).hasSize(5);
-    }
-  }
-
-  public static List<DiagnosisKey> buildDiagnosisKeys(int startIntervalNumber, long submissionTimestamp, int number) {
-    return IntStream.range(0, number)
-        .mapToObj(__ -> DiagnosisKey.builder()
-            .withKeyData(new byte[16])
-            .withRollingStartIntervalNumber(startIntervalNumber)
-            .withTransmissionRiskLevel(2)
-            .withSubmissionTimestamp(submissionTimestamp).build())
-        .collect(Collectors.toList());
-  }
-}
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDirectoryTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDirectoryTest.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDirectoryTest.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/DiagnosisKeysDirectoryTest.java
@@ -21,6 +21,7 @@
 package app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.directory;

 import static app.coronawarn.server.services.distribution.common.Helpers.buildDiagnosisKeyForSubmissionTimestamp;
+import static app.coronawarn.server.services.distribution.common.Helpers.buildDiagnosisKeys;
 import static java.lang.String.join;
 import static org.assertj.core.api.Assertions.assertThat;

@@ -28,6 +29,7 @@
 import app.coronawarn.server.services.distribution.assembly.component.CryptoProvider;
 import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.DemoDiagnosisKeyBundler;
 import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.DiagnosisKeyBundler;
+import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.ProdDiagnosisKeyBundler;
 import app.coronawarn.server.services.distribution.assembly.structure.WritableOnDisk;
 import app.coronawarn.server.services.distribution.assembly.structure.directory.Directory;
 import app.coronawarn.server.services.distribution.assembly.structure.directory.DirectoryOnDisk;
@@ -35,6 +37,7 @@
 import app.coronawarn.server.services.distribution.config.DistributionServiceConfig;
 import java.io.File;
 import java.io.IOException;
+import java.time.LocalDateTime;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
@@ -81,17 +84,18 @@ void setupAll() throws IOException {
     // 01.01.1970 - 00:00 UTC
     long startTimestamp = 0;

-    // Generate diagnosis keys covering 30 hours of submission timestamps
-    // Until 02.01.1970 - 06:00 UTC -> 1 full day + 6 hours
+    // Generate diagnosis keys covering 29 hours of submission timestamps (one gap)
+    // Until 04.01.1970 - 06:00 UTC -> 1 full day + 5 hours
     diagnosisKeys = IntStream.range(0, 30)
-        .mapToObj(
-            currentHour -> buildDiagnosisKeyForSubmissionTimestamp(startTimestamp + currentHour))
+        .filter(currentHour -> currentHour != 20)
+        .mapToObj(currentHour -> buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 3, 0, 0).plusHours(currentHour), 5))
+        .flatMap(List::stream)
         .collect(Collectors.toList());
   }

   @Test
   void checkBuildsTheCorrectDirectoryStructureWhenNoKeys() {
-    DiagnosisKeyBundler bundler = new DemoDiagnosisKeyBundler(distributionServiceConfig);
+    DiagnosisKeyBundler bundler = new ProdDiagnosisKeyBundler(distributionServiceConfig);
     Directory<WritableOnDisk> directory = new DiagnosisKeysDirectory(bundler, cryptoProvider,
         distributionServiceConfig);
     parentDirectory.addWritable(directory);
@@ -101,7 +105,6 @@ void checkBuildsTheCorrectDirectoryStructureWhenNoKeys() {
     String s = File.separator;
     Set<String> expectedFiles = Set.of(
         join(s, "diagnosis-keys", "country", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "index"),
         join(s, "diagnosis-keys", "country", "DE", "date", "index")
     );

@@ -112,8 +115,8 @@ void checkBuildsTheCorrectDirectoryStructureWhenNoKeys() {

   @Test
   void checkBuildsTheCorrectDirectoryStructure() {
-    DiagnosisKeyBundler bundler = new DemoDiagnosisKeyBundler(distributionServiceConfig);
-    bundler.setDiagnosisKeys(diagnosisKeys);
+    DiagnosisKeyBundler bundler = new ProdDiagnosisKeyBundler(distributionServiceConfig);
+    bundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
     Directory<WritableOnDisk> directory = new DiagnosisKeysDirectory(bundler, cryptoProvider,
         distributionServiceConfig);
     parentDirectory.addWritable(directory);
@@ -123,42 +126,41 @@ void checkBuildsTheCorrectDirectoryStructure() {
     String s = File.separator;
     Set<String> expectedFiles = Set.of(
         join(s, "diagnosis-keys", "country", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "index"),
         join(s, "diagnosis-keys", "country", "DE", "date", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "0", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "1", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "2", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "3", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "4", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "5", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "6", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "7", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "8", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "9", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "10", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "11", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "12", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "13", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "14", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "15", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "16", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "17", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "18", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "19", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "20", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "21", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "22", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-01", "hour", "23", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-02", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-02", "hour", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-02", "hour", "0", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-02", "hour", "1", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-02", "hour", "2", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-02", "hour", "3", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-02", "hour", "4", "index"),
-        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-02", "hour", "5", "index")
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "0", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "1", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "2", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "3", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "4", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "5", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "6", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "7", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "8", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "9", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "10", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "11", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "12", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "13", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "14", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "15", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "16", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "17", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "18", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "19", "index"),
+        // One missing
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "21", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "22", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-03", "hour", "23", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-04", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-04", "hour", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-04", "hour", "0", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-04", "hour", "1", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-04", "hour", "2", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-04", "hour", "3", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-04", "hour", "4", "index"),
+        join(s, "diagnosis-keys", "country", "DE", "date", "1970-01-04", "hour", "5", "index")
     );

     Set<String> actualFiles = getActualFiles(outputFile);
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/decorator/DateIndexingDecoratorTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/decorator/DateIndexingDecoratorTest.java
new file mode 100644
--- /dev/null
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/decorator/DateIndexingDecoratorTest.java
@@ -0,0 +1,106 @@
+/*
+ * ---license-start
+ * Corona-Warn-App
+ * ---
+ * Copyright (C) 2020 SAP SE and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.directory.decorator;
+
+import static app.coronawarn.server.services.distribution.common.Helpers.buildDiagnosisKeys;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
+import app.coronawarn.server.services.distribution.assembly.component.CryptoProvider;
+import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.DiagnosisKeyBundler;
+import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.ProdDiagnosisKeyBundler;
+import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.directory.DiagnosisKeysDateDirectory;
+import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.directory.DiagnosisKeysHourDirectory;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.config.DistributionServiceConfig;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.test.context.ConfigFileApplicationContextInitializer;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+@EnableConfigurationProperties(value = DistributionServiceConfig.class)
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = {CryptoProvider.class, DistributionServiceConfig.class},
+    initializers = ConfigFileApplicationContextInitializer.class)
+class DateIndexingDecoratorTest {
+
+  @Autowired
+  DistributionServiceConfig distributionServiceConfig;
+
+  @Autowired
+  CryptoProvider cryptoProvider;
+
+  private DiagnosisKeyBundler diagnosisKeyBundler;
+
+  @BeforeEach
+  void setup() {
+    diagnosisKeyBundler = new ProdDiagnosisKeyBundler(distributionServiceConfig);
+  }
+
+  @Test
+  void excludesEmptyDatesFromIndex() {
+    List<DiagnosisKey> diagnosisKeys = Stream
+        .of(buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 3, 0, 0), 5),
+            buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 4, 0, 0), 0),
+            buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 5, 0, 0), 5))
+        .flatMap(List::stream)
+        .collect(Collectors.toList());
+    diagnosisKeyBundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 6, 0, 0));
+    DateIndexingDecorator decorator = makeDecoratedDateDirectory(diagnosisKeyBundler);
+    decorator.prepare(new ImmutableStack<>().push("DE"));
+
+    Set<LocalDate> index = decorator.getIndex(new ImmutableStack<>());
+
+    assertThat(index).contains(LocalDate.of(1970, 1, 3));
+    assertThat(index).doesNotContain(LocalDate.of(1970, 1, 4));
+    assertThat(index).contains(LocalDate.of(1970, 1, 5));
+  }
+
+  @Test
+  void excludesCurrentDateFromIndex() {
+    List<DiagnosisKey> diagnosisKeys = buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 4, 0, 0), 5);
+    diagnosisKeyBundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    DateIndexingDecorator decorator = makeDecoratedDateDirectory(diagnosisKeyBundler);
+    decorator.prepare(new ImmutableStack<>().push("DE"));
+
+    Set<LocalDate> index = decorator.getIndex(new ImmutableStack<>());
+
+    assertThat(index).contains(LocalDate.of(1970, 1, 4));
+    assertThat(index).doesNotContain(LocalDate.of(1970, 1, 5));
+  }
+
+  private DateIndexingDecorator makeDecoratedDateDirectory(DiagnosisKeyBundler diagnosisKeyBundler) {
+    return new DateIndexingDecorator(
+        new DiagnosisKeysDateDirectory(diagnosisKeyBundler, cryptoProvider, distributionServiceConfig),
+        distributionServiceConfig
+    );
+  }
+}
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/decorator/HourIndexingDecoratorTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/decorator/HourIndexingDecoratorTest.java
new file mode 100644
--- /dev/null
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/structure/directory/decorator/HourIndexingDecoratorTest.java
@@ -0,0 +1,105 @@
+/*
+ * ---license-start
+ * Corona-Warn-App
+ * ---
+ * Copyright (C) 2020 SAP SE and all other contributors
+ * ---
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ---license-end
+ */
+
+package app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.directory.decorator;
+
+import static app.coronawarn.server.services.distribution.common.Helpers.buildDiagnosisKeys;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
+import app.coronawarn.server.services.distribution.assembly.component.CryptoProvider;
+import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.DiagnosisKeyBundler;
+import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.ProdDiagnosisKeyBundler;
+import app.coronawarn.server.services.distribution.assembly.diagnosiskeys.structure.directory.DiagnosisKeysHourDirectory;
+import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
+import app.coronawarn.server.services.distribution.config.DistributionServiceConfig;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.test.context.ConfigFileApplicationContextInitializer;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+@EnableConfigurationProperties(value = DistributionServiceConfig.class)
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = {CryptoProvider.class, DistributionServiceConfig.class},
+    initializers = ConfigFileApplicationContextInitializer.class)
+class HourIndexingDecoratorTest {
+
+  @Autowired
+  DistributionServiceConfig distributionServiceConfig;
+
+  @Autowired
+  CryptoProvider cryptoProvider;
+
+  private DiagnosisKeyBundler diagnosisKeyBundler;
+
+  @BeforeEach
+  void setup() {
+    diagnosisKeyBundler = new ProdDiagnosisKeyBundler(distributionServiceConfig);
+  }
+
+  @Test
+  void excludesEmptyHoursFromIndex() {
+    List<DiagnosisKey> diagnosisKeys = Stream
+        .of(buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 3, 4, 0), 5),
+            buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 3, 5, 0), 0),
+            buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 3, 6, 0), 5))
+        .flatMap(List::stream)
+        .collect(Collectors.toList());
+    diagnosisKeyBundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 0, 0));
+    HourIndexingDecorator decorator = makeDecoratedHourDirectory(diagnosisKeyBundler);
+    decorator.prepare(new ImmutableStack<>().push("DE").push(LocalDate.of(1970, 1, 3)));
+
+    Set<LocalDateTime> index = decorator.getIndex(new ImmutableStack<>().push(LocalDate.of(1970, 1, 3)));
+
+    assertThat(index).contains(LocalDateTime.of(1970, 1, 3, 4, 0));
+    assertThat(index).doesNotContain(LocalDateTime.of(1970, 1, 3, 5, 0));
+    assertThat(index).contains(LocalDateTime.of(1970, 1, 3, 6, 0));
+  }
+
+  @Test
+  void excludesCurrentHourFromIndex() {
+    List<DiagnosisKey> diagnosisKeys = buildDiagnosisKeys(6, LocalDateTime.of(1970, 1, 5, 0, 0), 5);
+    diagnosisKeyBundler.setDiagnosisKeys(diagnosisKeys, LocalDateTime.of(1970, 1, 5, 1, 0));
+    HourIndexingDecorator decorator = makeDecoratedHourDirectory(diagnosisKeyBundler);
+    decorator.prepare(new ImmutableStack<>().push("DE").push(LocalDate.of(1970, 1, 5)));
+
+    Set<LocalDateTime> index = decorator.getIndex(new ImmutableStack<>().push(LocalDate.of(1970, 1, 5)));
+
+    assertThat(index).contains(LocalDateTime.of(1970, 1, 5, 0, 0));
+    assertThat(index).doesNotContain(LocalDateTime.of(1970, 1, 5, 1, 0));
+  }
+
+  private HourIndexingDecorator makeDecoratedHourDirectory(DiagnosisKeyBundler diagnosisKeyBundler) {
+    return new HourIndexingDecorator(
+        new DiagnosisKeysHourDirectory(diagnosisKeyBundler, cryptoProvider, distributionServiceConfig),
+        distributionServiceConfig
+    );
+  }
+}
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/DateTimeTest.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/DateTimeTest.java
deleted file mode 100644
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/assembly/diagnosiskeys/util/DateTimeTest.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*-
- * ---license-start
- * Corona-Warn-App
- * ---
- * Copyright (C) 2020 SAP SE and all other contributors
- * ---
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ---license-end
- */
-
-package app.coronawarn.server.services.distribution.assembly.diagnosiskeys.util;
-
-import static app.coronawarn.server.services.distribution.common.Helpers.buildDiagnosisKeyForDateTime;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.emptySet;
-import static org.assertj.core.api.Assertions.assertThat;
-
-import app.coronawarn.server.common.persistence.domain.DiagnosisKey;
-import app.coronawarn.server.services.distribution.common.Helpers;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-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;
-
-class DateTimeTest {
-
-  @Test
-  void testGetDatesForEmptyList() {
-    assertThat(DateTime.getDates(emptyList())).isEmpty();
-  }
-
-  @ParameterizedTest
-  @MethodSource("createDiagnosisKeysForEpochDay0")
-  void testGetDatesForEpochDay0(DiagnosisKey diagnosisKey) {
-    var expDates = Set.of(LocalDate.ofEpochDay(0L));
-    var actDates = DateTime.getDates(Set.of(diagnosisKey));
-
-    assertThat(actDates)
-        .withFailMessage(
-            "Failed for submission timestamp: " + diagnosisKey.getSubmissionTimestamp())
-        .isEqualTo(expDates);
-  }
-
-  private static Stream<Arguments> createDiagnosisKeysForEpochDay0() {
-    return Stream.of(
-        buildDiagnosisKeyForDateTime(LocalDateTime.of(1970, 1, 1, 0, 0)),
-        buildDiagnosisKeyForDateTime(LocalDateTime.of(1970, 1, 1, 1, 0)),
-        buildDiagnosisKeyForDateTime(LocalDateTime.of(1970, 1, 1, 23, 59, 59))
-    ).map(Arguments::of);
-  }
-
-  @Test
-  void testGetDatesFor2Days() {
-    var diagnosisKeys = Set.of(
-        buildDiagnosisKeyForDateTime(LocalDateTime.of(1970, 1, 1, 1, 0)),
-        buildDiagnosisKeyForDateTime(LocalDateTime.of(1970, 1, 2, 1, 0)));
-    var expDates = Set.of(LocalDate.ofEpochDay(0L), LocalDate.ofEpochDay(1L));
-
-    assertThat(DateTime.getDates(diagnosisKeys)).isEqualTo(expDates);
-  }
-
-  @ParameterizedTest
-  @MethodSource("createDiagnosisKeysForEpochDay1And3")
-  void testGetHoursReturnsHoursOnlyForSpecifiedDate(Set<DiagnosisKey> diagnosisKeys) {
-    var expHours = Set.of(
-        LocalDateTime.of(1970, 1, 2, 0, 0),
-        LocalDateTime.of(1970, 1, 2, 5, 0));
-
-    var diagnosisKeysIncludingExpHours = new HashSet<>(diagnosisKeys);
-    diagnosisKeysIncludingExpHours.addAll(expHours.stream()
-        .map(Helpers::buildDiagnosisKeyForDateTime).collect(Collectors.toSet()));
-
-    var actHours = DateTime.getHours(LocalDate.ofEpochDay(1L), diagnosisKeysIncludingExpHours);
-
-    assertThat(actHours).isEqualTo(expHours);
-  }
-
-  private static Stream<Arguments> createDiagnosisKeysForEpochDay1And3() {
-    return Stream.of(
-        emptySet(),
-        Set.of(buildDiagnosisKeyForDateTime(LocalDateTime.of(1970, 1, 1, 23, 59))),
-        Set.of(
-            buildDiagnosisKeyForDateTime(LocalDateTime.of(1970, 1, 1, 23, 59, 59)),
-            buildDiagnosisKeyForDateTime(LocalDateTime.of(1970, 1, 3, 0, 0)))
-    ).map(Arguments::of);
-  }
-}
diff --git a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/common/Helpers.java b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/common/Helpers.java
--- a/services/distribution/src/test/java/app/coronawarn/server/services/distribution/common/Helpers.java
+++ b/services/distribution/src/test/java/app/coronawarn/server/services/distribution/common/Helpers.java
@@ -25,6 +25,9 @@
 import app.coronawarn.server.services.distribution.assembly.structure.util.ImmutableStack;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;

 public class Helpers {

@@ -36,7 +39,7 @@ public static void prepareAndWrite(Directory directory) {
   public static DiagnosisKey buildDiagnosisKeyForSubmissionTimestamp(long submissionTimeStamp) {
     return DiagnosisKey.builder()
         .withKeyData(new byte[16])
-        .withRollingStartIntervalNumber(600)
+        .withRollingStartIntervalNumber(1)
         .withTransmissionRiskLevel(2)
         .withSubmissionTimestamp(submissionTimeStamp).build();
   }
@@ -44,4 +47,19 @@ public static DiagnosisKey buildDiagnosisKeyForSubmissionTimestamp(long submissi
   public static DiagnosisKey buildDiagnosisKeyForDateTime(LocalDateTime dateTime) {
     return buildDiagnosisKeyForSubmissionTimestamp(dateTime.toEpochSecond(ZoneOffset.UTC) / 3600);
   }
+
+  public static List<DiagnosisKey> buildDiagnosisKeys(int startIntervalNumber, LocalDateTime submissionTimestamp, int number) {
+    long timestamp = submissionTimestamp.toEpochSecond(ZoneOffset.UTC) / 3600;
+    return buildDiagnosisKeys(startIntervalNumber, timestamp, number);
+  }
+
+  public static List<DiagnosisKey> buildDiagnosisKeys(int startIntervalNumber, long submissionTimestamp, int number) {
+    return IntStream.range(0, number)
+        .mapToObj(__ -> DiagnosisKey.builder()
+            .withKeyData(new byte[16])
+            .withRollingStartIntervalNumber(startIntervalNumber)
+            .withTransmissionRiskLevel(2)
+            .withSubmissionTimestamp(submissionTimestamp).build())
+        .collect(Collectors.toList());
+  }
 }

Remove "empty" dates/hours from index files
## Current Implementation
All dates/hours between the first submission and now (last full hour) are included in the index files, even if there were no submissions.

## Suggested Enhancement
Only list dates/hours for which there were submissions in the index files. The "empty" date/hour files should, however, still be generated, even if they are not in the index.

## Expected Benefits
Mobile clients need to do fewer requests (they can simply skip empty dates/hours).

Why should the empty files still be generated?
Basically to attest that there were really no submissions in that time window (and that the distribution run didn't just fail). Since we are not (yet) signing index files, I guess the best we can do is to just create and sign the empty exports.