# # Utilities to handle timestamps / durations from/to integers and strings # # datetime.{datetime,timedelta} are powerful tools, but these objects are not # natively marshalled over xmlrpc # import time, calendar import datetime from PLC.Faults import * from PLC.Parameter import Parameter, Mixed # a dummy class mostly used as a namespace class Timestamp: debug = False # debug=True # this is how we expose times to SQL sql_format = "%Y-%m-%d %H:%M:%S" sql_format_utc = "%Y-%m-%d %H:%M:%S UTC" # this one (datetime.isoformat) would work too but that's less readable - we support this input though iso_format = "%Y-%m-%dT%H:%M:%S" # sometimes it's convenient to understand more formats input_formats = [ sql_format, sql_format_utc, iso_format, "%Y-%m-%d %H:%M", "%Y-%m-%d %H:%M UTC", ] # for timestamps we usually accept either an int, or an ISO string, # the datetime.datetime stuff can in general be used locally, # but not sure it can be marshalled over xmlrpc though @staticmethod def Parameter (doc): return Mixed (Parameter (int, doc + " (unix timestamp)"), Parameter (str, doc + " (formatted as %s)"%Timestamp.sql_format), ) @staticmethod def sql_validate (input, timezone=False, check_future = False): """ Validates the specified GMT timestamp, returns a standardized string suitable for SQL input. Input may be a number (seconds since UNIX epoch back in 1970, or a string (in one of the supported input formats). If timezone is True, the resulting string contains timezone information, which is hard-wired as 'UTC' If check_future is True, raises an exception if timestamp is in the past. Returns a GMT timestamp string suitable to feed SQL. """ output_format = (Timestamp.sql_format_utc if timezone else Timestamp.sql_format) if Timestamp.debug: print('sql_validate, in:', input, end=' ') if isinstance(input, str): sql = '' # calendar.timegm() is the inverse of time.gmtime() for time_format in Timestamp.input_formats: try: timestamp = calendar.timegm(time.strptime(input, time_format)) sql = time.strftime(output_format, time.gmtime(timestamp)) break # wrong format: ignore except ValueError: pass # could not parse it if not sql: raise PLCInvalidArgument("Cannot parse timestamp %r - not in any of %r formats"%(input,Timestamp.input_formats)) elif isinstance(input, (int, float)): try: timestamp = int(input) sql = time.strftime(output_format, time.gmtime(timestamp)) except Exception as e: raise PLCInvalidArgument("Timestamp %r not recognized -- %r"%(input,e)) else: raise PLCInvalidArgument("Timestamp %r - unsupported type %r"%(input,type(input))) if check_future and input < time.time(): raise PLCInvalidArgument("'%s' not in the future" % sql) if Timestamp.debug: print('sql_validate, out:',sql) return sql @staticmethod def sql_validate_utc (timestamp): "For convenience, return sql_validate(intput, timezone=True, check_future=False)" return Timestamp.sql_validate (timestamp, timezone=True, check_future=False) @staticmethod def cast_long(input): """ Translates input timestamp as a unix timestamp. Input may be a number (seconds since UNIX epoch, i.e., 1970-01-01 00:00:00 GMT), a string (in one of the supported input formats above). """ if Timestamp.debug: print('cast_long, in:', input, end=' ') if isinstance(input, str): timestamp = 0 for time_format in Timestamp.input_formats: try: result = calendar.timegm(time.strptime(input, time_format)) if Timestamp.debug: print('out:', result) return result # wrong format: ignore except ValueError: pass raise PLCInvalidArgument("Cannot parse timestamp %r - not in any of %r formats"%(input, Timestamp.input_formats)) elif isinstance(input, (int, float)): result = int(input) if Timestamp.debug: print('out:',result) return result else: raise PLCInvalidArgument("Timestamp %r - unsupported type %r"%(input,type(input))) # utility for displaying durations # be consistent in avoiding the datetime stuff class Duration: MINUTE = 60 HOUR = 3600 DAY = 3600*24 @staticmethod def to_string(duration): result=[] left = duration (days, left) = divmod(left, Duration.DAY) if days: result.append("%d d)"%td.days) (hours, left) = divmod (left, Duration.HOUR) if hours: result.append("%d h"%hours) (minutes, seconds) = divmod (left, Duration.MINUTE) if minutes: result.append("%d m"%minutes) if seconds: result.append("%d s"%seconds) if not result: result = ['void'] return "-".join(result) @staticmethod def validate (duration): # support seconds only for now, works for int/long/str try: return int(duration) except: raise PLCInvalidArgument("Could not parse duration %r"%duration)