Datumsänderung

Je nach den bestehenden Geschäftsanforderungen kann es sein, dass Sie eine Zeitlinie auf der Grundlage einer anderen Zeitlinie erstellen müssen, wobei die Datumsangaben für die Wertänderung in der resultierenden Zeitlinie von den entsprechenden Angaben in der Eingabezeitlinie abweichen.

CER enthält keine Ausdrücke für Datumsänderungen, da die erforderlichen Typen für die Datumsänderung tendenziell geschäftsspezifisch sind. Die empfohlene Strategie besteht darin, eine statische Java-Methode für die Erstellung der erforderlichen Zeitlinie zu erstellen und die statische Methode unter Verwendung des Ausdrucks "call" (siehe call) aus den Regeln heraus aufzurufen.

Wichtig: Beim Implementieren eines Algorithmus für die Datumsänderung müssen Sie unbedingt sicherstellen, dass nicht versucht wird, eine Zeitlinie mit mehreren Werten für ein jeweiliges Datum zu erstellen, weil ein solcher Versuch während der Laufzeit fehlschlägt.

Beim Testen des Algorithmus sollten auch alle Tests für Grenzfälle (z. B. Schaltjahre oder Monate mit unterschiedlicher Anzahl von Tagen) einbezogen werden.

Beispiel für Datumsaddition

Die folgende Geschäftsanforderung ist gegeben: Eine Person kann einen Leistungsbezug erst drei Monate nach Ablauf des Leistungsbezugs erneut beantragen.

Zur Implementierung dieser Geschäftsanforderung ist bereits eine Zeitlinie isReceivingBenefitTimeline vorhanden, die die Zeiträume für den Leistungsbezug durch eine Person darstellt.

Sie benötigen nun eine weitere Zeitlinie namens isDisallowedFromApplyingForBenefitTimeline, in der die Zeiträume abgebildet sind, in denen der erneute Bezug dieser Leistung durch die Person nicht zulässig ist. Diese Zeitlinie wird durch eine Datumsaddition von 3 Monaten zu den Datumsangaben für die Wertänderung in der ZeitlinieisReceivingBenefitTimeline erzeugt:

Abbildung 1. Anforderung für eine Zeitlinie mit DatumsadditionBeispiel für Zeitlinie

Die folgende Beispielimplementierung zeigt eine statische Methode, die aus CER-Regeln heraus aufgerufen werden kann:

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 {

  /**
   * Creates a Timeline based on the input timeline, with the date
   * shifted by the number of months specified.
   * <p>
   * Note that the timeline's parameter can be of any type.
   *
   * @param session
   *          the CER session
   * @param inputTimeline
   *          the timeline whose dates must be shifted
   * @param monthsToAdd
   *          the number of months to add to the timeline change
   *          dates
   * @param <VALUE>
   *          the type of value held in the input/output timelines
   * @return a new timeline with the values from the input
   *         timeline, shifted by the number of months specified
   */
  public static <VALUE> Timeline<VALUE> addMonthsTimeline(
      final Session session, final Timeline<VALUE> inputTimeline,
      final Number monthsToAdd) {

    /*
     * CER will typically pass a Number, which must be converted to
     * an integer
     */
    final int monthsToAddInteger = monthsToAdd.intValue();

    /*
     * Find the intervals within the input timeline
     */
    final List<? extends Interval<VALUE>> inputIntervals =
        inputTimeline.intervals();

    /*
     * Amass the output intervals. Note that we map by start date,
     * because when adding months, it is possible for several
     * different input dates to be shifted to the same output date.
     *
     * For example 3 months after these dates: 2002-11-28,
     * 2002-11-29, 2002-11-30, are all calculated as 2003-02-28
     *
     * In this situation, we use the value from the earliest input
     * date only - input dates are processed in ascending order
     */
    final Map<Date, Interval<VALUE>> outputIntervalsMap =
        new HashMap<Date, Interval<VALUE>>(inputIntervals.size());

    for (final Interval<VALUE> inputInterval : inputIntervals) {
      // get the interval start date
      final Date inputStartDate = inputInterval.startDate();

      /*
       * Add the number of months - but n months after the start of
       * time is still the start of time
       */

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

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

      }

      // check that this output date has not yet been processed
      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);
      }
    }

    // create a timeline from the output intervals
    final Collection<Interval<VALUE>> outputIntervals =
        outputIntervalsMap.values();
    final Timeline<VALUE> outputTimeline =
        new Timeline<VALUE>(outputIntervals);
    return outputTimeline;

  }
}

Beispiel für Datumsstreuung

Es besteht die folgende Geschäftsanforderung: Ein Fahrzeug ist für jeden Monat steuerpflichtig, in dem das Fahrzeug an einem oder mehreren Tagen "im Straßenverkehr eingesetzt wird".1

Zur Implementierung dieser Geschäftsanforderung ist bereits eine Zeitlinie isOnRoadTimeline vorhanden, die die Zeiträume für die Nutzung des Fahrzeugs "im Straßenverkehr" darstellt.

Sie benötigen nun eine weitere Zeitlinie namens taxDueTimeline, in denen die Zeiträume für die Steuerpflichtigkeit des Fahrzeugs abgebildet sind. Diese Zeitlinie ist eine Streuung der Datumsangaben aus der Zeitlinie isOnRoadTimeline:

Abbildung 2. Anforderung für eine Zeitlinie mit DatumsstreuungBeispiel für Zeitlinie

Die folgende Beispielimplementierung zeigt eine statische Methode, die aus CER-Regeln heraus aufgerufen werden kann:

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 {

  /**
   * Creates a Timeline for the period for which a car must be
   * taxed.
   * <p>
   * The car must be taxed for the entire month for any month where
   * that car is on-the-road for one or more days during that
   * month.
   */
  public static Timeline<Boolean> taxDue(final Session session,
      final Timeline<Boolean> isOnRoadTimeline) {

    /*
     * Find the intervals within the input timeline
     */
    final List<? extends Interval<Boolean>> isOnRoadIntervals =
        isOnRoadTimeline.intervals();

    /*
     * Amass the output intervals. Note that we map by start date;
     * a car may go off the road during a month, which would imply
     * that no tax is required at the start of the next month, only
     * to return to the road part-way through the next month, in
     * which case it does require taxing after all.
     *
     * For example, car is put back on the road 2001-01-15, so tax
     * is required (retrospectively) from 2001-01-01.
     *
     * On 2001-01-24 the car is taken back off the road, so it's
     * possible that the car does not require taxing from
     * 2001-02-01.
     *
     * However, on 2001-02-05 the car is put back on the road, and
     * so it does require taxing from 2001-02-01 after all. The
     * resultant timeline will merge these periods to show that the
     * car requires taxing from 2001-01-01 onwards (thus covering
     * from 2001-02-01 too).
     */
    final Map<Date, Interval<Boolean>> taxDueIntervalsMap =
        new HashMap<Date, Interval<Boolean>>(
            isOnRoadIntervals.size());

    for (final Interval<Boolean> isOnRoadInterval :
 isOnRoadIntervals) {
      // get the interval start date
      final Date isOnRoadStartDate = isOnRoadInterval.startDate();

      if (isOnRoadStartDate == null) {
        // at the start of time, the car must be taxed if it is on
        // the road
        taxDueIntervalsMap.put(null, new Interval<Boolean>(null,
            isOnRoadInterval.value()));
      } else if (isOnRoadInterval.value()) {
        /*
         * start of a period of the car being on-the-road - the car
         * must be taxed from the start of the month containing the
         * start of this period
         */

        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);

        /*
         * Add to the map of tax due periods - note that this will
         * push out of the map any "tax not due" interval
         * speculatively added if the car went off-the-road during
         * the previous month
         */
        taxDueIntervalsMap.put(startOfMonthDate,
            new Interval<Boolean>(startOfMonthDate, true));
      } else {
        /*
         * Start of a period of the car being off the road -
         * speculate that from the start of next month, the car may
         * not require tax. This speculation will hold unless the
         * car is subsequently found to be put back on the road
         * next month, in which case this speculation will be
         * discarded (i.e. pushed out of the map).
         */

        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);

        /*
         * Add to the map of tax due periods - note that this will
         * push out of the map any "tax not due" interval
         * speculatively added if the car went off-the-road during
         * the previous month
         */
        taxDueIntervalsMap.put(startOfNextMonthDate,
            new Interval<Boolean>(startOfNextMonthDate, false));
      }

    }

    // create a timeline from the tax due intervals
    final Collection<Interval<Boolean>> taxDueIntervals =
        taxDueIntervalsMap.values();
    final Timeline<Boolean> taxDueTimeline =
        new Timeline<Boolean>(taxDueIntervals);
    return taxDueTimeline;

  }
}
1 Falls ein Fahrzeug im Verlauf eines Monats wieder im Straßenverkehr benutzt wird, bedeutet dies also, dass der Fahrzeughalter sicherstellen muss, dass die Steuer für den gesamten Monat rückwirkend gezahlt wird.