Java tutorial
/* * 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; }