private List<ProcessablePeriod> getProcessPeriods(
            final DataRetentionRules dataRetentionRules,
            final List<DataRetentionRule> activeRules,
            final Instant now) {

        // Get effective earliest meta creation times for each rule.
        // A meta creation time earlier than one of these minimums is valid
        // for deletion unless retained by another rule
        final Map<Instant, Set<DataRetentionRule>> earliestRetainedCreationTimeMap =
                getEarliestRetainedCreationTimeMap(activeRules, now);

        final Map<Instant, DataRetentionTracker> trackersByEarliestRetainedCreateTime =
                getTrackersByEarliestCreateTime(dataRetentionRules, earliestRetainedCreationTimeMap);

        // We need to work down the list of min time thresholds in newest -> oldest order
        final List<Instant> descendingCreationTimes = earliestRetainedCreationTimeMap.keySet()
                .stream()
                .sorted(Comparator.reverseOrder())
                .collect(Collectors.toList());

        final Instant mostRecentCreationTime = descendingCreationTimes.get(0);

        // We don't need to delete anything newer than this time as all rules have a minCreateTime
        // older than or equal to it.  Thus make this the end of our first deletion period
        Instant toTime = mostRecentCreationTime;
        // Ignore the period from latestMinCreationTime => now
        descendingCreationTimes.remove(mostRecentCreationTime);

        final List<ProcessablePeriod> processablePeriods = new ArrayList<>();

        for (final Instant creationTime : descendingCreationTimes) {

            final String ruleAge = getRuleAge(earliestRetainedCreationTimeMap, creationTime);

            // Use tracker to establish how long ago we last ran this process so we can avoid
            // scanning over data that has already been evaluated. Tracker may be null
            final DataRetentionTracker tracker = trackersByEarliestRetainedCreateTime.get(creationTime);
            final Optional<Duration> optTimeSinceLastRun = getTimeSinceLastRun(now, tracker);

            // Calculate a from time for the period that takes account of the
            // time passed since the last execution.
            // e.g if rule is for 1 month and we are considering the period from
            // now - 1month  to now then from time is now - 1month but if
            // we ran this process 1day ago then the period is from now -1d to now
            // as the rest of the data has already been considered.

            final Instant fromTime;
            if (optTimeSinceLastRun.isPresent()) {
                final Instant adjustedFromTime = toTime.minus(optTimeSinceLastRun.get());

                // If the time since last run takes us before the time period we are working with
                // then leave it unadjusted
                fromTime = Ordering.natural()
                        .max(creationTime, adjustedFromTime);

                LOGGER.debug("toTime: {}, createTime: {}, adjustedToTime {}, fromTime: {}",
                        toTime, creationTime, adjustedFromTime, fromTime);
            } else {
                fromTime = creationTime;
                LOGGER.debug("No tracker so use un-adjusted fromTime: {}", fromTime);
            }

            final TimePeriod period = TimePeriod.between(fromTime, toTime);

            LOGGER.debug(() -> LogUtil.message("creationTime: {} ({} ago), ruleAge: '{}', tracker: ({}), period: {}",
                    creationTime,
                    TimeUtils.instantAsAge(creationTime, now),
                    ruleAge,
                    (tracker != null
                            ? "ruleAge: '" + tracker.getRuleAge() + "', lastRunTime: " + tracker.getLastRunTime()
                            : "null"),
                    getPeriodInfo(period, now)));

            final List<DataRetentionRuleAction> ruleActions = activeRules.stream()
                    .map(rule -> {
                        final RetentionRuleOutcome ruleOutcome = getRuleOutcome(period, rule, now);

                        final DataRetentionRuleAction ruleAction = new DataRetentionRuleAction(rule, ruleOutcome);

                        LOGGER.debug(() -> LogUtil.message(
                                "  {} Rule: {}, ruleMinCreateTime: {} ({} ago)",
                                ruleOutcome,
                                rule,
                                getEarliestRetainedCreateTime(rule, now),
                                TimeUtils.instantAsAge(getEarliestRetainedCreateTime(rule, now), now)));
                        return ruleAction;
                    })
                    .collect(Collectors.toList());

            final ProcessablePeriod processablePeriod = new ProcessablePeriod(
                    period,
                    ruleAge,
                    ruleActions,
                    tracker);
            processablePeriods.add(processablePeriod);

            // Move the toTime ready for the next time period in the loop
            toTime = creationTime;
        }

        return processablePeriods;
    }
Pkg private so we can test with a known now time
stroom-data/stroom-data-retention-impl/src/main/java/stroom/data/retention/impl/DataRetentionPolicyExecutor.java