Example #1
0
 /**
  * Obtains a {@code PeriodFields} from an amount and unit, by extending any fractional remainder
  * onto smaller units.
  *
  * <p>The parameters represent the two parts of a phrase like 'one-and-a-half Hours'. The
  * fractional parts will be distributed into the smaller units, and rounded down when no smaller
  * units exist. If the {@code fractionalAmount} is negative, the amount of the biggest unit will
  * be negative, the rest will be positive.
  *
  * @param fractionalAmount the amount of create with, positive or negative
  * @param unit the period unit, not null
  * @return the {@code PeriodFields} instance, not null
  */
 public static PeriodFields of(double fractionalAmount, PeriodUnit unit) {
   checkNotNull(unit, "PeriodUnit must not be null");
   PeriodUnit currentUnit = unit;
   double fudge = 0.000000000000001d;
   TreeMap<PeriodUnit, PeriodField> internalMap = createMap();
   do {
     long floor = (long) Math.floor(fractionalAmount + fudge);
     if (floor != 0) {
       internalMap.put(currentUnit, PeriodField.of(floor, currentUnit));
     }
     double remainder = fractionalAmount - floor; // will be positive
     PeriodField nextEquivalent = currentUnit.getNextEquivalentPeriod();
     if (nextEquivalent != null) {
       currentUnit = nextEquivalent.getUnit();
       fractionalAmount = remainder * nextEquivalent.getAmount();
       fudge *= nextEquivalent.getAmount();
     } else {
       // No smaller units exist, we're done
       currentUnit = null;
     }
   } while (currentUnit != null && Math.abs(fractionalAmount) > fudge);
   if (internalMap.isEmpty()) {
     return of(0L, unit);
   } else {
     return create(internalMap);
   }
 }
Example #2
0
 /**
  * Returns a copy of this period with the amounts normalized to the specified units.
  *
  * <p>This will normalize the period around the specified units. The calculation examines each
  * pair of units that have a fixed conversion factor. Each pair is adjusted so that the amount in
  * the smaller unit does not exceed the amount of the fixed conversion factor. At least one unit
  * must be specified for this method to have any effect.
  *
  * <p>For example, a period of '2 Decades, 2 Years, 17 Months' normalized using 'Years' and
  * 'Months' will return '23 Years, 5 Months'.
  *
  * <p>Any part of this period that cannot be converted to one of the specified units will be
  * unaffected in the result.
  *
  * <p>The result will always contain all the specified units, even if they are zero. The result
  * will be equivalent to this period.
  *
  * @param units the unit array to normalize to, not altered, not null, no nulls
  * @return a period equivalent to this period with the amounts normalized, not null
  * @throws ArithmeticException if the calculation overflows
  */
 public PeriodFields normalizedTo(PeriodUnit... units) {
   checkNotNull(units, "PeriodUnit array must not be null");
   PeriodFields result = this;
   TreeSet<PeriodUnit> targetUnits = new TreeSet<PeriodUnit>(Collections.reverseOrder());
   targetUnits.addAll(Arrays.asList(units));
   // normalize any fields in this period that have a unit greater than the
   // largest unit in the target set that can be normalized
   // eg. normalize Years-Months when the target set only contains Months
   for (PeriodUnit loopUnit : unitFieldMap.keySet()) {
     for (PeriodUnit targetUnit : targetUnits) {
       if (targetUnits.contains(loopUnit) == false) {
         PeriodField conversion = loopUnit.getEquivalentPeriod(targetUnit);
         if (conversion != null) {
           long amount = result.getAmount(loopUnit);
           result = result.plus(conversion.multipliedBy(amount)).without(loopUnit);
           break;
         }
       }
     }
   }
   // algorithm works by finding pairs to check
   // the first rule is to avoid numeric overflow wherever possible, such as when
   // Seconds and Minutes are both MAX_VALUE -
   // eg. the Hour-Minute and Hour-Second pair must be processed before the Minute-Second pair
   // the second rule is to handle the case where processing two pairs causes a knock on
   // effect on a pair that has already been processed according to the first rule -
   // eg. when the Hour-Minute pair is 59 and the Minute-Second pair is 61
   // this is achieved by restarting the whole algorithm (the process loop)
   for (boolean process = true; process; ) {
     process = false;
     for (PeriodUnit targetUnit : targetUnits) {
       for (PeriodUnit loopUnit : result.unitFieldMap.keySet()) {
         if (targetUnit.equals(loopUnit) == false) {
           PeriodField conversion = targetUnit.getEquivalentPeriod(loopUnit);
           if (conversion != null) {
             long convertAmount = conversion.getAmount();
             long amount = result.getAmount(loopUnit);
             if (amount >= convertAmount || amount <= -convertAmount) {
               result =
                   result
                       .with(amount % convertAmount, loopUnit)
                       .plus(amount / convertAmount, targetUnit);
               process = (units.length > 2); // need to re-check from start
             }
           }
         }
       }
       result = result.plus(0, targetUnit); // ensure unit is in the result
     }
   }
   return result;
 }
Example #3
0
 /**
  * Returns a copy of this period with only those units that can be converted to the specified
  * units.
  *
  * <p>This method will return a new period where every field can be converted to one of the
  * specified units. In the result, each of the retained periods will have the same amount as they
  * do in this period - no conversion or normalization occurs.
  *
  * <p>For example, if this period is '2 Days, 5 Hours, 7 Minutes' and the specified unit array
  * contains 'Seconds' then the output will be '5 Hours, 7 Minutes'. The 'Days' unit is not
  * retained as it cannot be converted to 'Seconds'.
  *
  * <p>This instance is immutable and unaffected by this method call.
  *
  * @param units the units to retain, not altered, not null, no nulls
  * @return a {@code PeriodFields} based on this period with the specified units retained, not null
  */
 public PeriodFields retainConvertible(PeriodUnit... units) {
   checkNotNull(units, "PeriodUnit array must not be null");
   TreeMap<PeriodUnit, PeriodField> copy = clonedMap();
   outer:
   for (Iterator<PeriodUnit> it = copy.keySet().iterator(); it.hasNext(); ) {
     PeriodUnit loopUnit = it.next();
     for (PeriodUnit unit : units) {
       checkNotNull(unit, "PeriodUnit array must not contain null");
       if (loopUnit.isConvertibleTo(unit)) {
         continue outer;
       }
     }
     it.remove();
   }
   return create(copy);
 }
 private Period parsePeriod(XMLDocument xmlDoc, String checkDataQueryPath)
     throws XPathExpressionException {
   final String periodValue =
       parseTextValue(xmlDoc, checkDataQueryPath + CHKDATA_DOMAIN_PERIOD_EXPR);
   final String unitValue =
       parseTextValue(xmlDoc, checkDataQueryPath + CHKDATA_DOMAIN_PERIOD_UNIT_EXPR);
   return (periodValue != null)
       ? new Period(PeriodUnit.value(unitValue), Integer.parseInt(periodValue))
       : null;
 }