Gateway Message Handler translates a datetime.now() command into a java.sql.Timestamp, how can I change it back?

I am trying to pass a datetime down from the gateway so that I have a single source of truth for my scripts where timing is important. For this I made a simple Request Handler:

def handleMessage(payload):
	from datetime import datetime
	return datetime.now()

the problem is that the object that is returned is reading as a java.sql.Timestamp, which evidently can't be delta'd against a datetime.datetime() object. Is there a way to fix this so that it returns the object I asked for (unlikely, because java), or at least translate it back on the recieving side without getting into stringification -> destringification?

Also, it drops timezone information in the process, regardless of if I use datetime.now() or datetime.utcnow().

return a system.date.now(), which is a java.util.Date, (and java.sql.Timestamp is a Date subclass).

edit: I'm not sure why a datetime.datetime becomes a java.sql.Timestamp after serialization/deserialization. Regardless, don't use janky Python types, just use Java's Date in your code instead. All the date/time scripting functions are built to work with it.

This.

Also, see this topic (not just for Perspective):

From my perspective, Java types seem equally janky, and having python types but not truly supporting them (as evidenced by the serialization in gateway comms that is apparently required for a lot of consistent scripting) is the most painful middle ground.

Half of the draw of Ignition is its advertised capability to script in Python (my preferred language, and the language used for almost all of the supporting software we have), and the more I use it the more I am frustrated by it. I don't know how much of it is jython, I don't know how much of it is the implementation in the gateway backend, but also I don't want to have to care.

@pturmel

Also, see this topic (not just for Perspective):

Thank you, it provides some context. I suppose what I have to do, but was hoping I didn't have to do, is write my own ISO 8601 parser in ignition. Its reinventing the wheel for the upteenth time. I already tried to transit my timestamps in ISO 8601 format, but the system.date.parse method (which I am already using, out of necessity) doesn't seem to happily take the T that goes between the date and time. I do know that this was made optional in ISO 8601-2019, but it should still be handled.

@Kevin.Herron If anything is to be taken from my venting -- please fast-lane basic ISO 8601-1:2019 and extended ISO 8601-2:2019 methods for parsing datetime strings. I see other requests for this in the forums.

1 Like

Python is the scripting language, the runtime, however is Jython, not CPython. For most users this is a meaningless distinction, but unfortunately it trips up people who have some Python or programming experience.

You don't need to transit your timestamps in ISO-8601, Java's Date is already UTC. Just use that. Don't get fooled by the fact that when you display it it gets displayed in the local timezone. If you need to control how it gets displayed, use one of the date format functions at that point to convert it to a String for display.

2 Likes

unfortunately it trips up people who have some Python or programming experience.

Somewhat of an understatement, I'd say. Still not sure how my datetime.datetime is coming back with java.sql.Timestamp.

You don't need to transit your timestamps in ISO-8601, Java's Date is already UTC. Just use that. Don't get fooled by the fact that when you display it it gets displayed in the local timezone. If you need to control how it gets displayed, use one of the date format functions at that point to convert it to a String for display.

And if the strings coming through are not in UTC? Operating in different timezones with different programmers formatting different awful timestamp strings? ISO 8601 exists for a reason, and it is a good reason: timestamps are amongst the most painful data types to work with, for a multitude of reasons. Allowing or perhaps even encouraging strict ISO 8601 adherence is very valuable.

Also having a builtin method that can be run in client or in gateway that will return the gateway's current time (as UTC milliseconds since the epoch, or something), very valuable.

I don't know what's meant by this. You can format and parse ISO 8601 right now.

now = system.date.now()

print now

iso_now = system.date.format(now, "yyyy-MM-dd'T'HH:mm:ss'Z'")

print iso_now

parsed_from_iso = system.date.parse(iso_now, "yyyy-MM-dd'T'HH:mm:ss'Z'")

print parsed_from_iso
Wed Oct 18 15:19:02 PDT 2023
2023-10-18T15:19:02Z
Wed Oct 18 15:19:02 PDT 2023
>>> 

Sure, we've already talked about it. It's system.date.now(). It returns the current Date, which is just milliseconds since epoch UTC under the hood.

Sure, we've already talked about it. It's system.date.now() . It returns the current Date, which is just milliseconds since epoch since UTC under the hoo

How can I know that this is gateway, and not client? Half of the battles I seem to be fighting stem from this question.

Partly you should just know what scope you are executing your code in... but there's also system.util.getSystemFlags if you really need to programmatically figure out where you are.

https://docs.inductiveautomation.com/display/DOC81/system.util.getSystemFlags

Not sure how this relates to the discussion about time though. An instant in time, represented in milliseconds since epoch UTC, is the same instant no matter what scope you are in.

Not sure how this relates to the discussion about time though. An instant in time, represented in milliseconds since epoch UTC, is the same instant no matter what scope you are in.

You might want to go take a look at some of the vision client behavior -- I am assuming this is a windows clock problem; I can't force windows to syncronize system clocks well enough to solve this to the second. I see something like a 35 second difference in the results of datetime.now() between my laptop (designer and client) and another laptop with a vision client installed. It was the impetus of this entire exercise.

As someone who has been using Ignition a fair amount for about a year now, I still have a very hard time "just knowing" which it is. I am getting better and better, but clearly it is not clear and obvious.

"You should just know" is the kind of thing that some of my worst professors would say. The professors that have been so deep for so long in the understanding of a system that they have forgotten what it is like to not "just know" everything.

I need to know how to know, otherwise I will be bothering people on the forums for a lot longer than anyone would like :wink:

I mean, I get what you're saying, but it's not a crazy leap. Did you write your code in a button handler? It's client. Did you write it in a tag change script? It's gateway.

Perhaps a cheat sheet would be useful.

1 Like

Yeah, might be. I'll raise that with the docs team. Might be something for the user manual if it doesn't exist already.

And if it does exist, some kind of hook to have it obviously available and indexed well -- I have not come across one and I have been 10 tabs deep in the docs for a year now.

I didn't notice because I was going too fast, but this doesn't actually work. It assumes the timezone is UTC already. The code I wrote to parse an ISO-8601 formatted date afterwards does work, though.

The actual code to format a date into UTC time for display is ugly enough that it probably does warrant a system scripting function.

1 Like

That said, I think the date formatting stuff was a distraction.

Your original goal to pass a time reference to clients should be as simple as:

def handleMessage(payload):
	return system.date.now()

The question is: what do you need to do with that reference once you've received it in the client that you weren't able to do?

Yes I will be using this and going from there.

I was trying to build a timer that I could override with a button. It worked (using datetime.now()) on my machine exactly as expected, but does not work on a deployed vision client. I believe this is because my laptop has better time syncronization with our gateway, somehow. I actually don't know for sure.

The implementation of the timer was using an expression tag that takes a delta from a memory tag loaded with a datetime.now() object (which I believe runs on the gateway). I had a second expression tag that was whether or not the timer had hit the threshold, which returned a bool value that was then bound to the enabled field on a button. I believe the problem stemmed from the setting of the timer, which was (in the original implementation) a datetime.now() object from the client. I observed by triggering the system that it was getting an initial time delta of -35 rather than zero.

I actually have not been able to iterate on this today, due to a GUI bug that caused an engineer to delete the contents of an entire tag provider while he was trying to delete SFC objects. Our backups just came online. We are trying to determine the exact repro steps so that we can submit a bug report.

You should be using an NTP server to keep your clients and gateway synchronized.

2 Likes