cron expresion to run a JIRA service every other Wednesday

Tomas Arguinzones Yahoo February 6, 2017

Hi...I have a JIRA service that I need to run every other Wednesday at 11:59 PM...I have been searching and apparently is not as easy as it sounds, since I have to create some kind of expression within the cron expression. The following cron expression: 0 59 23 ? * 4 will run every Wednesday at 11:59 PM and I need it to run every other Wednesday at 11:59 PM. Somehow I have to "divide" the nth week of the year by 2 and if the remaining is 1 then that means the script should run that Wednesday. For instance, the script should run the first Wednesday of the year, then the third Wednesday, then the fifth Wednesday and so on. How do I write an expression like that in the cron expression field in the Service section in JIRA?

 

Thanks

1 answer

1 accepted

0 votes
Answer accepted
crf
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
February 6, 2017

The short answer is that you can't.  The cron expression syntax just isn't powerful enough to support that.

 

 

crf
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
February 6, 2017

Longer answer:

Both Quartz (the scheduler that JIRA used to use) and Caesium (what I wrote to replace it) evaluate the cron expression by parsing it to a set of matching conditions and then tweaking what the current time is to figure out when it will next match.  Quartz does this using a TreeSet of accepted integer values (pretty silly for the year field!) and Caesium has a more flexible rule-based implementation, but the expression language is identical (except that Caesium's implementation has a lot fewer bugs in it).

But other than the fact that the day-of-week value depends on the year, month, and day values, these fields are all evaluated more or less independently.  When what you want involves an interaction between multiple fields that involves an OR condition or on knowledge of some flip-flopping state, there just isn't any way to say it.

For example, there is no way to say "run this job every 90 minutes".  I've seen people attempt to do this by writing something like "0 0/90 * * * ?" and Quartz actually accepts this as valid, but it does not do what you wanted.  Due to how the "0/90" is actually interpreted, it ends up exactly the same as just saying "0", and the job runs every hour instead of every 90 minutes.

For that specialized case, you can awkwardly work around the problem by creating multiple rules that fill in the gaps.  For example, by combining:

  • 0 0 */3 * * ? – to get the ones that fall on the hour
  • 0 30 1/3 * * ? – to get the ones that fall on half-hour marks

But since there can occasionally be an odd number of Wednesdays in a month, there isn't any way to use this same strategy.  You could get, say, the first and third Wednesday, but it would take 3 weeks instead of 2 when one of the months has an odd number of them.

Tomas Arguinzones Yahoo February 7, 2017

Hi Chris...thank you for your detailed answer...so the question is, how do I run my JIRA service every other Wednesday? This is an script done in ScriptRunner so should I put the condition in the script itself? and If so, should I set the cron expression in the Interval field in JIRA to 

0 59 23 ? * 4 (like it will execute every Wednesday at 11:59 PM) and then the condition in the script would decide whether that Wednesday the script should run or not?

 

Thank you for your assistance

crf
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
February 7, 2017

That sounds like a sensible plan, yes.  The only problem is figuring out whether this is a time to run or a time to ignore it.  Here are a few ideas:

  1. You could save whether or not it ran last time (say, in a property set or plugin settings) and do the opposite.  The only problem with this is that it relies on the job definitely running every other week, which wouldn't necessarily be true if you had extended downtime or restored from a backup.
  2. You could calculate for yourself when the next time to run is, store that somewhere, and refuse to run unless it's actually that time (or close enough that clock skew and daylight savings won't cause trouble).

If I had to solve this problem, I think I would take the second path, and the logic would look something like this:

long now = System.currentTimeMillis();
long stored = Optional.ofNullable(getStoredTime()).orElse(0L);
if (now < stored) {
    // too soon; wait for the next one
    return;
}
 
long later = Instant.now().plus(13, ChronoUnit.DAYS).toEpochMilli();
setStoredTime(later);
// run the job
Tomas Arguinzones Yahoo February 9, 2017

Hi Chris...I really appreciate your help...do I enter that piece of code at the beginning of the script, correct? As I mentioned the script is an script runner script which uses groovy so I am not really sure where to add that code and most likely I need to import some libraries that I dont know of. This is the script

import com.atlassian.greenhopper.service.sprint.Sprint
import com.atlassian.greenhopper.service.sprint.SprintManager
import com.onresolve.scriptrunner.runner.customisers.PluginModuleCompilationCustomiser
import com.onresolve.scriptrunner.runner.customisers.WithPlugin


@WithPlugin("com.pyxis.greenhopper.jira")
def sprintServiceOutcome = PluginModuleCompilationCustomiser.getGreenHopperBean(SprintManager).getAllSprints()

if (sprintServiceOutcome.valid) {
sprintServiceOutcome.getValue().findAll { it.state == Sprint.State.ACTIVE }?.each { updateSprintState(it, Sprint.State.CLOSED) }
}
else {
log.error "Invalid sprint service outcome, ${sprintServiceOutcome.errors}"
}

def updateSprintState(Sprint sprint, Sprint.State state) {

def sprintManager = PluginModuleCompilationCustomiser.getGreenHopperBean(SprintManager)
def newSprint = sprint.builder(sprint).state(state).build()
def outcome = sprintManager.updateSprint(newSprint)

if (outcome.isInvalid()) {
log.warn "Could not update sprint with name ${sprint.name}, ${outcome.getErrors()}"
} else {
log.warn "${sprint.name} updated."
}
}

crf
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
February 9, 2017

The example I wrote was in Java, which should probably translate easily into groovy, but isn't something I have much experience with, myself.  The check was indeed meant to be at the start of the script (just after the imports).  There are also some bits of supporting code that I did not implement because you would need to work out for yourself how you want to do them.

For example, I did not show an implementation for getStoredTime() or setStoredTime(later), but an obvious way to implement them might be with a PropertySet using the ps.getLong(String) and ps.setLong(String, Long) calls.  There are other options too, but picking exactly how to do that and implementing it is a bit beyond the amount of help that I can really offer you.  I would suggest looking for other questions on here that talk about PropertySet, PluginSettings, and Active Objects for more information about what options are available for this.

Tomas Arguinzones Yahoo February 10, 2017

Thank you for all your help Chris!...I really appreciate it.

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events