# # 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 # from types import StringTypes 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. """ if not timezone: output_format = Timestamp.sql_format else: output_format = Timestamp.sql_format_utc if Timestamp.debug: print 'sql_validate, in:',input, if isinstance(input, StringTypes): 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,long,float)): try: timestamp = long(input) sql = time.strftime(output_format, time.gmtime(timestamp)) except Exception,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, if isinstance(input, StringTypes): 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,long,float)): result=long(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 long (duration) except: raise PLCInvalidArgument, "Could not parse duration %r"%duration