Joda时间间隔的周期数

时间:2014-03-31 18:13:16

标签: java jodatime

我试图计算Joda时间间隔内的完整连续周期数(其中周期是任意的但是不变)。

我提出的简单解决方案是使用while循环进行线性搜索:

public static long periodsInAnInterval(Interval interval, Period period) {
    int periods = -1;
    DateTime marker = interval.getStart();
    while (marker.isBefore(interval.getEnd()) || marker.isEqual(interval.getEnd())) {
        marker = marker.plus(period);
        periods++;
    }
    return periods;
}

O(n)解决方案显然非常可怕,所以有人能想到更好的方法吗?我想知道是否可以使用某种二进制搜索...

这是一个测试用例:https://gist.github.com/Mahoney/9899832

编辑 - 记住一个句点没有已知的秒数; Period.toStandardDuration()只是一个近似值,假设年份为365天,月份为30天,日期为24小时。 (实际上,如果您在此期间有数年或数月,则快速测试会显示Period.toStandardDuration会发生异常爆炸。)

编辑2 - 我很高兴地假设第一个时期从时间间隔的开始开始 - 否则我怀疑答案可能会有所不同,具体取决于剩余时间是在开始时间,结尾时间还是两者之间。 / p>

2 个答案:

答案 0 :(得分:1)

这是我的首选解决方案:使用句点的平均长度来形成最佳猜测,然后对其进行优化。这似乎是最有效和最优雅的方式。

import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import org.joda.time.*;

import static com.google.common.collect.FluentIterable.from;
import static java.util.Arrays.asList;
import static org.joda.time.DurationFieldType.*;

public class PeriodArithmetic {

    public static long periodsInAnInterval(Interval interval, Period period) {
        int bestGuess = (int) (interval.toDurationMillis() / toAverageMillis(period));
        if (bestGuess < 0) return 0;
        if (startPlusScaledPeriodIsAfterEnd(interval, period, bestGuess + 1)) {
            return searchDownwards(interval, period, bestGuess);
        } else {
            return searchUpwards(interval, period, bestGuess);
        }
    }

    private static long searchDownwards(Interval interval, Period period, int currentGuess) {
        if (startPlusScaledPeriodIsAfterEnd(interval, period, currentGuess)) {
            return searchDownwards(interval, period, currentGuess - 1);
        } else {
            return currentGuess;
        }
    }

    private static long searchUpwards(Interval interval, Period period, int currentGuess) {
        if (!startPlusScaledPeriodIsAfterEnd(interval, period, currentGuess + 1)) {
            return searchUpwards(interval, period, currentGuess + 1);
        } else {
            return currentGuess;
        }
    }

    private static boolean startPlusScaledPeriodIsAfterEnd(Interval interval, Period period, int scalar) {
        return interval.getStart().plus(period.multipliedBy(scalar)).isAfter(interval.getEnd());
    }

    private static final long MILLIS_IN_DAY = Days.ONE.toStandardSeconds().getSeconds() * 1000L;
    private static final long MILLIS_IN_YEAR = Days.ONE.toStandardSeconds().getSeconds() * 365250L;

    private static final ImmutableMap<DurationFieldType, Long> averageLengthMillis
            = ImmutableMap.<DurationFieldType, Long>builder()
            .put(millis(),    1L)
            .put(seconds(),   1000L)
            .put(minutes(),   Minutes.ONE.toStandardSeconds().getSeconds() * 1000L)
            .put(hours(),     Hours.ONE.toStandardSeconds().getSeconds() * 1000L)
            .put(halfdays(),  MILLIS_IN_DAY / 2)
            .put(days(),      MILLIS_IN_DAY)
            .put(weeks(),     Weeks.ONE.toStandardSeconds().getSeconds() * 1000L)
            .put(months(),    MILLIS_IN_YEAR / 12)
            .put(years(),     MILLIS_IN_YEAR)
            .put(weekyears(), MILLIS_IN_YEAR)
            .put(centuries(), MILLIS_IN_YEAR * 100)
            .put(eras(),      Long.MAX_VALUE)
            .build();

    private static long toAverageMillis(Period period) {
        final Iterable<Long> milliValues = from(asList(period.getFieldTypes())).transform(toAverageMillisForFieldType(period));
        return total(milliValues);
    }

    private static Function<DurationFieldType, Long> toAverageMillisForFieldType(final Period period) {
        return new Function<DurationFieldType, Long>() {
            @Override
            public Long apply(DurationFieldType durationFieldType) {
                final Long averageDuration = averageLengthMillis.get(durationFieldType);
                return period.get(durationFieldType) * averageDuration;
            }
        };
    }

    private static long total(Iterable<Long> milliValues) {
        long acc = 0;
        for (Long milliValue : milliValues) {
            acc += milliValue;
        }
        return acc;
    }
}

答案 1 :(得分:0)

我已将二进制搜索解决方案的开头放在此处:https://gist.github.com/Mahoney/9899936

它比线性搜索复杂得多;另一方面,它在1000年内找到月数的速度大约快了100倍。

它还没有完成 - 更多的是实验而不是任何事情,所以我确定有未经测试的边缘情况(负面时期?)。

仍然很想知道是否有人有更优雅的解决方案(或者只是我不需要编写,测试和维护!)。