killbill-memoizeit

util: revisit DST handling Instead of relying on a fixed +1/-1

2/9/2016 9:46:12 PM

Changes

Details

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 812511f..1dfc111 100644
--- a/api/src/main/java/org/killbill/billing/callcontext/TimeAwareContext.java
+++ b/api/src/main/java/org/killbill/billing/callcontext/TimeAwareContext.java
@@ -27,6 +27,9 @@ import org.joda.time.LocalTime;
 // TODO Cache the accountTimeZone, reference time and clock in the context
 public class TimeAwareContext {
 
+    /// Generic functions
+    /// TODO Move to ClockUtil
+
     // From JDK to Joda (see http://www.joda.org/joda-time/userguide.html#JDK_Interoperability)
     public DateTime toUTCDateTime(final Date date) {
         return toUTCDateTime(new DateTime(date));
@@ -37,31 +40,43 @@ public class TimeAwareContext {
         return toDateTime(dateTime, DateTimeZone.UTC);
     }
 
-    // Create a DateTime object using the specified reference time and timezone (usually, the one on the account)
-    public DateTime toUTCDateTime(final LocalDate localDate, final DateTime referenceDateTime, final DateTimeZone accountTimeZone) {
-        return toUTCDateTime(toDateTime(localDate, referenceDateTime, accountTimeZone));
+    // Create a DateTime object using the specified timezone (usually, the one on the account)
+    public DateTime toDateTime(final DateTime dateTime, final DateTimeZone accountTimeZone) {
+        return dateTime.toDateTime(accountTimeZone);
     }
 
+    /// DateTime <-> LocalDate transformations
+
     // Create a DateTime object using the specified reference time and timezone (usually, the one on the account)
-    public DateTime toDateTime(final LocalDate localDate, final DateTime referenceDateTime, final DateTimeZone accountTimeZone) {
-        final LocalTime referenceLocalTime = toDateTime(referenceDateTime, accountTimeZone).toLocalTime();
+    public DateTime toUTCDateTime(final LocalDate localDate, final DateTime referenceDateTime, final DateTimeZone accountTimeZone) {
+        final DateTimeZone normalizedAccountTimezone = getNormalizedAccountTimezone(referenceDateTime, accountTimeZone);
+
+        final LocalTime referenceLocalTime = toDateTime(referenceDateTime, normalizedAccountTimezone).toLocalTime();
+
+        final DateTime targetDateTime = new DateTime(localDate.getYear(),
+                                                     localDate.getMonthOfYear(),
+                                                     localDate.getDayOfMonth(),
+                                                     referenceLocalTime.getHourOfDay(),
+                                                     referenceLocalTime.getMinuteOfHour(),
+                                                     referenceLocalTime.getSecondOfMinute(),
+                                                     normalizedAccountTimezone);
 
-        return new DateTime(localDate.getYear(),
-                            localDate.getMonthOfYear(),
-                            localDate.getDayOfMonth(),
-                            referenceLocalTime.getHourOfDay(),
-                            referenceLocalTime.getMinuteOfHour(),
-                            referenceLocalTime.getSecondOfMinute(),
-                            accountTimeZone);
+        return toUTCDateTime(targetDateTime);
     }
 
-    // Create a DateTime object using the specified timezone (usually, the one on the account)
-    public DateTime toDateTime(final DateTime dateTime, final DateTimeZone accountTimeZone) {
-        return dateTime.toDateTime(accountTimeZone);
+    // Create a LocalDate object using the specified timezone (usually, the one on the account), respecting the offset at the time of the referenceDateTime
+    public LocalDate toLocalDate(final DateTime dateTime, final DateTime referenceDateTime, final DateTimeZone accountTimeZone) {
+        final DateTimeZone normalizedAccountTimezone = getNormalizedAccountTimezone(referenceDateTime, accountTimeZone);
+        return new LocalDate(dateTime, normalizedAccountTimezone);
     }
 
-    // Create a LocalDate object using the specified timezone (usually, the one on the account)
-    public LocalDate toLocalDate(final DateTime dateTime, final DateTimeZone accountTimeZone) {
-        return new LocalDate(dateTime, accountTimeZone);
+    private DateTimeZone getNormalizedAccountTimezone(final DateTime referenceDateTime, final DateTimeZone accountTimeZone) {
+        // Check if DST was in effect at the reference date time
+        final boolean shouldUseDST = !accountTimeZone.isStandardOffset(referenceDateTime.getMillis());
+        if (shouldUseDST) {
+            return DateTimeZone.forOffsetMillis(accountTimeZone.getOffset(referenceDateTime.getMillis()));
+        } else {
+            return DateTimeZone.forOffsetMillis(accountTimeZone.getStandardOffset(referenceDateTime.getMillis()));
+        }
     }
 }
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 c044cf1..07d2ecd 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
@@ -107,7 +107,7 @@ public class TestWithTimeZones extends TestIntegrationBase {
         }
     }
 
-    // Verify cancellation logic when we exit daylight savind period
+    // Verify cancellation logic when we exit daylight saving period
     @Test(groups = "slow")
     public void testCancellationFrom_PDT_to_PST() throws Exception {
         // Start with a date in daylight saving period (PDT) and make sure we use a time of 7 hour so that we we reach standard time (PST)
@@ -140,22 +140,14 @@ public class TestWithTimeZones extends TestIntegrationBase {
         // Cancel the next month specifying just a LocalDate
         final LocalDate cancellationDate = new LocalDate("2015-12-01", tz);
         entitlement = entitlement.cancelEntitlementWithDate(cancellationDate, true, ImmutableList.<PluginProperty>of(), callContext);
+        assertListenerStatus();
 
         // Verify first entitlement is correctly cancelled on the right date
         Assert.assertEquals(entitlement.getEffectiveEndDate(), cancellationDate);
 
-        busHandler.pushExpectedEvent(NextEvent.NULL_INVOICE);
-        // We move the clock to the date of the next invoice notification 2015-12-01 07:01:01 (invoice is using a fixed offset of 7 hours and all billing events are converted using that offset)
-        clock.setTime(new DateTime("2015-12-01T07:01:02"));
-        assertListenerStatus();
-
-        // We now move the clock to the date of the cancellation (one hour later), which match the cancellation day from the client point of view
-        //
-        // For the curious reader, the reason why the time end up being '8:01:0' comes from (https://github.com/killbill/killbill-commons/blob/master/clock/src/main/java/org/killbill/clock/ClockUtil.java#L51):
-        // We compute a DateTime in the account timezone by specifying explicitly the year-month-day we want to end up in, and shoving *a time*. The reason why we end up on an 8:01:02 is not necessarily so important,
-        // What's important is that by construction that DateTime is guaranteed to match a LocalDate of 2015-12-01
-        busHandler.pushExpectedEvents(NextEvent.CANCEL, NextEvent.BLOCK, NextEvent.NULL_INVOICE);
-        clock.setTime(new DateTime("2015-12-01T08:01:02"));
+        // We now move the clock to the date of the cancellation, which match the cancellation day from the client point of view
+        busHandler.pushExpectedEvents(NextEvent.NULL_INVOICE, NextEvent.CANCEL, NextEvent.BLOCK, NextEvent.NULL_INVOICE);
+        clock.setTime(new DateTime("2015-12-01T07:01:02Z"));
         assertListenerStatus();
 
         // Verify second that there was no repair (so the cancellation did correctly happen on the "2015-12-01")
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/BlockingStateOrdering.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/BlockingStateOrdering.java
index 617db77..f5ad8f2 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/BlockingStateOrdering.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/BlockingStateOrdering.java
@@ -59,22 +59,24 @@ public class BlockingStateOrdering extends EntitlementOrderingBase {
     private void computeEvents(final Iterable<Entitlement> entitlements, final DateTimeZone accountTimeZone, final InternalTenantContext internalTenantContext, final LinkedList<SubscriptionEvent> result) {
         final Collection<UUID> allEntitlementUUIDs = new HashSet<UUID>();
         final Collection<BlockingState> blockingStates = new LinkedList<BlockingState>();
+        final Map<UUID, DateTime> referenceTimes = new HashMap<UUID, DateTime>();
         for (final Entitlement entitlement : entitlements) {
             allEntitlementUUIDs.add(entitlement.getId());
             Preconditions.checkState(entitlement instanceof DefaultEntitlement, "Entitlement %s is not a DefaultEntitlement", entitlement);
             blockingStates.addAll(((DefaultEntitlement) entitlement).getEventsStream().getBlockingStates());
+            referenceTimes.put(entitlement.getId(), ((DefaultEntitlement) entitlement).getSubscriptionBase().getStartDate());
         }
 
         // Trust the incoming ordering here: blocking states were sorted using ProxyBlockingStateDao#sortedCopy
         for (final BlockingState bs : blockingStates) {
             final List<SubscriptionEvent> newEvents = new ArrayList<SubscriptionEvent>();
-            final int index = insertFromBlockingEvent(accountTimeZone, internalTenantContext, allEntitlementUUIDs, result, bs, bs.getEffectiveDate(), newEvents);
+            final int index = insertFromBlockingEvent(referenceTimes, accountTimeZone, internalTenantContext, allEntitlementUUIDs, result, bs, bs.getEffectiveDate(), newEvents);
             insertAfterIndex(result, newEvents, index);
         }
     }
 
     // Returns the index and the newEvents generated from the incoming blocking state event. Those new events will all be created for the same effectiveDate and should be ordered.
-    private int insertFromBlockingEvent(final DateTimeZone accountTimeZone, final InternalTenantContext internalTenantContext, final Collection<UUID> allEntitlementUUIDs, final List<SubscriptionEvent> result, final BlockingState bs, final DateTime bsEffectiveDate, final Collection<SubscriptionEvent> newEvents) {
+    private int insertFromBlockingEvent(final Map<UUID, DateTime> referenceTimes, final DateTimeZone accountTimeZone, final InternalTenantContext internalTenantContext, final Collection<UUID> allEntitlementUUIDs, final List<SubscriptionEvent> result, final BlockingState bs, final DateTime bsEffectiveDate, final Collection<SubscriptionEvent> newEvents) {
         // Keep the current state per entitlement
         final Map<UUID, TargetState> targetStates = new HashMap<UUID, TargetState>();
         for (final UUID cur : allEntitlementUUIDs) {
@@ -133,7 +135,7 @@ public class BlockingStateOrdering extends EntitlementOrderingBase {
 
             final List<SubscriptionEventType> eventTypes = curTargetState.addStateAndReturnEventTypes(bs);
             for (final SubscriptionEventType t : eventTypes) {
-                newEvents.add(toSubscriptionEvent(prevNext[0], prevNext[1], targetEntitlementId, bs, t, accountTimeZone, internalTenantContext));
+                newEvents.add(toSubscriptionEvent(prevNext[0], prevNext[1], targetEntitlementId, bs, t, referenceTimes.get(targetEntitlementId), accountTimeZone, internalTenantContext));
             }
         }
         return index;
@@ -176,7 +178,7 @@ public class BlockingStateOrdering extends EntitlementOrderingBase {
 
     private SubscriptionEvent toSubscriptionEvent(@Nullable final SubscriptionEvent prev, @Nullable final SubscriptionEvent next,
                                                   final UUID entitlementId, final BlockingState in, final SubscriptionEventType eventType,
-                                                  final DateTimeZone accountTimeZone, final InternalTenantContext internalTenantContext) {
+                                                  final DateTime referenceTime, final DateTimeZone accountTimeZone, final InternalTenantContext internalTenantContext) {
         final Product prevProduct;
         final Plan prevPlan;
         final PlanPhase prevPlanPhase;
@@ -257,6 +259,7 @@ public class BlockingStateOrdering extends EntitlementOrderingBase {
                                             nextPriceList,
                                             nextBillingPeriod,
                                             in.getCreatedDate(),
+                                            referenceTime,
                                             accountTimeZone,
                                             internalTenantContext);
     }
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
index 4eafd2b..3d43c6b 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultEntitlement.java
@@ -244,7 +244,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
 
     @Override
     public LocalDate getEffectiveStartDate() {
-        return internalTenantContext.toLocalDate(getSubscriptionBase().getStartDate(), eventsStream.getAccountTimeZone());
+        return internalTenantContext.toLocalDate(getSubscriptionBase().getStartDate(), getSubscriptionBase().getStartDate(), eventsStream.getAccountTimeZone());
     }
 
     @Override
@@ -460,7 +460,7 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
                 break;
             case END_OF_TERM:
                 if (getSubscriptionBase().getChargedThroughDate() != null) {
-                    cancellationDate = internalTenantContext.toLocalDate(getSubscriptionBase().getChargedThroughDate(), eventsStream.getAccountTimeZone());
+                    cancellationDate = internalTenantContext.toLocalDate(getSubscriptionBase().getChargedThroughDate(), getSubscriptionBase().getStartDate(), eventsStream.getAccountTimeZone());
                 } else {
                     cancellationDate = clock.getToday(eventsStream.getAccountTimeZone());
                 }
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscription.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscription.java
index 8a953e2..520d688 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscription.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscription.java
@@ -31,7 +31,7 @@ public class DefaultSubscription extends DefaultEntitlement implements Subscript
 
     @Override
     public LocalDate getBillingStartDate() {
-        return internalTenantContext.toLocalDate(getSubscriptionBase().getStartDate(), getAccountTimeZone());
+        return internalTenantContext.toLocalDate(getSubscriptionBase().getStartDate(), getSubscriptionBase().getStartDate(), getAccountTimeZone());
     }
 
     @Override
@@ -51,12 +51,12 @@ public class DefaultSubscription extends DefaultEntitlement implements Subscript
             futureOrCurrentEndDate = futureOrCurrentEndDateForSubscription;
         }
 
-        return futureOrCurrentEndDate != null ? internalTenantContext.toLocalDate(futureOrCurrentEndDate, getAccountTimeZone()) : null;
+        return futureOrCurrentEndDate != null ? internalTenantContext.toLocalDate(futureOrCurrentEndDate, getSubscriptionBase().getStartDate(), getAccountTimeZone()) : null;
     }
 
     @Override
     public LocalDate getChargedThroughDate() {
-        return getSubscriptionBase().getChargedThroughDate() != null ? internalTenantContext.toLocalDate(getSubscriptionBase().getChargedThroughDate(), getAccountTimeZone()) : null;
+        return getSubscriptionBase().getChargedThroughDate() != null ? internalTenantContext.toLocalDate(getSubscriptionBase().getChargedThroughDate(), getSubscriptionBase().getStartDate(), getAccountTimeZone()) : null;
     }
 
     @Override
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionEvent.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionEvent.java
index e518e96..49641eb 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionEvent.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/DefaultSubscriptionEvent.java
@@ -52,6 +52,7 @@ public class DefaultSubscriptionEvent implements SubscriptionEvent {
     private final PriceList nextPriceList;
     private final BillingPeriod nextBillingPeriod;
     private final DateTime createdDate;
+    private final DateTime referenceTime;
     private final DateTimeZone accountTimeZone;
     private final InternalTenantContext internalTenantContext;
 
@@ -74,6 +75,7 @@ public class DefaultSubscriptionEvent implements SubscriptionEvent {
                                     final PriceList nextPriceList,
                                     final BillingPeriod nextBillingPeriod,
                                     final DateTime createDate,
+                                    final DateTime referenceTime,
                                     final DateTimeZone accountTimeZone,
                                     final InternalTenantContext internalTenantContext) {
         this.id = id;
@@ -96,6 +98,7 @@ public class DefaultSubscriptionEvent implements SubscriptionEvent {
         this.nextPriceList = nextPriceList;
         this.nextBillingPeriod = nextBillingPeriod;
         this.createdDate = createDate;
+        this.referenceTime = referenceTime;
         this.accountTimeZone = accountTimeZone;
         this.internalTenantContext = internalTenantContext;
     }
@@ -124,12 +127,12 @@ public class DefaultSubscriptionEvent implements SubscriptionEvent {
 
     @Override
     public LocalDate getEffectiveDate() {
-        return effectiveDate != null ? internalTenantContext.toLocalDate(effectiveDate, accountTimeZone) : null;
+        return effectiveDate != null ? internalTenantContext.toLocalDate(effectiveDate, referenceTime, accountTimeZone) : null;
     }
 
     @Override
     public LocalDate getRequestedDate() {
-        return requestedDate != null ? internalTenantContext.toLocalDate(requestedDate, accountTimeZone) : null;
+        return requestedDate != null ? internalTenantContext.toLocalDate(requestedDate, referenceTime, accountTimeZone) : null;
     }
 
     @Override
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementDateHelper.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementDateHelper.java
index 1136f11..1038986 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementDateHelper.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/EntitlementDateHelper.java
@@ -65,9 +65,9 @@ public class EntitlementDateHelper {
      * @return true if the inputDate, once converted into a LocalDate using account timezone is less or equals than today
      */
     // TODO Move to ClockUtils
-    public boolean isBeforeOrEqualsToday(final DateTime inputDate, final DateTimeZone accountTimeZone, final InternalTenantContext internalTenantContext) {
+    public boolean isBeforeOrEqualsToday(final DateTime inputDate, final DateTime referenceDatetime, final DateTimeZone accountTimeZone, final InternalTenantContext internalTenantContext) {
         final LocalDate localDateNowInAccountTimezone = clock.getToday(accountTimeZone);
-        final LocalDate targetDateInAccountTimezone = internalTenantContext.toLocalDate(inputDate, accountTimeZone);
+        final LocalDate targetDateInAccountTimezone = internalTenantContext.toLocalDate(inputDate, referenceDatetime, accountTimeZone);
         return targetDateInAccountTimezone.compareTo(localDateNowInAccountTimezone) <= 0;
     }
 }
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/SubscriptionEventOrdering.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/SubscriptionEventOrdering.java
index 01bff1d..ad5e4db 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/SubscriptionEventOrdering.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/SubscriptionEventOrdering.java
@@ -24,6 +24,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
+import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.entitlement.DefaultEntitlementService;
@@ -80,7 +81,7 @@ public class SubscriptionEventOrdering extends EntitlementOrderingBase {
             for (final SubscriptionBaseTransition tr : baseTransitions) {
                 final List<SubscriptionEventType> eventTypes = toEventTypes(tr.getTransitionType());
                 for (final SubscriptionEventType eventType : eventTypes) {
-                    final SubscriptionEvent event = toSubscriptionEvent(tr, eventType, accountTimeZone, internalTenantContext);
+                    final SubscriptionEvent event = toSubscriptionEvent(tr, eventType, base.getStartDate(), accountTimeZone, internalTenantContext);
                     insertSubscriptionEvent(event, result);
                 }
             }
@@ -151,7 +152,7 @@ public class SubscriptionEventOrdering extends EntitlementOrderingBase {
         result.add(index, event);
     }
 
-    private SubscriptionEvent toSubscriptionEvent(final SubscriptionBaseTransition in, final SubscriptionEventType eventType, final DateTimeZone accountTimeZone, final InternalTenantContext internalTenantContext) {
+    private SubscriptionEvent toSubscriptionEvent(final SubscriptionBaseTransition in, final SubscriptionEventType eventType, final DateTime referenceTime, final DateTimeZone accountTimeZone, final InternalTenantContext internalTenantContext) {
         return new DefaultSubscriptionEvent(in.getId(),
                                             in.getSubscriptionId(),
                                             in.getEffectiveTransitionTime(),
@@ -171,6 +172,7 @@ public class SubscriptionEventOrdering extends EntitlementOrderingBase {
                                             in.getNextPriceList(),
                                             (in.getNextPlan() != null ? in.getNextPlan().getRecurringBillingPeriod() : null),
                                             in.getCreatedDate(),
+                                            referenceTime,
                                             accountTimeZone,
                                             internalTenantContext);
     }
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java
index 1733db4..fe2284f 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/api/svcs/DefaultEntitlementApiBase.java
@@ -172,7 +172,7 @@ public class DefaultEntitlementApiBase {
                     final SubscriptionBase baseSubscription = subscriptionInternalApi.getBaseSubscription(bundleId, internalCallContext);
                     final DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getEffectiveDate(), baseSubscription.getStartDate(), internalCallContext);
 
-                    if (!dateHelper.isBeforeOrEqualsToday(effectiveDate, account.getTimeZone(), internalCallContext)) {
+                    if (!dateHelper.isBeforeOrEqualsToday(effectiveDate, baseSubscription.getStartDate(), account.getTimeZone(), internalCallContext)) {
                         recordPauseResumeNotificationEntry(baseSubscription.getId(), bundleId, effectiveDate, true, internalCallContext);
                         return null;
                     }
@@ -224,7 +224,7 @@ public class DefaultEntitlementApiBase {
 
                     final DateTime effectiveDate = dateHelper.fromLocalDateAndReferenceTime(updatedPluginContext.getEffectiveDate(), baseSubscription.getStartDate(), internalCallContext);
 
-                    if (!dateHelper.isBeforeOrEqualsToday(effectiveDate, account.getTimeZone(), internalCallContext)) {
+                    if (!dateHelper.isBeforeOrEqualsToday(effectiveDate, baseSubscription.getStartDate(), account.getTimeZone(), internalCallContext)) {
                         recordPauseResumeNotificationEntry(baseSubscription.getId(), bundleId, effectiveDate, false, internalCallContext);
                         return null;
                     }
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/DefaultEntitlementService.java b/entitlement/src/main/java/org/killbill/billing/entitlement/DefaultEntitlementService.java
index 15612fc..82688be 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/DefaultEntitlementService.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/DefaultEntitlementService.java
@@ -156,9 +156,9 @@ public class DefaultEntitlementService implements EntitlementService {
                 EntitlementNotificationKeyAction.CANCEL.equals(entitlementNotificationKeyAction)) {
                 blockAddOnsIfRequired(key, (DefaultEntitlement) entitlement, callContext, internalCallContext);
             } else if (EntitlementNotificationKeyAction.PAUSE.equals(entitlementNotificationKeyAction)) {
-                entitlementInternalApi.pause(key.getBundleId(), internalCallContext.toLocalDate(key.getEffectiveDate(), immutableAccountData.getTimeZone()), ImmutableList.<PluginProperty>of(), internalCallContext);
+                entitlementInternalApi.pause(key.getBundleId(), internalCallContext.toLocalDate(key.getEffectiveDate(), ((DefaultEntitlement) entitlement).getSubscriptionBase().getStartDate(), immutableAccountData.getTimeZone()), ImmutableList.<PluginProperty>of(), internalCallContext);
             } else if (EntitlementNotificationKeyAction.RESUME.equals(entitlementNotificationKeyAction)) {
-                entitlementInternalApi.resume(key.getBundleId(), internalCallContext.toLocalDate(key.getEffectiveDate(), immutableAccountData.getTimeZone()), ImmutableList.<PluginProperty>of(), internalCallContext);
+                entitlementInternalApi.resume(key.getBundleId(), internalCallContext.toLocalDate(key.getEffectiveDate(), ((DefaultEntitlement) entitlement).getSubscriptionBase().getStartDate(), immutableAccountData.getTimeZone()), ImmutableList.<PluginProperty>of(), internalCallContext);
             }
         } catch (final EntitlementApiException e) {
             log.error("Error processing event for entitlement {}" + entitlement.getId(), e);
diff --git a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
index 5e15873..84c8743 100644
--- a/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
+++ b/entitlement/src/main/java/org/killbill/billing/entitlement/engine/core/DefaultEventsStream.java
@@ -401,12 +401,12 @@ public class DefaultEventsStream implements EventsStream {
                                                                           return DefaultEntitlementApi.ENT_STATE_CANCELLED.equals(input.getStateName());
                                                                       }
                                                                   }).orNull();
-        entitlementEffectiveEndDate = entitlementCancelEvent != null ? internalTenantContext.toLocalDate(entitlementCancelEvent.getEffectiveDate(), account.getTimeZone()) : null;
+        entitlementEffectiveEndDate = entitlementCancelEvent != null ? internalTenantContext.toLocalDate(entitlementCancelEvent.getEffectiveDate(),getSubscriptionBase().getStartDate(),  account.getTimeZone()) : null;
     }
 
     private void computeStateForEntitlement() {
         // Current state for the ENTITLEMENT_SERVICE_NAME is set to cancelled
-        if (entitlementEffectiveEndDate != null && entitlementEffectiveEndDate.compareTo(internalTenantContext.toLocalDate(utcNow, account.getTimeZone())) <= 0) {
+        if (entitlementEffectiveEndDate != null && entitlementEffectiveEndDate.compareTo(internalTenantContext.toLocalDate(utcNow, getSubscriptionBase().getStartDate(), account.getTimeZone())) <= 0) {
             entitlementState = EntitlementState.CANCELLED;
         } else {
             // Gather states across all services and check if one of them is set to 'blockEntitlement'
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
index f38553c..bb6ffc4 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
@@ -86,7 +86,8 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
                                                 null,
                                                 null,
                                                 null,
-                                                null,
+                                                effectiveDate,
+                                                DateTimeZone.UTC,
                                                 internalCallContext);
 
         }
@@ -1328,6 +1329,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
         final SubscriptionBase base = Mockito.mock(SubscriptionBase.class);
         Mockito.when(base.getAllTransitions()).thenReturn(allTransitions);
         Mockito.when(result.getSubscriptionBase()).thenReturn(base);
+        Mockito.when(result.getSubscriptionBase().getStartDate()).thenReturn(new DateTime(DateTimeZone.UTC));
         return result;
     }
 
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestEntitlementDateHelper.java b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestEntitlementDateHelper.java
index c1bd678..65e34c7 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestEntitlementDateHelper.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/api/TestEntitlementDateHelper.java
@@ -151,6 +151,7 @@ public class TestEntitlementDateHelper extends EntitlementTestSuiteNoDB {
         // Check that our input date is greater than now
         assertTrue(inputDateEquals.compareTo(clock.getUTCNow()) > 0);
         // And yet since the LocalDate match the function returns true
-        assertTrue(dateHelper.isBeforeOrEqualsToday(inputDateEquals, timeZoneUtcMinus8, internalCallContext));
+        final DateTime referenceDateTimeThatDoesNotMatter = new DateTime();
+        assertTrue(dateHelper.isBeforeOrEqualsToday(inputDateEquals, referenceDateTimeThatDoesNotMatter, timeZoneUtcMinus8, internalCallContext));
     }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/timezone/DefaultAccountDateAndTimeZoneContext.java b/util/src/main/java/org/killbill/billing/util/timezone/DefaultAccountDateAndTimeZoneContext.java
index cee1333..9910076 100644
--- a/util/src/main/java/org/killbill/billing/util/timezone/DefaultAccountDateAndTimeZoneContext.java
+++ b/util/src/main/java/org/killbill/billing/util/timezone/DefaultAccountDateAndTimeZoneContext.java
@@ -20,9 +20,7 @@ package org.killbill.billing.util.timezone;
 
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
-import org.joda.time.Days;
 import org.joda.time.LocalDate;
-import org.joda.time.LocalTime;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.util.AccountDateAndTimeZoneContext;
 
@@ -33,50 +31,24 @@ import org.killbill.billing.util.AccountDateAndTimeZoneContext;
  */
 public final class DefaultAccountDateAndTimeZoneContext implements AccountDateAndTimeZoneContext {
 
-    private final LocalTime referenceTime;
+    private final DateTime referenceTime;
     private final DateTimeZone accountTimeZone;
     private final InternalTenantContext internalTenantContext;
 
-    private final int offsetFromUtc;
-
-    /// referenceTime is compute from first billing event and so offsetFromUtc will remain constant with regard to daylight saving time
-    public DefaultAccountDateAndTimeZoneContext(final DateTime effectiveDateTime, final DateTimeZone accountTimeZone, final InternalTenantContext internalTenantContext) {
-        this.referenceTime = effectiveDateTime != null ? effectiveDateTime.toLocalTime() : null;
+    public DefaultAccountDateAndTimeZoneContext(final DateTime referenceTime, final DateTimeZone accountTimeZone, final InternalTenantContext internalTenantContext) {
+        this.referenceTime = referenceTime;
         this.accountTimeZone = accountTimeZone;
         this.internalTenantContext = internalTenantContext;
-
-        this.offsetFromUtc = computeOffsetFromUtc(effectiveDateTime, accountTimeZone, internalTenantContext);
-    }
-
-    static int computeOffsetFromUtc(final DateTime effectiveDateTime, final DateTimeZone accountTimeZone, final InternalTenantContext internalTenantContext) {
-        final LocalDate localDateInAccountTimeZone = internalTenantContext.toLocalDate(effectiveDateTime, accountTimeZone);
-        final LocalDate localDateInUTC = new LocalDate(effectiveDateTime, DateTimeZone.UTC);
-        return Days.daysBetween(localDateInUTC, localDateInAccountTimeZone).getDays();
     }
 
     @Override
     public LocalDate computeLocalDateFromFixedAccountOffset(final DateTime targetDateTime) {
-        final DateTime dateWithOriginalAccountTimeZoneOffset = targetDateTime.plusDays(offsetFromUtc);
-        return dateWithOriginalAccountTimeZoneOffset.toLocalDate();
+        return internalTenantContext.toLocalDate(targetDateTime, referenceTime, accountTimeZone);
     }
 
     @Override
     public DateTime computeUTCDateTimeFromLocalDate(final LocalDate invoiceItemEndDate) {
-        //
-        // Since we create the targetDate for next invoice using the date from the notificationQ, we need to make sure
-        // that this datetime once transformed into a LocalDate points to the correct day.
-        //
-        // All we need to do is figure if the transformation from DateTime (point in time) to LocalDate (date in account time zone)
-        // changed the day; if so, when we recompute a UTC date from LocalDate (date in account time zone), we can simply chose a reference
-        // time and apply the offset backward to end up on the right day
-        //
-        // We use clock.getUTCNow() to get the offset with account timezone but that may not be correct
-        // when we transition from standard time and daylight saving time. We could end up with a result
-        // that is slightly in advance and therefore results in a null invoice.
-        // We will fix that by re-inserting ourselves in the notificationQ if we detect that there is no invoice
-        // and yet the subscription is recurring and not cancelled.
-        //
-        return invoiceItemEndDate.toDateTime(referenceTime, DateTimeZone.UTC).plusDays(-offsetFromUtc);
+        return internalTenantContext.toUTCDateTime(invoiceItemEndDate, referenceTime, accountTimeZone);
     }
 
     @Override
diff --git a/util/src/test/java/org/killbill/billing/util/timezone/TestDateAndTimeZoneContext.java b/util/src/test/java/org/killbill/billing/util/timezone/TestDateAndTimeZoneContext.java
index 905b9db..31ec696 100644
--- a/util/src/test/java/org/killbill/billing/util/timezone/TestDateAndTimeZoneContext.java
+++ b/util/src/test/java/org/killbill/billing/util/timezone/TestDateAndTimeZoneContext.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 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:
  *
@@ -22,9 +24,8 @@ import org.joda.time.LocalDate;
 import org.joda.time.format.DateTimeFormatter;
 import org.joda.time.format.ISODateTimeFormat;
 import org.killbill.billing.util.AccountDateAndTimeZoneContext;
-import org.testng.annotations.Test;
-
 import org.killbill.billing.util.UtilTestSuiteNoDB;
+import org.testng.annotations.Test;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
@@ -38,99 +39,12 @@ import static org.testng.Assert.assertTrue;
 public class TestDateAndTimeZoneContext extends UtilTestSuiteNoDB {
 
     private final DateTimeFormatter DATE_TIME_FORMATTER = ISODateTimeFormat.dateTimeParser();
-
-    final String effectiveDateTime1 = "2012-01-20T07:30:42.000Z";
-    final String effectiveDateTime2 = "2012-01-20T08:00:00.000Z";
-    final String effectiveDateTime3 = "2012-01-20T08:45:33.000Z";
-
-    final String effectiveDateTimeA = "2012-01-20T16:30:42.000Z";
-    final String effectiveDateTimeB = "2012-01-20T16:00:00.000Z";
-    final String effectiveDateTimeC = "2012-01-20T15:30:42.000Z";
-
-
-    //
-    // Take an negative timezone offset and a reference time that is less than the offset (07:30:42 < 8)
-    // => to expect a negative offset of one day
-    //
-    @Test(groups = "fast")
-    public void testComputeOffset1() {
-
-        final DateTimeZone timeZone = DateTimeZone.forOffsetHours(-8);
-        final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTime1);
-
-        int offset = DefaultAccountDateAndTimeZoneContext.computeOffsetFromUtc(effectiveDateTime, timeZone, internalCallContext);
-        assertEquals(offset, -1);
-    }
-
-    //
-    // Take an negative timezone offset and a reference time that is equal than the offset (08:00:00 = 8)
-    // => to expect an offset of 0
-    //
-    @Test(groups = "fast")
-    public void testComputeOffset2() {
-
-        final DateTimeZone timeZone = DateTimeZone.forOffsetHours(-8);
-        final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTime2);
-
-        int offset = DefaultAccountDateAndTimeZoneContext.computeOffsetFromUtc(effectiveDateTime, timeZone, internalCallContext);
-        assertEquals(offset, 0);
-    }
-
-    //
-    // Take an negative timezone offset and a reference time that is greater than the offset (08:45:33 > 8)
-    // => to expect an offset of 0
-    //
-    @Test(groups = "fast")
-    public void testComputeOffset3() {
-
-        final DateTimeZone timeZone = DateTimeZone.forOffsetHours(-8);
-        final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTime3);
-
-        int offset = DefaultAccountDateAndTimeZoneContext.computeOffsetFromUtc(effectiveDateTime, timeZone, internalCallContext);
-        assertEquals(offset, 0);
-    }
-
-    //
-    // Take an positive timezone offset and a reference time that closer to the end of the day than the timezone (16:30:42 + 8 > 24)
-    // => to expect a positive offset of one day
-    //
-    @Test(groups = "fast")
-    public void testComputeOffsetA() {
-
-        final DateTimeZone timeZone = DateTimeZone.forOffsetHours(8);
-        final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTimeA);
-
-        int offset = DefaultAccountDateAndTimeZoneContext.computeOffsetFromUtc(effectiveDateTime, timeZone, internalCallContext);
-        assertEquals(offset, 1);
-    }
-
-    //
-    // Take an positive timezone offset and a reference time that brings us exactly at the end of the day (16:00:00 + 8 = 24)
-    // => to expect an offset of 1
-    //
-    @Test(groups = "fast")
-    public void testComputeOffsetB() {
-
-        final DateTimeZone timeZone = DateTimeZone.forOffsetHours(8);
-        final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTimeB);
-
-        int offset = DefaultAccountDateAndTimeZoneContext.computeOffsetFromUtc(effectiveDateTime, timeZone, internalCallContext);
-        assertEquals(offset, 1);
-    }
-
-    //
-    // Take an positive timezone offset and a reference time that further away to the end of the day  (15:30:42 + 8 < 24)
-    // =>  to expect an offset of 0
-    //
-    @Test(groups = "fast")
-    public void testComputeOffsetC() {
-
-        final DateTimeZone timeZone = DateTimeZone.forOffsetHours(8);
-        final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTimeC);
-
-        int offset = DefaultAccountDateAndTimeZoneContext.computeOffsetFromUtc(effectiveDateTime, timeZone, internalCallContext);
-        assertEquals(offset, 0);
-    }
+    private final String effectiveDateTime1 = "2012-01-20T07:30:42.000Z";
+    private final String effectiveDateTime2 = "2012-01-20T08:00:00.000Z";
+    private final String effectiveDateTime3 = "2012-01-20T08:45:33.000Z";
+    private final String effectiveDateTimeA = "2012-01-20T16:30:42.000Z";
+    private final String effectiveDateTimeB = "2012-01-20T16:00:00.000Z";
+    private final String effectiveDateTimeC = "2012-01-20T15:30:42.000Z";
 
     @Test(groups = "fast")
     public void testComputeUTCDateTimeFromLocalDate1() {
@@ -138,35 +52,33 @@ public class TestDateAndTimeZoneContext extends UtilTestSuiteNoDB {
         final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTime1);
 
         final DateTimeZone timeZone = DateTimeZone.forOffsetHours(-8);
-        final DefaultAccountDateAndTimeZoneContext dateContext = new DefaultAccountDateAndTimeZoneContext(effectiveDateTime, timeZone, internalCallContext);
+        final AccountDateAndTimeZoneContext dateContext = new DefaultAccountDateAndTimeZoneContext(effectiveDateTime, timeZone, internalCallContext);
 
         final LocalDate endDate = new LocalDate(2013, 01, 19);
         final DateTime endDateTimeInUTC = dateContext.computeUTCDateTimeFromLocalDate(endDate);
         assertTrue(endDateTimeInUTC.compareTo(effectiveDateTime.plusYears(1)) == 0);
     }
 
-
     @Test(groups = "fast")
     public void testComputeUTCDateTimeFromLocalDate2() {
 
         final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTime2);
 
         final DateTimeZone timeZone = DateTimeZone.forOffsetHours(-8);
-        final DefaultAccountDateAndTimeZoneContext dateContext = new DefaultAccountDateAndTimeZoneContext(effectiveDateTime, timeZone, internalCallContext);
+        final AccountDateAndTimeZoneContext dateContext = new DefaultAccountDateAndTimeZoneContext(effectiveDateTime, timeZone, internalCallContext);
 
         final LocalDate endDate = new LocalDate(2013, 01, 20);
         final DateTime endDateTimeInUTC = dateContext.computeUTCDateTimeFromLocalDate(endDate);
         assertTrue(endDateTimeInUTC.compareTo(effectiveDateTime.plusYears(1)) == 0);
     }
 
-
     @Test(groups = "fast")
     public void testComputeUTCDateTimeFromLocalDate3() {
 
         final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTime3);
 
         final DateTimeZone timeZone = DateTimeZone.forOffsetHours(-8);
-        final DefaultAccountDateAndTimeZoneContext dateContext = new DefaultAccountDateAndTimeZoneContext(effectiveDateTime, timeZone, internalCallContext);
+        final AccountDateAndTimeZoneContext dateContext = new DefaultAccountDateAndTimeZoneContext(effectiveDateTime, timeZone, internalCallContext);
 
         final LocalDate endDate = new LocalDate(2013, 01, 20);
         final DateTime endDateTimeInUTC = dateContext.computeUTCDateTimeFromLocalDate(endDate);
@@ -179,7 +91,7 @@ public class TestDateAndTimeZoneContext extends UtilTestSuiteNoDB {
         final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTimeA);
 
         final DateTimeZone timeZone = DateTimeZone.forOffsetHours(8);
-        final DefaultAccountDateAndTimeZoneContext dateContext = new DefaultAccountDateAndTimeZoneContext(effectiveDateTime, timeZone, internalCallContext);
+        final AccountDateAndTimeZoneContext dateContext = new DefaultAccountDateAndTimeZoneContext(effectiveDateTime, timeZone, internalCallContext);
 
         final LocalDate endDate = new LocalDate(2013, 01, 21);
         final DateTime endDateTimeInUTC = dateContext.computeUTCDateTimeFromLocalDate(endDate);
@@ -192,7 +104,7 @@ public class TestDateAndTimeZoneContext extends UtilTestSuiteNoDB {
         final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTimeB);
 
         final DateTimeZone timeZone = DateTimeZone.forOffsetHours(8);
-        final DefaultAccountDateAndTimeZoneContext dateContext = new DefaultAccountDateAndTimeZoneContext(effectiveDateTime, timeZone, internalCallContext);
+        final AccountDateAndTimeZoneContext dateContext = new DefaultAccountDateAndTimeZoneContext(effectiveDateTime, timeZone, internalCallContext);
 
         final LocalDate endDate = new LocalDate(2013, 01, 21);
         final DateTime endDateTimeInUTC = dateContext.computeUTCDateTimeFromLocalDate(endDate);
@@ -205,36 +117,34 @@ public class TestDateAndTimeZoneContext extends UtilTestSuiteNoDB {
         final DateTime effectiveDateTime = DATE_TIME_FORMATTER.parseDateTime(effectiveDateTimeC);
 
         final DateTimeZone timeZone = DateTimeZone.forOffsetHours(8);
-        final DefaultAccountDateAndTimeZoneContext dateContext = new DefaultAccountDateAndTimeZoneContext(effectiveDateTime, timeZone, internalCallContext);
+        final AccountDateAndTimeZoneContext dateContext = new DefaultAccountDateAndTimeZoneContext(effectiveDateTime, timeZone, internalCallContext);
 
         final LocalDate endDate = new LocalDate(2013, 01, 20);
         final DateTime endDateTimeInUTC = dateContext.computeUTCDateTimeFromLocalDate(endDate);
         assertTrue(endDateTimeInUTC.compareTo(effectiveDateTime.plusYears(1)) == 0);
     }
 
-
     @Test(groups = "fast")
-    public void testComputeTargetDateWithDayLigthSaving() {
+    public void testComputeTargetDateWithDayLightSaving() {
+        final DateTime dateTime1 = new DateTime("2015-01-01T08:01:01.000Z");
+        final DateTime dateTime2 = new DateTime("2015-09-01T08:01:01.000Z");
+        final DateTime dateTime3 = new DateTime("2015-12-01T08:01:01.000Z");
 
+        // Alaska Standard Time
         final DateTimeZone tz = DateTimeZone.forID("America/Juneau");
-        final DateTime originalDateTime = new DateTime("2015-09-01T08:01:01.000Z");
-
-        final DefaultAccountDateAndTimeZoneContext dateAndTimeZoneContext = new DefaultAccountDateAndTimeZoneContext(originalDateTime, tz, internalCallContext);
-
-
-        final LocalDate l1 = dateAndTimeZoneContext.computeLocalDateFromFixedAccountOffset(originalDateTime);
-        assertEquals(l1, new LocalDate("2015-09-01"));
-
-        final LocalDate l1ComputedFromAccountTz = new LocalDate(originalDateTime, tz);
-        assertEquals(l1ComputedFromAccountTz, new LocalDate("2015-09-01"));
-
-        final DateTime newDateTime = new DateTime("2015-12-01T08:01:01.000Z");
-
-        final LocalDate l2 = dateAndTimeZoneContext.computeLocalDateFromFixedAccountOffset(newDateTime);
-        assertEquals(l2, new LocalDate("2015-12-01"));
-
-        final LocalDate l2ComputedFromAccountTz = new LocalDate(newDateTime, tz);
-        assertEquals(l2ComputedFromAccountTz, new LocalDate("2015-11-30"));
 
+        // Time zone is AKDT (UTC-8h) between March and November
+        final DateTime referenceDateTimeWithDST = new DateTime("2015-09-01T08:01:01.000Z");
+        final AccountDateAndTimeZoneContext tzContextWithDST = new DefaultAccountDateAndTimeZoneContext(referenceDateTimeWithDST, tz, internalCallContext);
+        assertEquals(tzContextWithDST.computeLocalDateFromFixedAccountOffset(dateTime1), new LocalDate("2015-01-01"));
+        assertEquals(tzContextWithDST.computeLocalDateFromFixedAccountOffset(dateTime2), new LocalDate("2015-09-01"));
+        assertEquals(tzContextWithDST.computeLocalDateFromFixedAccountOffset(dateTime3), new LocalDate("2015-12-01"));
+
+        // Time zone is AKST (UTC-9h) otherwise
+        final DateTime referenceDateTimeWithoutDST = new DateTime("2015-02-01T08:01:01.000Z");
+        final AccountDateAndTimeZoneContext tzContextWithoutDST = new DefaultAccountDateAndTimeZoneContext(referenceDateTimeWithoutDST, tz, internalCallContext);
+        assertEquals(tzContextWithoutDST.computeLocalDateFromFixedAccountOffset(dateTime1), new LocalDate("2014-12-31"));
+        assertEquals(tzContextWithoutDST.computeLocalDateFromFixedAccountOffset(dateTime2), new LocalDate("2015-08-31"));
+        assertEquals(tzContextWithoutDST.computeLocalDateFromFixedAccountOffset(dateTime3), new LocalDate("2015-11-30"));
     }
 }