# Can We Make Jython Date Arithmetic More Pythonic?

**URL:** <https://forum.inductiveautomation.com/t/can-we-make-jython-date-arithmetic-more-pythonic/86379>\
**Category:** Ignition\
**Tags:** ignition81, scripting\
**Created:** [February 26, 2024, 5:38pm UTC](https://forum.inductiveautomation.com/t/can-we-make-jython-date-arithmetic-more-pythonic/86379 "2024-02-26T17:38:57Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![Brandon\_Peterson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/brandon_peterson/32/32960_2.png) [@Brandon\_Peterson](https://forum.inductiveautomation.com/u/Brandon_Peterson)\
**Post date:** [February 26, 2024, 5:38pm UTC](https://forum.inductiveautomation.com/t/can-we-make-jython-date-arithmetic-more-pythonic/86379/1 "2024-02-26T17:38:57Z")

</div>

Ignition has no concept of "Duration" in its type system (I'm not counting datetimes that you treat like durations), but Java does. For this reason, I like using the [java.time.Duration](https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html) class for all my duration needs.

The Duration class has everything I need, but time arithmetic (with both [java.util.Date](https://docs.oracle.com/javase/8/docs/api/java/util/Date.html), Ignition's datetime type, and java.time.Duration) still has to be done with functions instead of operators. I want to be able to write `my_date = my_prev_date + my_duration`, and `weighted_duration = first_duration * 0.75 + second_duration * 0.25`.

**My question is this:**  
If I `import TimeUtils` at the top of any script in my Ignition project, will I then be able to use operators to perform my `java.time.Duration` calculations?

My concern is that I will either be unable to write these properties to the imported Jython objects, or that those properties could get dropped between different executions from the same script (scripts are loaded/ran once, but their function definitions may then be used elsewhere).

```auto
# Module: TimeUtils
from java.time import Duration
from java.util import Date

NANOS_IN_SEC = 1000000000.0
NANOS_IN_MILLIS = 1000000.0

Duration. __neg__ = Duration.negated
Duration. __abs__ = Duration.abs

def duration __add__ (self, other):
	if type(other) is Duration:
		pass
	elif type(other) is Date:
		pass
	else:
		pass

Duration. __add__ = Duration.plus
Duration. __sub__ = Duration.minus

def duration __str__ (self):
	sign = '-' if self.isNegative() else ''
	pos = abs(self) #take the positive to avoid conjugates
	hours = pos.toHours()
	minutes = pos.toMinutesPart()
	seconds = pos.toSecondsPart()
	nanoseconds = pos.toNanosPart()
	return '{}{:0>2d}:{:0>2d}:{:0>2d}.{:0>9d}' \
		.format(sign, hours, minutes, seconds, nanoseconds)

Duration. __str__ = duration __str__

def duration __repr__ (self):
	return 'Duration({})'.format(str(self))

Duration. __repr__ = duration __repr__

def duration __mul__ (self, other):
	
	if type(other) in [int, long, float]:
		
		secs = self.toSeconds() * other
		nanos = self.toNanosPart() * other
		
		extra_secs = secs % 1
		nanos += NANOS_IN_SEC * extra_secs
		secs -= extra_secs
		
		if nanos > NANOS_IN_SEC:
			secs += nanos // NANOS_IN_SEC
			nanos %= NANOS_IN_SEC
		elif nanos < -NANOS_IN_SEC:
			secs -= -nanos // NANOS_IN_SEC
			nanos = -(-nanos % NANOS_IN_SEC)

		return Duration.ofSeconds(long(secs), int(nanos))
		
	else:
		raise TypeError("Must multiply by a number")
		
Duration. __mul__ = duration __mul__
	
def duration __div__ (self, other):
	return self * (1.0 / other)
	
Duration. __div__ = duration __div__

def date __add__ (self, other):
	if type(other) is Duration:
		return system.date.addMillis(self, int(other.toNanos() / NANOS_IN_MILLIS))
	else:
		raise TypeError('can only add a Duration to a date')

Date. __add__ = date __add__

def date __sub__ (self, other):
	if type(other) is Duration:
		return system.date.addMillis(self, int((-other).toNanos() / NANOS_IN_MILLIS))
	elif type(other) is Date:
		millisDiff = system.date.millisBetween(other, self)
		return Duration.ofMillis(millisDiff)
	else:
		raise TypeError('can only add a Duration to a date')

Date. __sub__ = date __sub__

```

---

<div class="post-metadata">

**Author:** ![lrose](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/lrose/32/62724_2.png) [@lrose](https://forum.inductiveautomation.com/u/lrose)\
**Post date:** [February 26, 2024, 5:46pm UTC](https://forum.inductiveautomation.com/t/can-we-make-jython-date-arithmetic-more-pythonic/86379/2 "2024-02-26T17:46:29Z")

</div>

> [@Brandon\_Peterson](#):
>
> If I `import TimeUtils` at the top of any script in my Ignition project

You should write this at the top most level of a project script, and then call into those scripting functions from everywhere else. The whole idea of a project script is that it is Project Global, there is no need to import that in other places.

As long as (with addition for example) there is an ` __add__ ` function declared, then you should be able to use the `+` operator with that type.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [February 26, 2024, 6:00pm UTC](https://forum.inductiveautomation.com/t/can-we-make-jython-date-arithmetic-more-pythonic/86379/3 "2024-02-26T18:00:10Z")

</div>

This should probably be done in java with a custom python wrapper. Not really trivial.

---

<div class="post-metadata">

**Author:** ![Brandon\_Peterson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/brandon_peterson/32/32960_2.png) [@Brandon\_Peterson](https://forum.inductiveautomation.com/u/Brandon_Peterson)\
**Post date:** [February 26, 2024, 6:13pm UTC](https://forum.inductiveautomation.com/t/can-we-make-jython-date-arithmetic-more-pythonic/86379/4 "2024-02-26T18:13:45Z")

</div>

> [@lrose](#):
>
> The whole idea of a project script is that it is Project Global, there is no need to import that in other places.

I'm a little confused. Are you saying that whenever I have a script on a project resource, like a Perspective component property binding or change script, it uses the project context that's shared between all other scripts in the project?

I'd like to understand more about how project libraries execute. When I modify the `java.time.Duration` class in a Gateway Scripting Project Library Module, is that the same `java.time.Duration` class definition that would be used in a Perspective Binding script? My hope is yes, but my guess is no.

Is the `java.time.Duration` class definition ever updated without triggering a full Project Library refresh?

When an import statement runs `import TimeUtils`, does that run the `TimeUtils.py` project library file before executing the rest of the code, or is there a shortcut, a way of preloading libraries into the script context before running?

Maybe I just need to learn more about how python executes in general, but I also wonder how specific some of these questions are to Ignition in particular. Any clarification is appreciated.

> **[Project Library | Ignition User Manual](https://www.docs.inductiveautomation.com/docs/8.1/platform/scripting/scripting-in-ignition/project-library#project-libraries-and-execution)**
>
> Scripts under the Project Library are a project-based resource that allows user created Python scripts to be configured. Objects and functions created in a Project Library script can be called from anywhere in the project. Project Library scripts are...

---

<div class="post-metadata">

**Author:** ![Brandon\_Peterson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/brandon_peterson/32/32960_2.png) [@Brandon\_Peterson](https://forum.inductiveautomation.com/u/Brandon_Peterson)\
**Post date:** [February 26, 2024, 6:14pm UTC](https://forum.inductiveautomation.com/t/can-we-make-jython-date-arithmetic-more-pythonic/86379/5 "2024-02-26T18:14:51Z")

</div>

> [@pturmel](#):
>
> This should probably be done in java with a custom python wrapper. Not really trivial.

Do you mean that I would need to create a custom module to properly implement this?

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [February 26, 2024, 6:16pm UTC](https://forum.inductiveautomation.com/t/can-we-make-jython-date-arithmetic-more-pythonic/86379/6 "2024-02-26T18:16:08Z")

</div>

Yes.

---

<div class="post-metadata">

**Author:** ![lrose](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/lrose/32/62724_2.png) [@lrose](https://forum.inductiveautomation.com/u/lrose)\
**Post date:** [February 26, 2024, 6:18pm UTC](https://forum.inductiveautomation.com/t/can-we-make-jython-date-arithmetic-more-pythonic/86379/7 "2024-02-26T18:18:01Z")

</div>

> [@Brandon\_Peterson](#):
>
> Are you saying that whenever I have a script on a project resource

What I am saying is, that project script resources are project global.

So on a Perspective component property binding script transform instead of doing something like:

```auto
import TimeUtils

#multiple lines of code

```

You should instead do:

```auto
timeUtils.getDurration() #single line call into project script.

```

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [February 26, 2024, 6:23pm UTC](https://forum.inductiveautomation.com/t/can-we-make-jython-date-arithmetic-more-pythonic/86379/8 "2024-02-26T18:23:17Z")

</div>

> [@Brandon\_Peterson](#):
>
> it uses the project context that's shared between all other scripts in the project?

Yes. Although some thread locals that Ignition uses for Perspective will only be present when called from a Perspective event. When the project library is called from a pure gateway event, those thread locals won't exist, and `system.perspective.*` won't work.

> [@Brandon\_Peterson](#):
>
> how project libraries execute

They operate like python site packages (mostly), but auto-imported by name into project scopes. That they are **imported** is important, because it means they only execute top-to-bottom **once** per project startup (plus project restarts after edits). The auto-import occurs within the context as soon as any event anywhere in that project references any name within that library script.

`TimeUtils` in one project in the gateway won't be the same java object as `TimeUtils` created in another project. Only with a 3rd party module could you uniformly make them truly the same.

And keep in mind that project libraries' scope is also confined to the JVM in which they are running--Vision Client and Designer scopes are separate JVMs, so separate namespaces, too.
