Desplazamiento de fecha

En función de los requisitos de negocio, es posible que necesite crear una línea de tiempo basada en otra línea de tiempo, donde las fechas de cambio de valor en la línea de tiempo resultante sean diferentes de las de la línea de tiempo de entrada.

CER no incluye expresiones para el desplazamiento de fecha, porque los tipos de desplazamiento de fecha necesarios tienden a ser específicos del negocio. El enfoque recomendado es crear un método Java estático para crear la línea de tiempo necesaria e invocar el método estático de reglas mediante el uso de la expresión call.

Importante: Al implementar un algoritmo de desplazamiento de fecha, asegúrese de que no existe ningún intento de crear una línea de tiempo con más de un valor en cualquier fecha determinada, ya que un intento de este tipo fallará en el tiempo de ejecución.

Las pruebas para el algoritmo deben incluir pruebas para casos límite, por ejemplo años bisiestos o meses que tienen un número de días diferente.

Ejemplo de adición de fecha

Tiene el siguiente requisito de negocio: una persona no puede solicitar un tipo de prestación en tres meses desde la recepción de dicha prestación.

Para implementar este requisito de negocio, ya tiene una línea de tiempo isReceivingBenefitTimeline que muestra los periodos de tiempo durante los cuales una persona está recibiendo la prestación.

Ahora necesita otra línea de tiempo isDisallowedFromApplyingForBenefitTimeline que muestre los periodos en que no es válido que dicha persona vuelva a solicitar la prestación. Esta línea de tiempo es una adición de fecha de 3 meses a las fechas de cambio de valor en isReceivingBenefitTimeline:

Figura 1. Requisito para una línea de tiempo de adición de fechaEjemplo de línea de tiempo

A continuación se muestra una implementación de ejemplo de un método estático al que se puede llamar desde las reglas de 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 {

  /**
   * Crea una línea de tiempo basada en la línea de tiempo de entrada, con la fecha
   * desplazada en el número de meses especificados.
   * <p>
   * Tenga en cuenta que el parámetro de línea de tiempo puede ser de cualquier tipo.
   *
   * @param session
   *          la sesión CER
   * @param inputTimeline
   *          la línea de tiempo cuyas fechas se deben desplazar
   * @param monthsToAdd
   *          el número de meses a añadir a las fechas de cambio de
   *          línea de tiempo
   * @param <VALUE>
   *          el tipo de valor mantenido en las líneas de tiempo de entrada/salida
   * @return una línea de tiempo nueva con los valores de la línea
   *         de tiempo de entrada, desplazada por el número de meses especificados
   */
  public static <VALUE> Timeline<VALUE> addMonthsTimeline(
      final Session session, final Timeline<VALUE> inputTimeline,
      final Number monthsToAdd) {

    /*
     * Normalmente CER pasará un número, que se debe convertir en
     * un entero
     */
    final int monthsToAddInteger = monthsToAdd.intValue();

    /*
     * Buscar los intervalos en la línea de tiempo de entrada
     */
    final List<? extends Interval<VALUE>> inputIntervals =
        inputTimeline.intervals();

    /*
     * Reunir los intervalos de salida. Observe que correlacionamos por fecha de inicio,
     * porque al añadir meses, es posible que varias
     * fechas de entrada diferentes se desplacen a la misma fecha de salida.
     *
     * Por ejemplo 3 meses después de estas fechas: 28-11-2002,
     * 29-11-2002, 30-11-2002, se calculan todas como 28-02-2003
     *
     * En esta situación, se utiliza el valor de la primera fecha de entrada
     * sólo - las fechas de entrada se procesan en orden ascendente
     */
    final Map<Date, Interval<VALUE>> outputIntervalsMap =
        new HashMap<Date, Interval<VALUE>>(inputIntervals.size());

    for (final Interval<VALUE> inputInterval : inputIntervals) {
      // obtener la fecha de inicio de intervalo
      final Date inputStartDate = inputInterval.startDate();

      /*
       * Añadir el número de meses - pero n meses después del inicio de
       * tiempo sigue siendo el inicio de tiempo
       */

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

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

      }

      // comprobar que esta fecha de salida aún no se ha procesado
      if (!outputIntervalsMap.containsKey(outputStartDate)) {

        /*
         * el intervalo de salida utiliza el mismo valor que el intervalo de
         * entrada, pero con una fecha de inicio desplazada
         */

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

    // crear una línea de tiempo desde intervalos de salida
    final Collection<Interval<VALUE>> outputIntervals =
        outputIntervalsMap.values();
    final Timeline<VALUE> outputTimeline =
        new Timeline<VALUE>(outputIntervals);
    return outputTimeline;

  }
}

Ejemplo de dispersión de fecha

Tiene el requisito de negocio siguiente: un automóvil debe ser gravado por cualquier mes en el que esté "circulando" durante uno o más días de ese mes 1.

Para implementar este requisito de negocio, ya tiene una línea de tiempo isOnRoadTimeline que muestra los periodos de tiempo durante los cuales un automóvil está "circulando".

Ahora necesita otra línea de tiempo taxDueTimeline que muestre los periodos en que se debe gravar al automóvil. Esta línea de tiempo es una dispersión de las fechas en isOnRoadTimeline:

Figura 2. Requisito para una línea de tiempo de dispersión de fechaEjemplo de línea de tiempo

A continuación se muestra una implementación de ejemplo de un método estático al que se puede llamar desde las reglas de 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 {

  /**
   * Crea una línea de tiempo para el periodo durante el cual un automóvil debe
   * gravarse.
   * <p>
   * El automóvil debe gravarse durante el mes entero en cualquier mes en el que
   * dicho automóvil esté circulando durante uno o más días durante ese
   * mes.
   */
  public static Timeline<Boolean> taxDue(final Session session,
      final Timeline<Boolean> isOnRoadTimeline) {

    /*
     * Buscar los intervalos en la línea de tiempo de entrada
     */
    final List<? extends Interval<Boolean>> isOnRoadIntervals =
        isOnRoadTimeline.intervals();

    /*
     * Reunir los intervalos de salida. Observe que correlacionamos por fecha de inicio;
     * un automóvil puede estar fuera de circulación durante un mes, lo que implicaría
     * que no sería necesario ningún impuesto al principio del mes siguiente, pero
     * vuelve a estar en circulación durante una parte del mes siguiente, en
     * cuyo caso se debe gravar a pesar de todo.
     *
     * Por ejemplo, el automóvil vuelve a estar circulando el 15-01-2001, de modo que el impuesto
     * es necesario (retrospectivamente) desde el 01-01-2001.
     *
     * El 24-01-2001, el automóvil vuelve estar fuera de circulación, de modo que es
     * posible que no necesite gravarse a partir del
     * 01-02-2001.
     *
     * Sin embargo, el 05-02-2001-02-05 el automóvil vuelve a circular y
     * por lo tanto necesita gravarse a partir del 01-02-2001. La
     * línea de tiempo resultante fusionará estos periodos para mostrar que el
     * automóvil necesita gravarse a partir del 01-01-2001 en adelante (cubriendo así
     * también desde el 01-02-2001).
     */
    final Map<Date, Interval<Boolean>> taxDueIntervalsMap =
        new HashMap<Date, Interval<Boolean>>(
            isOnRoadIntervals.size());

    for (final Interval<Boolean> isOnRoadInterval :
 isOnRoadIntervals) {
      // obtener la fecha de inicio de intervalo
      final Date isOnRoadStartDate = isOnRoadInterval.startDate();

      if (isOnRoadStartDate == null) {
        // al principio de la hora, el automóvil se debe gravar si está
        // circulando
        taxDueIntervalsMap.put(null, new Interval<Boolean>(null,
            isOnRoadInterval.value()));
      } else if (isOnRoadInterval.value()) {
        /*
         * inicio de un periodo de circulación del automóvil - el automóvil
         * se debe gravar desde el inicio del mes que contiene el
         * inicio de este periodo
         */

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

        /*
         * Añadir al mapa de periodos de vencimiento de impuestos - tenga en cuenta que esto
         * extraerá del mapa cualquier intervalo de "impuesto no vencido"
         * añadido de forma especulativa si el automóvil ha estado fuera de circulación durante
         * el mes anterior
         */
        taxDueIntervalsMap.put(startOfMonthDate,
            new Interval<Boolean>(startOfMonthDate, true));
      } else {
        /*
         * Inicio de un periodo en el que el automóvil ha estado fuera de circulación -
         * especular que desde el inicio del siguiente mes, el automóvil puede
         * no requerir impuestos. Esta especulación se mantendrá a menos que
         * posteriormente se encuentre que el automóvil está de nuevo circulando
         * el siguiente mes, en cuyo caso esta especulación se
         * descartará (es decir, se extraerá del mapa).
         */

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

        /*
         * Añadir al mapa de periodos de vencimiento de impuestos - tenga en cuenta que esto
         * extraerá del mapa cualquier intervalo de "impuesto no vencido"
         * añadido de forma especulativa si el automóvil ha estado fuera de circulación durante
         * el mes anterior
         */
        taxDueIntervalsMap.put(startOfNextMonthDate,
            new Interval<Boolean>(startOfNextMonthDate, false));
      }

    }

    // crear una línea de tiempo desde los intervalos de vencimiento de impuestos
    final Collection<Interval<Boolean>> taxDueIntervals =
        taxDueIntervalsMap.values();
    final Timeline<Boolean> taxDueTimeline =
        new Timeline<Boolean>(taxDueIntervals);
    return taxDueTimeline;

  }
}
1 Es decir, si un automóvil vuelve a estar circulando una parte de un mes, el propietario del automóvil debe asegurarse de que el impuesto se paga retrospectivamente para el mes entero