Issues with DateTimeType Cast

Platform & System Information:
Ubuntu 18.04.6 LTS (GNU/Linux 4.15.0-213-generic x86_64)
Openhab Version: 4.1.1 (Build)
OpenJDK Runtime Environment (build 17.0.7+7-Ubuntu-0ubuntu118.04)

I have some javascript integrated into a rule to regularly poll information from my photovoltaic inverter via HTTP. This worked fine since yesterday 03:58am when the token needed to be refreshed. The actual token refresh was fine, token is fine, all good. But I store the expiry date and time of the token and then check in the rule every 10 seconds if the token is expired. This check was also working fine up to now. Below you can find the check:

	if (now.isAfter((SigenergyTokenExpiry.state as DateTimeType).zonedDateTime)) {
		return SigenergyAuthenticate.apply("grant_type=refresh_token&refresh_token=" + SigenergyRefreshToken.state.toString())
	} else {
		return SigenergyAccessToken.state.toString()
	}

The check in the if-statement suddenly shows an error. It say:
2025-02-05 21:41:00.350 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'Photovoltaik-1' failed: Could not cast 2025-02-04 09:22:39.741+0000 to org.openhab.core.library.types.DateTimeType; line 66, column 21, length 42 in Photovoltaik

The content of the SigenergyTokenExpiry item state is “2025-02-04T09:22:39.741+0000”. Seems totally fine for me. And it was working up to now. I also tried to modify the date/time manually, nothing helped.

At the top of my rule fileI have set import java.time.format.DateTimeFormatter.

I am a little desperate and I am not sure where to look to get this back to work.
Thanks for your support in advance!

Best regards,
Matthias

If this is JS as indicated, this code could never have worked. This can only be Rules DSL. So the posting is confusing. The log proves it’s Rules DSL (the rule ID is Photovoltaik-1 which is a Rules DSL ID).

Is this a function? What’s with the returns?

That is not usually how java.time.ZonedDateTime represents the time. It uses the name of the timezone, not an offset like shown there. But joda-js ZonedDateTime does show the timezone as an offset. :man_shrugging: As far as I know, if you passed that as a String to a Java ZonedDateTime to parse it wouldn’t like it and fail.

So either there is a ton of missing details (e.g. the type of Item ZigenergyTokenExpiry) and you are doing something weird to use JS and Rules DSL at the same time (is this supposed to be a JS transformation called from a Rules DSL rule?), or this code never could have worked as written.

Clearly there is a lot of context not shown.

Thanks for your reply and sorry for the confusion!

Correct, it is a DSL Rule. The java function (sorry, not JS), called from a Rule DSL is not shown (SigenergyAuthenticate). As the date/time seemed to be correct to me, I was assuming, I skipped that. Here is the code:

val Functions$Function1 <String, String> SigenergyAuthenticate = [
    authContent |
		var authURL = "https://api-eu.sigencloud.com/auth/oauth/token"
		var authContentType = "application/x-www-form-urlencoded"
		var authHeaders = newHashMap(
			"Authorization" -> "Basic c2lnZW46c2lnZW4=",
            "Connection" -> "Keep-Alive",
            "Accept-Encoding" -> "gzip,deflate",
            "User-Agent" -> "Apache-HttpClient/4.5.5 (Java/16.0.2)"
		)

		logInfo("Sigenergy", "Authenticate, send Post Request")
		
		var authResponse = sendHttpPostRequest(authURL, authContentType, authContent, authHeaders, 3000)
		var access_token = transform("JSONPATH", "data.access_token", authResponse)
		var refresh_token = transform("JSONPATH", "data.refresh_token", authResponse)
		var token_type = transform("JSONPATH", "data.token_type", authResponse)
		var expires_in = transform("JSONPATH", "data.expires_in", authResponse)

		logInfo("Sigenergy", "token_type = {}", token_type)
		logInfo("Sigenergy", "expires_in = {}", expires_in)

        //logInfo("Sigenergy", "Auth Content: "+authContent)
        //logInfo("Sigenergy", "Full response: "+authResponse)


		if (token_type.contains("error_description") || token_type.contains("Invalid grant")) {
			logError("Sigenergy", "No access token found! Full response: "+authResponse)
			return ""
		} else {
			//Convert epoch to a human readable
			val DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
			val long expires_in_milli = Integer.valueOf(expires_in) * 1000 - 60000 // expire 60 seconds before expiration
			val long epoch = now.toInstant.toEpochMilli
			val long epoch_plus = now.toInstant.toEpochMilli + expires_in_milli
			val ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(epoch), ZoneOffset.UTC);
			val ZonedDateTime zdt_plus = ZonedDateTime.ofInstant(Instant.ofEpochMilli(epoch_plus), ZoneOffset.UTC);
			val currentTime = zdt.format(formatter)
			val currentTime_plus = zdt_plus.format(formatter)

			logInfo("Sigenergy", "Updating Access Token")
			SigenergyAccessToken.postUpdate(access_token)
			SigenergyRefreshToken.postUpdate(refresh_token)
			SigenergyTokenType.postUpdate(token_type)
			SigenergyExpiresIn.postUpdate(expires_in)
			SigenergyTokenCreated.postUpdate(currentTime)
			SigenergyTokenExpiry.postUpdate(currentTime_plus)

			return access_token
		}
]

The item where the expiry date is stored is a string item (String SigenergyTokenExpiry). The format was taken from the doc (Items | openHAB) with type 1, RFC 822 time zone.

I hope this helps.

This part is key.

This code could never have worked as written. (SigenergyTokenExpiry.state as DateTimeType) could never work because the state of a String Item is a StringType and never a DateTimeType. The as operator only works to “convert” an Object into something it already is. .state on an Item returns an Object of type State. Both StringType and DateTimeType are also of type State. But that doesn’t mean that StringType and DateTimeType are related in the same way. You can cast a StringType to a State and back because that’s an ancestor. Similarly, you can cast a DateTimeType to a State and back. But StringType and DateTimeType are siblings. You cannot cast between siblings.

To convert a String to a DateTimeType you must parse the string. Probably the easiest way to do that would be to just make SigenergyTokenExpiry be a DateTime Item instead of a String Item. Assuming a DateTime Item can handle the timezone like that, OH will parse it and create a DateTimeType out of it for you before your rule ever has to deal with it. If it is a DateTime Item then the State will also be a DateTimeType and you can cast it to one using as.

But the only way this code could have ever worked in the past as written is if the Item was a DateTime Item to begin with. So something must have changed or this code never actually ran in the first place.

I found it. An you are completely right (of course :wink: )
What I did 2 days ago was to move the items from a definition in the UI to a text file.
During this migration, it seems I changed the item type unintended. However, as the item had the same namem, the content still seemed to be in a DateTime format and the rule was working fine. But once the item was updated 2 days later, the rule crashed.
Thanks a lot for your support!! :slight_smile: