날짜 이동

비즈니스 요구사항에 따라 다른 타임라인을 기준으로 하는 타임라인을 작성해야 할 수 있습니다. 이 경우 결과적으로 생긴 타임라인의 값 변경 날짜는 입력 타임라인의 값 변경 날짜와 다릅니다.

필요한 날짜 이동 유형은 비즈니스에 따라 달라지는 경향이 있으므로 CER에는 날짜 이동 표현식이 포함되지 않습니다. 권장 방법은 정적 java 메소드를 작성하여 필요한 타임라인을 작성하고 call 표현식을 사용하여 규칙에서 정적 메소드를 호출하는 것입니다.

중요사항: 날짜 이동 알고리즘을 구현할 때 지정된 날짜에 값이 두 개 이상인 타임라인을 작성하지 않도록 유의하십시오. 이 경우 런타임 시 실패합니다.

알고리즘 테스트에는 날짜 수가 서로 다른 달이나 윤년과 같은 에지 케이스 테스트도 포함되어야 합니다.

날짜 추가 예제

다음과 같은 비즈니스 요구사항이 있습니다. 개인이 혜택을 받은 3개월 안에 동일한 유형의 혜택을 신청할 수 없습니다.

이 비즈니스 요구사항을 구현하려면 개인이 혜택을 받고 있는 기간을 표시하는 타임라인 isReceivingBenefitTimeline이 이미 있어야 합니다.

이제 개인이 혜택을 다시 신청할 수 없는 기간을 표시하는 또 다른 타임라인 isDisallowedFromApplyingForBenefitTimeline이 필요합니다. 이 타임라인은 isReceivingBenefitTimeline의 값 변경 날짜에 적용된 3개월의 날짜 추가입니다.

그림 1. 날짜 추가 타임라인의 요구사항타임라인 예제.

다음은 CER 규칙에서 호출할 수 있는 정적 메소드의 샘플 구현입니다.

package curam.creole.example;

import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import curam.creole.execution.session.Session;
import curam.creole.value.Interval;
import curam.creole.value.Timeline;
import curam.util.type.Date;

public class DateAdditionTimeline {

  /**
   * 지정된 개월 수 만큼 날짜가 이동된 입력 타임라인을
   * 기준으로 타임라인을 작성합니다.
   * <p>
   * 타임라인의 매개변수는 임의의 유형일 수 있습니다.
   *
   * @param session
   *          CER 세션
   * @param inputTimeline
   *          날짜를 이동해야 하는 타임라인
   * @param monthsToAdd
   *          타임라인 변경 날짜에 추가할
   *          개월 수
   * @param <VALUE>
   *          입/출력(I/O) 타임라인에 보유된 값 유형
   * @return 입력 타임라인의 값을 지정된 개월 수만큼
   *         이동한 새 타임라인
   */
  public static <VALUE> Timeline<VALUE> addMonthsTimeline(
      final Session session, final Timeline<VALUE> inputTimeline,
      final Number monthsToAdd) {

    /*
     * 일반적으로 CER은 숫자를 전달합니다. 이 숫자는 정수로 변환되어야
     * 합니다.
     */
    final int monthsToAddInteger = monthsToAdd.intValue();

    /*
     * 입력 타임라인에서 간격 찾기
     */
    final List<? extends Interval<VALUE>> inputIntervals =
        inputTimeline.intervals();

    /*
     * 결과 간격을 축적합니다. 개월 수를 추가하면
     * 서로 다른 여러 입력 날짜가 동일한 출력 날짜로
     * 이동될 수 있으므로 시작 날짜로 맵핑합니다.
     *
     * 예를 들어, 2002-11-28, 2002-11-29,
     * 2002-11-30 이후의 3개월은 모두 2003-02-28로 계산됩니다.
     *
     * 이 경우 가장 빠른 입력 날짜의 값만
     * 사용합니다. 입력 날짜는 오름차순으로 처리됩니다.
     */
    final Map<Date, Interval<VALUE>> outputIntervalsMap =
        new HashMap<Date, Interval<VALUE>>(inputIntervals.size());

    for (final Interval<VALUE> inputInterval : inputIntervals) {
      // 간격 시작 날짜 가져오기
      final Date inputStartDate = inputInterval.startDate();

      /*
       * 개월 수 추가 - 그러나 시작 시간 이후 n개월이 여전히
       * 시작 시간임
       */

      final Date outputStartDate;
      if (inputStartDate == null) {
        outputStartDate = null;
      } else {
        final Calendar startDateCalendar =
            inputStartDate.getCalendar();

        startDateCalendar.add(Calendar.MONTH, monthsToAddInteger);
        outputStartDate = new Date(startDateCalendar);

      }

      // 이 결과 날짜가 아직 처리되지 않았는지 확인
      if (!outputIntervalsMap.containsKey(outputStartDate)) {

        /*
         * the output interval uses the same value as the input
         * interval, but with a shifted start date
         */

        final Interval<VALUE> outputInterval =
            new Interval<VALUE>(outputStartDate,
                inputInterval.value());
        outputIntervalsMap.put(outputStartDate, outputInterval);
      }
    }

    // 결과 간격에서 타임라인 작성
    final Collection<Interval<VALUE>> outputIntervals =
        outputIntervalsMap.values();
    final Timeline<VALUE> outputTimeline =
        new Timeline<VALUE>(outputIntervals);
    return outputTimeline;

  }
}

날짜 분산 예제

다음과 같은 비즈니스 요구사항이 있습니다. 자동차가 일정한 달에 하루 이상 "운행"된 경우 해당 달에 대해 과세되어야 합니다. 1

이 비즈니스 요구사항을 구현하려면 자동차가 "운행 중"인 기간을 표시하는 타임라인 isOnRoadTimeline이 이미 있어야 합니다.

이제 자동차세를 부과해야 하는 기간을 표시하는 또 다른 타임라인 taxDueTimeline이 필요합니다. 이 타임라인은 isOnRoadTimeline의 모든 날짜에 걸쳐 분산되어 있습니다.

그림 2. 날짜 분산 타임라인 요구사항타임라인 예제.

다음은 CER 규칙에서 호출할 수 있는 정적 메소드의 샘플 구현입니다.

package curam.creole.example;

import java.util.Calendar;
import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import curam.creole.execution.session.Session;
import curam.creole.value.Interval;
import curam.creole.value.Timeline;
import curam.util.type.Date;

public class DateSpreadingTimeline {

  /**
   * 자동차세를 과세해야 하는 기간에 대한 타임라인을
   * 작성합니다.
   * <p>
   * 임의의 달에 자동차가 하루 이상 운행된 경우
   * 해당 달 전체에 대해 자동차세를 부과해야
   * 합니다.
   */
  public static Timeline<Boolean> taxDue(final Session session,
      final Timeline<Boolean> isOnRoadTimeline) {

    /*
     * 입력 타임라인의 간격 찾기
     */
    final List<? extends Interval<Boolean>> isOnRoadIntervals =
        isOnRoadTimeline.intervals();

    /*
     * 결과 간격을 축적합니다. 시작 날짜를 사용하여 맵핑합니다.
     * 일정한 달에 자동차를 운행하지 않을 수 있습니다.
     * 즉, 다음 달 초에 과세될 필요가 없습니다. 다음 달을
     * 보내는 중에 다시 운행을 시작한 경우에만 과세되어야
     * 합니다.
     *
     * 예를 들어, 자동차가 2001-01-15에 다시 운행을 시작하면
     * 2001-01-01부터 과세되어야 합니다(소급 적용).
     *
     * 2001-01-24에 자동차의 운행을 중단한 경우에는
     * 2001-02-01부터 적용되는 자동차세를 부과하지 않아도
     * 됩니다.
     *
     * 그러나 2001-02-05에 자동차를 다시 운행하면
     * 결국 2001-02-01부터 적용되는 세금을 부과해야 합니다. 결과적으로
     * 작성된 타임라인은 이러한 기간을 병합하여 자동차가
     * 2001-01-01부터 계속(따라서 2001-02-01도 포함) 과세되어야 함을
     * 보여줍니다.
     */
    final Map<Date, Interval<Boolean>> taxDueIntervalsMap =
        new HashMap<Date, Interval<Boolean>>(
            isOnRoadIntervals.size());

    for (final Interval<Boolean> isOnRoadInterval :
 isOnRoadIntervals) {
      // 간격 시작 날짜 가져오기
      final Date isOnRoadStartDate = isOnRoadInterval.startDate();

      if (isOnRoadStartDate == null) {
        // 시작 시간에 자동차를 운행 중인 경우
        // 과세되어야 함
        taxDueIntervalsMap.put(null, new Interval<Boolean>(null,
            isOnRoadInterval.value()));
      } else if (isOnRoadInterval.value()) {
        /*
         * 자동차를 운행 중인 기간의 시작 날짜 - 자동차는
         * 이 기간의 시작 날짜가 포함된 달의 시작일부터
         * 과세되어야 함
         */

        final Calendar carOnRoadStartCalendar =
            isOnRoadStartDate.getCalendar();
        final Calendar startOfMonthCalendar =
            new GregorianCalendar(
                carOnRoadStartCalendar.get(Calendar.YEAR),
                carOnRoadStartCalendar.get(Calendar.MONTH), 1);
        final Date startOfMonthDate =
            new Date(startOfMonthCalendar);

        /*
         * 납세 기간의 맵에 추가 - 그러면
         * 자동차가 지난 달에 운행이 중단된 경우
         * 추측에 의해 추가된 "납세 의무 없음" 간격이
         * 제거됩니다.
         */
        taxDueIntervalsMap.put(startOfMonthDate,
            new Interval<Boolean>(startOfMonthDate, true));
      } else {
        /*
         * 자동차가 운행이 중단된 기간의 시작 -
         * 다음 달 시작부터 자동차세를 과세할 필요가 없다고
         * 추측합니다. 자동차가 다음 달에 다시 운행되었음이
         * 발견되지 않으면 이 추측은 계속
         * 유지됩니다. 다시 운행된 경우 이 추측은
         * 제거됩니다(즉, 맵에서 제거됨).
         */

        final Calendar carOffRoadStartCalendar =
            isOnRoadStartDate.getCalendar();
        final Calendar startOfNextMonthCalendar =
            new GregorianCalendar(
                carOffRoadStartCalendar.get(Calendar.YEAR),
                carOffRoadStartCalendar.get(Calendar.MONTH), 1);
        startOfNextMonthCalendar.add(Calendar.MONTH, 1);

        final Date startOfNextMonthDate =
            new Date(startOfNextMonthCalendar);

        /*
         * 납세 기간의 맵에 추가 - 그러면
         * 자동차가 지난 달에 운행이 중단된 경우
         * 추측에 의해 추가된 "납세 의무 없음" 간격이
         * 제거됩니다.
         */
        taxDueIntervalsMap.put(startOfNextMonthDate,
            new Interval<Boolean>(startOfNextMonthDate, false));
      }

    }

    // 납세 기간 간격에서 타임라인 작성
    final Collection<Interval<Boolean>> taxDueIntervals =
        taxDueIntervalsMap.values();
    final Timeline<Boolean> taxDueTimeline =
        new Timeline<Boolean>(taxDueIntervals);
    return taxDueTimeline;

  }
}
1 즉, 자동차가 일정한 달의 도중에 다시 운행된 경우 자동차 소유주는 해당 전체 달에 대해 소급 적용하여 납세해야 합니다.