Jython converting DateTime item to Joda DateTime fails

I converted my DSL rules to Jython a while ago, and I believe it all worked in OH 2.4. I was recently checking my logs, and they show a date format conversion problem in some of my rules.

Here is the rule triggering the error:

@rule("Dim indoor lights on clear sky")
@when("Item PVPowerGS2 changed")
@when("Item test changed")
def dimIndoorLightsOnClearSky(event):

    log.debug("Dim indoor lights on clear sky")
    log.debug("Sunrise_time item: {}".format(items["Sunrise_Time"]))
    sunrise = to_joda_datetime(items["Sunrise_Time"])
    log.debug("Sunrise time = {}".format(sunrise))

Here is the log:

   2020-02-18 17:09:56.006 [DEBUG] [jsr223.jython.sunrise.py            ] - Dim indoor lights on clear sky
   2020-02-18 17:09:56.017 [DEBUG] [jsr223.jython.sunrise.py            ] - Sunrise_time item: 2020-02-18T07:53:00.000+0100
   2020-02-18 17:09:56.040 [ERROR] [ython.Dim indoor lights on clear sky] - Traceback (most recent call last):
      File "/etc/openhab2/automation/lib/python/core/log.py", line 51, in wrapper
        return fn(*args, **kwargs)
      File "<script>", line 66, in dimIndoorLightsOnClearSky
      File "/etc/openhab2/automation/lib/python/core/date.py", line 294, in to_joda_datetime
        return DateTime(
    IllegalArgumentException: java.lang.IllegalArgumentException: The datetime zone id 'GMT+01:00' is not recognised
    2020-02-18 17:09:56.046 [ERROR] [e.automation.internal.RuleEngineImpl] - Failed to execute rule '56cb914b-5c96-4fe6-bf68-3a8d8d044a73': Fail to execute action: 1

It looks like the to_joda_datetime function cannot cope with the timezone information. I checked the date.py file and I noticed it first converts to Java ZonedDateTime internally. The timezone id it gets from there does not seem to match the timezone id required for Joda DateTime.

While I would like to convert all to Java ZonedDateTime, I still need Joda DateTime, as I will use that value as a parameter in the scriptExecution.createTimer action. This one need Joda DateTime.

Does anyone have any suggestions?

I’m out boiling some maple sap, but I’ll head inside in a bit and take a look! Have you grabbed the latest libraries? I think there was a fix for something like this already. I might need to do a push though.

2 posts were split to a new topic: Last update in python

Mark, there is definitely a bug in core.date. Somebody submitted a PR to resolve it, but it was not rebased after some other changes. It also included some other changes that should have gone into separate PRs, so I just closed it. I will submit a PR with a fix this issue

In the meantime, you can easily work around this by converting the argument to ZonedDateTime…

sunrise = to_joda_datetime(items["Sunrise_Time"].zonedDateTime)

Please provide more details on how to reproduce the issue you are reporting. When I log the state of an Item using the old rule engine, I get the same result when I log it in the new rule engine.

2020-02-18 22:24:17.208 [WARN ] [jsr223.jython.TEST] - Virtual_DateTime_1.state: 2020-05-05T05:55:55.000-0500
2020-02-18 22:24:19.196 [WARN ] [org.eclipse.smarthome.model.script.Rules] - Virtual_DateTime_1.state: 2020-05-05T05:55:55.000-0500

Curious, what are you going to use with sunrise time? If you want to know if it’s currently before or after sunrise, as an alternative you can just check the Sun Elevation. If it’s < 0, it’s before sunrise / after sunset, i.e. the sun is below the horizon. I find it much easier than dealing with sunrise / sunset time and comparing against the current time.

1 Like

Scott, thanks for the workaround. That solves it for now.

1 Like

If you’d like a full solution, change to_joda_datetime to this (PR coming)…

def to_joda_datetime(value):
    """
    Converts any of the supported date types to ``org.joda.time.DateTime``. If
    ``value`` does not have timezone information, the system default will be
    used.

    Examples:
        .. code-block::

            joda_time = to_joda_datetime(items["date_item"])

    Args:
        value: the value to convert

    Returns:
        org.joda.time.DateTime: the converted value

    Raises:
        TypeError: if the type of ``value`` is not suported by this package
    """
    if isinstance(value, DateTime):
        return value

    value_zoneddatetime = to_java_zoneddatetime(value)
    return DateTime(value_zoneddatetime.toInstant().toEpochMilli(),
        DateTimeZone.forTimeZone(TimeZone.getTimeZone(value_zoneddatetime.getZone()))
    )

@JimT Your suggestion makes a lot of sense, but won’t work the way I have structured my rules. Here are the 2 rules creating the following logic:

  • Dim my indoor lights a set amount of time after sunrise (where that amount of time is a constant set at the beginning of the script file). I could have used offsets in the astro binding for it, but would need several astro things if I wanted different offsets, so decided to keep it as a constant in my script.
  • Check the power output of my solar panels and if is above a certain treshold, and the timer is set to switch of the lights, immediately switch off the lights and cancel the timer. This will happen on a very clear day.
    Here is the script for that:
@rule("Dim indoor lights")
@when("Channel astro:sun:local:rise#event triggered START")
def dimIndoorLights(event):

	log.info("Dim indoor lights")

	sunrise = to_joda_datetime(items["Sunrise_Time"].zonedDateTime)
	log.debug("Sunrise time = {}".format(sunrise))

	# Schedule to turn all indoor lights off 'indoorDelay' minutes after sunrise
	# Cancel timer to avoid reschedule
	global tIndoorLights
	if (tIndoorLights is not None) and (not tIndoorLights.hasTerminated()):
		log.debug("Timer tIndoorLights cancelled") 
		tIndoorLights.cancel()
		tIndoorLights = None
	log.debug("Timer tIndoorLights created for {}".format(sunrise.plusMinutes(indoorDelay)))
	tIndoorLights = ScriptExecution.createTimer(sunrise.plusMinutes(indoorDelay), lambda: events.sendCommand("gLichten", "OFF"))

@rule("Dim indoor lights on clear sky")
@when("Item PVPowerGS2 changed")
def dimIndoorLightsOnClearSky(event):

	log.debug("Dim indoor lights on clear sky")

	# It is light enough and the indoor lights turn off timer is running, so we can turn off earlier
	global tIndoorLights
	if (event.itemState > DecimalType(minSunPower)) and (tIndoorLights is not None):
		log.debug("Minimum sunpower {}".format(minSunPower))
		log.debug("Current sunpower {}".format(event.itemState))
		log.debug("Timer tIndoorLights cancelled")
		tIndoorLights.cancel()
		tIndoorLights = None
		log.info("Lights turned off because it is light enough already")
		events.sendCommand("gLichten","OFF")
@rule("Dim indoor lights")
@when("Channel astro:sun:local:rise#event triggered START")
def dimIndoorLights(event):

	log.info("Dim indoor lights")

	sunrise = DateTime.now() # Isn't this the same, because the rule is triggered by the sunrise event?
....

Having same issue:

        sunset_time = DateTime(ir.getItem("sunSetEnd"))

what inports are needed in order to resolve everything needed in code for to_joda_datetime

There is no way you’d be able to construct a DateTime from an Item. It would also be better to use items for getting states. You probably are looking for…

sunset_time = DateTime(items["sunSetEnd"].toString())

Or you can do…

from core.date import to_joda_datetime
sunset_time = to_joda_datetime(items["sunSetEnd"].zonedDateTime)
1 Like

solved my problem, thanks.

just discovering what sort of attributes (depending of item type) items[] has :slight_smile:
(toString, intValue, logValue,…)
was doing all sort of float(str(items[“xxx”])) constructs

1 Like

Something like this is usually easier and occasionally more helpful than reading through the code…

LOG.warn("type(items[\"Test_Item\"]): {}\ndir(items[\"Test_Item\"]): {}".format(type(items["Virtual_Switch_1"]), dir(items["Virtual_Switch_1"])))

I mention using dir() in the docs, but it probably deserves its own page. I also have a section intended to help with conversions.

1 Like

Moving my rules DSL to python, and was looking for long time to convert Item state to DateTime.

Thanks…This sure helps…

1 Like