Skip to content

Commit

Permalink
Merge branch 'release/v1.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
asahaf committed Feb 18, 2022
2 parents df89e04 + e503dd8 commit 28d5474
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 50 deletions.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
JavaCron
============

JavaCron is a java library which provides functionality for parsing crontab expression
JavaCron is a java library which provides functionality for parsing crontab expression
and calculating the next run, based on current or specified date time.

## Features
Expand All @@ -22,7 +22,7 @@ next run(s).
│ │ ┌───────────── day of the month (1 - 31)
│ │ │ ┌───────────── month (1 - 12 or Jan/January - Dec/December)
│ │ │ │ ┌───────────── day of the week (0 - 6 or Sun/Sunday - Sat/Saturday)
│ │ │ │ │
│ │ │ │ │
│ │ │ │ │
│ │ │ │ │
* * * * *
Expand All @@ -36,7 +36,7 @@ next run(s).
│ │ │ ┌───────────── day of the month (1 - 31)
│ │ │ │ ┌───────────── month (1 - 12 or Jan/January - Dec/December)
│ │ │ │ │ ┌───────────── day of the week (0 - 6 or Sun/Sunday - Sat/Saturday)
│ │ │ │ │ │
│ │ │ │ │ │
│ │ │ │ │ │
│ │ │ │ │ │
* * * * * *
Expand Down Expand Up @@ -75,6 +75,10 @@ public class App {
Schedule schedule4 = Schedule.create("0 0 0 29 2 */4");
System.out.println(dateFormatter.format(schedule4.next(baseDate))); // 2024-02-29 00:00:00

// at 00:00:00 last Friday in Jan
Schedule schedule5 = Schedule.create("0 0 0 * * 5L");
System.out.println(dateFormatter.format(schedule5.next(baseDate))); // 2019-01-25 00:00:00

// Calculating the next 5 runs
Date[] nextRuns = schedule3.next(baseDate, 5);
for (Date run : nextRuns) {
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.asahaf.javacron</groupId>
<artifactId>javacron</artifactId>
<version>1.2.1</version>
<version>1.3.0</version>
<name>javacron</name>
<description>A java library for parsing crontab expressions and calculating the next run time</description>
<url>https://github.com/asahaf/javacron</url>
Expand Down
72 changes: 39 additions & 33 deletions src/main/java/com/asahaf/javacron/CronFieldParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,39 +62,39 @@ class CronFieldParser {
CronFieldParser(CronFieldType fieldType) {
this.fieldType = fieldType;
switch (fieldType) {
case SECOND:
case MINUTE:
this.fieldName = this.fieldType.toString().toLowerCase();
this.length = 60;
this.maxAllowedValue = 59;
this.minAllowedValue = 0;
break;
case HOUR:
this.fieldName = this.fieldType.toString().toLowerCase();
this.length = 24;
this.maxAllowedValue = 23;
this.minAllowedValue = 0;
break;
case DAY:
this.fieldName = this.fieldType.toString().toLowerCase();
this.length = 31;
this.maxAllowedValue = 31;
this.minAllowedValue = 1;
break;
case MONTH:
this.choices = CronFieldParser.MONTHS_NAMES;
this.fieldName = this.fieldType.toString().toLowerCase();
this.length = 12;
this.maxAllowedValue = 12;
this.minAllowedValue = 1;
break;
case DAY_OF_WEEK:
this.choices = CronFieldParser.DAYS_OF_WEEK_NAMES;
this.fieldName = "day of week";
this.length = 7;
this.maxAllowedValue = 6;
this.minAllowedValue = 0;
break;
case SECOND:
case MINUTE:
this.fieldName = this.fieldType.toString().toLowerCase();
this.length = 60;
this.maxAllowedValue = 59;
this.minAllowedValue = 0;
break;
case HOUR:
this.fieldName = this.fieldType.toString().toLowerCase();
this.length = 24;
this.maxAllowedValue = 23;
this.minAllowedValue = 0;
break;
case DAY:
this.fieldName = this.fieldType.toString().toLowerCase();
this.length = 31;
this.maxAllowedValue = 31;
this.minAllowedValue = 1;
break;
case MONTH:
this.choices = CronFieldParser.MONTHS_NAMES;
this.fieldName = this.fieldType.toString().toLowerCase();
this.length = 12;
this.maxAllowedValue = 12;
this.minAllowedValue = 1;
break;
case DAY_OF_WEEK:
this.choices = CronFieldParser.DAYS_OF_WEEK_NAMES;
this.fieldName = "day of week";
this.length = 7;
this.maxAllowedValue = 6;
this.minAllowedValue = 0;
break;
}
}

Expand Down Expand Up @@ -145,6 +145,12 @@ private int parseValue(String token) {
}

public BitSet parse(String token) throws InvalidExpressionException {
// This is when last day of the month is specified
if (this.fieldType == CronFieldType.DAY_OF_WEEK) {
if (token.length() == 2 && token.endsWith("l")) {
return this.parseLiteral(token.substring(0, 1));
}
}
if (token.indexOf(",") > -1) {
BitSet bitSet = new BitSet(this.length);
String[] items = token.split(",");
Expand Down
45 changes: 32 additions & 13 deletions src/main/java/com/asahaf/javacron/Schedule.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ private Schedule() {
private BitSet months;
private BitSet daysOfWeek;
private BitSet daysOf5Weeks;
private boolean isSpecificLastDayOfMonth;

/**
* Parses crontab expression and create a Schedule object representing that
Expand Down Expand Up @@ -117,10 +118,10 @@ public static Schedule create(String expression) throws InvalidExpressionExcepti
token = fields[index++];
schedule.hours = Schedule.HOURS_FIELD_PARSER.parse(token);

token = fields[index++];
schedule.days = Schedule.DAYS_FIELD_PARSER.parse(token);
String daysToken = fields[index++];
schedule.days = Schedule.DAYS_FIELD_PARSER.parse(daysToken);
boolean daysStartWithAsterisk = false;
if (token.startsWith("*"))
if (daysToken.startsWith("*"))
daysStartWithAsterisk = true;

token = fields[index++];
Expand All @@ -131,6 +132,16 @@ public static Schedule create(String expression) throws InvalidExpressionExcepti
boolean daysOfWeekStartAsterisk = false;
if (token.startsWith("*"))
daysOfWeekStartAsterisk = true;

if (token.length() == 2 && token.endsWith("l")) {
if (!daysToken.equalsIgnoreCase("*")) {
throw new InvalidExpressionException(
"when last days of month is specified. the day of the month must be \"*\"");
}
// this flag will be used later duing finding the next schedual
// this is because some months has less than 31 days
schedule.isSpecificLastDayOfMonth = true;
}
schedule.daysOf5Weeks = generateDaysOf5Weeks(schedule.daysOfWeek);

schedule.daysAndDaysOfWeekRelation = (daysStartWithAsterisk || daysOfWeekStartAsterisk)
Expand Down Expand Up @@ -223,7 +234,7 @@ public Date next(Date baseDate) {
day = 1;
}
month = candidateMonth;
BitSet adjustedDaysSet = getUpdatedDays(year, month);
BitSet adjustedDaysSet = getUpdatedDays(this, year, month);
candidateDay = adjustedDaysSet.nextSetBit(day - 1) + 1;
if (candidateDay < 1) {
month++;
Expand Down Expand Up @@ -331,7 +342,7 @@ public int compareTo(Schedule anotherSchedule) {
* if and only if the argument is not {@code null} and is a {@code Schedule}
* object that whose seconds, minutes, hours, days, months, and days of
* weeks sets are equal to those of this schedule.
*
*
* The expression string used to create the schedule is not considered, as two
* different expressions may produce same schedules.
*
Expand Down Expand Up @@ -397,32 +408,40 @@ private static BitSet generateDaysOf5Weeks(BitSet daysOfWeek) {
return bitSet;
}

private BitSet getUpdatedDays(int year, int month) {
private BitSet getUpdatedDays(Schedule schedule, int year, int month) {
Date date = new Date(year, month, 1);
int daysOf5WeeksOffset = date.getDay();
BitSet updatedDays = new BitSet(31);
updatedDays.or(this.days);
BitSet monthDaysOfWeeks = this.daysOf5Weeks.get(daysOf5WeeksOffset, daysOf5WeeksOffset + 31);
if (this.daysAndDaysOfWeekRelation == DaysAndDaysOfWeekRelation.INTERSECT) {
if (schedule.isSpecificLastDayOfMonth
|| this.daysAndDaysOfWeekRelation == DaysAndDaysOfWeekRelation.INTERSECT) {
updatedDays.and(monthDaysOfWeeks);
} else {
updatedDays.or(monthDaysOfWeeks);
}
int i;
int monthDaysCount;
if (month == 1 /* Feb */) {
i = 28;
monthDaysCount = 28;
if (isLeapYear(year)) {
i++;
monthDaysCount++;
}
} else {
// We cannot use lengthOfMonth method with the month Feb
// because it returns incorrect number of days for years
// that are dividable by 400 like the year 2000, a bug??
i = YearMonth.of(year, month + 1).lengthOfMonth();
monthDaysCount = YearMonth.of(year, month + 1).lengthOfMonth();
}
// remove days beyond month length
for (; i < 31; i++) {
updatedDays.set(i, false);
for (int j = monthDaysCount; j < 31; j++) {
updatedDays.set(j, false);
}

// remove days before the last 7 days
if (schedule.isSpecificLastDayOfMonth) {
for (int j = 0; j < monthDaysCount - 7; j++) {
updatedDays.set(j, false);
}
}
return updatedDays;
}
Expand Down
12 changes: 12 additions & 0 deletions src/test/java/com/asahaf/javacron/ScheduleTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,18 @@ public static Collection getTestCases() {
{ "2019-01-01 00:00:00", "0 0 0 29 2 4", "2019-02-07 00:00:00" },
{ "2019-01-01 00:00:00", "0 0 0 29 2 5", "2019-02-01 00:00:00" },

// last day of month
{ "2019-01-01 00:00:00", "0 0 0 * * 5l", "2019-01-25 00:00:00" },
{ "2019-01-01 00:00:00", "0 0 0 * * 6l", "2019-01-26 00:00:00" },
{ "2019-01-01 00:00:00", "0 0 0 * * 0L", "2019-01-27 00:00:00" },
{ "2019-01-01 00:00:00", "0 0 0 * * 5l", "2019-01-25 00:00:00" },
{ "2019-01-01 00:00:00", "0 0 0 * 2 5L", "2019-02-22 00:00:00" },
{ "2019-01-01 00:00:00", "0 0 0 * 9 0l", "2019-09-29 00:00:00" },
{ "2019-01-01 00:00:00", "0 0 0 * 5 2L", "2019-05-28 00:00:00" },
{ "2019-01-01 00:00:00", "0 0 0 * 2 6L", "2019-02-23 00:00:00" },
{ "2019-01-01 00:00:00", "0 0 0 * 4 3L", "2019-04-24 00:00:00" },
{ "2019-01-01 00:00:00", "0 0 0 * 4 2L", "2019-04-30 00:00:00" },

});
}

Expand Down

0 comments on commit 28d5474

Please sign in to comment.