I thought I would post some functions that I’m using to give back a little to the community. I find myself needing Elapsed time for many different cases in my OH system, for notifications and other purposes, so this was one of the first helper functions I put into action once I got Jython up and running. I also wanted a shorter way of updating a DateTime item to “now”, so I can now simply use joda_now()
, and over the years have found many reasons to use a millisecond timestamp for timing and other things in python code, so I have a millis()
function in there as well. There are also a few other functions that are dependencies within the code. I’m sure there’s a 95% chance that I’ve re-invented the wheel here, and I’m self-taught at programming in general, so this will probably look pretty “hacky” to some folks. I’d love to hear suggestions on ways to make the code better, or point me in the right direction if this sort of thing already exists somewhere. I personally think it would be cool if these functions could end up in the openHAB helper libraries date.py
, which I’d be glad to figure out how to contribute to if anyone was interested.
I have the following code in a file called utilities.py
placed in the /automation/lib/python/personal
folder. Then in my jython rule script I place (as needed):
from personal.utilities import joda_now, millis, elapsed
Calling millis()
returns the milliseconds as int
since Unix epoch.
Calling joda_now()
returns the Joda DateTime type string for that instant in the form 2019-09-03T15:22:33.650-05:00
. This can be directly placed into an item update, like this: events.postUpdate("SomeDateTimeItem", joda_now())
. By calling joda_now(False)
or joda_now(string=False)
it will instead return the Joda DateTime type object.
Calling elapsed()
will return the elapsed time in various forms. elapsed()
only requires one argument, a DateTime object, item or Joda DTT string. When only one argument is included, the elapsed time from that time stamp to “now” will be used. If two arguments are included, the elapsed time between them will be given. The elapsed time will default to a digital string output in the form Dd HH:MM:SS
(d will only be included for elapsed times greater than 24 hours). See the code block for the other output possibilities, one of which is human readable text string, which should be voiceTTS friendly.
Here’s the code!
# ---------------
# --- utilities.py
# ----- Module for various utility functions to be used across jython rules scripts
# ----- Place in /conf/automation/lib/python/personal
# ---------------
# *** = If incorporated into date.py, these would NOT be needed
from core.date import to_python_datetime # ***
from core.date import to_joda_datetime # ***
from core.date import human_readable_seconds # ***
from core.date import _pythonTimezone # ***
from java.time import ZonedDateTime # ***, Note Uppercase letters for Java/Joda
import datetime # ***, Note Lowercase letters for Python
import time
from core.log import logging, LOG_PREFIX # Only needed if logging from this script
title = "personal.utilities.py"
default_log = logging.getLogger("{}.{}".format(LOG_PREFIX, title))
# ----- FUNCTION: Get the current time in millis -----
def millis():
# Get the current time in millis (unix epoch) as an int
# - To simplify generic timing applications
return int(round(time.time() * 1000))
# ----- FUNCTION: Get the current time (Joda DateTimeType) -----
def joda_now(string=True):
# If string == True: (Default)
# Returns the string of the Joda DateTimeType to be used
# in updating a DateTime item
# If string == False:
# Returns a Joda DateTime object
if string == True:
return str(to_joda_datetime(ZonedDateTime.now()))
else:
return to_joda_datetime(ZonedDateTime.now())
# ----- FUNCTION: Get the elapsed time between two times -----
def elapsed(start, end=joda_now(), format='digital'):
# Args:
# start - Start time in any datetime type or joda zdtt string (Required)
# end - End time, optional. If not included the current time will be used
# format - 'digital' - Returns string '(-)HH:MM:SS' or '(-)Dd HH:MM:SS'
# - 'text' - Returns human readable string '2 days, 3 hours, 45 minutes'
# - 'seconds' - Returns the seconds as a float (with ms precision)
e_digital = '00:00:00'
e_text = '0 seconds'
e_seconds = 0.0
try: start_pdt = to_python_datetime(start)
except TypeError: # 'start' may have been a string returned by joda_now()
start_pdt = str_to_python_datetime(start)
try: end_pdt = to_python_datetime(end)
except TypeError: # 'end' may have been a string returned by joda_now()
end_pdt = str_to_python_datetime(end)
td = (end_pdt - start_pdt) # Total time delay (as timedelta)
ts = e_seconds = td.total_seconds()
sign = '-' if (ts < 0) else '' # Get the sign of 'ts'
ts = abs(ts) # Make 'ts' a positive number
days, seconds = divmod(ts, 86400)
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)
seconds = int(round(float(seconds) + (float(td.microseconds) / 1000.0 / 1000.0)))
days = int(days)
hours = int(hours)
minutes = int(minutes)
# Format the digital elapsed output
if days > 0:
e_digital = "{}{}d {:02d}:{:02d}:{:02d}".format(sign, days, hours, minutes, seconds)
else:
e_digital = "{}{:02d}:{:02d}:{:02d}".format(sign, hours, minutes, seconds)
# Format the text (voice TTS) elapsed output
e_text = human_readable_seconds(ts) # Added
if sign == '-': e_text = "Negative " + e_text
#default_log.debug("DIGITAL: {}".format(e_digital))
#default_log.debug("TEXT: {}".format(e_text))
#default_log.debug("SECONDS: {}".format(e_seconds))
if format == 'seconds': return e_seconds
elif format == 'text': return e_text
else: return e_digital
'''
Example Use:
from personal.<this_script> import elapsed, joda_now
example.log.info("Elapsed: {}".format(elapsed(items.SomeItem, joda_now())))
example.log.info("Elapsed: {}".format(elapsed(items.SomeItem, joda_now(False), format='text')))
example.log.info("Elapsed: {}".format(elapsed(items.SomeItem, joda_now(), format='seconds')))
Output:
Elapsed: 405d 13:37:17
Elapsed: 405 days, 13 hours, 37 minutes
Elapsed: 35041036.273
'''
# ----- FUNCTION: Convert Joda Zoned Date Time String to Python datetime -----
def str_to_python_datetime(time_str):
# Manually parse TZ info, generate the offset and replace in python datetime
# Accepts time_str: String representation of joda zoned datetimetype
# Returns: Timezone aware python datetime object
naive_time = time_str[:23] # removes '-05:00' (TZ) from joda zdtt str
tz_hr, tz_min = time_str[23:].split(':') # Splits TZ into hr, min
offset = int(tz_hr) * 60 + int(tz_min) # Gets minutes of offset
naive_pdt = datetime.datetime.strptime(naive_time, '%Y-%m-%dT%H:%M:%S.%f')
return naive_pdt.replace(tzinfo=_pythonTimezone(offset))
# Build tzinfo objects for fixed-offset time zones
# https://docs.python.org/2.7/library/datetime.html