diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/scheduler/SchedulingUtil.java b/service/src/main/java/org/whispersystems/textsecuregcm/scheduler/SchedulingUtil.java index e1c688d6b..d45df08ef 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/scheduler/SchedulingUtil.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/scheduler/SchedulingUtil.java @@ -9,7 +9,6 @@ import java.time.Clock; import java.time.Instant; import java.time.LocalTime; import java.time.ZoneId; -import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.zone.ZoneRules; import java.util.Comparator; @@ -19,19 +18,20 @@ import java.util.Objects; import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; +import io.micrometer.core.instrument.Metrics; +import org.whispersystems.textsecuregcm.metrics.MetricsUtil; import org.whispersystems.textsecuregcm.storage.Account; public class SchedulingUtil { + private static final String NO_TIMEZONE_COUNTER_NAME = MetricsUtil.name(SchedulingUtil.class, "noTimezone"); /** * Gets a present or future time at which to send a notification to a device associated with the given account. This * is mainly intended to facilitate scheduling notifications such that they arrive during a recipient's waking hours. *
* This method will attempt to use a timezone derived from the account's phone number to choose an appropriate time - * to send a notification. If a timezone cannot be derived from the account's phone number, then this method will use - * the account's creation time as a hint. As an example, if the account was created at 07:17 in the server's local - * time, we can assume that the account holder was awake at that time of day, and it's likely a safe time to send a - * notification in the absence of other hints. + * to send a notification. If a timezone cannot be derived from the account's phone number, then this method will + * default to the preferred time in the server's timezone. * * @param account the account that will receive the notification * @param preferredTime the preferred local time (e.g. "noon") at which to deliver the notification @@ -46,12 +46,8 @@ public class SchedulingUtil { final ZonedDateTime candidateNotificationTime = getZoneId(account, clock) .map(zoneId -> ZonedDateTime.now(clock.withZone(zoneId)).with(preferredTime)) .orElseGet(() -> { - // We couldn't find a reasonable timezone for the account for some reason, so make an educated guess at a - // reasonable time to send a notification based on the account's creation time. - final Instant accountCreation = Instant.ofEpochMilli(account.getPrimaryDevice().getCreated()); - final LocalTime accountCreationLocalTime = LocalTime.ofInstant(accountCreation, ZoneId.systemDefault()); - - return ZonedDateTime.now(ZoneId.systemDefault()).with(accountCreationLocalTime); + Metrics.counter(NO_TIMEZONE_COUNTER_NAME).increment(); + return ZonedDateTime.now(ZoneId.systemDefault()).with(preferredTime); }); if (candidateNotificationTime.toInstant().isBefore(clock.instant())) { diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/scheduler/SchedulingUtilTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/scheduler/SchedulingUtilTest.java index ee95cc976..96f38ccf2 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/scheduler/SchedulingUtilTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/scheduler/SchedulingUtilTest.java @@ -4,7 +4,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import com.google.i18n.phonenumbers.PhoneNumberToTimeZonesMapper; import com.google.i18n.phonenumbers.PhoneNumberUtil; import java.time.Clock; import java.time.Instant; @@ -13,13 +12,9 @@ import java.time.LocalTime; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; -import java.util.List; -import java.util.Objects; -import java.util.Optional; import com.google.i18n.phonenumbers.Phonenumber; import org.junit.jupiter.api.Test; import org.whispersystems.textsecuregcm.storage.Account; -import org.whispersystems.textsecuregcm.storage.Device; import org.whispersystems.textsecuregcm.util.TestClock; class SchedulingUtilTest { @@ -51,26 +46,23 @@ class SchedulingUtilTest { { final Account account = mock(Account.class); - final Device primaryDevice = mock(Device.class); // The account does not have a phone number that can be connected to a region/time zone when(account.getNumber()).thenReturn("Not a parseable number"); - when(account.getPrimaryDevice()).thenReturn(primaryDevice); - when(primaryDevice.getCreated()) - .thenReturn(ZonedDateTime.of(2024, 7, 10, 9, 53, 12, 0, ZoneId.systemDefault()).toInstant().toEpochMilli()); - final ZonedDateTime beforeNotificationTime = ZonedDateTime.now(ZoneId.systemDefault()).with(LocalTime.of(9, 0)); + final ZonedDateTime beforeNotificationTime = ZonedDateTime.now(ZoneId.systemDefault()).with(LocalTime.of(13, 59)); + final LocalTime preferredNotificationTime = LocalTime.of(14, 0); assertEquals( - beforeNotificationTime.with(LocalTime.of(9, 53, 12)).toInstant(), - SchedulingUtil.getNextRecommendedNotificationTime(account, LocalTime.of(14, 0), + beforeNotificationTime.with(preferredNotificationTime).toInstant(), + SchedulingUtil.getNextRecommendedNotificationTime(account, preferredNotificationTime, Clock.fixed(beforeNotificationTime.toInstant(), ZoneId.systemDefault()))); - final ZonedDateTime afterNotificationTime = ZonedDateTime.now(ZoneId.systemDefault()).with(LocalTime.of(10, 0)); + final ZonedDateTime afterNotificationTime = ZonedDateTime.now(ZoneId.systemDefault()).with(LocalTime.of(14, 1)); assertEquals( - afterNotificationTime.with(LocalTime.of(9, 53, 12)).plusDays(1).toInstant(), - SchedulingUtil.getNextRecommendedNotificationTime(account, LocalTime.of(14, 0), + afterNotificationTime.with(preferredNotificationTime).plusDays(1).toInstant(), + SchedulingUtil.getNextRecommendedNotificationTime(account, preferredNotificationTime, Clock.fixed(afterNotificationTime.toInstant(), ZoneId.systemDefault()))); } }