killbill-aplcache

Merge branch 'BillingPeriod-refactoring' into work-for-release-0.17.0 Signed-off-by:

4/14/2016 5:47:17 PM

Details

diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
index acee264..fd03318 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegration.java
@@ -732,4 +732,117 @@ public class TestIntegration extends TestIntegrationBase {
         invoiceChecker.checkInvoice(account.getId(), 14, callContext, expectedInvoices);
         expectedInvoices.clear();
     }
+
+    @Test(groups = "slow")
+    public void testThirtyDaysPlanWithFixedTermMonthlyAddOn() throws Exception {
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDay(new LocalDate(2015, 4, 1));
+
+        final AccountData accountData = getAccountData(1);
+        final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+        accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+        final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+
+        // First invoice
+        final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Pistol", ProductCategory.BASE, BillingPeriod.THIRTY_DAYS, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 4, 1), null, InvoiceItemType.FIXED, BigDecimal.ZERO));
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        // Second invoice -> first recurring for Refurbish-Maintenance
+        addAOEntitlementAndCheckForCompletion(bpSubscription.getBundleId(), "Refurbish-Maintenance", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("599.95")));
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 4, 1), new LocalDate(2015, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("199.95")));
+        invoiceChecker.checkInvoice(account.getId(), 2, callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        // 2015-5-1
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 5, 1), new LocalDate(2015, 5, 31), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 5, 1), new LocalDate(2015, 6, 1), InvoiceItemType.RECURRING, new BigDecimal("199.95")));
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.NULL_INVOICE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(30); // Also = 1 month because or initial date 2015, 4, 1
+        assertListenerStatus();
+        invoiceChecker.checkInvoice(account.getId(), 3, callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        // Next 20 invoices, including last recurring for Refurbish-Maintenance
+        LocalDate startDateBase = new LocalDate(2015, 5, 31);
+        LocalDate startDateAddOn = new LocalDate(2015, 6, 1);
+        for (int i = 0; i < 10; i++) {
+            final LocalDate endDateBase = startDateBase.plusDays(30);
+            expectedInvoices.add(new ExpectedInvoiceItemCheck(startDateBase, endDateBase, InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+            busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+            clock.setDay(startDateBase);
+            assertListenerStatus();
+            invoiceChecker.checkInvoice(account.getId(), 4 + 2 * i, callContext, expectedInvoices);
+            expectedInvoices.clear();
+
+            final LocalDate endDateAddOn = startDateAddOn.plusMonths(1);
+            expectedInvoices.add(new ExpectedInvoiceItemCheck(startDateAddOn, endDateAddOn, InvoiceItemType.RECURRING, new BigDecimal("199.95")));
+            busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+            clock.setDay(startDateAddOn);
+            assertListenerStatus();
+            invoiceChecker.checkInvoice(account.getId(), 5 + 2 * i, callContext, expectedInvoices);
+            expectedInvoices.clear();
+
+            startDateBase = endDateBase;
+            startDateAddOn = endDateAddOn;
+        }
+        // clock at 2016-03-01
+
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2016, 3, 26), new LocalDate(2016, 4, 25), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.setDay(new LocalDate(2016, 3, 26));
+        assertListenerStatus();
+        invoiceChecker.checkInvoice(account.getId(), 24, callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        // We check there is no more recurring for Refurbish-Maintenance
+        busHandler.pushExpectedEvents(NextEvent.NULL_INVOICE);
+        clock.setDay(new LocalDate(2016, 4, 1));
+        assertListenerStatus();
+    }
+
+    @Test(groups = "slow")
+    public void testWeeklyPlan() throws Exception {
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDay(new LocalDate(2015, 4, 1));
+
+        final AccountData accountData = getAccountData(1);
+        final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+        accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+        final List<ExpectedInvoiceItemCheck> expectedInvoices = new ArrayList<ExpectedInvoiceItemCheck>();
+
+        // First invoice
+        createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Pistol", ProductCategory.BASE, BillingPeriod.WEEKLY, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 4, 1), null, InvoiceItemType.FIXED, BigDecimal.ZERO));
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        // 2015-5-1
+        expectedInvoices.add(new ExpectedInvoiceItemCheck(new LocalDate(2015, 5, 1), new LocalDate(2015, 5, 8), InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        clock.addDays(30); // Also = 1 month because or initial date 2015, 4, 1
+        assertListenerStatus();
+        invoiceChecker.checkInvoice(account.getId(), 2, callContext, expectedInvoices);
+        expectedInvoices.clear();
+
+        LocalDate startDateBase = new LocalDate(2015, 5, 8);
+        for (int i = 0; i < 10; i++) {
+            final LocalDate endDateBase = startDateBase.plusDays(7);
+            expectedInvoices.add(new ExpectedInvoiceItemCheck(startDateBase, endDateBase, InvoiceItemType.RECURRING, new BigDecimal("29.95")));
+            busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+            clock.setDay(startDateBase);
+            assertListenerStatus();
+            invoiceChecker.checkInvoice(account.getId(), 3 + i, callContext, expectedInvoices);
+            expectedInvoices.clear();
+
+            startDateBase = endDateBase;
+        }
+    }
 }
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
index aad1c94..f55940d 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
@@ -563,7 +563,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
                     assertNotNull(entitlement);
                     return entitlement;
                 } catch (final EntitlementApiException e) {
-                    fail(e.toString());
+                    fail("Unable to create entitlement", e);
                     return null;
                 }
             }
diff --git a/catalog/src/test/resources/catalogTest.xml b/catalog/src/test/resources/catalogTest.xml
index 4b1ee25..a1cdec3 100644
--- a/catalog/src/test/resources/catalogTest.xml
+++ b/catalog/src/test/resources/catalogTest.xml
@@ -275,7 +275,80 @@
                 </recurring>
             </finalPhase>
         </plan>
-
+        <plan name="pistol-weekly">
+            <product>Pistol</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>DAYS</unit>
+                        <number>30</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice>
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>WEEKLY</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>GBP</currency>
+                            <value>29.95</value>
+                        </price>
+                        <price>
+                            <currency>EUR</currency>
+                            <value>29.95</value>
+                        </price>
+                        <price>
+                            <currency>USD</currency>
+                            <value>29.95</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
+        <plan name="pistol-thirty-days">
+            <product>Pistol</product>
+            <initialPhases>
+                <phase type="TRIAL">
+                    <duration>
+                        <unit>DAYS</unit>
+                        <number>30</number>
+                    </duration>
+                    <fixed>
+                        <fixedPrice>
+                        </fixedPrice>
+                    </fixed>
+                </phase>
+            </initialPhases>
+            <finalPhase type="EVERGREEN">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <recurring>
+                    <billingPeriod>THIRTY_DAYS</billingPeriod>
+                    <recurringPrice>
+                        <price>
+                            <currency>GBP</currency>
+                            <value>29.95</value>
+                        </price>
+                        <price>
+                            <currency>EUR</currency>
+                            <value>29.95</value>
+                        </price>
+                        <price>
+                            <currency>USD</currency>
+                            <value>29.95</value>
+                        </price>
+                    </recurringPrice>
+                </recurring>
+            </finalPhase>
+        </plan>
         <plan name="pistol-monthly">
             <product>Pistol</product>
             <initialPhases>
@@ -1101,6 +1174,8 @@
     <priceLists>
         <defaultPriceList name="DEFAULT">
             <plans>
+                <plan>pistol-weekly</plan>
+                <plan>pistol-thirty-days</plan>
                 <plan>blowdart-monthly</plan>
                 <plan>pistol-monthly</plan>
                 <plan>shotgun-monthly</plan>
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java
index 76843fd..846b057 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/BillingIntervalDetail.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you 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:
  *
@@ -45,7 +47,11 @@ public class BillingIntervalDetail {
         this.startDate = startDate;
         this.endDate = endDate;
         this.targetDate = targetDate;
-        this.billingCycleDay = billingCycleDay;
+        if (billingPeriod.getPeriod().getMonths() != 0 || billingPeriod.getPeriod().getYears() != 0) {
+            this.billingCycleDay = billingCycleDay;
+        } else {
+            this.billingCycleDay = startDate.getDayOfMonth();
+        }
         this.billingPeriod = billingPeriod;
         this.billingMode = billingMode;
         computeAll();
@@ -59,9 +65,8 @@ public class BillingIntervalDetail {
         return effectiveEndDate;
     }
 
-    public LocalDate getFutureBillingDateFor(int nbPeriod) {
-        final int numberOfMonthsPerBillingPeriod = billingPeriod.getNumberOfMonths();
-        LocalDate proposedDate = firstBillingCycleDate.plusMonths((nbPeriod) * numberOfMonthsPerBillingPeriod);
+    public LocalDate getFutureBillingDateFor(final int nbPeriod) {
+        final LocalDate proposedDate = InvoiceDateUtils.advanceByNPeriods(firstBillingCycleDate, billingPeriod, nbPeriod);
         return alignProposedBillCycleDate(proposedDate, billingCycleDay);
     }
 
@@ -70,8 +75,7 @@ public class BillingIntervalDetail {
     }
 
     public LocalDate getNextBillingCycleDate() {
-        final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
-        final LocalDate proposedDate = lastBillingCycleDate != null ? lastBillingCycleDate.plusMonths(numberOfMonthsInPeriod) : firstBillingCycleDate;
+        final LocalDate proposedDate = lastBillingCycleDate != null ? lastBillingCycleDate.plus(billingPeriod.getPeriod()) : firstBillingCycleDate;
         final LocalDate nextBillingCycleDate = alignProposedBillCycleDate(proposedDate, billingCycleDay);
         return nextBillingCycleDate;
     }
@@ -88,8 +92,7 @@ public class BillingIntervalDetail {
         calculateLastBillingCycleDate();
     }
 
-    @VisibleForTesting
-    void calculateFirstBillingCycleDate() {
+    private void calculateFirstBillingCycleDate() {
 
         final int lastDayOfMonth = startDate.dayOfMonth().getMaximumValue();
         final LocalDate billingCycleDate;
@@ -99,10 +102,9 @@ public class BillingIntervalDetail {
             billingCycleDate = new LocalDate(startDate.getYear(), startDate.getMonthOfYear(), billingCycleDay, startDate.getChronology());
         }
 
-        final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
         LocalDate proposedDate = billingCycleDate;
         while (proposedDate.isBefore(startDate)) {
-            proposedDate = proposedDate.plusMonths(numberOfMonthsInPeriod);
+            proposedDate = proposedDate.plus(billingPeriod.getPeriod());
         }
         firstBillingCycleDate = alignProposedBillCycleDate(proposedDate, billingCycleDay);
     }
@@ -127,15 +129,13 @@ public class BillingIntervalDetail {
             return;
         }
 
-        final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
         int numberOfPeriods = 0;
         LocalDate proposedDate = firstBillingCycleDate;
-        LocalDate nextProposedDate = proposedDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
-
+        LocalDate nextProposedDate = InvoiceDateUtils.advanceByNPeriods(firstBillingCycleDate, billingPeriod, numberOfPeriods);
 
         while (!nextProposedDate.isAfter(targetDate)) {
             proposedDate = nextProposedDate;
-            nextProposedDate = firstBillingCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
+            nextProposedDate = InvoiceDateUtils.advanceByNPeriods(firstBillingCycleDate, billingPeriod, numberOfPeriods);
             numberOfPeriods += 1;
         }
         proposedDate = alignProposedBillCycleDate(proposedDate, billingCycleDay);
@@ -161,12 +161,11 @@ public class BillingIntervalDetail {
             return;
         }
 
-        final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
         int numberOfPeriods = 0;
         LocalDate proposedDate = firstBillingCycleDate;
 
         while (!proposedDate.isAfter(targetDate)) {
-            proposedDate = firstBillingCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
+            proposedDate = InvoiceDateUtils.advanceByNPeriods(firstBillingCycleDate, billingPeriod, numberOfPeriods);
             numberOfPeriods += 1;
         }
         proposedDate = alignProposedBillCycleDate(proposedDate, billingCycleDay);
@@ -191,12 +190,12 @@ public class BillingIntervalDetail {
         LocalDate proposedDate = firstBillingCycleDate;
         int numberOfPeriods = 0;
         while (!proposedDate.isAfter(effectiveEndDate)) {
-            proposedDate = firstBillingCycleDate.plusMonths(numberOfPeriods * billingPeriod.getNumberOfMonths());
+            proposedDate = InvoiceDateUtils.advanceByNPeriods(firstBillingCycleDate, billingPeriod, numberOfPeriods);
             numberOfPeriods += 1;
         }
 
         // Our proposed date is billingCycleDate prior to the effectiveEndDate
-        proposedDate = proposedDate.plusMonths(-billingPeriod.getNumberOfMonths());
+        proposedDate = proposedDate.minus(billingPeriod.getPeriod());
         proposedDate = alignProposedBillCycleDate(proposedDate, billingCycleDay);
 
         if (proposedDate.isBefore(firstBillingCycleDate)) {
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceDateUtils.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceDateUtils.java
index 879ef43..fabac45 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceDateUtils.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/InvoiceDateUtils.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you 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:
  *
@@ -21,12 +23,45 @@ import java.math.BigDecimal;
 import org.joda.time.Days;
 import org.joda.time.LocalDate;
 import org.joda.time.Months;
-
+import org.joda.time.Weeks;
+import org.joda.time.Years;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.util.currency.KillBillMoney;
 
 public class InvoiceDateUtils {
 
+    public static int calculateNumberOfWholeBillingPeriods(final LocalDate startDate, final LocalDate endDate, final BillingPeriod billingPeriod) {
+        final int numberBetween;
+        final int numberInPeriod;
+        if (billingPeriod.getPeriod().getDays() != 0) {
+            numberBetween = Days.daysBetween(startDate, endDate).getDays();
+            numberInPeriod = billingPeriod.getPeriod().getDays();
+        } else if (billingPeriod.getPeriod().getWeeks() != 0) {
+            numberBetween = Weeks.weeksBetween(startDate, endDate).getWeeks();
+            numberInPeriod = billingPeriod.getPeriod().getWeeks();
+        } else if (billingPeriod.getPeriod().getMonths() != 0) {
+            numberBetween = Months.monthsBetween(startDate, endDate).getMonths();
+            numberInPeriod = billingPeriod.getPeriod().getMonths();
+        } else {
+            numberBetween = Years.yearsBetween(startDate, endDate).getYears();
+            numberInPeriod = billingPeriod.getPeriod().getYears();
+        }
+        return numberBetween / numberInPeriod;
+    }
+
+    public static BigDecimal calculateProRationBeforeFirstBillingPeriod(final LocalDate startDate, final LocalDate nextBillingCycleDate,
+                                                                        final BillingPeriod billingPeriod) {
+        final LocalDate previousBillingCycleDate = nextBillingCycleDate.minus(billingPeriod.getPeriod());
+        return calculateProrationBetweenDates(startDate, nextBillingCycleDate, previousBillingCycleDate, nextBillingCycleDate);
+    }
+
+    public static BigDecimal calculateProRationAfterLastBillingCycleDate(final LocalDate endDate, final LocalDate previousBillThroughDate,
+                                                                         final BillingPeriod billingPeriod) {
+        // Note: assumption is that previousBillThroughDate is correctly aligned with the billing cycle day
+        final LocalDate nextBillThroughDate = previousBillThroughDate.plus(billingPeriod.getPeriod());
+        return calculateProrationBetweenDates(previousBillThroughDate, endDate, previousBillThroughDate, nextBillThroughDate);
+    }
+
     /**
      * Called internally to calculate proration or when we recalculate approximate repair amount
      *
@@ -34,9 +69,8 @@ public class InvoiceDateUtils {
      * @param endDate                  end date of the prorated interval
      * @param previousBillingCycleDate start date of the period
      * @param nextBillingCycleDate     end date of the period
-     * @return
      */
-    public static BigDecimal calculateProrationBetweenDates(final LocalDate startDate, final LocalDate endDate, final LocalDate previousBillingCycleDate, final LocalDate nextBillingCycleDate) {
+    private static BigDecimal calculateProrationBetweenDates(final LocalDate startDate, final LocalDate endDate, final LocalDate previousBillingCycleDate, final LocalDate nextBillingCycleDate) {
         final int daysBetween = Days.daysBetween(previousBillingCycleDate, nextBillingCycleDate).getDays();
         return calculateProrationBetweenDates(startDate, endDate, daysBetween);
     }
@@ -52,122 +86,18 @@ public class InvoiceDateUtils {
         return days.divide(daysInPeriod, KillBillMoney.MAX_SCALE, KillBillMoney.ROUNDING_METHOD);
     }
 
-    public static BigDecimal calculateProRationBeforeFirstBillingPeriod(final LocalDate startDate, final LocalDate nextBillingCycleDate,
-                                                                        final BillingPeriod billingPeriod) {
-        final LocalDate previousBillingCycleDate = nextBillingCycleDate.plusMonths(-billingPeriod.getNumberOfMonths());
-
-        return calculateProrationBetweenDates(startDate, nextBillingCycleDate, previousBillingCycleDate, nextBillingCycleDate);
-    }
-
-    public static int calculateNumberOfWholeBillingPeriods(final LocalDate startDate, final LocalDate endDate, final BillingPeriod billingPeriod) {
-        final int numberOfMonths = Months.monthsBetween(startDate, endDate).getMonths();
-        final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
-        return numberOfMonths / numberOfMonthsInPeriod;
-    }
-
-    public static LocalDate calculateLastBillingCycleDateBefore(final LocalDate date, final LocalDate previousBillCycleDate,
-                                                                final int billingCycleDay, final BillingPeriod billingPeriod) {
-        LocalDate proposedDate = previousBillCycleDate;
-
-        int numberOfPeriods = 0;
-        while (!proposedDate.isAfter(date)) {
-            proposedDate = previousBillCycleDate.plusMonths(numberOfPeriods * billingPeriod.getNumberOfMonths());
-            numberOfPeriods += 1;
-        }
-
-        proposedDate = proposedDate.plusMonths(-billingPeriod.getNumberOfMonths());
-
-        if (proposedDate.dayOfMonth().get() < billingCycleDay) {
-            final int lastDayOfTheMonth = proposedDate.dayOfMonth().getMaximumValue();
-            if (lastDayOfTheMonth < billingCycleDay) {
-                proposedDate = new LocalDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), lastDayOfTheMonth);
-            } else {
-                proposedDate = new LocalDate(proposedDate.getYear(), proposedDate.getMonthOfYear(), billingCycleDay);
-            }
-        }
-
-        if (proposedDate.isBefore(previousBillCycleDate)) {
-            // Make sure not to go too far in the past
-            return previousBillCycleDate;
-        } else {
-            return proposedDate;
-        }
-    }
-
-    public static LocalDate calculateEffectiveEndDate(final LocalDate billCycleDate, final LocalDate targetDate,
-                                                      final BillingPeriod billingPeriod) {
-        if (targetDate.isBefore(billCycleDate)) {
-            return billCycleDate;
-        }
-
-        final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
-        int numberOfPeriods = 0;
-        LocalDate proposedDate = billCycleDate;
-
-        while (!proposedDate.isAfter(targetDate)) {
-            proposedDate = billCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
-            numberOfPeriods += 1;
-        }
-
-        return proposedDate;
-    }
-
-    public static LocalDate calculateEffectiveEndDate(final LocalDate billCycleDate, final LocalDate targetDate,
-                                                      final LocalDate endDate, final BillingPeriod billingPeriod) {
-        if (targetDate.isBefore(endDate)) {
-            if (targetDate.isBefore(billCycleDate)) {
-                return billCycleDate;
-            }
-
-            final int numberOfMonthsInPeriod = billingPeriod.getNumberOfMonths();
-            int numberOfPeriods = 0;
-            LocalDate proposedDate = billCycleDate;
-
-            while (!proposedDate.isAfter(targetDate)) {
-                proposedDate = billCycleDate.plusMonths(numberOfPeriods * numberOfMonthsInPeriod);
-                numberOfPeriods += 1;
-            }
-
-            // the current period includes the target date
-            // check to see whether the end date truncates the period
-            if (endDate.isBefore(proposedDate)) {
-                return endDate;
-            } else {
-                return proposedDate;
-            }
-        } else {
-            return endDate;
-        }
-    }
-
-    public static BigDecimal calculateProRationAfterLastBillingCycleDate(final LocalDate endDate, final LocalDate previousBillThroughDate,
-                                                                         final BillingPeriod billingPeriod) {
-        // Note: assumption is that previousBillThroughDate is correctly aligned with the billing cycle day
-        final LocalDate nextBillThroughDate = previousBillThroughDate.plusMonths(billingPeriod.getNumberOfMonths());
-        return calculateProrationBetweenDates(previousBillThroughDate, endDate, previousBillThroughDate, nextBillThroughDate);
-    }
-
-    public static LocalDate calculateBillingCycleDateOnOrAfter(final LocalDate date, final int billingCycleDayLocal) {
-        final int lastDayOfMonth = date.dayOfMonth().getMaximumValue();
-
-        final LocalDate fixedDate;
-        if (billingCycleDayLocal > lastDayOfMonth) {
-            fixedDate = new LocalDate(date.getYear(), date.getMonthOfYear(), lastDayOfMonth, date.getChronology());
-        } else {
-            fixedDate = new LocalDate(date.getYear(), date.getMonthOfYear(), billingCycleDayLocal, date.getChronology());
-        }
-
-        LocalDate proposedDate = fixedDate;
-        while (proposedDate.isBefore(date)) {
-            proposedDate = proposedDate.plusMonths(1);
+    public static LocalDate advanceByNPeriods(final LocalDate initialDate, final BillingPeriod billingPeriod, final int nbPeriods) {
+        LocalDate proposedDate = initialDate;
+        for (int i = 0; i < nbPeriods; i++) {
+            proposedDate = proposedDate.plus(billingPeriod.getPeriod());
         }
         return proposedDate;
     }
 
-    public static LocalDate calculateBillingCycleDateAfter(final LocalDate date, final int billingCycleDayLocal) {
-        LocalDate proposedDate = calculateBillingCycleDateOnOrAfter(date, billingCycleDayLocal);
-        if (date.compareTo(proposedDate) == 0) {
-            proposedDate = proposedDate.plusMonths(1);
+    public static LocalDate recedeByNPeriods(final LocalDate initialDate, final BillingPeriod billingPeriod, final int nbPeriods) {
+        LocalDate proposedDate = initialDate;
+        for (int i = 0; i < nbPeriods; i++) {
+            proposedDate = proposedDate.minus(billingPeriod.getPeriod());
         }
         return proposedDate;
     }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/usage/RawUsageOptimizer.java b/invoice/src/main/java/org/killbill/billing/invoice/usage/RawUsageOptimizer.java
index e9c2239..c3d3caf 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/usage/RawUsageOptimizer.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/usage/RawUsageOptimizer.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -30,6 +30,7 @@ import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.catalog.api.Usage;
 import org.killbill.billing.invoice.api.InvoiceItem;
+import org.killbill.billing.invoice.generator.InvoiceDateUtils;
 import org.killbill.billing.invoice.model.UsageInvoiceItem;
 import org.killbill.billing.usage.InternalUserApi;
 import org.killbill.billing.usage.RawUsage;
@@ -101,7 +102,7 @@ public class RawUsageOptimizer {
         int idx = 0;
         for (BillingPeriod bp : BillingPeriod.values()) {
             if (bp != BillingPeriod.NO_BILLING_PERIOD) {
-                final LocalDate makerDateThanCannotBeChosenAsTheMinOfAllDates = targetDate.plusMonths(config.getMaxRawUsagePreviousPeriod(internalCallContext) * bp.getNumberOfMonths());
+                final LocalDate makerDateThanCannotBeChosenAsTheMinOfAllDates = InvoiceDateUtils.advanceByNPeriods(targetDate, bp, config.getMaxRawUsagePreviousPeriod(internalCallContext));
                 perBillingPeriodMostRecentConsumableInArrearItemEndDate[idx++] = (knownUsageBillingPeriod.contains(bp)) ? null : makerDateThanCannotBeChosenAsTheMinOfAllDates;
             }
         }
@@ -127,7 +128,7 @@ public class RawUsageOptimizer {
         for (BillingPeriod bp : BillingPeriod.values()) {
             if (bp != BillingPeriod.NO_BILLING_PERIOD) {
                 final LocalDate tmp = perBillingPeriodMostRecentConsumableInArrearItemEndDate[idx];
-                final LocalDate targetBillingPeriodDate = tmp != null ? tmp.minusMonths(config.getMaxRawUsagePreviousPeriod(internalCallContext) * bp.getNumberOfMonths()) : null;
+                final LocalDate targetBillingPeriodDate = tmp != null ? InvoiceDateUtils.recedeByNPeriods(tmp, bp, config.getMaxRawUsagePreviousPeriod(internalCallContext)) : null;
                 if (targetStartDate == null || (targetBillingPeriodDate != null && targetBillingPeriodDate.compareTo(targetStartDate) < 0)) {
                     targetStartDate = targetBillingPeriodDate;
                 }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
index ec7929f..ba7da87 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestDefaultInvoiceGenerator.java
@@ -183,6 +183,45 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
         assertEquals(invoice.getNumberOfItems(), 2);
         assertEquals(invoice.getBalance(), KillBillMoney.of(TWENTY, invoice.getCurrency()));
         assertEquals(invoice.getInvoiceItems().get(0).getSubscriptionId(), sub.getId());
+
+        assertEquals(invoice.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.RECURRING);
+        assertEquals(invoice.getInvoiceItems().get(0).getStartDate(), new LocalDate(2011, 9, 1));
+        assertEquals(invoice.getInvoiceItems().get(0).getEndDate(), new LocalDate(2011, 10, 1));
+
+        assertEquals(invoice.getInvoiceItems().get(1).getInvoiceItemType(), InvoiceItemType.RECURRING);
+        assertEquals(invoice.getInvoiceItems().get(1).getStartDate(), new LocalDate(2011, 10, 1));
+        assertEquals(invoice.getInvoiceItems().get(1).getEndDate(), new LocalDate(2011, 11, 1));
+    }
+
+    @Test(groups = "fast")
+    public void testWithSingleThirtyDaysEvent() throws InvoiceApiException, CatalogApiException {
+        final BillingEventSet events = new MockBillingEventSet();
+
+        final SubscriptionBase sub = createSubscription();
+        final LocalDate startDate = invoiceUtil.buildDate(2011, 9, 1);
+
+        final Plan plan = new MockPlan();
+        final BigDecimal rate1 = TEN;
+        final PlanPhase phase = createMockThirtyDaysPlanPhase(rate1);
+
+        final BillingEvent event = createBillingEvent(sub.getId(), sub.getBundleId(), startDate, plan, phase, 1);
+        events.add(event);
+
+        final LocalDate targetDate = invoiceUtil.buildDate(2011, 10, 3);
+        final InvoiceWithMetadata invoiceWithMetadata = generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext);
+        final Invoice invoice = invoiceWithMetadata.getInvoice();
+        assertNotNull(invoice);
+        assertEquals(invoice.getNumberOfItems(), 2);
+        assertEquals(invoice.getBalance(), KillBillMoney.of(TWENTY, invoice.getCurrency()));
+        assertEquals(invoice.getInvoiceItems().get(0).getSubscriptionId(), sub.getId());
+
+        assertEquals(invoice.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.RECURRING);
+        assertEquals(invoice.getInvoiceItems().get(0).getStartDate(), new LocalDate(2011, 9, 1));
+        assertEquals(invoice.getInvoiceItems().get(0).getEndDate(), new LocalDate(2011, 10, 1));
+
+        assertEquals(invoice.getInvoiceItems().get(1).getInvoiceItemType(), InvoiceItemType.RECURRING);
+        assertEquals(invoice.getInvoiceItems().get(1).getStartDate(), new LocalDate(2011, 10, 1));
+        assertEquals(invoice.getInvoiceItems().get(1).getEndDate(), new LocalDate(2011, 10, 31));
     }
 
     private SubscriptionBase createSubscription() {
@@ -773,6 +812,11 @@ public class TestDefaultInvoiceGenerator extends InvoiceTestSuiteNoDB {
         generator.generateInvoice(account, events, null, targetDate, Currency.USD, internalCallContext);
     }
 
+    private MockPlanPhase createMockThirtyDaysPlanPhase(@Nullable final BigDecimal recurringRate) {
+        return new MockPlanPhase(new MockInternationalPrice(new DefaultPrice(recurringRate, Currency.USD)),
+                                 null, BillingPeriod.THIRTY_DAYS);
+    }
+
     private MockPlanPhase createMockMonthlyPlanPhase() {
         return new MockPlanPhase(null, null, BillingPeriod.MONTHLY);
     }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInAdvanceBillingIntervalDetail.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInAdvanceBillingIntervalDetail.java
index 2328f56..121ff05 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInAdvanceBillingIntervalDetail.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInAdvanceBillingIntervalDetail.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you 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:
  *
@@ -18,11 +20,10 @@ package org.killbill.billing.invoice.generator;
 
 import org.joda.time.LocalDate;
 import org.killbill.billing.catalog.api.BillingMode;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.invoice.InvoiceTestSuiteNoDB;
+import org.testng.Assert;
+import org.testng.annotations.Test;
 
 public class TestInAdvanceBillingIntervalDetail extends InvoiceTestSuiteNoDB {
 
@@ -35,10 +36,18 @@ public class TestInAdvanceBillingIntervalDetail extends InvoiceTestSuiteNoDB {
     @Test(groups = "fast")
     public void testCalculateFirstBillingCycleDate1() throws Exception {
         final LocalDate from = new LocalDate("2012-01-16");
+        final LocalDate to = null;
+        final LocalDate targetDate = new LocalDate();
         final int bcd = 17;
-        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), bcd, BillingPeriod.ANNUAL, BillingMode.IN_ADVANCE);
-        billingIntervalDetail.calculateFirstBillingCycleDate();
-        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-17"));
+
+        final BillingIntervalDetail annualBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.ANNUAL, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(annualBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-17"));
+
+        final BillingIntervalDetail monthlyBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(monthlyBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-17"));
+
+        final BillingIntervalDetail thirtyDaysBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.THIRTY_DAYS, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(thirtyDaysBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-16"));
     }
 
     /*
@@ -50,16 +59,22 @@ public class TestInAdvanceBillingIntervalDetail extends InvoiceTestSuiteNoDB {
     @Test(groups = "fast")
     public void testCalculateFirstBillingCycleDate2() throws Exception {
         final LocalDate from = new LocalDate("2012-02-16");
+        final LocalDate to = null;
+        final LocalDate targetDate = new LocalDate();
         final int bcd = 30;
-        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), bcd, BillingPeriod.ANNUAL, BillingMode.IN_ADVANCE);
-        billingIntervalDetail.calculateFirstBillingCycleDate();
-        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-29"));
+
+        final BillingIntervalDetail annualBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.ANNUAL, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(annualBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-29"));
+
+        final BillingIntervalDetail monthlyBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(monthlyBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-29"));
+
+        final BillingIntervalDetail thirtyDaysBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.THIRTY_DAYS, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(thirtyDaysBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-16"));
     }
 
     /*
-     * Here the interesting part is that BCD is prior start and
-     *  i) we use MONTHLY billing period
-     * ii) on the next month, there is no such date (2012-02-30 does not exist)
+     * Here the interesting part is that BCD is prior start
      *
      *                                      Start
      *                              BCD     END_MONTH
@@ -67,12 +82,20 @@ public class TestInAdvanceBillingIntervalDetail extends InvoiceTestSuiteNoDB {
      *
      */
     @Test(groups = "fast")
-    public void testCalculateFirstBillingCycleDate4() throws Exception {
+    public void testCalculateFirstBillingCycleDate3() throws Exception {
         final LocalDate from = new LocalDate("2012-01-31");
+        final LocalDate to = null;
+        final LocalDate targetDate = new LocalDate();
         final int bcd = 30;
-        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), bcd, BillingPeriod.MONTHLY, BillingMode.IN_ADVANCE);
-        billingIntervalDetail.calculateFirstBillingCycleDate();
-        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-29"));
+
+        final BillingIntervalDetail annualBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.ANNUAL, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(annualBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2013-01-30"));
+
+        final BillingIntervalDetail monthlyBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(monthlyBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-29"));
+
+        final BillingIntervalDetail thirtyDaysBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.THIRTY_DAYS, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(thirtyDaysBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-01-31"));
     }
 
     /*
@@ -82,12 +105,20 @@ public class TestInAdvanceBillingIntervalDetail extends InvoiceTestSuiteNoDB {
      *
      */
     @Test(groups = "fast")
-    public void testCalculateFirstBillingCycleDate3() throws Exception {
+    public void testCalculateFirstBillingCycleDate4() throws Exception {
         final LocalDate from = new LocalDate("2012-02-16");
+        final LocalDate to = null;
+        final LocalDate targetDate = new LocalDate();
         final int bcd = 14;
-        final BillingIntervalDetail billingIntervalDetail = new BillingIntervalDetail(from, null, new LocalDate(), bcd, BillingPeriod.ANNUAL, BillingMode.IN_ADVANCE);
-        billingIntervalDetail.calculateFirstBillingCycleDate();
-        Assert.assertEquals(billingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2013-02-14"));
+
+        final BillingIntervalDetail annualBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.ANNUAL, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(annualBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2013-02-14"));
+
+        final BillingIntervalDetail monthlyBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.MONTHLY, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(monthlyBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-03-14"));
+
+        final BillingIntervalDetail thirtyDaysBillingIntervalDetail = new BillingIntervalDetail(from, to, targetDate, bcd, BillingPeriod.THIRTY_DAYS, BillingMode.IN_ADVANCE);
+        Assert.assertEquals(thirtyDaysBillingIntervalDetail.getFirstBillingCycleDate(), new LocalDate("2012-02-16"));
     }
 
     @Test(groups = "fast")
@@ -165,5 +196,4 @@ public class TestInAdvanceBillingIntervalDetail extends InvoiceTestSuiteNoDB {
         final LocalDate effectiveEndDate = billingIntervalDetail.getEffectiveEndDate();
         Assert.assertEquals(effectiveEndDate, new LocalDate("2012-05-31"));
     }
-
 }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInvoiceDateUtils.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInvoiceDateUtils.java
index 485329b..67b1f42 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInvoiceDateUtils.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestInvoiceDateUtils.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you 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:
  *
@@ -18,33 +20,15 @@ package org.killbill.billing.invoice.generator;
 
 import java.math.BigDecimal;
 
-import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.catalog.api.BillingPeriod;
 import org.killbill.billing.invoice.InvoiceTestSuiteNoDB;
+import org.testng.Assert;
+import org.testng.annotations.Test;
 
 public class TestInvoiceDateUtils extends InvoiceTestSuiteNoDB {
 
     @Test(groups = "fast")
-    public void testLastBCDShouldNotBeBeforePreviousBCD() throws Exception {
-        final LocalDate from = new LocalDate("2012-07-16");
-        final LocalDate previousBCD = new LocalDate("2012-08-15");
-        final int bcdLocal = 15;
-        final LocalDate lastBCD = InvoiceDateUtils.calculateLastBillingCycleDateBefore(from, previousBCD, bcdLocal, BillingPeriod.MONTHLY);
-        Assert.assertEquals(lastBCD, new LocalDate("2012-08-15"));
-    }
-
-    @Test(groups = "fast")
-    public void testNextBCDShouldNotBeInThePast() throws Exception {
-        final LocalDate from = new LocalDate("2012-07-16");
-        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(from, 15);
-        Assert.assertEquals(to, new LocalDate("2012-08-15"));
-    }
-
-    @Test(groups = "fast")
     public void testProRationAfterLastBillingCycleDate() throws Exception {
         final LocalDate endDate = new LocalDate("2012-06-02");
         final LocalDate previousBillThroughDate = new LocalDate("2012-03-02");
@@ -53,72 +37,77 @@ public class TestInvoiceDateUtils extends InvoiceTestSuiteNoDB {
     }
 
     @Test(groups = "fast")
-    public void testBeforeBCDWithAfter() throws Exception {
-        final LocalDate from = new LocalDate("2012-03-02");
-        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateAfter(from, 3);
-        Assert.assertEquals(to, new LocalDate("2012-03-03"));
-    }
-
-    @Test(groups = "fast")
-    public void testEqualBCDWithAfter() throws Exception {
-        final LocalDate from = new LocalDate("2012-03-03");
-        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateAfter(from, 3);
-        Assert.assertEquals(to, new LocalDate("2012-04-03"));
-    }
-
-    @Test(groups = "fast")
-    public void testAfterBCDWithAfter() throws Exception {
-        final LocalDate from = new LocalDate("2012-03-04");
-        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateAfter(from, 3);
-        Assert.assertEquals(to, new LocalDate("2012-04-03"));
-    }
-
-    @Test(groups = "fast")
-    public void testBeforeBCDWithOnOrAfter() throws Exception {
-        final LocalDate from = new LocalDate("2012-03-02");
-        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(from, 3);
-        Assert.assertEquals(to, new LocalDate("2012-03-03"));
-    }
-
-    @Test(groups = "fast")
-    public void testEqualBCDWithOnOrAfter() throws Exception {
-        final LocalDate from = new LocalDate("2012-03-03");
-        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(from, 3);
-        Assert.assertEquals(to, new LocalDate("2012-03-03"));
-    }
+    public void testCalculateNbOfBillingPeriods() throws Exception {
+        Assert.assertEquals(InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(new LocalDate(2012, 7, 16), new LocalDate(2012, 9, 15), BillingPeriod.MONTHLY), 1);
+        Assert.assertEquals(InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(new LocalDate(2012, 7, 16), new LocalDate(2012, 9, 16), BillingPeriod.MONTHLY), 2);
+        Assert.assertEquals(InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(new LocalDate(2012, 7, 16), new LocalDate(2012, 9, 17), BillingPeriod.MONTHLY), 2);
 
-    @Test(groups = "fast")
-    public void testAfterBCDWithOnOrAfter() throws Exception {
-        final LocalDate from = new LocalDate("2012-03-04");
-        final LocalDate to = InvoiceDateUtils.calculateBillingCycleDateOnOrAfter(from, 3);
-        Assert.assertEquals(to, new LocalDate("2012-04-03"));
-    }
+        Assert.assertEquals(InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(new LocalDate(2012, 7, 16), new LocalDate(2012, 9, 13), BillingPeriod.THIRTY_DAYS), 1);
+        Assert.assertEquals(InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(new LocalDate(2012, 7, 16), new LocalDate(2012, 9, 14), BillingPeriod.THIRTY_DAYS), 2);
+        Assert.assertEquals(InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(new LocalDate(2012, 7, 16), new LocalDate(2012, 9, 15), BillingPeriod.THIRTY_DAYS), 2);
 
-    @Test(groups = "fast")
-    public void testEffectiveEndDate() throws Exception {
-        final LocalDate firstBCD = new LocalDate(2012, 7, 16);
-        final LocalDate targetDate = new LocalDate(2012, 8, 16);
-        final BillingPeriod billingPeriod = BillingPeriod.MONTHLY;
-        final LocalDate effectiveEndDate = InvoiceDateUtils.calculateEffectiveEndDate(firstBCD, targetDate, billingPeriod);
-        // TODO should that be 2012-09-15?
-        Assert.assertEquals(effectiveEndDate, new LocalDate(2012, 9, 16));
+        Assert.assertEquals(InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(new LocalDate(2012, 7, 16), new LocalDate(2012, 7, 29), BillingPeriod.WEEKLY), 1);
+        Assert.assertEquals(InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(new LocalDate(2012, 7, 16), new LocalDate(2012, 7, 30), BillingPeriod.WEEKLY), 2);
+        Assert.assertEquals(InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(new LocalDate(2012, 7, 16), new LocalDate(2012, 7, 31), BillingPeriod.WEEKLY), 2);
     }
 
     @Test(groups = "fast")
-    public void testLastBCD() throws Exception {
-        final LocalDate firstBCD = new LocalDate(2012, 7, 16);
-        final LocalDate effectiveEndDate = new LocalDate(2012, 9, 15);
-        final BillingPeriod billingPeriod = BillingPeriod.MONTHLY;
-        final LocalDate lastBCD = InvoiceDateUtils.calculateLastBillingCycleDateBefore(effectiveEndDate, firstBCD, 16, billingPeriod);
-        Assert.assertEquals(lastBCD, new LocalDate(2012, 8, 16));
+    public void testAdvanceByNPeriods() throws Exception {
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.MONTHLY, 0), new LocalDate(2016, 4, 8));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.MONTHLY, 1), new LocalDate(2016, 5, 8));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.MONTHLY, 2), new LocalDate(2016, 6, 8));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.MONTHLY, 3), new LocalDate(2016, 7, 8));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.MONTHLY, 4), new LocalDate(2016, 8, 8));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.MONTHLY, 5), new LocalDate(2016, 9, 8));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.MONTHLY, 6), new LocalDate(2016, 10, 8));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.MONTHLY, 7), new LocalDate(2016, 11, 8));
+
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.THIRTY_DAYS, 0), new LocalDate(2016, 4, 8));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.THIRTY_DAYS, 1), new LocalDate(2016, 5, 8));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.THIRTY_DAYS, 2), new LocalDate(2016, 6, 7));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.THIRTY_DAYS, 3), new LocalDate(2016, 7, 7));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.THIRTY_DAYS, 4), new LocalDate(2016, 8, 6));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.THIRTY_DAYS, 5), new LocalDate(2016, 9, 5));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.THIRTY_DAYS, 6), new LocalDate(2016, 10, 5));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.THIRTY_DAYS, 7), new LocalDate(2016, 11, 4));
+
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.WEEKLY, 0), new LocalDate(2016, 4, 8));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.WEEKLY, 1), new LocalDate(2016, 4, 15));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.WEEKLY, 2), new LocalDate(2016, 4, 22));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.WEEKLY, 3), new LocalDate(2016, 4, 29));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.WEEKLY, 4), new LocalDate(2016, 5, 6));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.WEEKLY, 5), new LocalDate(2016, 5, 13));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.WEEKLY, 6), new LocalDate(2016, 5, 20));
+        Assert.assertEquals(InvoiceDateUtils.advanceByNPeriods(new LocalDate(2016, 4, 8), BillingPeriod.WEEKLY, 7), new LocalDate(2016, 5, 27));
     }
 
     @Test(groups = "fast")
-    public void testCalculateNbOfBillingPeriods() throws Exception {
-        final LocalDate firstBCD = new LocalDate(2012, 7, 16);
-        final LocalDate lastBCD = new LocalDate(2012, 9, 16);
-        final BillingPeriod billingPeriod = BillingPeriod.MONTHLY;
-        final int numberOfWholeBillingPeriods = InvoiceDateUtils.calculateNumberOfWholeBillingPeriods(firstBCD, lastBCD, billingPeriod);
-        Assert.assertEquals(numberOfWholeBillingPeriods, 2);
+    public void testRecedeByNPeriods() throws Exception {
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 8), BillingPeriod.MONTHLY, 7), new LocalDate(2016, 4, 8));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 8), BillingPeriod.MONTHLY, 6), new LocalDate(2016, 5, 8));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 8), BillingPeriod.MONTHLY, 5), new LocalDate(2016, 6, 8));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 8), BillingPeriod.MONTHLY, 4), new LocalDate(2016, 7, 8));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 8), BillingPeriod.MONTHLY, 3), new LocalDate(2016, 8, 8));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 8), BillingPeriod.MONTHLY, 2), new LocalDate(2016, 9, 8));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 8), BillingPeriod.MONTHLY, 1), new LocalDate(2016, 10, 8));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 8), BillingPeriod.MONTHLY, 0), new LocalDate(2016, 11, 8));
+
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 4), BillingPeriod.THIRTY_DAYS, 7), new LocalDate(2016, 4, 8));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 4), BillingPeriod.THIRTY_DAYS, 6), new LocalDate(2016, 5, 8));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 4), BillingPeriod.THIRTY_DAYS, 5), new LocalDate(2016, 6, 7));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 4), BillingPeriod.THIRTY_DAYS, 4), new LocalDate(2016, 7, 7));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 4), BillingPeriod.THIRTY_DAYS, 3), new LocalDate(2016, 8, 6));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 4), BillingPeriod.THIRTY_DAYS, 2), new LocalDate(2016, 9, 5));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 4), BillingPeriod.THIRTY_DAYS, 1), new LocalDate(2016, 10, 5));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 11, 4), BillingPeriod.THIRTY_DAYS, 0), new LocalDate(2016, 11, 4));
+
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 5, 27), BillingPeriod.WEEKLY, 7), new LocalDate(2016, 4, 8));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 5, 27), BillingPeriod.WEEKLY, 6), new LocalDate(2016, 4, 15));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 5, 27), BillingPeriod.WEEKLY, 5), new LocalDate(2016, 4, 22));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 5, 27), BillingPeriod.WEEKLY, 4), new LocalDate(2016, 4, 29));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 5, 27), BillingPeriod.WEEKLY, 3), new LocalDate(2016, 5, 6));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 5, 27), BillingPeriod.WEEKLY, 2), new LocalDate(2016, 5, 13));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 5, 27), BillingPeriod.WEEKLY, 1), new LocalDate(2016, 5, 20));
+        Assert.assertEquals(InvoiceDateUtils.recedeByNPeriods(new LocalDate(2016, 5, 27), BillingPeriod.WEEKLY, 0), new LocalDate(2016, 5, 27));
     }
 }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/GenericProRationTestBase.java b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/GenericProRationTestBase.java
index c05b8fe..51647a5 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/GenericProRationTestBase.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/proRations/inAdvance/GenericProRationTestBase.java
@@ -1,7 +1,9 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you 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:
  *
@@ -53,7 +55,7 @@ public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBas
     @Test(groups = "fast")
     public void testSinglePlan_OnePeriodLessADayAfterStart() throws InvalidDateSequenceException {
         final LocalDate startDate = invoiceUtil.buildDate(2011, 2, 15);
-        final LocalDate targetDate = startDate.plusMonths(getBillingPeriod().getNumberOfMonths()).plusDays(-1);
+        final LocalDate targetDate = startDate.plus(getBillingPeriod().getPeriod()).plusDays(-1);
 
         testCalculateNumberOfBillingCycles(startDate, targetDate, 15, ONE);
     }
@@ -61,7 +63,7 @@ public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBas
     @Test(groups = "fast")
     public void testSinglePlan_ExactlyOnePeriodAfterStart() throws InvalidDateSequenceException {
         final LocalDate startDate = invoiceUtil.buildDate(2011, 2, 15);
-        final LocalDate targetDate = startDate.plusMonths(getBillingPeriod().getNumberOfMonths());
+        final LocalDate targetDate = startDate.plus(getBillingPeriod().getPeriod());
 
         testCalculateNumberOfBillingCycles(startDate, targetDate, 15, TWO);
     }
@@ -69,7 +71,7 @@ public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBas
     @Test(groups = "fast")
     public void testSinglePlan_SlightlyMoreThanOnePeriodAfterStart() throws InvalidDateSequenceException {
         final LocalDate startDate = invoiceUtil.buildDate(2011, 2, 15);
-        final LocalDate targetDate = startDate.plusMonths(getBillingPeriod().getNumberOfMonths()).plusDays(1);
+        final LocalDate targetDate = startDate.plus(getBillingPeriod().getPeriod()).plusDays(1);
 
         testCalculateNumberOfBillingCycles(startDate, targetDate, 15, TWO);
     }
@@ -77,7 +79,7 @@ public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBas
     @Test(groups = "fast")
     public void testSinglePlan_CrossingYearBoundary() throws InvalidDateSequenceException {
         final LocalDate startDate = invoiceUtil.buildDate(2011, 12, 15);
-        final LocalDate oneCycleLater = startDate.plusMonths(getBillingPeriod().getNumberOfMonths());
+        final LocalDate oneCycleLater = startDate.plus(getBillingPeriod().getPeriod());
 
         // test just before the billing cycle day
         testCalculateNumberOfBillingCycles(startDate, oneCycleLater.plusDays(-1), 15, ONE);
@@ -92,7 +94,7 @@ public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBas
     @Test(groups = "fast")
     public void testSinglePlan_StartingMidFebruary() throws InvalidDateSequenceException {
         final LocalDate startDate = invoiceUtil.buildDate(2011, 2, 15);
-        final LocalDate targetDate = startDate.plusMonths(getBillingPeriod().getNumberOfMonths());
+        final LocalDate targetDate = startDate.plus(getBillingPeriod().getPeriod());
 
         testCalculateNumberOfBillingCycles(startDate, targetDate, 15, TWO);
     }
@@ -100,7 +102,7 @@ public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBas
     @Test(groups = "fast")
     public void testSinglePlan_StartingMidFebruaryOfLeapYear() throws InvalidDateSequenceException {
         final LocalDate startDate = invoiceUtil.buildDate(2012, 2, 15);
-        final LocalDate targetDate = startDate.plusMonths(getBillingPeriod().getNumberOfMonths());
+        final LocalDate targetDate = startDate.plus(getBillingPeriod().getPeriod());
 
         testCalculateNumberOfBillingCycles(startDate, targetDate, 15, TWO);
     }
@@ -111,7 +113,10 @@ public abstract class GenericProRationTestBase extends ProRationInAdvanceTestBas
         BigDecimal expectedValue = ONE;
 
         for (int i = 1; i <= 12; i++) {
-            final LocalDate oneCycleLater = startDate.plusMonths(i * getBillingPeriod().getNumberOfMonths());
+            LocalDate oneCycleLater = startDate;
+            for (int j = 0; j < i; j++) {
+                oneCycleLater = oneCycleLater.plus(getBillingPeriod().getPeriod());
+            }
             // test just before the billing cycle day
             testCalculateNumberOfBillingCycles(startDate, oneCycleLater.plusDays(-1), 31, expectedValue);
 
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
index 6ed0e1b..687e61e 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/JaxRsResourceBase.java
@@ -387,6 +387,11 @@ public abstract class JaxRsResourceBase implements JaxrsResource {
         }
     }
 
+    protected LocalDate toLocalDateDefaultToday(final UUID accountId, @Nullable final String inputDate, final TenantContext context) throws AccountApiException {
+        final Account account = accountId != null ? accountUserApi.getAccountById(accountId, context) : null;
+        return toLocalDateDefaultToday(account, inputDate, context);
+    }
+
     protected LocalDate toLocalDateDefaultToday(final Account account, @Nullable final String inputDate, final TenantContext context) {
         // TODO Switch to cached normalized timezone when available
         return MoreObjects.firstNonNull(toLocalDate(inputDate), clock.getToday(account.getTimeZone()));
diff --git a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/UsageResource.java b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/UsageResource.java
index ea8271a..feb9b52 100644
--- a/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/UsageResource.java
+++ b/jaxrs/src/main/java/org/killbill/billing/jaxrs/resources/UsageResource.java
@@ -42,6 +42,8 @@ import org.killbill.billing.entitlement.api.EntitlementApi;
 import org.killbill.billing.entitlement.api.EntitlementApiException;
 import org.killbill.billing.jaxrs.json.RolledUpUsageJson;
 import org.killbill.billing.jaxrs.json.SubscriptionUsageRecordJson;
+import org.killbill.billing.jaxrs.json.SubscriptionUsageRecordJson.UnitUsageRecordJson;
+import org.killbill.billing.jaxrs.json.SubscriptionUsageRecordJson.UsageRecordJson;
 import org.killbill.billing.jaxrs.util.Context;
 import org.killbill.billing.jaxrs.util.JaxrsUriBuilder;
 import org.killbill.billing.payment.api.PaymentApi;
@@ -59,6 +61,7 @@ import org.killbill.commons.metrics.TimedResource;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 import com.google.inject.Singleton;
 import com.wordnik.swagger.annotations.Api;
 import com.wordnik.swagger.annotations.ApiOperation;
@@ -109,7 +112,15 @@ public class UsageResource extends JaxRsResourceBase {
         verifyNonNullOrEmpty(json.getSubscriptionId(), "SubscriptionUsageRecordJson subscriptionId needs to be set",
                              json.getUnitUsageRecords(), "SubscriptionUsageRecordJson unitUsageRecords needs to be set");
         Preconditions.checkArgument(!json.getUnitUsageRecords().isEmpty());
-
+        for (final UnitUsageRecordJson unitUsageRecordJson : json.getUnitUsageRecords()) {
+            verifyNonNullOrEmpty(unitUsageRecordJson.getUnitType(), "UnitUsageRecordJson unitType need to be set");
+            Preconditions.checkArgument(Iterables.size(unitUsageRecordJson.getUsageRecords()) > 0,
+                                        "UnitUsageRecordJson usageRecords must have at least one element.");
+            for (final UsageRecordJson usageRecordJson : unitUsageRecordJson.getUsageRecords()) {
+                verifyNonNull(usageRecordJson.getAmount(), "UsageRecordJson amount needs to be set");
+                verifyNonNull(usageRecordJson.getRecordDate(), "UsageRecordJson recordDate needs to be set");
+            }
+        }
         final CallContext callContext = context.createContext(createdBy, reason, comment, request);
         // Verify subscription exists..
         final Entitlement entitlement = entitlementApi.getEntitlementForId(UUID.fromString(json.getSubscriptionId()), callContext);
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/alignment/BaseAligner.java b/subscription/src/main/java/org/killbill/billing/subscription/alignment/BaseAligner.java
index 57ce150..e0a1128 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/alignment/BaseAligner.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/alignment/BaseAligner.java
@@ -1,11 +1,13 @@
 /*
- * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2010-2014 Ning, Inc.
+ * Copyright 2014-2016 Groupon, Inc
+ * Copyright 2014-2016 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project licenses this file to you 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
+ *    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
@@ -17,7 +19,6 @@
 package org.killbill.billing.subscription.alignment;
 
 import org.joda.time.DateTime;
-
 import org.killbill.billing.catalog.api.Duration;
 
 public class BaseAligner {
@@ -30,25 +31,7 @@ public class BaseAligner {
         return addOrRemoveDuration(input, duration, false);
     }
 
-    private DateTime addOrRemoveDuration(final DateTime input, final Duration duration, boolean add) {
-        DateTime result = input;
-        switch (duration.getUnit()) {
-            case DAYS:
-                result = add ? result.plusDays(duration.getNumber()) : result.minusDays(duration.getNumber());
-                ;
-                break;
-
-            case MONTHS:
-                result = add ? result.plusMonths(duration.getNumber()) : result.minusMonths(duration.getNumber());
-                break;
-
-            case YEARS:
-                result = add ? result.plusYears(duration.getNumber()) : result.minusYears(duration.getNumber());
-                break;
-            case UNLIMITED:
-            default:
-                throw new RuntimeException("Trying to move to unlimited time period");
-        }
-        return result;
+    private DateTime addOrRemoveDuration(final DateTime input, final Duration duration, final boolean add) {
+        return add ? input.plus(duration.toJodaPeriod()) : input.minus(duration.toJodaPeriod());
     }
 }