Scheduled Cron Job Firing Unexpectedly


(Brock) #1

Hello all – I’m having trouble getting a cron job to fire correctly. The app I’m working on is for a scheduled mode change by day of the week.

The job WILL fire at the correct time when testing it. For instance, if it is 9 PM when I install the app, and set the time to fire at 9:02, the mode will change successfully. What has caught me off guard was that yesterday my mode change was set for 11:59 PM and fired around 9:30 PM ish.

If it matters, the time zone I am in is EST, but it seems like when I put logic in to modify the time to EST, the job will not fire.

I know that the code is a bit sloppy – I haven’t had much time to play around with Groovy to learn all of the functions that are available to make the code the most efficient.

preferences {
    section("Configuration") {
    	input "dayOfWeek", "enum", 
			title: "Which day of the week?",
			multiple: false,
			metadata: [ 
            	values: [
                    'Monday',
                    'Tuesday',
                    'Wednesday',
                    'Thursday',
                    'Friday',
                    'Saturday',
                    'Sunday',
                    'Monday to Friday',
                    'Saturday & Sunday',
                    'All Week'
                ]
			]
		input "time", "time", title: "At this time"
		input "newMode", "mode", title: "Change to this mode"
    }
	section( "Notifications" ) {
		input "sendPushMessage", "enum", title: "Send a push notification?", metadata:[values:["Yes", "No"]], required: false
		input "phoneNumber", "phone", title: "Send a text message?", required: false
	}
}

def installed() {
	initialize()
}

def initialize() {

    def d = timeToday(time)
 	def h = d.format('HH')
 	def m = d.format('mm')
    
 	//def h = d.format('HH', TimeZone.getTimeZone("EST")) // SET TIME ZONE TO EST
 	//def m = d.format('mm', TimeZone.getTimeZone("EST")) // SET TIME ZONE TO EST
    
    log.debug "hour=" + h + " minute=" + m
    
    def weekDay
    if(dayOfWeek == "Monday"){
    	weekDay = '1'
    }
    if(dayOfWeek == "Tuesday"){
    	weekDay = '2'
    }
    if(dayOfWeek == "Wednesday"){
    	weekDay = '3'
    }
    if(dayOfWeek == "Thursday"){
    	weekDay = '4'
    }
    if(dayOfWeek == "Friday"){
    	weekDay = '5'
    }
    if(dayOfWeek == "Saturday"){
    	weekDay = '6'
    }
    if(dayOfWeek == "Sunday"){
    	weekDay = '7'
    }
    if(dayOfWeek == "Monday to Friday"){
    	weekDay = '1-5'
    }
    if(dayOfWeek == "Saturday & Sunday"){
    	weekDay = '6,7'
    }
    if(dayOfWeek == "All Week"){
    	weekDay = '1-7'
    }
    
    def cronString = h + " " + m + " * * " + weekDay + " ?"
    
    log.debug cronString
    
	schedule(cronString, changeModeByDay)
}

def changeModeByDay() {
	log.debug "changeModeByDay, location.mode = $location.mode, newMode = $newMode, location.modes = $location.modes"
	if (location.mode != newMode) {
		if (location.modes?.find{it.name == newMode}) {
			setLocationMode(newMode)
			send "${label} has changed the mode to '${newMode}'"
		}
		else {
			send "${label} tried to change to undefined mode '${newMode}'"
		}
	}
}

FYI – I’ve omitted some of the functions. I didn’t think including them would be necessary. Any help would be appreciated, as well as any code optimizations that you may see.


(C Chen) #2

A cron expression has the form

second minute hour day-of-month month weekday year

See http://quartz-scheduler.org/documentation/quartz-1.x/tutorials/crontrigger.


(Brock) #3

http://www.thegeekstuff.com/2009/06/15-practical-crontab-examples/

It seems like every example I look at is different. Can anyone confirm what the PROPER format is that actually works with Groovy / ST?


(C Chen) #4

The quartz link is from their documentation for the ‘schedule’ function. And I use “0 0,30 * * * ?” to log temperature every 30 minutes and “0 0 0 * * ?” to log batteries at midnight. So I am pretty sure that’s how ST’s work. :slight_smile:


(Brock) #5

Great! Thank you! I will be fixing this tonight then and will update you with an outcome.

Edit: FYI – I wasn’t trying to be a jerk if I came across that way! :slight_smile:

Edit 2: Where did you find the documentation for the function? I’m having a hard time finding the documentation for the system other than from here: https://support.smartthings.com/forums/20782109-SmartApp-Reference-Materials


(C Chen) #6

When you are in the IDE, there is a link to Documentation (in the same line that has MySmartApps). Click on that and you will find a link to SmartApp, schedule is somewhere there.


(Brock) #7

This still isn’t working for me. I keep getting an “Unparsable date” error for the cron string when I change it to anything other than the format I had.

0 10 04 * * 1-7 should be completely valid according to that documentation.

Any ideas?


(C Chen) #8

I get the same error, too. I can’t get it to parse even with just ‘1’ or ‘SUN’, or even ‘*’ in the day-of-week field.

However, if you want it to run 7 days a week, you can just use ‘0 10 04 * * ?’.


(Brock) #9

I guess I’ll just use the traditional schedule by time and let it fire everyday. I’ll just do a check in the logic to determine what day of the week it is.

I’d still be interested if anyone knows why this cron string isn’t working!

Thank you, C. Chen for the help!


(Chrisb) #10

Well, this explains why I’m having so hard of a time getting my program to work… I’m running into the same parse errors that you are @brock.

I’m trying to create a Once A Day type of app (turn on/off at a given time) but also let the user select which days of the week. BTW, here’s how I handled the selection of days. I ask the user to say yes or no on each day, and then build the part that of the cron expression for days with this bit of code:

state.dayOfWeek = string
       		// We're declaring a variable here as a string (ie, text).
            
    state.dayOfWeek = ""
    		// Starting by making the variable empty.  It's important to do this because when an update is done we don't want this
            // variable to start with existing data in it.
           
    if (sun == "Yes"){
    	state.dayOfWeek = "0"
        } 
        	// If Sunday is a day we want, we'll change our variable to 0.
            
    if (mon == "Yes"){
		if (state.dayOfWeek == "") {}
    		else {state.dayOfWeek = state.dayOfWeek + ","}
        state.dayOfWeek = state.dayOfWeek + "1"
        }
        	// A couple of things are happening here.  First we're checking if we need to add Monday.  If yes, then we need to check
            // if the variable is empty or not.  That is, did we add Sunday?  If the variable is empty, then we're doing nothing extra.
            // But if the variable ISN'T empty, then we need to first add a common to our string.  After this we add the 1 for Monday.
        
    if (tue == "Yes"){
		if (state.dayOfWeek == "") {}
    		else {state.dayOfWeek = state.dayOfWeek + ","}
        state.dayOfWeek = state.dayOfWeek + "2"
        }
			// We're doing the same thing here that we did above.  If the variable is empty (ie, we haven't added Sunday or Monday)
            // we'll just add the 2 for Tuesday.  If it isn't empty we add a comma, then the 2 for Tuesday.  Next we'll run the same
            // If-Then statements for each remaining day.
            
	if (wed == "Yes"){
		if (state.dayOfWeek == "") {}
    		else {state.dayOfWeek = state.dayOfWeek + ","}
        state.dayOfWeek = state.dayOfWeek + "3"
        }
    if (thu == "Yes"){
		if (state.dayOfWeek == "") {}
    		else {state.dayOfWeek = state.dayOfWeek + ","}
        state.dayOfWeek = state.dayOfWeek + "4"
        }
    if (fri == "Yes"){
		if (state.dayOfWeek == "") {}
    		else {state.dayOfWeek = state.dayOfWeek + ","}
        state.dayOfWeek = state.dayOfWeek + "5"
        }
    if (sat == "Yes"){
		if (state.dayOfWeek == "") {}
    		else {state.dayOfWeek = state.dayOfWeek + ","}
        state.dayOfWeek = state.dayOfWeek + "6"
        }

That part is working great… it correctly builds the variable for the expression. But when I try to run it I get a parse error.

The other thing you brought up in your original post has we concerned too. How does this happen timezones? I believe the server is working off UTC so it adjusts -5 for Eastern. But is it smart enough to know when to change days? For example, if I want to turn something on Monday night at 9:00pm… well, that’s 2am Tuesday morning according to UTC.

If I tell me program to turn on a light at 9:00pm Eastern on Monday night the server should convert that to 2:00am correctly. But… will it convert to run 2:00am Monday UTC or 2:00am Tuesday UTC? My feeling is that it will probably run it 2:00am Monday UTC.


(Chrisb) #11

Thinking about this some more… is there a way to check to see what day it is in a specified timezone?

I’m wondering if it would be smarter to run the program as just a regular schedule time, and then within in the procedure that the schedule calls I’d pull up the current day for my timezone and compare against the list of days the user indicated to decide it it turns on or off the device.

This would take care of the difference in day of week between UTC and my timezone. If I say I want my procedure to run at 9:00pm only on Monday, then the server schedules it for 2:00am everyday.

So when my procedure would run at 2:00am Tuesday, the procedure would check for what day of the week it was in Eastern Standard Time. It would report Monday… so it would turn on the device.

Alternately, when my procedure would run at 2:00am Monday, the check of the day would say Sunday for Eastern. So it would NOT turn on the device.

Logically this should work. But how do I check for day of week of a specific timezone?

And thinking more long term… how do I let the user input a timezone so it would work for anyone?


(Chrisb) #12

After some searching and a lot of trial and error I got this working:

    def dayOWeek = new Date()
    def today = dayOWeek.format('EEE')

When I ran it just now I got this:

3:57:58 PM: debug The day is = Wed
3:57:58 PM: debug Today = Wed Feb 05 20:57:58 UTC 2014

So, I’m part way there. I would be able to create a procedure that runs at a given time. The procedure will grab this day and then check if if the day specified by $today is also a day that is accepted, then the procedure will turn on the light.

But, as you can see the new Date() pulls UTC, which won’t work. Anyone know how to change the date to local vs. UTC?


(C Chen) #13

@chrisb I don’t know if there is a way to pass Date a timezone, but you can do

  dayOWeek.format('EEE', TimeZone.getTimeZone('America/New_York'))

Here is a list of available timezone
https://publib.boulder.ibm.com/infocenter/iseries/v5r4/index.jsp?topic=%2Frzamy%2F50%2Fadmin%2Freftzval.htm


(Chrisb) #14

Thanks @stepchen,

Just tested it and it returns the correct day. Right now without the Timezone it’s saying it’s Thursday, but with the Timezone it’s showing Wednesday, so that part works.