diff --git a/core/src/main/java/hudson/scheduler/CronTab.java b/core/src/main/java/hudson/scheduler/CronTab.java index 8d0ad9f7f2..4b271bc1ad 100644 --- a/core/src/main/java/hudson/scheduler/CronTab.java +++ b/core/src/main/java/hudson/scheduler/CronTab.java @@ -27,6 +27,7 @@ import antlr.ANTLRException; 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; @@ -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); } @@ -81,15 +87,29 @@ public final class CronTab { * 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) { @@ -103,15 +123,24 @@ public final class CronTab { * 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; diff --git a/core/src/main/java/hudson/scheduler/CronTabList.java b/core/src/main/java/hudson/scheduler/CronTabList.java index da3d0065f7..fa55ca6a2f 100644 --- a/core/src/main/java/hudson/scheduler/CronTabList.java +++ b/core/src/main/java/hudson/scheduler/CronTabList.java @@ -25,12 +25,16 @@ package hudson.scheduler; 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). * @@ -71,6 +75,20 @@ public final class CronTabList { 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); } @@ -78,17 +96,31 @@ public final class CronTabList { public static CronTabList create(String format, Hash hash) throws ANTLRException { Vector r = new Vector(); int lineNumber = 0; + String timezone = null; + for (String line : format.split("\\r?\\n")) { lineNumber++; line = line.trim(); + + if(lineNumber == 1 && line.startsWith("TZ=")) { + 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); } @@ -115,5 +147,6 @@ public final class CronTabList { } return nearest; } - + + private static final Logger LOGGER = Logger.getLogger(CronTabList.class.getName()); } diff --git a/core/src/test/java/hudson/scheduler/CronTabTest.java b/core/src/test/java/hudson/scheduler/CronTabTest.java index ea291835bb..6ddf859128 100644 --- a/core/src/test/java/hudson/scheduler/CronTabTest.java +++ b/core/src/test/java/hudson/scheduler/CronTabTest.java @@ -27,6 +27,7 @@ import antlr.ANTLRException; import java.text.DateFormat; import java.util.ArrayList; import java.util.Calendar; +import java.util.TimeZone; import java.util.GregorianCalendar; import java.util.Locale; @@ -301,4 +302,17 @@ public class CronTabTest { // ok } } + + @Issue("JENKINS-9283") + @Test public void testTimezone() throws Exception { + CronTabList tabs = CronTabList.create("TZ=Australia/Sydney\nH * * * *\nH * * * *", Hash.from("seed")); + List times = new ArrayList(); + 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()); + } + }