Merge pull request #1 from crain/develop

Sync with latest Mifos I/O
diff --git a/api/src/main/java/io/mifos/rhythm/api/v1/client/RhythmManager.java b/api/src/main/java/io/mifos/rhythm/api/v1/client/RhythmManager.java
index 4b10d4f..a4f0981 100644
--- a/api/src/main/java/io/mifos/rhythm/api/v1/client/RhythmManager.java
+++ b/api/src/main/java/io/mifos/rhythm/api/v1/client/RhythmManager.java
@@ -17,6 +17,7 @@
 
 import io.mifos.core.api.util.CustomFeignClientsConfiguration;
 import io.mifos.rhythm.api.v1.domain.Beat;
+import io.mifos.rhythm.api.v1.domain.ClockOffset;
 import org.springframework.cloud.netflix.feign.FeignClient;
 import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -31,6 +32,21 @@
 @SuppressWarnings("unused")
 @FeignClient(value="rhythm-v1", path="/rhythm/v1", configuration = CustomFeignClientsConfiguration.class)
 public interface RhythmManager {
+  @RequestMapping(
+      value = "/clockoffset",
+      method = RequestMethod.PUT,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  void setClockOffset(ClockOffset clockOffset);
+
+  @RequestMapping(
+      value = "/clockoffset",
+      method = RequestMethod.GET,
+      produces = MediaType.APPLICATION_JSON_VALUE,
+      consumes = MediaType.APPLICATION_JSON_VALUE
+  )
+  ClockOffset getClockOffset();
 
   @RequestMapping(
           value = "/applications/{applicationidentifier}",
diff --git a/api/src/main/java/io/mifos/rhythm/api/v1/domain/ClockOffset.java b/api/src/main/java/io/mifos/rhythm/api/v1/domain/ClockOffset.java
new file mode 100644
index 0000000..fbb8132
--- /dev/null
+++ b/api/src/main/java/io/mifos/rhythm/api/v1/domain/ClockOffset.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed 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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.rhythm.api.v1.domain;
+
+import org.hibernate.validator.constraints.Range;
+
+import java.util.Objects;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+public class ClockOffset {
+  @Range(min = 0, max = 23)
+  private Integer hours;
+
+  @Range(min = 0, max = 59)
+  private Integer minutes;
+
+  @Range(min = 0, max = 59)
+  private Integer seconds;
+
+  public ClockOffset() {
+    this.hours = 0;
+    this.minutes = 0;
+    this.seconds = 0;
+  }
+
+  public ClockOffset(Integer hours, Integer minutes) {
+    this.hours = hours;
+    this.minutes = minutes;
+    this.seconds = 0;
+  }
+
+  public ClockOffset(Integer hours, Integer minutes, Integer seconds) {
+    this.hours = hours;
+    this.minutes = minutes;
+    this.seconds = seconds;
+  }
+
+  public Integer getHours() {
+    return hours;
+  }
+
+  public void setHours(Integer hours) {
+    this.hours = hours;
+  }
+
+  public Integer getMinutes() {
+    return minutes;
+  }
+
+  public void setMinutes(Integer minutes) {
+    this.minutes = minutes;
+  }
+
+  public Integer getSeconds() {
+    return seconds;
+  }
+
+  public void setSeconds(Integer seconds) {
+    this.seconds = seconds;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+    ClockOffset that = (ClockOffset) o;
+    return Objects.equals(hours, that.hours) &&
+        Objects.equals(minutes, that.minutes) &&
+        Objects.equals(seconds, that.seconds);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(hours, minutes, seconds);
+  }
+
+  @Override
+  public String toString() {
+    return "ClockOffset{" +
+        "hours=" + hours +
+        ", minutes=" + minutes +
+        ", seconds=" + seconds +
+        '}';
+  }
+}
diff --git a/api/src/main/java/io/mifos/rhythm/api/v1/events/EventConstants.java b/api/src/main/java/io/mifos/rhythm/api/v1/events/EventConstants.java
index 878999c..9f84439 100644
--- a/api/src/main/java/io/mifos/rhythm/api/v1/events/EventConstants.java
+++ b/api/src/main/java/io/mifos/rhythm/api/v1/events/EventConstants.java
@@ -27,8 +27,10 @@
   String POST_BEAT = "post-beat";
   String DELETE_APPLICATION = "delete-application";
   String DELETE_BEAT = "delete-beat";
+  String PUT_CLOCKOFFSET = "put-clockoffset";
   String SELECTOR_INITIALIZE = SELECTOR_NAME + " = '" + INITIALIZE + "'";
   String SELECTOR_POST_BEAT = SELECTOR_NAME + " = '" + POST_BEAT + "'";
   String SELECTOR_DELETE_APPLICATION = SELECTOR_NAME + " = '" + DELETE_APPLICATION + "'";
   String SELECTOR_DELETE_BEAT = SELECTOR_NAME + " = '" + DELETE_BEAT + "'";
+  String SELECTOR_PUT_CLOCKOFFSET = SELECTOR_NAME + " = '" + PUT_CLOCKOFFSET + "'";
 }
diff --git a/api/src/test/java/io/mifos/rhythm/api/v1/domain/ClockOffsetTest.java b/api/src/test/java/io/mifos/rhythm/api/v1/domain/ClockOffsetTest.java
new file mode 100644
index 0000000..62bdc52
--- /dev/null
+++ b/api/src/test/java/io/mifos/rhythm/api/v1/domain/ClockOffsetTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed 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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.rhythm.api.v1.domain;
+
+import io.mifos.core.test.domain.ValidationTest;
+import io.mifos.core.test.domain.ValidationTestCase;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * @author Myrle Krantz
+ */
+public class ClockOffsetTest extends ValidationTest<ClockOffset> {
+  public ClockOffsetTest(final ValidationTestCase<ClockOffset> testCase) {
+    super(testCase);
+  }
+
+  @Override
+  protected ClockOffset createValidTestSubject() {
+    return new ClockOffset();
+  }
+
+  @Parameterized.Parameters
+  public static Collection testCases() {
+    final Collection<ValidationTestCase> ret = new ArrayList<>();
+    ret.add(new ValidationTestCase<ClockOffset>("basicCase")
+        .adjustment(x -> {})
+        .valid(true));
+    ret.add(new ValidationTestCase<ClockOffset>("everythingMidRange")
+        .adjustment(x -> {x.setHours(12); x.setMinutes(30); x.setSeconds(30);})
+        .valid(true));
+    ret.add(new ValidationTestCase<ClockOffset>("negativeHours")
+        .adjustment(x -> x.setHours(-1))
+        .valid(false));
+    ret.add(new ValidationTestCase<ClockOffset>("outOfDayHours")
+        .adjustment(x -> x.setHours(24))
+        .valid(false));
+    ret.add(new ValidationTestCase<ClockOffset>("negativeMinutes")
+        .adjustment(x -> x.setMinutes(-1))
+        .valid(false));
+    ret.add(new ValidationTestCase<ClockOffset>("outOfRangeMinutes")
+        .adjustment(x -> x.setMinutes(60))
+        .valid(false));
+    ret.add(new ValidationTestCase<ClockOffset>("negativeSeconds")
+        .adjustment(x -> x.setMinutes(-1))
+        .valid(false));
+    ret.add(new ValidationTestCase<ClockOffset>("outOfRangeSeconds")
+        .adjustment(x -> x.setMinutes(60))
+        .valid(false));
+    return ret;
+  }
+}
\ No newline at end of file
diff --git a/component-test/src/main/java/io/mifos/rhythm/AbstractRhythmTest.java b/component-test/src/main/java/io/mifos/rhythm/AbstractRhythmTest.java
index 0af10b2..ff11d54 100644
--- a/component-test/src/main/java/io/mifos/rhythm/AbstractRhythmTest.java
+++ b/component-test/src/main/java/io/mifos/rhythm/AbstractRhythmTest.java
@@ -25,6 +25,7 @@
 import io.mifos.core.test.listener.EventRecorder;
 import io.mifos.rhythm.api.v1.client.RhythmManager;
 import io.mifos.rhythm.api.v1.domain.Beat;
+import io.mifos.rhythm.api.v1.domain.ClockOffset;
 import io.mifos.rhythm.api.v1.events.BeatEvent;
 import io.mifos.rhythm.api.v1.events.EventConstants;
 import io.mifos.rhythm.service.config.RhythmConfiguration;
@@ -64,10 +65,11 @@
 /**
  * @author Myrle Krantz
  */
+@SuppressWarnings("SpringAutowiredFieldsWarningInspection")
 @RunWith(SpringRunner.class)
 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
         classes = {AbstractRhythmTest.TestConfiguration.class},
-        properties = {"rhythm.user=homer", "rhythm.beatCheckRate=500"}
+        properties = {"rhythm.user=homer", "rhythm.beatCheckRate=1000"}
 )
 public class AbstractRhythmTest {
 
@@ -117,7 +119,7 @@
   EventRecorder eventRecorder;
 
   @MockBean
-  BeatPublisherService beatPublisherServiceSpy;
+  BeatPublisherService beatPublisherServiceMock;
 
   @Autowired
   @Qualifier(LOGGER_NAME)
@@ -148,9 +150,17 @@
     final LocalDateTime now = LocalDateTime.now(ZoneId.of("UTC"));
     int alignmentHour = now.getHour();
     final LocalDateTime expectedBeatTimestamp = getExpectedBeatTimestamp(now, alignmentHour);
+
+    Mockito.doAnswer(new Returns(true)).when(beatPublisherServiceMock).publishBeat(
+            Matchers.eq(beatIdentifier),
+            Matchers.eq(tenantDataStoreContext.getTenantName()),
+            Matchers.eq(applicationIdentifier),
+            Matchers.eq(expectedBeatTimestamp));
+
     final Beat ret = createBeat(applicationIdentifier, beatIdentifier, alignmentHour, expectedBeatTimestamp);
 
-    Mockito.verify(beatPublisherServiceSpy, Mockito.timeout(2_000).times(1)).publishBeat(beatIdentifier, tenantDataStoreContext.getTenantName(), applicationIdentifier, expectedBeatTimestamp);
+    Mockito.verify(beatPublisherServiceMock, Mockito.timeout(2_000).times(1))
+        .publishBeat(beatIdentifier, tenantDataStoreContext.getTenantName(), applicationIdentifier, expectedBeatTimestamp);
 
     return ret;
   }
@@ -182,25 +192,34 @@
     beat.setIdentifier(beatIdentifier);
     beat.setAlignmentHour(alignmentHour);
 
-    Mockito.doAnswer(new AnswerWithDelay<>(2_000, new Returns(Optional.of(PermittableGroupIds.forApplication(applicationIdentifier))))).when(beatPublisherServiceSpy).requestPermissionForBeats(Matchers.eq(tenantIdentifier), Matchers.eq(applicationIdentifier));
-    Mockito.doAnswer(new AnswerWithDelay<>(2_000, new Returns(true))).when(beatPublisherServiceSpy).publishBeat(Matchers.eq(beatIdentifier), Matchers.eq(tenantIdentifier), Matchers.eq(applicationIdentifier),
+    Mockito.doAnswer(new AnswerWithDelay<>(2_000, new Returns(Optional.of(PermittableGroupIds.forApplication(applicationIdentifier))))).when(beatPublisherServiceMock).requestPermissionForBeats(Matchers.eq(tenantIdentifier), Matchers.eq(applicationIdentifier));
+    Mockito.doAnswer(new AnswerWithDelay<>(2_000, new Returns(true))).when(beatPublisherServiceMock).publishBeat(Matchers.eq(beatIdentifier), Matchers.eq(tenantIdentifier), Matchers.eq(applicationIdentifier),
             AdditionalMatchers.or(Matchers.eq(expectedBeatTimestamp), Matchers.eq(getNextTimeStamp(expectedBeatTimestamp))));
 
     this.testSubject.createBeat(applicationIdentifier, beat);
 
-    Assert.assertTrue(this.eventRecorder.wait(EventConstants.POST_BEAT, new BeatEvent(applicationIdentifier, beat.getIdentifier())));
+    Assert.assertTrue(beat.getIdentifier(), this.eventRecorder.wait(EventConstants.POST_BEAT, new BeatEvent(applicationIdentifier, beat.getIdentifier())));
 
-    Mockito.verify(beatPublisherServiceSpy, Mockito.timeout(2_500).times(1)).requestPermissionForBeats(tenantIdentifier, applicationIdentifier);
+    Mockito.verify(beatPublisherServiceMock, Mockito.timeout(2_500).times(1)).requestPermissionForBeats(tenantIdentifier, applicationIdentifier);
 
     return beat;
   }
 
   LocalDateTime getExpectedBeatTimestamp(final LocalDateTime fromTime, final Integer alignmentHour) {
+    return getExpectedBeatTimestamp(fromTime, alignmentHour, new ClockOffset());
+  }
+
+  LocalDateTime getExpectedBeatTimestamp(
+      final LocalDateTime fromTime,
+      final Integer alignmentHour,
+      final ClockOffset clockOffset) {
     final LocalDateTime midnight = fromTime.truncatedTo(ChronoUnit.DAYS);
-    return midnight.plusHours(alignmentHour);
+    return midnight.plusHours(alignmentHour + clockOffset.getHours())
+        .plusMinutes(clockOffset.getMinutes())
+        .plusSeconds(clockOffset.getSeconds());
   }
 
   private LocalDateTime getNextTimeStamp(final LocalDateTime fromTime) {
     return fromTime.plusDays(1);
   }
-}
+}
\ No newline at end of file
diff --git a/component-test/src/main/java/io/mifos/rhythm/TestBeats.java b/component-test/src/main/java/io/mifos/rhythm/TestBeats.java
index 1f57da7..76c28b7 100644
--- a/component-test/src/main/java/io/mifos/rhythm/TestBeats.java
+++ b/component-test/src/main/java/io/mifos/rhythm/TestBeats.java
@@ -17,24 +17,35 @@
 
 import io.mifos.core.api.util.NotFoundException;
 import io.mifos.rhythm.api.v1.domain.Beat;
+import io.mifos.rhythm.api.v1.domain.ClockOffset;
 import io.mifos.rhythm.api.v1.events.BeatEvent;
 import io.mifos.rhythm.api.v1.events.EventConstants;
+import io.mifos.rhythm.service.internal.repository.BeatEntity;
+import io.mifos.rhythm.service.internal.repository.BeatRepository;
 import org.junit.Assert;
 import org.junit.Test;
 import org.mockito.Matchers;
 import org.mockito.Mockito;
+import org.mockito.internal.stubbing.answers.Returns;
+import org.springframework.beans.factory.annotation.Autowired;
 
+import javax.transaction.Transactional;
+import java.time.Clock;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.TimeUnit;
 
 /**
  * @author Myrle Krantz
  */
 public class TestBeats extends AbstractRhythmTest {
+  @SuppressWarnings("SpringAutowiredFieldsWarningInspection")
+  @Autowired
+  BeatRepository beatRepository;
 
   @Test
   public void shouldCreateBeat() throws InterruptedException {
@@ -93,14 +104,14 @@
 
     final LocalDateTime expectedBeatTimestamp = getExpectedBeatTimestamp(now, beat.getAlignmentHour());
 
-    Mockito.doReturn(Optional.of("boop")).when(beatPublisherServiceSpy).requestPermissionForBeats(Matchers.eq(tenantIdentifier), Matchers.eq(applicationIdentifier));
-    Mockito.when(beatPublisherServiceSpy.publishBeat(beatId, tenantIdentifier, applicationIdentifier, expectedBeatTimestamp)).thenReturn(false, false, true);
+    Mockito.doReturn(Optional.of("boop")).when(beatPublisherServiceMock).requestPermissionForBeats(Matchers.eq(tenantIdentifier), Matchers.eq(applicationIdentifier));
+    Mockito.when(beatPublisherServiceMock.publishBeat(beatId, tenantIdentifier, applicationIdentifier, expectedBeatTimestamp)).thenReturn(false, false, true);
 
     this.testSubject.createBeat(applicationIdentifier, beat);
 
     Assert.assertTrue(this.eventRecorder.wait(EventConstants.POST_BEAT, new BeatEvent(applicationIdentifier, beat.getIdentifier())));
 
-    Mockito.verify(beatPublisherServiceSpy, Mockito.timeout(10_000).times(3)).publishBeat(beatId, tenantIdentifier, applicationIdentifier, expectedBeatTimestamp);
+    Mockito.verify(beatPublisherServiceMock, Mockito.timeout(10_000).times(3)).publishBeat(beatId, tenantIdentifier, applicationIdentifier, expectedBeatTimestamp);
   }
 
   @Test
@@ -122,4 +133,94 @@
 
     beats.forEach(x -> Assert.assertTrue(allEntities.contains(x)));
   }
+
+  @Test
+  public void shouldBeatForMissingDays() throws InterruptedException {
+    final String applicationIdentifier = "funnybusiness-v6";
+    final String beatIdentifier = "fiddlebeat";
+    createBeatForThisHour(applicationIdentifier, beatIdentifier);
+
+    final int daysAgo = 10;
+    final LocalDateTime nextBeat = setBack(applicationIdentifier, beatIdentifier, daysAgo);
+
+    for (int i = daysAgo; i > 0; i--) {
+      Mockito.verify(beatPublisherServiceMock, Mockito.timeout(4_000).times(1))
+          .publishBeat(
+              beatIdentifier,
+              tenantDataStoreContext.getTenantName(),
+              applicationIdentifier,
+              nextBeat.minusDays(i));
+    }
+  }
+
+  @Test
+  public void clockOffsetShouldEffectBeatTiming() throws InterruptedException {
+    final String tenantIdentifier = tenantDataStoreContext.getTenantName();
+    final String applicationIdentifier = "funnybusiness-v7";
+    final String beatIdentifier = "fiddlebeat0";
+
+    final ClockOffset initialClockOffset = this.testSubject.getClockOffset();
+    Assert.assertEquals(Integer.valueOf(0), initialClockOffset.getHours());
+    Assert.assertEquals(Integer.valueOf(0), initialClockOffset.getMinutes());
+    Assert.assertEquals(Integer.valueOf(0), initialClockOffset.getSeconds());
+
+    final LocalDateTime now = LocalDateTime.now(Clock.systemUTC());
+    final ClockOffset offsetToNow = new ClockOffset(now.getHour(), now.getMinute(), now.getSecond());
+    this.testSubject.setClockOffset(offsetToNow);
+
+    Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_CLOCKOFFSET, offsetToNow));
+    TimeUnit.SECONDS.sleep(1);
+    final ClockOffset changedClockOffset = this.testSubject.getClockOffset();
+    Assert.assertEquals(offsetToNow, changedClockOffset);
+
+    final Beat beat = new Beat();
+    beat.setIdentifier(beatIdentifier);
+    beat.setAlignmentHour(0);
+
+    final LocalDateTime expectedBeatTimestamp = getExpectedBeatTimestamp(now, 0, offsetToNow);
+
+    Mockito.doReturn(Optional.of("boop")).when(beatPublisherServiceMock)
+        .requestPermissionForBeats(Matchers.eq(tenantIdentifier), Matchers.eq(applicationIdentifier));
+    Mockito.when(beatPublisherServiceMock
+        .publishBeat(beatIdentifier, tenantIdentifier, applicationIdentifier, expectedBeatTimestamp))
+        .thenReturn(true);
+
+    this.testSubject.createBeat(applicationIdentifier, beat);
+
+    Assert.assertTrue(this.eventRecorder.wait(EventConstants.POST_BEAT, new BeatEvent(applicationIdentifier, beat.getIdentifier())));
+
+    Mockito.verify(beatPublisherServiceMock, Mockito.timeout(10_000).times(1)).publishBeat(beatIdentifier, tenantIdentifier, applicationIdentifier, expectedBeatTimestamp);
+
+    //Set back to zero'ed clock offset so you don't break the rest of the tests.
+    this.testSubject.setClockOffset(initialClockOffset);
+    Assert.assertTrue(this.eventRecorder.wait(EventConstants.PUT_CLOCKOFFSET, initialClockOffset));
+    TimeUnit.SECONDS.sleep(1);
+  }
+
+  @Transactional
+  LocalDateTime setBack(
+      final String applicationIdentifier,
+      final String beatIdentifier,
+      final int daysAgo) {
+
+    final BeatEntity beatEntity = beatRepository.findByTenantIdentifierAndApplicationIdentifierAndBeatIdentifier(
+        tenantDataStoreContext.getTenantName(),
+        applicationIdentifier,
+        beatIdentifier).orElseThrow(IllegalStateException::new);
+
+    Mockito.reset(beatPublisherServiceMock);
+    Mockito.doAnswer(new Returns(true)).when(beatPublisherServiceMock)
+        .publishBeat(
+            Matchers.eq(beatIdentifier),
+            Matchers.eq(tenantDataStoreContext.getTenantName()),
+            Matchers.eq(applicationIdentifier),
+            Matchers.any(LocalDateTime.class));
+    final LocalDateTime nextBeat = beatEntity.getNextBeat();
+
+    beatEntity.setNextBeat(nextBeat.minusDays(daysAgo));
+
+    beatRepository.save(beatEntity);
+
+    return nextBeat;
+  }
 }
\ No newline at end of file
diff --git a/component-test/src/main/java/io/mifos/rhythm/listener/ClockOffsetEventListener.java b/component-test/src/main/java/io/mifos/rhythm/listener/ClockOffsetEventListener.java
new file mode 100644
index 0000000..890df79
--- /dev/null
+++ b/component-test/src/main/java/io/mifos/rhythm/listener/ClockOffsetEventListener.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed 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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.rhythm.listener;
+
+import io.mifos.core.lang.config.TenantHeaderFilter;
+import io.mifos.core.test.listener.EventRecorder;
+import io.mifos.rhythm.api.v1.domain.ClockOffset;
+import io.mifos.rhythm.api.v1.events.EventConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jms.annotation.JmsListener;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+@Component
+public class ClockOffsetEventListener {
+  private final EventRecorder eventRecorder;
+
+  @Autowired
+  public ClockOffsetEventListener(@SuppressWarnings("SpringJavaAutowiringInspection") final EventRecorder eventRecorder) {
+    super();
+    this.eventRecorder = eventRecorder;
+  }
+
+  @JmsListener(
+      subscription = EventConstants.DESTINATION,
+      destination = EventConstants.DESTINATION,
+      selector = EventConstants.SELECTOR_PUT_CLOCKOFFSET
+  )
+  public void onChangeClockOffset(
+      @Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+      final String payload) {
+    this.eventRecorder.event(tenant, EventConstants.PUT_CLOCKOFFSET, payload, ClockOffset.class);
+  }
+}
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/command/ChangeClockOffsetCommand.java b/service/src/main/java/io/mifos/rhythm/service/internal/command/ChangeClockOffsetCommand.java
new file mode 100644
index 0000000..36104bd
--- /dev/null
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/command/ChangeClockOffsetCommand.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed 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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.rhythm.service.internal.command;
+
+import io.mifos.rhythm.api.v1.domain.ClockOffset;
+
+/**
+ * @author Myrle Krantz
+ */
+public class ChangeClockOffsetCommand {
+  private final String tenantIdentifier;
+
+  private final ClockOffset instance;
+
+  public ChangeClockOffsetCommand(
+      final String tenantIdentifier,
+      final ClockOffset instance) {
+    this.tenantIdentifier = tenantIdentifier;
+    this.instance = instance;
+  }
+
+  public String getTenantIdentifier() {
+    return tenantIdentifier;
+  }
+
+  public ClockOffset getInstance() {
+    return instance;
+  }
+
+  @Override
+  public String toString() {
+    return "ChangeClockOffsetCommand{" +
+        "tenantIdentifier='" + tenantIdentifier + '\'' +
+        ", instance=" + instance +
+        '}';
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/BeatCommandHandler.java b/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/BeatCommandHandler.java
index e0bf47b..922d831 100644
--- a/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/BeatCommandHandler.java
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/BeatCommandHandler.java
@@ -18,7 +18,7 @@
 import io.mifos.core.command.annotation.Aggregate;
 import io.mifos.core.command.annotation.CommandHandler;
 import io.mifos.core.command.annotation.CommandLogLevel;
-import io.mifos.core.lang.ServiceException;
+import io.mifos.rhythm.api.v1.domain.ClockOffset;
 import io.mifos.rhythm.api.v1.events.BeatEvent;
 import io.mifos.rhythm.api.v1.events.EventConstants;
 import io.mifos.rhythm.service.ServiceConstants;
@@ -27,14 +27,13 @@
 import io.mifos.rhythm.service.internal.mapper.BeatMapper;
 import io.mifos.rhythm.service.internal.repository.BeatEntity;
 import io.mifos.rhythm.service.internal.repository.BeatRepository;
+import io.mifos.rhythm.service.internal.service.ClockOffsetService;
 import io.mifos.rhythm.service.internal.service.IdentityPermittableGroupService;
 import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.util.Optional;
-
 /**
  * @author Myrle Krantz
  */
@@ -43,18 +42,21 @@
 public class BeatCommandHandler {
   private final IdentityPermittableGroupService identityPermittableGroupService;
   private final BeatRepository beatRepository;
+  private final ClockOffsetService clockOffsetService;
   private final EventHelper eventHelper;
   private final Logger logger;
 
   @Autowired
   public BeatCommandHandler(
-          final IdentityPermittableGroupService identityPermittableGroupService,
-          final BeatRepository beatRepository,
-          final EventHelper eventHelper,
-          @Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger) {
+      final IdentityPermittableGroupService identityPermittableGroupService,
+      final BeatRepository beatRepository,
+      final ClockOffsetService clockOffsetService,
+      final EventHelper eventHelper,
+      @Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger) {
     super();
     this.identityPermittableGroupService = identityPermittableGroupService;
     this.beatRepository = beatRepository;
+    this.clockOffsetService = clockOffsetService;
     this.eventHelper = eventHelper;
     this.logger = logger;
   }
@@ -73,34 +75,30 @@
   //stuff that should happen in the transaction.
   @SuppressWarnings("WeakerAccess")
   @Transactional
-  public void processCreateBeatCommand(CreateBeatCommand createBeatCommand) {
+  public void processCreateBeatCommand(final CreateBeatCommand createBeatCommand) {
     final boolean applicationHasRequestForAccessPermission = identityPermittableGroupService.checkThatApplicationHasRequestForAccessPermission(
-            createBeatCommand.getTenantIdentifier(), createBeatCommand.getApplicationIdentifier());
+        createBeatCommand.getTenantIdentifier(), createBeatCommand.getApplicationIdentifier());
     if (!applicationHasRequestForAccessPermission) {
       logger.info("Rhythm needs permission to publish beats to application, but couldn't request that permission for tenant '{}' and application '{}'.",
-              createBeatCommand.getTenantIdentifier(), createBeatCommand.getApplicationIdentifier());
+          createBeatCommand.getTenantIdentifier(), createBeatCommand.getApplicationIdentifier());
     }
+    final ClockOffset clockOffset = clockOffsetService.findByTenantIdentifier(createBeatCommand.getTenantIdentifier());
 
     final BeatEntity entity = BeatMapper.map(
-            createBeatCommand.getTenantIdentifier(),
-            createBeatCommand.getApplicationIdentifier(),
-            createBeatCommand.getInstance());
+        createBeatCommand.getTenantIdentifier(),
+        createBeatCommand.getApplicationIdentifier(),
+        createBeatCommand.getInstance(),
+        clockOffset);
     this.beatRepository.save(entity);
   }
 
   @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.NONE)
   @Transactional
   public void process(final DeleteBeatCommand deleteBeatCommand) {
-    final Optional<BeatEntity> toDelete = this.beatRepository.findByTenantIdentifierAndApplicationIdentifierAndBeatIdentifier(
+    this.beatRepository.deleteByTenantIdentifierAndApplicationIdentifierAndBeatIdentifier(
             deleteBeatCommand.getTenantIdentifier(),
             deleteBeatCommand.getApplicationIdentifier(),
             deleteBeatCommand.getIdentifier());
-    final BeatEntity toDeleteForReal
-            = toDelete.orElseThrow(() -> ServiceException.notFound(
-                    "Beat for the application ''" + deleteBeatCommand.getApplicationIdentifier() +
-                            "'' with the identifier ''" + deleteBeatCommand.getIdentifier() + "'' not found."));
-
-    this.beatRepository.delete(toDeleteForReal);
 
     eventHelper.sendEvent(EventConstants.DELETE_BEAT, deleteBeatCommand.getTenantIdentifier(),
             new BeatEvent(deleteBeatCommand.getApplicationIdentifier(), deleteBeatCommand.getIdentifier()));
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/ClockOffsetCommandHandler.java b/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/ClockOffsetCommandHandler.java
new file mode 100644
index 0000000..08c39cf
--- /dev/null
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/command/handler/ClockOffsetCommandHandler.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed 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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.rhythm.service.internal.command.handler;
+
+import io.mifos.core.command.annotation.Aggregate;
+import io.mifos.core.command.annotation.CommandHandler;
+import io.mifos.core.command.annotation.CommandLogLevel;
+import io.mifos.rhythm.api.v1.events.EventConstants;
+import io.mifos.rhythm.service.ServiceConstants;
+import io.mifos.rhythm.service.internal.command.ChangeClockOffsetCommand;
+import io.mifos.rhythm.service.internal.mapper.ClockOffsetMapper;
+import io.mifos.rhythm.service.internal.repository.ClockOffsetEntity;
+import io.mifos.rhythm.service.internal.repository.ClockOffsetRepository;
+import io.mifos.rhythm.service.internal.service.Drummer;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+
+import javax.transaction.Transactional;
+import java.util.Optional;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings("unused")
+@Aggregate
+public class ClockOffsetCommandHandler {
+  private final ClockOffsetRepository clockOffsetRepository;
+  private final Drummer drummer;
+  private final EventHelper eventHelper;
+  private final Logger logger;
+
+  @Autowired
+  public ClockOffsetCommandHandler(
+      final ClockOffsetRepository clockOffsetRepository,
+      final Drummer drummer,
+      final EventHelper eventHelper,
+      @Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger) {
+    super();
+    this.clockOffsetRepository = clockOffsetRepository;
+    this.drummer = drummer;
+    this.eventHelper = eventHelper;
+    this.logger = logger;
+  }
+
+  @Transactional
+  @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.NONE)
+  public void process(final ChangeClockOffsetCommand changeClockOffsetCommand) {
+
+    final Optional<ClockOffsetEntity> oldClockOffsetEntity =
+        clockOffsetRepository.findByTenantIdentifier(changeClockOffsetCommand.getTenantIdentifier());
+
+    final ClockOffsetEntity newOffsetEntity = ClockOffsetMapper.map(
+        changeClockOffsetCommand.getTenantIdentifier(),
+        changeClockOffsetCommand.getInstance(),
+        oldClockOffsetEntity);
+
+    clockOffsetRepository.save(newOffsetEntity);
+
+    drummer.realignAllBeatsForTenant(
+        changeClockOffsetCommand.getTenantIdentifier(),
+        oldClockOffsetEntity.orElseGet(ClockOffsetEntity::new),
+        newOffsetEntity);
+
+    logger.info("Sending change clock offset event.");
+    eventHelper.sendEvent(
+        EventConstants.PUT_CLOCKOFFSET,
+        changeClockOffsetCommand.getTenantIdentifier(),
+        changeClockOffsetCommand.getInstance());
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/mapper/BeatMapper.java b/service/src/main/java/io/mifos/rhythm/service/internal/mapper/BeatMapper.java
index 2f37f95..8e31e58 100644
--- a/service/src/main/java/io/mifos/rhythm/service/internal/mapper/BeatMapper.java
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/mapper/BeatMapper.java
@@ -16,10 +16,12 @@
 package io.mifos.rhythm.service.internal.mapper;
 
 import io.mifos.rhythm.api.v1.domain.Beat;
+import io.mifos.rhythm.api.v1.domain.ClockOffset;
 import io.mifos.rhythm.service.internal.repository.BeatEntity;
+import io.mifos.rhythm.service.internal.repository.ClockOffsetEntity;
 
+import java.time.Clock;
 import java.time.LocalDateTime;
-import java.time.ZoneId;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.List;
@@ -42,14 +44,41 @@
     return ret;
   }
 
-  static BeatEntity map(final String tenantIdentifier, final String applicationIdentifier, final Beat instance) {
+  static BeatEntity map(
+      final String tenantIdentifier,
+      final String applicationIdentifier,
+      final Beat instance,
+      final ClockOffset clockOffset) {
     final BeatEntity ret = new BeatEntity();
     ret.setBeatIdentifier(instance.getIdentifier());
     ret.setTenantIdentifier(tenantIdentifier);
     ret.setApplicationIdentifier(applicationIdentifier);
     ret.setAlignmentHour(instance.getAlignmentHour());
-    //First beat is today.  If it's in the past, it will be created nearly immediately.
-    ret.setNextBeat(LocalDateTime.now(ZoneId.of("UTC")).truncatedTo(ChronoUnit.DAYS).plusHours(instance.getAlignmentHour()));
+    //First beat is today.  If it's in the past, it will be published nearly immediately.
+    ret.setNextBeat(alignDateTime(
+        LocalDateTime.now(Clock.systemUTC()),
+        instance.getAlignmentHour(),
+        clockOffset));
     return ret;
   }
+
+  static LocalDateTime alignDateTime(
+      final LocalDateTime localDateTime,
+      final int alignmentHour,
+      final ClockOffset clockOffset) {
+    return localDateTime.truncatedTo(ChronoUnit.DAYS)
+        .plusHours(alignmentHour + clockOffset.getHours())
+        .plusMinutes(clockOffset.getMinutes())
+        .plusSeconds(clockOffset.getSeconds());
+  }
+
+  static LocalDateTime alignDateTime(
+      final LocalDateTime localDateTime,
+      final int alignmentHour,
+      final ClockOffsetEntity clockOffset) {
+    return localDateTime.truncatedTo(ChronoUnit.DAYS)
+        .plusHours(alignmentHour + clockOffset.getHours())
+        .plusMinutes(clockOffset.getMinutes())
+        .plusSeconds(clockOffset.getSeconds());
+  }
 }
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/mapper/ClockOffsetMapper.java b/service/src/main/java/io/mifos/rhythm/service/internal/mapper/ClockOffsetMapper.java
new file mode 100644
index 0000000..2188fda
--- /dev/null
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/mapper/ClockOffsetMapper.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed 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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.rhythm.service.internal.mapper;
+
+import io.mifos.rhythm.api.v1.domain.ClockOffset;
+import io.mifos.rhythm.service.internal.repository.ClockOffsetEntity;
+
+import java.util.Optional;
+
+/**
+ * @author Myrle Krantz
+ */
+public interface ClockOffsetMapper {
+  static ClockOffset map(final ClockOffsetEntity entity) {
+    final ClockOffset ret = new ClockOffset();
+    ret.setHours(entity.getHours());
+    ret.setMinutes(entity.getMinutes());
+    ret.setSeconds(entity.getSeconds());
+    return ret;
+  }
+
+  @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+  static ClockOffsetEntity map(
+      final String tenantIdentifier,
+      final ClockOffset instance,
+      final Optional<ClockOffsetEntity> existingClockOffset) {
+    final ClockOffsetEntity ret = new ClockOffsetEntity();
+    existingClockOffset.ifPresent(x -> ret.setId(x.getId()));
+    ret.setTenantIdentifier(tenantIdentifier);
+    ret.setHours(instance.getHours());
+    ret.setMinutes(instance.getMinutes());
+    ret.setSeconds(instance.getSeconds());
+    return ret;
+  }
+}
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/repository/ApplicationRepository.java b/service/src/main/java/io/mifos/rhythm/service/internal/repository/ApplicationRepository.java
index 022b64a..b195b93 100644
--- a/service/src/main/java/io/mifos/rhythm/service/internal/repository/ApplicationRepository.java
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/repository/ApplicationRepository.java
@@ -16,8 +16,10 @@
 package io.mifos.rhythm.service.internal.repository;
 
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Lock;
 import org.springframework.stereotype.Repository;
 
+import javax.persistence.LockModeType;
 import java.util.Optional;
 
 /**
@@ -25,6 +27,7 @@
  */
 @Repository
 public interface ApplicationRepository extends JpaRepository<ApplicationEntity, Long> {
+  @Lock(LockModeType.PESSIMISTIC_WRITE)
   void deleteByTenantIdentifierAndApplicationIdentifier(String tenantIdentifier, String applicationIdentifier);
   Optional<ApplicationEntity> findByTenantIdentifierAndApplicationIdentifier(String tenantIdentifier, String applicationIdentifier);
 }
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/repository/BeatRepository.java b/service/src/main/java/io/mifos/rhythm/service/internal/repository/BeatRepository.java
index 05f0a4e..88b51dd 100644
--- a/service/src/main/java/io/mifos/rhythm/service/internal/repository/BeatRepository.java
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/repository/BeatRepository.java
@@ -16,8 +16,10 @@
 package io.mifos.rhythm.service.internal.repository;
 
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Lock;
 import org.springframework.stereotype.Repository;
 
+import javax.persistence.LockModeType;
 import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Optional;
@@ -28,11 +30,20 @@
  */
 @Repository
 public interface BeatRepository extends JpaRepository<BeatEntity, Long> {
+  @Lock(LockModeType.PESSIMISTIC_WRITE)
   void deleteByTenantIdentifierAndApplicationIdentifier
-          (String tenantIdentifier, String applicationIdentifier);
+      (String tenantIdentifier, String applicationIdentifier);
+  @Lock(LockModeType.PESSIMISTIC_WRITE)
+  void deleteByTenantIdentifierAndApplicationIdentifierAndBeatIdentifier
+      (String tenantIdentifier,
+       String applicationIdentifier,
+       String beatIdentifier);
   List<BeatEntity> findByTenantIdentifierAndApplicationIdentifier
           (String tenantIdentifier, String applicationIdentifier);
   Optional<BeatEntity> findByTenantIdentifierAndApplicationIdentifierAndBeatIdentifier
           (String tenantIdentifier, String applicationIdentifier, String beatIdentifier);
+  @Lock(LockModeType.PESSIMISTIC_WRITE)
   Stream<BeatEntity> findByNextBeatBefore(LocalDateTime currentTime);
+  @Lock(LockModeType.PESSIMISTIC_WRITE)
+  Stream<BeatEntity> findByTenantIdentifier(String tenantIdentifier);
 }
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/repository/ClockOffsetEntity.java b/service/src/main/java/io/mifos/rhythm/service/internal/repository/ClockOffsetEntity.java
new file mode 100644
index 0000000..b7ebe36
--- /dev/null
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/repository/ClockOffsetEntity.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed 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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.rhythm.service.internal.repository;
+
+import javax.persistence.*;
+
+/**
+ * @author Myrle Krantz
+ */
+@SuppressWarnings({"unused", "WeakerAccess"})
+@Entity
+@Table(name = "khepri_clockoffsets")
+public class ClockOffsetEntity {
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Column(name = "id")
+  private Long id;
+
+  @Column(name = "tenant_identifier", nullable = false)
+  private String tenantIdentifier;
+
+  @Column(name = "hours", nullable = false)
+  private Integer hours = 0;
+
+  @Column(name = "minutes", nullable = false)
+  private Integer minutes = 0;
+
+  @Column(name = "seconds", nullable = false)
+  private Integer seconds = 0;
+
+  public Long getId() {
+    return id;
+  }
+
+  public void setId(Long id) {
+    this.id = id;
+  }
+
+  public String getTenantIdentifier() {
+    return tenantIdentifier;
+  }
+
+  public void setTenantIdentifier(String tenantIdentifier) {
+    this.tenantIdentifier = tenantIdentifier;
+  }
+
+  public Integer getHours() {
+    return hours;
+  }
+
+  public void setHours(Integer hours) {
+    this.hours = hours;
+  }
+
+  public Integer getMinutes() {
+    return minutes;
+  }
+
+  public void setMinutes(Integer minutes) {
+    this.minutes = minutes;
+  }
+
+  public Integer getSeconds() {
+    return seconds;
+  }
+
+  public void setSeconds(Integer seconds) {
+    this.seconds = seconds;
+  }
+}
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/repository/ClockOffsetRepository.java b/service/src/main/java/io/mifos/rhythm/service/internal/repository/ClockOffsetRepository.java
new file mode 100644
index 0000000..3005457
--- /dev/null
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/repository/ClockOffsetRepository.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed 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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.rhythm.service.internal.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+
+public interface ClockOffsetRepository extends JpaRepository<ClockOffsetEntity, Long> {
+  Optional<ClockOffsetEntity> findByTenantIdentifier(String tenantIdentifier);
+}
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/service/BeatPublisherService.java b/service/src/main/java/io/mifos/rhythm/service/internal/service/BeatPublisherService.java
index a070b20..14ef663 100644
--- a/service/src/main/java/io/mifos/rhythm/service/internal/service/BeatPublisherService.java
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/service/BeatPublisherService.java
@@ -18,6 +18,7 @@
 import io.mifos.anubis.api.v1.domain.AllowedOperation;
 import io.mifos.core.api.context.AutoUserContext;
 import io.mifos.core.api.util.ApiFactory;
+import io.mifos.core.api.util.InvalidTokenException;
 import io.mifos.core.lang.ApplicationName;
 import io.mifos.core.lang.AutoTenantContext;
 import io.mifos.core.lang.DateConverter;
@@ -97,12 +98,15 @@
         publishBeatPermission.setPermittableEndpointGroupIdentifier(consumerPermittableGroupIdentifier);
         try {
           applicationPermissionRequestCreator.createApplicationPermission(rhythmApplicationName.toString(), publishBeatPermission);
+          logger.debug("Successfully requested permission to send beats to application '{}' under tenant '{}'.", applicationIdentifier, tenantIdentifier);
+        }
+        catch (final InvalidTokenException e) {
+          logger.error("Failed to request permission for application {}, in tenant {} because rhythm does not have permission to access identity.", applicationIdentifier, tenantIdentifier, e);
         }
         catch (final ApplicationPermissionAlreadyExistsException e) {
           logger.debug("Failed to request permission for application {}, in tenant {} because the request already exists. {} was thrown.", applicationIdentifier, tenantIdentifier, e);
         }
 
-        logger.debug("Successfully requested permission to send beats to application '{}' under tenant '{}'.", applicationIdentifier, tenantIdentifier);
         return Optional.of(consumerPermittableGroupIdentifier);
       }
     }
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/service/ClockOffsetService.java b/service/src/main/java/io/mifos/rhythm/service/internal/service/ClockOffsetService.java
new file mode 100644
index 0000000..1700f5a
--- /dev/null
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/service/ClockOffsetService.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed 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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.rhythm.service.internal.service;
+
+import io.mifos.rhythm.api.v1.domain.ClockOffset;
+import io.mifos.rhythm.service.internal.mapper.ClockOffsetMapper;
+import io.mifos.rhythm.service.internal.repository.ClockOffsetRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author Myrle Krantz
+ */
+@Service
+public class ClockOffsetService {
+  final private ClockOffsetRepository clockOffsetRepository;
+
+  @Autowired
+  public ClockOffsetService(final ClockOffsetRepository clockOffsetRepository) {
+    this.clockOffsetRepository = clockOffsetRepository;
+  }
+
+  public ClockOffset findByTenantIdentifier(final String tenantIdentifier) {
+    return clockOffsetRepository.findByTenantIdentifier(tenantIdentifier)
+        .map(ClockOffsetMapper::map)
+        .orElseGet(ClockOffset::new); //If none is set, use 0,0,0
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/service/Drummer.java b/service/src/main/java/io/mifos/rhythm/service/internal/service/Drummer.java
index 88a21ca..c5802df 100644
--- a/service/src/main/java/io/mifos/rhythm/service/internal/service/Drummer.java
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/service/Drummer.java
@@ -15,9 +15,12 @@
  */
 package io.mifos.rhythm.service.internal.service;
 
+import io.mifos.rhythm.api.v1.domain.ClockOffset;
 import io.mifos.rhythm.service.ServiceConstants;
+import io.mifos.rhythm.service.internal.mapper.BeatMapper;
 import io.mifos.rhythm.service.internal.repository.BeatEntity;
 import io.mifos.rhythm.service.internal.repository.BeatRepository;
+import io.mifos.rhythm.service.internal.repository.ClockOffsetEntity;
 import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -26,44 +29,44 @@
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
 
-import javax.annotation.Nonnull;
+import java.time.Clock;
 import java.time.LocalDateTime;
-import java.time.ZoneId;
-import java.time.temporal.ChronoUnit;
-import java.util.Optional;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 
 /**
  * @author Myrle Krantz
  */
-@SuppressWarnings({"unused", "WeakerAccess"})
 @Component
 public class Drummer {
   private final IdentityPermittableGroupService identityPermittableGroupService;
   private final BeatPublisherService beatPublisherService;
   private final BeatRepository beatRepository;
+  private final ClockOffsetService clockOffsetService;
   private final Logger logger;
 
   @Autowired
   public Drummer(
-          final IdentityPermittableGroupService identityPermittableGroupService,
-          final BeatPublisherService beatPublisherService,
-          final BeatRepository beatRepository,
-          @Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger) {
+      final IdentityPermittableGroupService identityPermittableGroupService,
+      final BeatPublisherService beatPublisherService,
+      final BeatRepository beatRepository,
+      final ClockOffsetService clockOffsetService,
+      @Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger) {
     this.identityPermittableGroupService = identityPermittableGroupService;
     this.beatPublisherService = beatPublisherService;
     this.beatRepository = beatRepository;
+    this.clockOffsetService = clockOffsetService;
     this.logger = logger;
   }
 
   @Scheduled(initialDelayString = "${rhythm.beatCheckRate}", fixedRateString = "${rhythm.beatCheckRate}")
   @Transactional
   public synchronized void checkForBeatsNeeded() {
+    logger.info("checkForBeatsNeeded begin.");
     //In it's current form this function cannot be run in multiple instances of the same service.  We need to get
     //locking on selected entries corrected here, before this will work.
     try {
-      final LocalDateTime now = LocalDateTime.now(ZoneId.of("UTC"));
+      final LocalDateTime now = LocalDateTime.now(Clock.systemUTC());
       //Get beats from the last two hours in case restart/start happens close to hour begin.
       final Stream<BeatEntity> beats = beatRepository.findByNextBeatBefore(now);
       beats.forEach((beat) -> {
@@ -75,17 +78,18 @@
         }
         else {
           logger.info("Checking if beat {} needs publishing.", beat);
-          final Optional<LocalDateTime> nextBeat = checkBeatForPublish(
+          final LocalDateTime nextBeat = checkBeatForPublish(
                   now,
                   beat.getBeatIdentifier(),
                   beat.getTenantIdentifier(),
                   beat.getApplicationIdentifier(),
                   beat.getAlignmentHour(),
                   beat.getNextBeat());
-          nextBeat.ifPresent(y -> {
-            beat.setNextBeat(y);
+          if (!nextBeat.equals(beat.getNextBeat())) {
+            beat.setNextBeat(nextBeat);
             beatRepository.save(beat);
-          });
+          }
+          logger.info("Beat updated to {}.", beat);
         }
       });
 
@@ -94,50 +98,67 @@
       logger.info("InvalidDataAccessResourceUsageException in check for scheduled beats, probably " +
               "because initialize hasn't been called yet. {}", e);
     }
+    logger.info("checkForBeatsNeeded end.");
   }
 
-  public Optional<LocalDateTime> checkBeatForPublish(
-          final LocalDateTime now,
-          final String beatIdentifier,
-          final String tenantIdentifier,
-          final String applicationIdentifier,
-          final Integer alignmentHour,
-          final LocalDateTime nextBeat) {
-    return checkBeatForPublishHelper(now, alignmentHour, nextBeat,
+  @Transactional
+  public synchronized void realignAllBeatsForTenant(
+      final String tenantIdentifier,
+      final ClockOffsetEntity oldClockOffset,
+      final ClockOffsetEntity newClockOffset)
+  {
+    final Stream<BeatEntity> beatsToAdjust = beatRepository.findByTenantIdentifier(tenantIdentifier);
+    beatsToAdjust.forEach(x -> {
+      //Need to subtract old clock offset, because for large clock offsets and large alignments,
+      //time can "skip" into the next day through realignment.
+      final LocalDateTime oldBeatNextBeat = x.getNextBeat()
+          .minusHours(oldClockOffset.getHours())
+          .minusMinutes(oldClockOffset.getMinutes())
+          .minusSeconds(oldClockOffset.getSeconds());
+      x.setNextBeat(BeatMapper.alignDateTime(
+          oldBeatNextBeat,
+          x.getAlignmentHour(),
+          newClockOffset));
+      beatRepository.save(x);
+    });
+  }
+
+  private LocalDateTime checkBeatForPublish(
+      final LocalDateTime now,
+      final String beatIdentifier,
+      final String tenantIdentifier,
+      final String applicationIdentifier,
+      final Integer alignmentHour,
+      final LocalDateTime nextBeat) {
+    final ClockOffset clockOffset = clockOffsetService.findByTenantIdentifier(tenantIdentifier);
+    return checkBeatForPublishHelper(now, alignmentHour, nextBeat, clockOffset,
             x -> beatPublisherService.publishBeat(beatIdentifier, tenantIdentifier, applicationIdentifier, x));
   }
 
   //Helper is separated from original function so that it can be unit-tested separately from publishBeat.
-  static Optional<LocalDateTime> checkBeatForPublishHelper(
+  static LocalDateTime checkBeatForPublishHelper(
           final LocalDateTime now,
           final Integer alignmentHour,
           final LocalDateTime nextBeat,
+          final ClockOffset clockOffset,
           final Predicate<LocalDateTime> publishSucceeded) {
-    final long numberOfBeatPublishesNeeded = getNumberOfBeatPublishesNeeded(now, nextBeat);
-    if (numberOfBeatPublishesNeeded == 0)
-      return Optional.empty();
+    LocalDateTime beatToPublish = nextBeat;
+    for (;
+         !beatToPublish.isAfter(now);
+         beatToPublish = incrementToAlignment(beatToPublish, alignmentHour, clockOffset))
+    {
+      if (!publishSucceeded.test(beatToPublish))
+        break;
+    }
 
-    final Optional<LocalDateTime> firstFailedBeat = Stream.iterate(nextBeat,
-            x -> incrementToAlignment(x, alignmentHour))
-            .limit(numberOfBeatPublishesNeeded)
-            .filter(x -> !publishSucceeded.test(x))
-            .findFirst();
-
-    if (firstFailedBeat.isPresent())
-      return firstFailedBeat;
-    else
-      return Optional.of(incrementToAlignment(now, alignmentHour));
+    return beatToPublish;
   }
 
-  static long getNumberOfBeatPublishesNeeded(final LocalDateTime now, final @Nonnull LocalDateTime nextBeat) {
-    if (nextBeat.isAfter(now))
-      return 0;
-
-    return Math.max(1, nextBeat.until(now, ChronoUnit.DAYS));
-  }
-
-  static LocalDateTime incrementToAlignment(final LocalDateTime toIncrement, final Integer alignmentHour)
+  static LocalDateTime incrementToAlignment(
+      final LocalDateTime toIncrement,
+      final Integer alignmentHour,
+      final ClockOffset clockOffset)
   {
-    return toIncrement.plusDays(1).truncatedTo(ChronoUnit.DAYS).plusHours(alignmentHour);
+    return BeatMapper.alignDateTime(toIncrement.plusDays(1), alignmentHour, clockOffset);
   }
 }
\ No newline at end of file
diff --git a/service/src/main/java/io/mifos/rhythm/service/internal/service/IdentityPermittableGroupService.java b/service/src/main/java/io/mifos/rhythm/service/internal/service/IdentityPermittableGroupService.java
index 441def7..5c33f4c 100644
--- a/service/src/main/java/io/mifos/rhythm/service/internal/service/IdentityPermittableGroupService.java
+++ b/service/src/main/java/io/mifos/rhythm/service/internal/service/IdentityPermittableGroupService.java
@@ -15,9 +15,12 @@
  */
 package io.mifos.rhythm.service.internal.service;
 
+import io.mifos.rhythm.service.ServiceConstants;
 import io.mifos.rhythm.service.internal.repository.ApplicationEntity;
 import io.mifos.rhythm.service.internal.repository.ApplicationRepository;
+import org.slf4j.Logger;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.dao.DataIntegrityViolationException;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
@@ -32,19 +35,23 @@
 public class IdentityPermittableGroupService {
   private final ApplicationRepository applicationRepository;
   private final BeatPublisherService beatPublisherService;
+  private final Logger logger;
 
   @Autowired
   public IdentityPermittableGroupService(
-          final ApplicationRepository applicationRepository,
-          final BeatPublisherService beatPublisherService) {
+      final ApplicationRepository applicationRepository,
+      final BeatPublisherService beatPublisherService,
+      @Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger) {
     this.applicationRepository = applicationRepository;
     this.beatPublisherService = beatPublisherService;
+    this.logger = logger;
   }
 
   public synchronized boolean checkThatApplicationHasRequestForAccessPermission(
           final String tenantIdentifier,
           final String applicationIdentifier) {
     try {
+      logger.info("checkThatApplicationHasRequestForAccessPermission begin");
       return checkThatApplicationHasRequestForAccessPermissionHelper(tenantIdentifier, applicationIdentifier);
     }
     catch (final DataIntegrityViolationException e) {
diff --git a/service/src/main/java/io/mifos/rhythm/service/rest/BeatRestController.java b/service/src/main/java/io/mifos/rhythm/service/rest/BeatRestController.java
index a795a8b..63ab292 100644
--- a/service/src/main/java/io/mifos/rhythm/service/rest/BeatRestController.java
+++ b/service/src/main/java/io/mifos/rhythm/service/rest/BeatRestController.java
@@ -81,7 +81,7 @@
           @PathVariable("beatidentifier") final String beatIdentifier) {
     return this.beatService.findByIdentifier(tenantIdentifier, applicationIdentifier, beatIdentifier)
             .map(ResponseEntity::ok)
-            .orElseThrow(() -> ServiceException.notFound("Instance with identifier " + applicationIdentifier + " doesn't exist."));
+            .orElseThrow(() -> ServiceException.notFound("Instance with identifier ''" + applicationIdentifier + "'' doesn''t exist."));
   }
 
   @Permittable(value = AcceptedTokenType.SYSTEM, permittedEndpoint = "/applications/{applicationidentifier}/beats", acceptTokenIntendedForForeignApplication = true) //Allow apps to use this endpoint in their provisioning code.
diff --git a/service/src/main/java/io/mifos/rhythm/service/rest/ClockOffsetRestController.java b/service/src/main/java/io/mifos/rhythm/service/rest/ClockOffsetRestController.java
new file mode 100644
index 0000000..9793731
--- /dev/null
+++ b/service/src/main/java/io/mifos/rhythm/service/rest/ClockOffsetRestController.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2017 Kuelap, Inc.
+ *
+ * Licensed 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
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.mifos.rhythm.service.rest;
+
+import io.mifos.anubis.annotation.AcceptedTokenType;
+import io.mifos.anubis.annotation.Permittable;
+import io.mifos.core.command.gateway.CommandGateway;
+import io.mifos.rhythm.api.v1.domain.ClockOffset;
+import io.mifos.rhythm.service.internal.command.ChangeClockOffsetCommand;
+import io.mifos.rhythm.service.internal.service.ClockOffsetService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+
+import static io.mifos.core.lang.config.TenantHeaderFilter.TENANT_HEADER;
+
+/**
+ * @author Myrle Krantz
+ */
+@RestController
+@RequestMapping("/clockoffset")
+public class ClockOffsetRestController {
+  private final CommandGateway commandGateway;
+  private final ClockOffsetService clockOffsetService;
+
+  @Autowired
+  public ClockOffsetRestController(
+      final CommandGateway commandGateway,
+      final ClockOffsetService clockOffsetService) {
+    super();
+    this.commandGateway = commandGateway;
+    this.clockOffsetService = clockOffsetService;
+  }
+
+  @Permittable(value = AcceptedTokenType.SYSTEM)
+  @RequestMapping(
+      method = RequestMethod.GET,
+      consumes = MediaType.ALL_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<ClockOffset> getClockOffset(@RequestHeader(TENANT_HEADER) final String tenantIdentifier) {
+    return ResponseEntity.ok(this.clockOffsetService.findByTenantIdentifier(tenantIdentifier));
+  }
+
+  @Permittable(value = AcceptedTokenType.SYSTEM)
+  @RequestMapping(
+      method = RequestMethod.PUT,
+      consumes = MediaType.APPLICATION_JSON_VALUE,
+      produces = MediaType.APPLICATION_JSON_VALUE
+  )
+  public
+  @ResponseBody
+  ResponseEntity<Void> setClockOffset(
+      @RequestHeader(TENANT_HEADER) final String tenantIdentifier,
+      @RequestBody @Valid final ClockOffset instance) throws InterruptedException {
+    this.commandGateway.process(new ChangeClockOffsetCommand(tenantIdentifier, instance));
+    return ResponseEntity.accepted().build();
+  }
+}
diff --git a/service/src/main/resources/db/migrations/mariadb/V2__tenant_clock_offset.sql b/service/src/main/resources/db/migrations/mariadb/V2__tenant_clock_offset.sql
new file mode 100644
index 0000000..c6b3662
--- /dev/null
+++ b/service/src/main/resources/db/migrations/mariadb/V2__tenant_clock_offset.sql
@@ -0,0 +1,25 @@
+--
+-- Copyright 2017 Kuelap, Inc.
+--
+-- Licensed 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
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+CREATE TABLE khepri_clockoffsets (
+  id BIGINT NOT NULL AUTO_INCREMENT,
+  tenant_identifier        VARCHAR(32)  NOT NULL,
+  hours                    INT          NOT NULL,
+  minutes                  INT          NOT NULL,
+  seconds                  INT          NOT NULL,
+  CONSTRAINT khepri_clockoffsets_uq UNIQUE (tenant_identifier),
+  CONSTRAINT khepri_clockoffsets_pk PRIMARY KEY (id)
+);
\ No newline at end of file
diff --git a/service/src/test/java/io/mifos/rhythm/service/internal/service/DrummerTest.java b/service/src/test/java/io/mifos/rhythm/service/internal/service/DrummerTest.java
index 3cb376d..b9e899c 100644
--- a/service/src/test/java/io/mifos/rhythm/service/internal/service/DrummerTest.java
+++ b/service/src/test/java/io/mifos/rhythm/service/internal/service/DrummerTest.java
@@ -15,77 +15,191 @@
  */
 package io.mifos.rhythm.service.internal.service;
 
+import io.mifos.rhythm.api.v1.domain.ClockOffset;
 import org.junit.Assert;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 import org.mockito.Mockito;
 
+import java.time.Clock;
 import java.time.LocalDateTime;
-import java.time.ZoneId;
 import java.time.temporal.ChronoUnit;
-import java.util.Optional;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
 import java.util.function.Predicate;
 
 /**
  * @author Myrle Krantz
  */
+@RunWith(Parameterized.class)
 public class DrummerTest {
+  static class TestCase {
+    final String description;
+    LocalDateTime now = LocalDateTime.now(Clock.systemUTC());
+    LocalDateTime nextBeat = now.minusDays(1).truncatedTo(ChronoUnit.DAYS);
+    int alignmentHour = 0;
+    ClockOffset clockOffset = new ClockOffset();
+    LocalDateTime expectedIncrementedBeat = nextBeat.plusDays(1);
+    LocalDateTime expectedNextBeatAfterPublish = now.plusDays(1).truncatedTo(ChronoUnit.DAYS);
+    int expectedBeatPublishCount = 2;
+
+    TestCase(final String description) {
+      this.description = description;
+    }
+
+    TestCase now(final LocalDateTime now) {
+      this.now = now;
+      return this;
+    }
+
+    TestCase nextBeat(final LocalDateTime newVal) {
+      this.nextBeat = newVal;
+      return this;
+    }
+
+    TestCase alignmentHour(final int newVal) {
+      this.alignmentHour = newVal;
+      return this;
+    }
+
+    TestCase clockOffset(final ClockOffset newVal) {
+      this.clockOffset = newVal;
+      return this;
+    }
+
+    TestCase expectedIncrementedBeat(final LocalDateTime newVal) {
+      this.expectedIncrementedBeat = newVal;
+      return this;
+    }
+
+    TestCase expectedNextBeatAfterPublish(final LocalDateTime newVal) {
+      this.expectedNextBeatAfterPublish = newVal;
+      return this;
+    }
+
+    TestCase expectedBeatPublishCount(final int newVal) {
+      this.expectedBeatPublishCount = newVal;
+      return this;
+    }
+  }
+
+  @Parameterized.Parameters
+  public static Collection testCases() {
+    final Collection<TestCase> ret = new ArrayList<>();
+    final TestCase basicCase = new TestCase("basicCase");
+    ret.add(basicCase);
+    ret.add(new TestCase("3daysBack")
+        .nextBeat(basicCase.now.minusDays(3))
+        .expectedIncrementedBeat(basicCase.now.minusDays(2).truncatedTo(ChronoUnit.DAYS))
+        .expectedBeatPublishCount(4)); //Four because "now" gets published too.
+    ret.add(new TestCase("inFuture")
+        .nextBeat(basicCase.now.plusDays(1))
+        .expectedIncrementedBeat(basicCase.now.plusDays(2).truncatedTo(ChronoUnit.DAYS))
+        .expectedNextBeatAfterPublish(basicCase.now.plusDays(1))
+        .expectedBeatPublishCount(0));
+    ret.add(new TestCase("nonZeroClockOffset")
+        .now(LocalDateTime.of(2017, 12, 18, 15, 5, 2))
+        .nextBeat(LocalDateTime.of(2017, 12, 17, 15, 0, 0))
+        .clockOffset(new ClockOffset(15, 5, 2))
+        .expectedIncrementedBeat(LocalDateTime.of(2017, 12, 18, 15, 5, 2))
+        .expectedNextBeatAfterPublish(LocalDateTime.of(2017, 12, 19, 15, 5, 2))
+        .expectedBeatPublishCount(2));
+    ret.add(new TestCase("nonZeroAlignmentHour")
+        .now(LocalDateTime.of(2017, 12, 18, 15, 5, 2))
+        .nextBeat(LocalDateTime.of(2017, 12, 17, 15, 0, 0))
+        .alignmentHour(4)
+        .expectedIncrementedBeat(LocalDateTime.of(2017, 12, 18, 4, 0, 0))
+        .expectedNextBeatAfterPublish(LocalDateTime.of(2017, 12, 19, 4, 0, 0))
+        .expectedBeatPublishCount(2));
+    ret.add(new TestCase("clockOffsetAndAlignmentHour")
+        .now(LocalDateTime.of(2017, 12, 18, 15, 5, 2))
+        .nextBeat(LocalDateTime.of(2017, 12, 17, 15, 0, 0))
+        .alignmentHour(5)
+        .clockOffset(new ClockOffset(15, 5, 2))
+        .expectedIncrementedBeat(LocalDateTime.of(2017, 12, 18, 20, 5, 2))
+        .expectedNextBeatAfterPublish(LocalDateTime.of(2017, 12, 18, 20, 5, 2))
+        .expectedBeatPublishCount(1));
+    return ret;
+  }
+
+  private TestCase testCase;
+
+  public DrummerTest(final TestCase testCase) {
+    this.testCase = testCase;
+  }
 
   @Test
   public void incrementToAlignment() {
-    final LocalDateTime now = LocalDateTime.now(ZoneId.of("UTC"));
-    final LocalDateTime tomorrow = Drummer.incrementToAlignment(now, 3);
+    final LocalDateTime incrementedBeat = Drummer.incrementToAlignment(
+        testCase.nextBeat,
+        testCase.alignmentHour,
+        testCase.clockOffset);
 
-    Assert.assertEquals(tomorrow.minusDays(1).truncatedTo(ChronoUnit.DAYS), now.truncatedTo(ChronoUnit.DAYS));
-    Assert.assertEquals(3, tomorrow.getHour());
+    Assert.assertEquals(
+        "expectedIncrementedBeat",
+        testCase.expectedIncrementedBeat,
+        incrementedBeat);
   }
 
   @Test
-  public void getNumberOfBeatPublishesNeeded() {
-    final LocalDateTime now = LocalDateTime.now(ZoneId.of("UTC"));
-    final long eventsNeeded3 = Drummer.getNumberOfBeatPublishesNeeded(now, now.minus(3, ChronoUnit.DAYS));
-    Assert.assertEquals(3, eventsNeeded3);
+  public void checkBeatForPublishHelper()
+  {
+    final Set<LocalDateTime> calledForTimes = new HashSet<>();
+    final LocalDateTime nextBeatAfterPublish = Drummer.checkBeatForPublishHelper(
+        testCase.now,
+        testCase.alignmentHour,
+        testCase.nextBeat,
+        testCase.clockOffset,
+        x -> {
+          calledForTimes.add(x);
+          return true;
+        });
+    Assert.assertEquals(
+        "expectedNextBeatAfterPublish",
+        testCase.expectedNextBeatAfterPublish,
+        nextBeatAfterPublish);
+    Assert.assertEquals(
+        "expectedBeatPublishCount",
+        testCase.expectedBeatPublishCount,
+        calledForTimes.size());
 
-    final long eventsNeededPast = Drummer.getNumberOfBeatPublishesNeeded(now, now.plus(1, ChronoUnit.DAYS));
-    Assert.assertEquals(0, eventsNeededPast);
-
-    final long eventsNeededNow = Drummer.getNumberOfBeatPublishesNeeded(now, now.minus(2, ChronoUnit.MINUTES));
-    Assert.assertEquals(1, eventsNeededNow);
   }
 
   @Test
-  public void checkBeatForPublishAllBeatsSucceed() {
-    final LocalDateTime now = LocalDateTime.now(ZoneId.of("UTC"));
-    final Optional<LocalDateTime> ret = Drummer.checkBeatForPublishHelper(now, 0, now.minus(3, ChronoUnit.DAYS), x -> true);
-    Assert.assertEquals(Optional.of(Drummer.incrementToAlignment(now, 0)), ret);
-  }
-
-  @Test
-  public void checkBeatForPublishFirstFails() {
-    final LocalDateTime now = LocalDateTime.now(ZoneId.of("UTC"));
-    final LocalDateTime nextBeat = now.minus(3, ChronoUnit.DAYS);
-    @SuppressWarnings("unchecked") final Predicate<LocalDateTime> produceBeatsMock = Mockito.mock(Predicate.class);
-    Mockito.when(produceBeatsMock.test(nextBeat)).thenReturn(false);
-    final Optional<LocalDateTime> ret = Drummer.checkBeatForPublishHelper(now, 0, nextBeat, produceBeatsMock);
-    Assert.assertEquals(Optional.of(nextBeat), ret);
+  public void checkBeatForPublishHelperFirstFails() {
+    @SuppressWarnings("unchecked")
+    final Predicate<LocalDateTime> produceBeatsMock = Mockito.mock(Predicate.class);
+    Mockito.when(produceBeatsMock.test(testCase.nextBeat)).thenReturn(false);
+    final LocalDateTime nextBeatAfterPublish = Drummer.checkBeatForPublishHelper(
+        testCase.now,
+        testCase.alignmentHour,
+        testCase.nextBeat,
+        testCase.clockOffset,
+        produceBeatsMock);
+    Assert.assertEquals("nextBeat", testCase.nextBeat, nextBeatAfterPublish);
   }
 
   @Test
   public void checkBeatForPublishSecondFails() {
-    final LocalDateTime now = LocalDateTime.now(ZoneId.of("UTC"));
-    final LocalDateTime nextBeat = now.minus(3, ChronoUnit.DAYS);
-    final LocalDateTime secondBeat = Drummer.incrementToAlignment(nextBeat, 0);
-    @SuppressWarnings("unchecked") final Predicate<LocalDateTime> produceBeatsMock = Mockito.mock(Predicate.class);
-    Mockito.when(produceBeatsMock.test(nextBeat)).thenReturn(true);
-    Mockito.when(produceBeatsMock.test(secondBeat)).thenReturn(false);
-    final Optional<LocalDateTime> ret = Drummer.checkBeatForPublishHelper(now, 0, nextBeat, produceBeatsMock);
-    Assert.assertEquals(Optional.of(secondBeat), ret);
-  }
+    if (testCase.expectedBeatPublishCount < 2)
+      return;
 
-  @Test
-  public void checkBeatForPublishNoneNeeded() {
-    final LocalDateTime now = LocalDateTime.now(ZoneId.of("UTC"));
-    final Optional<LocalDateTime> ret = Drummer.checkBeatForPublishHelper(now, 0, now.plus(1, ChronoUnit.DAYS),
-            x -> { Assert.fail("Pubish shouldn't be called"); return true; });
-    Assert.assertEquals(Optional.empty(), ret);
+    final LocalDateTime secondBeat = Drummer.incrementToAlignment(
+        testCase.nextBeat,
+        testCase.alignmentHour,
+        testCase.clockOffset);
+    @SuppressWarnings("unchecked") final Predicate<LocalDateTime> produceBeatsMock = Mockito.mock(Predicate.class);
+    Mockito.when(produceBeatsMock.test(testCase.nextBeat)).thenReturn(true);
+    Mockito.when(produceBeatsMock.test(secondBeat)).thenReturn(false);
+    final LocalDateTime nextBeatAfterPublish = Drummer.checkBeatForPublishHelper(
+        testCase.now,
+        testCase.alignmentHour,
+        testCase.nextBeat,
+        testCase.clockOffset,
+        produceBeatsMock);
+    Assert.assertEquals(secondBeat, nextBeatAfterPublish);
   }
 }
\ No newline at end of file
diff --git a/shared.gradle b/shared.gradle
index dc7dd71..0170643 100644
--- a/shared.gradle
+++ b/shared.gradle
@@ -65,7 +65,9 @@
         xml = 'XML_STYLE'
         yml = 'SCRIPT_STYLE'
         yaml = 'SCRIPT_STYLE'
+        uxf = 'XML_STYLE'
     }
     ext.year = Calendar.getInstance().get(Calendar.YEAR)
-    ext.name = 'The Mifos Initiative'
+    ext.name = 'Kuelap, Inc'
+    skipExistingHeaders true
 }