diff --git a/api/src/main/java/org/killbill/billing/callcontext/TimeAwareContext.java b/api/src/main/java/org/killbill/billing/callcontext/TimeAwareContext.java
index ca38a75..1f913d0 100644
--- a/api/src/main/java/org/killbill/billing/callcontext/TimeAwareContext.java
+++ b/api/src/main/java/org/killbill/billing/callcontext/TimeAwareContext.java
@@ -64,7 +64,7 @@ public class TimeAwareContext {
getReferenceTime().getSecondOfMinute(),
getFixedOffsetTimeZone());
} catch (final IllegalInstantException e) {
- // DST gap
+ // DST gap (shouldn't happen when using fixed offset timezones)
targetDateTime = localDate.toDateTimeAtStartOfDay(getFixedOffsetTimeZone());
}
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTimeZones.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTimeZones.java
index e3bb373..2a5335b 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTimeZones.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestWithTimeZones.java
@@ -24,6 +24,7 @@ import java.util.UUID;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
+import org.joda.time.IllegalInstantException;
import org.joda.time.LocalDate;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.account.api.AccountData;
@@ -197,4 +198,44 @@ public class TestWithTimeZones extends TestIntegrationBase {
final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(account.getId(), callContext);
Assert.assertEquals(invoices.size(), 1);
}
+
+ @Test(groups = "slow")
+ public void testReferenceTimeInDSTGap() throws Exception {
+ final DateTimeZone tz = DateTimeZone.forID("America/Los_Angeles");
+ clock.setTime(new DateTime(2015, 3, 7, 2, 0, 0, tz));
+
+ final AccountData accountData = new MockAccountBuilder().currency(Currency.USD)
+ .timeZone(tz)
+ .build();
+ final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+ accountChecker.checkAccount(account.getId(), accountData, callContext);
+ Assert.assertEquals(account.getTimeZone(), tz);
+ Assert.assertEquals(account.getFixedOffsetTimeZone(), DateTimeZone.forOffsetHours(-8));
+
+ // Note the gap: 2015-03-07T02:00:00.000-08:00 to 2015-03-08T03:00:00.000-07:00
+ clock.addDays(1);
+
+ try {
+ // See TimeAwareContext#toUTCDateTime (which uses account.getFixedOffsetTimeZone() instead)
+ new DateTime(clock.getUTCToday().getYear(),
+ clock.getUTCToday().getMonthOfYear(),
+ clock.getUTCToday().getDayOfMonth(),
+ account.getReferenceTime().toDateTime(tz).getHourOfDay(),
+ account.getReferenceTime().toDateTime(tz).getMinuteOfHour(),
+ account.getReferenceTime().toDateTime(tz).getSecondOfMinute(),
+ account.getTimeZone());
+ Assert.fail();
+ } catch (final IllegalInstantException e) {
+ // Illegal instant due to time zone offset transition (daylight savings time 'gap'): 2015-03-08T10:00:00.000 (America/Los_Angeles)
+ }
+
+ busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Blowdart", ProductCategory.BASE, BillingPeriod.MONTHLY, "notrial", null);
+ // Pass a date of today, to trigger TimeAwareContext#toUTCDateTime
+ final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, "Something", ImmutableList.<PlanPhasePriceOverride>of(), clock.getUTCToday(), ImmutableList.<PluginProperty>of(), callContext);
+ assertListenerStatus();
+
+ Assert.assertEquals(entitlement.getEffectiveStartDate().compareTo(new LocalDate("2015-03-08")), 0);
+ Assert.assertEquals(((DefaultEntitlement) entitlement).getBasePlanSubscriptionBase().getStartDate().compareTo(new DateTime("2015-03-08T02:00:00.000-08:00")), 0);
+ }
}