diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..48dbd2b --- /dev/null +++ b/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + nl.andrewlalis + simply-scheduled + 1.0-SNAPSHOT + + + UTF-8 + 11 + 11 + 11 + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + + + + + + org.junit + junit-bom + 5.7.1 + pom + import + + + + + + + + org.junit.jupiter + junit-jupiter + 5.7.1 + test + + + \ No newline at end of file diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 0000000..5f23a1c --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,5 @@ +module simply_scheduled { + exports nl.andrewlalis.simply_scheduled; + exports nl.andrewlalis.simply_scheduled.schedule; + opens nl.andrewlalis.simply_scheduled; +} \ No newline at end of file diff --git a/src/main/java/nl/andrewlalis/simply_scheduled/BasicScheduler.java b/src/main/java/nl/andrewlalis/simply_scheduled/BasicScheduler.java new file mode 100644 index 0000000..8cae0ab --- /dev/null +++ b/src/main/java/nl/andrewlalis/simply_scheduled/BasicScheduler.java @@ -0,0 +1,46 @@ +package nl.andrewlalis.simply_scheduled; + +import nl.andrewlalis.simply_scheduled.schedule.Schedule; + +import java.time.Clock; +import java.time.Instant; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class BasicScheduler implements Scheduler { + private ScheduledExecutorService executorService; + private final Clock clock; + + public BasicScheduler(Clock clock) { + this.clock = clock; + this.executorService = new ScheduledThreadPoolExecutor(1); + } + + public BasicScheduler() { + this(Clock.systemDefaultZone()); + } + + @Override + public void addTask(Runnable task, Schedule schedule) { + Instant nextExecution = schedule.getNextExecutionTime(this.clock.instant()); + long diff = nextExecution.toEpochMilli() - System.currentTimeMillis(); + if (diff < 1) return; // Exit immediately, if the next scheduled execution is in the past. + this.executorService.schedule(task, diff, TimeUnit.MILLISECONDS); + } + + @Override + public void start() { + } + + @Override + public void stop(boolean force) { + if (this.executorService != null) { + if (force) { + this.executorService.shutdownNow(); + } else { + this.executorService.shutdown(); + } + } + } +} diff --git a/src/main/java/nl/andrewlalis/simply_scheduled/Scheduler.java b/src/main/java/nl/andrewlalis/simply_scheduled/Scheduler.java new file mode 100644 index 0000000..b7f7835 --- /dev/null +++ b/src/main/java/nl/andrewlalis/simply_scheduled/Scheduler.java @@ -0,0 +1,9 @@ +package nl.andrewlalis.simply_scheduled; + +import nl.andrewlalis.simply_scheduled.schedule.Schedule; + +public interface Scheduler { + void addTask(Runnable task, Schedule schedule); + void start(); + void stop(boolean force); +} diff --git a/src/main/java/nl/andrewlalis/simply_scheduled/schedule/DailySchedule.java b/src/main/java/nl/andrewlalis/simply_scheduled/schedule/DailySchedule.java new file mode 100644 index 0000000..aa8748d --- /dev/null +++ b/src/main/java/nl/andrewlalis/simply_scheduled/schedule/DailySchedule.java @@ -0,0 +1,28 @@ +package nl.andrewlalis.simply_scheduled.schedule; + +import java.time.*; + +public class DailySchedule implements Schedule { + private final ZoneId zoneId; + private final LocalTime time; + + public DailySchedule(LocalTime time) { + this(time, ZoneId.systemDefault()); + } + + public DailySchedule(LocalTime time, ZoneId zoneId) { + this.time = time; + this.zoneId = zoneId; + } + + @Override + public Instant getNextExecutionTime(Instant referenceInstant) { + ZonedDateTime currentTime = referenceInstant.atZone(this.zoneId); + LocalDate currentDay = LocalDate.from(referenceInstant); + ZonedDateTime sameDayExecution = currentDay.atTime(this.time).atZone(this.zoneId); + if (sameDayExecution.isBefore(currentTime)) { + return sameDayExecution.toInstant(); + } + return sameDayExecution.plusDays(1).toInstant(); + } +} diff --git a/src/main/java/nl/andrewlalis/simply_scheduled/schedule/HourlySchedule.java b/src/main/java/nl/andrewlalis/simply_scheduled/schedule/HourlySchedule.java new file mode 100644 index 0000000..1ccca9d --- /dev/null +++ b/src/main/java/nl/andrewlalis/simply_scheduled/schedule/HourlySchedule.java @@ -0,0 +1,33 @@ +package nl.andrewlalis.simply_scheduled.schedule; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; + +public class HourlySchedule implements Schedule { + private final ZoneId zoneId; + private final int minute; + + public HourlySchedule(int minute) { + this(minute, ZoneId.systemDefault()); + } + + public HourlySchedule(int minute, ZoneId zoneId) { + if (minute < 0 || minute > 59) { + throw new IllegalArgumentException("Minute must be in the range [0, 59]."); + } + this.zoneId = zoneId; + this.minute = minute; + } + + @Override + public Instant getNextExecutionTime(Instant referenceInstant) { + ZonedDateTime currentTime = referenceInstant.atZone(this.zoneId); + int currentMinute = currentTime.getMinute(); + if (currentMinute < this.minute) { + return currentTime.truncatedTo(ChronoUnit.MINUTES).plusMinutes(this.minute - currentMinute).toInstant(); + } + return currentTime.plusHours(1).plusMinutes(this.minute).truncatedTo(ChronoUnit.MINUTES).toInstant(); + } +} diff --git a/src/main/java/nl/andrewlalis/simply_scheduled/schedule/Schedule.java b/src/main/java/nl/andrewlalis/simply_scheduled/schedule/Schedule.java new file mode 100644 index 0000000..0a97cad --- /dev/null +++ b/src/main/java/nl/andrewlalis/simply_scheduled/schedule/Schedule.java @@ -0,0 +1,7 @@ +package nl.andrewlalis.simply_scheduled.schedule; + +import java.time.Instant; + +public interface Schedule { + Instant getNextExecutionTime(Instant referenceInstant); +} diff --git a/src/test/java/nl/andrewlalis/simply_scheduled/SchedulerTest.java b/src/test/java/nl/andrewlalis/simply_scheduled/SchedulerTest.java new file mode 100644 index 0000000..8f4d414 --- /dev/null +++ b/src/test/java/nl/andrewlalis/simply_scheduled/SchedulerTest.java @@ -0,0 +1,33 @@ +package nl.andrewlalis.simply_scheduled; + +import nl.andrewlalis.simply_scheduled.schedule.HourlySchedule; +import org.junit.jupiter.api.Test; + +import java.time.Clock; +import java.time.Instant; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SchedulerTest { + + @Test + void testSchedule() { + Clock clock = Clock.fixed(Instant.now(), ZoneOffset.UTC); + Scheduler scheduler = new BasicScheduler(clock); + LocalTime time = LocalTime.now(); + int secondsLeft = 60 - time.getSecond() + 1; + AtomicBoolean flag = new AtomicBoolean(false); + scheduler.addTask(() -> flag.set(true), new HourlySchedule(time.getMinute() + 1)); + scheduler.start(); + System.out.printf("Waiting %d seconds for task to run...", secondsLeft); + try { + Thread.sleep(secondsLeft * 1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + assertTrue(flag.get()); + } +}