Skip to content

Commit

Permalink
JENKINS-9283 - Allow timezone definition in scheduler
Browse files Browse the repository at this point in the history
  • Loading branch information
0bp committed May 19, 2015
1 parent e44e154 commit 6b4d044
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 8 deletions.
41 changes: 35 additions & 6 deletions core/src/main/java/hudson/scheduler/CronTab.java
Expand Up @@ -27,6 +27,7 @@

import java.io.StringReader;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.regex.Matcher;
Expand Down Expand Up @@ -58,6 +59,11 @@ public final class CronTab {
*/
private String spec;

/**
* Optional timezone string for calendar
*/
private @CheckForNull String specTimezone;

public CronTab(String format) throws ANTLRException {
this(format,null);
}
Expand All @@ -81,15 +87,29 @@ public CronTab(String format, int line) throws ANTLRException {
* of not spreading it out at all.
*/
public CronTab(String format, int line, Hash hash) throws ANTLRException {
set(format, line, hash);
this(format, line, hash, null);
}

/**
* @param timezone
* Used to schedule cron in a differnt timezone. Null to use the default system
* timezone
*/
public CronTab(String format, int line, Hash hash, String timezone) throws ANTLRException {
set(format, line, hash, timezone);
}

private void set(String format, int line, Hash hash) throws ANTLRException {
set(format, line, hash, null);
}

private void set(String format, int line, Hash hash, String timezone) throws ANTLRException {
CrontabLexer lexer = new CrontabLexer(new StringReader(format));
lexer.setLine(line);
CrontabParser parser = new CrontabParser(lexer);
parser.setHash(hash);
spec = format;
specTimezone = timezone;

parser.startRule(this);
if((dayOfWeek&(1<<7))!=0) {
Expand All @@ -103,15 +123,24 @@ private void set(String format, int line, Hash hash) throws ANTLRException {
* Returns true if the given calendar matches
*/
boolean check(Calendar cal) {
if(!checkBits(bits[0],cal.get(MINUTE)))

Calendar checkCal = cal;

if(specTimezone != null && !specTimezone.isEmpty()) {
Calendar tzCal = Calendar.getInstance(TimeZone.getTimeZone(specTimezone));
tzCal.setTime(cal.getTime());
checkCal = tzCal;
}

if(!checkBits(bits[0],checkCal.get(MINUTE)))
return false;
if(!checkBits(bits[1],cal.get(HOUR_OF_DAY)))
if(!checkBits(bits[1],checkCal.get(HOUR_OF_DAY)))
return false;
if(!checkBits(bits[2],cal.get(DAY_OF_MONTH)))
if(!checkBits(bits[2],checkCal.get(DAY_OF_MONTH)))
return false;
if(!checkBits(bits[3],cal.get(MONTH)+1))
if(!checkBits(bits[3],checkCal.get(MONTH)+1))
return false;
if(!checkBits(dayOfWeek,cal.get(Calendar.DAY_OF_WEEK)-1))
if(!checkBits(dayOfWeek,checkCal.get(Calendar.DAY_OF_WEEK)-1))
return false;

return true;
Expand Down
37 changes: 35 additions & 2 deletions core/src/main/java/hudson/scheduler/CronTabList.java
Expand Up @@ -25,12 +25,16 @@

import antlr.ANTLRException;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.Collection;
import java.util.Vector;
import javax.annotation.CheckForNull;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;

import java.util.logging.Level;
import java.util.logging.Logger;

/**
* {@link CronTab} list (logically OR-ed).
*
Expand Down Expand Up @@ -71,24 +75,52 @@ public String checkSanity() {
return null;
}

/**
* Checks if given timezone string is supported by TimeZone and returns
* the same string if valid, null otherwise
*/
public static @CheckForNull String getValidTimezone(String timezone) {
String[] validIDs = TimeZone.getAvailableIDs();
for (String str : validIDs) {
if (str != null && str.equals(timezone)) {
return timezone;
}
}
return null;
}

public static CronTabList create(String format) throws ANTLRException {
return create(format,null);
}

public static CronTabList create(String format, Hash hash) throws ANTLRException {
Vector<CronTab> r = new Vector<CronTab>();
int lineNumber = 0;
String timezone = null;

for (String line : format.split("\\r?\\n")) {
lineNumber++;
line = line.trim();

if(lineNumber == 1 && line.startsWith("TZ=")) {

This comment has been minimized.

Copy link
@oleg-nenashev

oleg-nenashev May 26, 2015

Member

@orrc
According to the code, TZ is being specified in the first line, which does not contain any other data. Hence the format would be:

TZ=Europe/London
# This job needs to be run in the morning, London time because ...
H 8 * * *
# BTW, nobody works at 5 o'clock, so we can run the test again
H(0-30) 15 * * * 
timezone = getValidTimezone(line.replace("TZ=",""));
if(timezone != null) {
LOGGER.log(Level.CONFIG, "cron with timezone {0}", timezone);
} else {
LOGGER.log(Level.CONFIG, "invalid timezone {0}", line);
}
continue;
}

if(line.length()==0 || line.startsWith("#"))
continue; // ignorable line
try {
r.add(new CronTab(line,lineNumber,hash));
r.add(new CronTab(line,lineNumber,hash,timezone));
} catch (ANTLRException e) {
throw new ANTLRException(Messages.CronTabList_InvalidInput(line,e.toString()),e);
}
}

return new CronTabList(r);
}

Expand All @@ -115,5 +147,6 @@ public static CronTabList create(String format, Hash hash) throws ANTLRException
}
return nearest;
}


private static final Logger LOGGER = Logger.getLogger(CronTabList.class.getName());
}
14 changes: 14 additions & 0 deletions core/src/test/java/hudson/scheduler/CronTabTest.java
Expand Up @@ -27,6 +27,7 @@
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.GregorianCalendar;
import java.util.Locale;

Expand Down Expand Up @@ -301,4 +302,17 @@ public int next(int n) {
// ok
}
}

@Issue("JENKINS-9283")
@Test public void testTimezone() throws Exception {
CronTabList tabs = CronTabList.create("TZ=Australia/Sydney\nH * * * *\nH * * * *", Hash.from("seed"));

This comment has been minimized.

Copy link
@bargemb

bargemb Jul 2, 2015

@oleg-nenashev When I change first string in test case to "TZ=Asia/Kolkata"(which is my timezone), test case works
also if I change my system timezone to Australia/Sydney then also it works

When are you planning to fix this issue ?

This comment has been minimized.

Copy link
@oleg-nenashev

oleg-nenashev Jul 2, 2015

Member

No ETA, but I hope to take a look on this week

This comment has been minimized.

Copy link
@oleg-nenashev

oleg-nenashev Dec 28, 2018

Member
List<Integer> times = new ArrayList<Integer>();
for (int i = 0; i < 60; i++) {
if (tabs.check(new GregorianCalendar(2013, 3, 3, 11, i, 0))) {
times.add(i);
}
}
assertEquals("[35, 56]", times.toString());
}

}

1 comment on commit 6b4d044

@orrc
Copy link
Member

@orrc orrc commented on 6b4d044 May 26, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagine having form validation for the TZ values would be very useful.

Please sign in to comment.