Skip to content

Commit

Permalink
feat: integrate cron with type
Browse files Browse the repository at this point in the history
  • Loading branch information
teletha committed Oct 9, 2024
1 parent b255c98 commit 78c4321
Show file tree
Hide file tree
Showing 4 changed files with 557 additions and 525 deletions.
102 changes: 68 additions & 34 deletions src/main/java/kiss/Cron.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
*/
package kiss;

import java.time.DayOfWeek;
import java.time.YearMonth;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand All @@ -26,9 +26,13 @@
*/
class Cron {
private static final Pattern FORMAT = Pattern
.compile("(?:(?:(\\*)|(\\?|L)) | ([0-9]{1,2}|[a-z]{3,3})(?:(L|W) | -([0-9]{1,2}|[a-z]{3,3}))?)(?:(/|\\#)([0-9]{1,7}))?", Pattern.CASE_INSENSITIVE | Pattern.COMMENTS);
.compile("(?:(?:(\\*)|(\\?|L|LW)) | ([0-9]{1,2}|[a-z]{3,3})(?:(L|W) | -([0-9]{1,2}|[a-z]{3,3}))?)(?:(/|\\#)([0-9]{1,7}))?", Pattern.CASE_INSENSITIVE | Pattern.COMMENTS);

final Type type;
private ChronoField field;

private int min;

private List<String> names;

/**
* [0] - start
Expand All @@ -37,17 +41,18 @@ class Cron {
* [3] - modifier
* [4] - modifierForIncrement
*/
final List<int[]> parts = new ArrayList();
private List<int[]> parts = new ArrayList();

/**
* Constructs a new Field instance based on the given type and expression.
*
* @param type The Type of this field.
* @param expr The expression string for this field.
* @throws IllegalArgumentException if the expression is invalid.
*/
Cron(Type type, String expr) {
this.type = type;
Cron(ChronoField field, int min, int max, String names, String modifier, String increment, String expr) {
this.field = field;
this.min = min;
this.names = Arrays.asList(names.split("(?<=\\G...)")); // split every three letters

for (String range : expr.split(",")) {
Matcher m = FORMAT.matcher(range);
Expand All @@ -61,22 +66,23 @@ class Cron {

int[] part = {-1, -1, -1, 0, 0};
if (start != null) {
part[0] = type.map(start);
part[0] = map(start);
part[3] = mod == null ? 0 : mod.charAt(0);
if (end != null) {
part[1] = type.map(end);
part[1] = map(end);
part[2] = 1;
} else if (inc != null) {
part[1] = type.max;
part[1] = max;
} else {
part[1] = part[0];
}
} else if (m.group(1) != null) {
part[0] = type.min;
part[1] = type.max;
part[0] = min;
part[1] = max;
part[2] = 1;
} else if (m.group(2) != null) {
part[3] = m.group(2).charAt(0);
mod = m.group(2);
part[3] = mod.length() == 2 ? 'W' : mod.charAt(0);
} else {
error(range);
}
Expand All @@ -87,14 +93,15 @@ class Cron {
}

// validate range
if ((part[0] != -1 && part[0] < type.min) || (part[1] != -1 && part[1] > type.max) || (part[0] != -1 && part[1] != -1 && part[0] > part[1])) {
if ((part[0] != -1 && part[0] < min) || (part[1] != -1 && part[1] > max) || (part[0] != -1 && part[1] != -1 && part[0] > part[1])) {
error(range);
}

// validate part
if (part[3] != 0 && Arrays.binarySearch(type.modifier, part[3]) < 0) {
if (part[3] != 0 && modifier.indexOf(part[3]) == -1) {
error(String.valueOf((char) part[3]));
} else if (part[4] != 0 && Arrays.binarySearch(type.increment, part[4]) < 0) {
}
if (part[4] != 0 && increment.indexOf(part[4]) == -1) {
error(String.valueOf((char) part[4]));
}
parts.add(part);
Expand All @@ -103,39 +110,66 @@ class Cron {
Collections.sort(parts, (x, y) -> Integer.compare(x[0], y[0]));
}

/**
* Maps a string representation to its corresponding numeric value for this field.
*
* @param name The string representation to map.
* @return The corresponding numeric value.
*/
private int map(String name) {
int index = names.indexOf(name.toUpperCase());
if (index != -1) {
return index + min;
}
int value = Integer.parseInt(name);
return value == 0 && field == ChronoField.DAY_OF_WEEK ? 7 : value;
}

/**
* Checks if the given date matches this field's constraints.
*
* @param date The LocalDate to check.
* @return true if the date matches, false otherwise.
*/
boolean matches(ZonedDateTime date) {
int day = date.getDayOfMonth();
int dow = date.getDayOfWeek().getValue();

for (int[] part : parts) {
if (part[3] == 'L') {
YearMonth ym = YearMonth.of(date.getYear(), date.getMonth().getValue());
if (type.max == 7) {
return date.getDayOfWeek() == DayOfWeek.of(part[0]) && date.getDayOfMonth() > (ym.lengthOfMonth() - 7);
if (field == ChronoField.DAY_OF_WEEK) {
return dow == part[0] && day > (ym.lengthOfMonth() - 7);
} else {
return date.getDayOfMonth() == (ym.lengthOfMonth() - (part[0] == -1 ? 0 : part[0]));
return day == (ym.lengthOfMonth() - (part[0] == -1 ? 0 : part[0]));
}
} else if (part[3] == 'W') {
if (date.getDayOfWeek().getValue() <= 5) {
if (date.getDayOfMonth() == part[0]) {
if (dow <= 5) {
int last = date.with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();
int target = part[0] == -1 ? last : part[0];

if (day == target) {
return true;
} else if (date.getDayOfWeek().getValue() == 5) {
return date.plusDays(1).getDayOfMonth() == part[0];
} else if (date.getDayOfWeek().getValue() == 1) {
return date.minusDays(1).getDayOfMonth() == part[0];
} else if (dow == 5) {
int diff = last - day;
if (2 >= diff && day + diff == target) {
return true;
}
} else if (dow == 1) {
int diff = 1 - day;
if (-2 <= diff && day + diff == target) {
return true;
}
}
}
} else if (part[4] == '#') {
if (date.getDayOfWeek() == DayOfWeek.of(part[0])) {
int num = date.getDayOfMonth() / 7;
return part[2] == (date.getDayOfMonth() % 7 == 0 ? num : num + 1);
if (dow == part[0]) {
int num = day / 7;
return part[2] == (day % 7 == 0 ? num : num + 1);
}
return false;
} else {
int value = date.get(type.field);
int value = date.get(field);
if (part[3] == '?' || (part[0] <= value && value <= part[1] && (value - part[0]) % part[2] == 0)) {
return true;
}
Expand All @@ -151,26 +185,26 @@ boolean matches(ZonedDateTime date) {
* @return true if a match was found, false if the field overflowed.
*/
boolean nextMatch(ZonedDateTime[] date) {
int value = date[0].get(type.field);
int value = date[0].get(field);

for (int[] part : parts) {
int nextMatch = nextMatch(value, part);
if (nextMatch > -1) {
if (nextMatch != value) {
if (type.field == ChronoField.MONTH_OF_YEAR) {
if (field == ChronoField.MONTH_OF_YEAR) {
date[0] = date[0].withMonth(nextMatch).withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS);
} else {
date[0] = date[0].with(type.field, nextMatch).truncatedTo(type.field.getBaseUnit());
date[0] = date[0].with(field, nextMatch).truncatedTo(field.getBaseUnit());
}
}
return true;
}
}

if (type.field == ChronoField.MONTH_OF_YEAR) {
if (field == ChronoField.MONTH_OF_YEAR) {
date[0] = date[0].plusYears(1).withMonth(1).withDayOfMonth(1).truncatedTo(ChronoUnit.DAYS);
} else {
date[0] = date[0].plus(1, type.upper).with(type.field, type.min).truncatedTo(type.field.getBaseUnit());
date[0] = date[0].plus(1, field.getRangeUnit()).with(field, min).truncatedTo(field.getBaseUnit());
}
return false;
}
Expand Down
11 changes: 8 additions & 3 deletions src/main/java/kiss/Scheduler.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import static java.util.concurrent.Executors.*;

import java.time.ZonedDateTime;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -241,12 +242,16 @@ public ScheduledFuture<?> scheduleAt(Runnable command, String format) {
* @throws IllegalArgumentException if the cron expression does not have 5 or 6 parts
*/
static Cron[] parse(String cron) {
String[] parts = cron.split("\\s+");
String[] parts = cron.strip().split("\\s+");
int i = parts.length == 5 ? 0 : parts.length == 6 ? 1 : Cron.error(cron);

return new Cron[] { //
new Cron(Type.SECOND, i == 1 ? parts[0] : "0"), new Cron(Type.MINUTE, parts[i++]), new Cron(Type.HOUR, parts[i++]),
new Cron(Type.DAY_OF_MONTH, parts[i++]), new Cron(Type.MONTH, parts[i++]), new Cron(Type.DAY_OF_WEEK, parts[i++])};
new Cron(ChronoField.SECOND_OF_MINUTE, 0, 59, "", "", "/", i == 1 ? parts[0] : "0"), //
new Cron(ChronoField.MINUTE_OF_HOUR, 0, 59, "", "", "/", parts[i++]), //
new Cron(ChronoField.HOUR_OF_DAY, 0, 23, "", "", "/", parts[i++]), //
new Cron(ChronoField.DAY_OF_MONTH, 1, 31, "", "?LW", "/", parts[i++]), //
new Cron(ChronoField.MONTH_OF_YEAR, 1, 12, "JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC", "", "/", parts[i++]), //
new Cron(ChronoField.DAY_OF_WEEK, 1, 7, "MONTUEWEDTHUFRISATSUN", "?L", "#/", parts[i++])};
}

/**
Expand Down
84 changes: 0 additions & 84 deletions src/main/java/kiss/Type.java

This file was deleted.

Loading

0 comments on commit 78c4321

Please sign in to comment.