kr.debop4j.timeperiod.calendars.DateAdd.java Source code

Java tutorial

Introduction

Here is the source code for kr.debop4j.timeperiod.calendars.DateAdd.java

Source

/*
 * Copyright 2011-2013 the original author or authors.
 *
 * 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.
 */

package kr.debop4j.timeperiod.calendars;

import kr.debop4j.core.Pair;
import kr.debop4j.core.ValueObjectBase;
import kr.debop4j.core.tools.StringTool;
import kr.debop4j.timeperiod.*;
import kr.debop4j.timeperiod.timeline.TimeGapCalculator;
import kr.debop4j.timeperiod.tools.Durations;
import kr.debop4j.timeperiod.tools.TimeSpec;
import lombok.Getter;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static kr.debop4j.core.Guard.shouldBe;

/**
 *  ? (Duration)? ? ? ?? . (?  ?? ?? )
 *
 * @author ? sunghyouk.bae@gmail.com
 * @since 13. 5. 19.  11:39
 */
public class DateAdd extends ValueObjectBase {

    private static final Logger log = LoggerFactory.getLogger(DateAdd.class);
    private static final boolean isTraceEnable = log.isTraceEnabled();
    private static final boolean isDebugEnable = log.isDebugEnabled();

    @Getter
    private final ITimePeriodCollection includePeriods = new TimePeriodCollection();
    @Getter
    private final ITimePeriodCollection excludePeriods = new TimePeriodCollection();

    /**  ?? */
    public DateAdd() {
    }

    /** start ? offset ?  ?? . */
    public DateTime add(DateTime start, Duration offset) {
        return add(start, offset, SeekBoundaryMode.Next);
    }

    /** start ? offset ?  ?? . */
    public DateTime add(DateTime start, Duration offset, SeekBoundaryMode seekBoundary) {
        if (isTraceEnable)
            log.trace("Add. start=[{}] + offset=[{}]? ?? . seekBoundaryMode=[{}]", start,
                    offset, seekBoundary);

        if (getIncludePeriods().size() == 0 && getExcludePeriods().size() == 0)
            return start.plus(offset);

        Pair<DateTime, Duration> results = offset.compareTo(Duration.ZERO) < 0
                ? calculateEnd(start, Durations.negate(offset), SeekDirection.Backward, seekBoundary)
                : calculateEnd(start, offset, SeekDirection.Forward, seekBoundary);

        DateTime end = (results != null) ? results.getV1() : null;
        Duration remaining = (results != null) ? results.getV2() : null;

        if (isDebugEnable)
            log.debug(
                    "Add. start=[{}] + offset=[{}] ?  end=[{}], remaining=[{}]. seekBoundaryMode=[{}]",
                    start, offset, end, remaining, seekBoundary);

        return end;
    }

    /** start ? offset ?  ( ??) ?? . */
    public DateTime subtract(DateTime start, Duration offset) {
        return subtract(start, offset, SeekBoundaryMode.Next);
    }

    /** start ? offset ?  ( ??) ?? . */
    public DateTime subtract(DateTime start, Duration offset, SeekBoundaryMode seekBoundary) {
        if (isTraceEnable)
            log.trace("Subtract. start=[{}] - offset=[{}]? ?? . seekBoundaryMode=[{}]",
                    start, offset, seekBoundary);

        Pair<DateTime, Duration> results = offset.compareTo(Duration.ZERO) < 0
                ? calculateEnd(start, Durations.negate(offset), SeekDirection.Forward, seekBoundary)
                : calculateEnd(start, offset, SeekDirection.Backward, seekBoundary);

        DateTime end = (results != null) ? results.getV1() : null;
        Duration remaining = (results != null) ? results.getV2() : null;

        if (isDebugEnable)
            log.debug(
                    "Subtract. start=[{}] - offset=[{}] ?  end=[{}], remaining=[{}]. seekBoundaryMode=[{}]",
                    start, offset, end, remaining, seekBoundary);

        return end;
    }

    /**
     *  ? offset ?  ?? .
     *
     * @param start         ?
     * @param offset       
     * @param seekDir      ? 
     * @param seekBoundary   ? 
     * @return ? ?,  
     */
    protected Pair<DateTime, Duration> calculateEnd(DateTime start, Duration offset, SeekDirection seekDir,
            SeekBoundaryMode seekBoundary) {
        if (isTraceEnable)
            log.trace(
                    "? ?  ?? ... start=[{}], offset=[{}], seekDir=[{}], seekBoundary=[{}]",
                    start, offset, seekDir, seekBoundary);
        shouldBe(offset.compareTo(Duration.ZERO) >= 0, "offset? 0 ??? . offset=[%d]",
                offset.getMillis());

        Duration remaining = offset;
        DateTime end;

        // search periods
        ITimePeriodCollection searchPeriods = new TimePeriodCollection(this.includePeriods);
        if (searchPeriods.size() == 0)
            searchPeriods.add(TimeRange.Anytime);

        // available periods
        ITimePeriodCollection availablePeriods = new TimePeriodCollection();
        if (excludePeriods.size() == 0) {
            availablePeriods.addAll(searchPeriods);
        } else {
            if (isTraceEnable)
                log.trace(" ? .");
            TimeGapCalculator<TimeRange> gapCalculator = new TimeGapCalculator<>();
            for (ITimePeriod p : searchPeriods) {
                if (excludePeriods.hasOverlapPeriods(p)) {
                    if (isTraceEnable)
                        log.trace(" ? ?  ? ");
                    for (ITimePeriod gap : gapCalculator.getGaps(excludePeriods, p))
                        availablePeriods.add(gap);
                } else {
                    availablePeriods.add(p);
                }
            }
        }

        if (availablePeriods.size() == 0) {
            if (isTraceEnable)
                log.trace(" period   .");
            return Pair.create(null, remaining);
        }

        if (isTraceEnable)
            log.trace("  ? ?   ? ...");
        TimePeriodCombiner periodCombiner = new TimePeriodCombiner<TimeRange>();
        availablePeriods = periodCombiner.combinePeriods(availablePeriods);

        if (isTraceEnable)
            log.trace(" ? .");

        Pair<ITimePeriod, DateTime> result = (seekDir == SeekDirection.Forward)
                ? findNextPeriod(start, availablePeriods)
                : findPrevPeriod(start, availablePeriods);

        ITimePeriod startPeriod = result.getV1();
        DateTime seekMoment = result.getV2();

        //   ?  .
        if (startPeriod == null) {
            if (isTraceEnable)
                log.trace("  ?  .");
            return Pair.create(null, remaining);
        }

        // offset ? 0 ??,  ? ? seekMoment  .
        if (offset.isEqual(Duration.ZERO)) {
            if (isTraceEnable)
                log.trace("offset ? 0?,  ? ? seekMoment .");
            return Pair.create(seekMoment, remaining);
        }

        if (seekDir == SeekDirection.Forward) {

            for (int i = availablePeriods.indexOf(startPeriod); i < availablePeriods.size(); i++) {
                ITimePeriod gap = availablePeriods.get(i);
                Duration gapRemaining = new Duration(seekMoment, gap.getEnd());

                if (isTraceEnable)
                    log.trace("Seek forward. gap=[{}], gapRemaining=[{}], remaining=[{}], seekMoment=[{}]", gap,
                            gapRemaining, remaining, seekMoment);

                boolean isTargetPeriod = (seekBoundary == SeekBoundaryMode.Fill)
                        ? gapRemaining.compareTo(remaining) >= 0
                        : gapRemaining.compareTo(remaining) > 0;

                if (isTargetPeriod) {
                    end = seekMoment.plus(remaining);
                    remaining = null;
                    return Pair.create(end, remaining);
                }

                remaining = remaining.minus(gapRemaining);
                if (i == availablePeriods.size() - 1)
                    return Pair.create(null, remaining);

                seekMoment = availablePeriods.get(i + 1).getStart(); // next period
            }
        } else {
            for (int i = availablePeriods.indexOf(startPeriod); i >= 0; i--) {
                ITimePeriod gap = availablePeriods.get(i);
                Duration gapRemaining = new Duration(gap.getStart(), seekMoment);

                if (isTraceEnable)
                    log.trace("Seek backward. gap=[{}], gapRemaining=[{}], remaining=[{}], seekMoment=[{}]", gap,
                            gapRemaining, remaining, seekMoment);

                boolean isTargetPeriod = (seekBoundary == SeekBoundaryMode.Fill)
                        ? gapRemaining.compareTo(remaining) >= 0
                        : gapRemaining.compareTo(remaining) > 0;

                if (isTargetPeriod) {
                    end = seekMoment.minus(remaining);
                    remaining = null;
                    return Pair.create(end, remaining);
                }
                remaining = remaining.minus(gapRemaining);
                if (i == 0)
                    return Pair.create(null, remaining);

                seekMoment = availablePeriods.get(i - 1).getEnd();
            }
        }

        if (isTraceEnable)
            log.trace(" ??  .");
        return Pair.create(null, remaining);
    }

    /**
     * start periods?  ?   ? ?   ? ,  start    period .
     *
     * @param start    ??
     * @param periods ? 
     * @return period  ?  ??
     */
    private static Pair<ITimePeriod, DateTime> findNextPeriod(DateTime start,
            Iterable<? extends ITimePeriod> periods) {
        if (isTraceEnable)
            log.trace("?? ? ? ... start=[{}], periods=[{}]", start,
                    StringTool.listToString(periods));

        ITimePeriod nearest = null;
        DateTime moment = start;
        Duration difference = TimeSpec.MaxDuration;

        for (ITimePeriod period : periods) {

            // ? start ??? (before)
            if (period.getEnd().compareTo(start) < 0)
                continue;

            // start ? ?...
            if (period.hasInside(start)) {
                nearest = period;
                moment = start;
                break;
            }
            //  ? ? ?
            Duration periodToMoment = new Duration(start, period.getStart());
            if (periodToMoment.compareTo(difference) >= 0)
                continue;

            difference = periodToMoment;
            nearest = period;
            moment = period.getStart();
        }

        if (isTraceEnable)
            log.trace("?? ? ? . start=[{}], moment=[{}], neearest=[{}]",
                    start, moment, nearest);

        return Pair.create(nearest, moment);
    }

    /**
     * start periods  ?   ? ?   ? ,  start    period .
     *
     * @param start    ??
     * @param periods ? 
     * @return period  ?  ??
     */
    private static Pair<ITimePeriod, DateTime> findPrevPeriod(DateTime start,
            Iterable<? extends ITimePeriod> periods) {
        if (isTraceEnable)
            log.trace("?? ? ? ... start=[{}], periods=[{}]", start,
                    StringTool.listToString(periods));

        ITimePeriod nearest = null;
        DateTime moment = start;
        Duration difference = TimeSpec.MaxDuration;

        for (ITimePeriod period : periods) {

            // ? start ??? (after)
            if (period.getStart().compareTo(start) > 0)
                continue;

            // start ? ?...
            if (period.hasInside(start)) {
                nearest = period;
                moment = start;
                break;
            }
            //  ? ? ?
            Duration periodToMoment = new Duration(start, period.getEnd());
            if (periodToMoment.compareTo(difference) >= 0)
                continue;

            difference = periodToMoment;
            nearest = period;
            moment = period.getEnd();
        }

        if (isTraceEnable)
            log.trace("?? ? ? . start=[{}], moment=[{}], neearest=[{}]",
                    start, moment, nearest);

        return Pair.create(nearest, moment);
    }

    private static final long serialVersionUID = 2352433294158169198L;
}