2 # Utilities to handle timestamps / durations from/to integers and strings
4 # datetime.{datetime,timedelta} are powerful tools, but these objects are not
5 # natively marshalled over xmlrpc
11 from PLC.Faults import *
12 from PLC.Parameter import Parameter, Mixed
14 # a dummy class mostly used as a namespace
20 # this is how we expose times to SQL
21 sql_format = "%Y-%m-%d %H:%M:%S"
22 sql_format_utc = "%Y-%m-%d %H:%M:%S UTC"
23 # this one (datetime.isoformat) would work too but that's less readable - we support this input though
24 iso_format = "%Y-%m-%dT%H:%M:%S"
25 # sometimes it's convenient to understand more formats
26 input_formats = [ sql_format,
33 # for timestamps we usually accept either an int, or an ISO string,
34 # the datetime.datetime stuff can in general be used locally,
35 # but not sure it can be marshalled over xmlrpc though
39 return Mixed (Parameter (int, doc + " (unix timestamp)"),
40 Parameter (str, doc + " (formatted as %s)"%Timestamp.sql_format),
44 def sql_validate (input, timezone=False, check_future = False):
46 Validates the specified GMT timestamp, returns a
47 standardized string suitable for SQL input.
49 Input may be a number (seconds since UNIX epoch back in 1970,
50 or a string (in one of the supported input formats).
52 If timezone is True, the resulting string contains
53 timezone information, which is hard-wired as 'UTC'
55 If check_future is True, raises an exception if timestamp is in
58 Returns a GMT timestamp string suitable to feed SQL.
61 output_format = (Timestamp.sql_format_utc if timezone
62 else Timestamp.sql_format)
65 print('sql_validate, in:', input, end=' ')
66 if isinstance(input, str):
68 # calendar.timegm() is the inverse of time.gmtime()
69 for time_format in Timestamp.input_formats:
71 timestamp = calendar.timegm(time.strptime(input, time_format))
72 sql = time.strftime(output_format, time.gmtime(timestamp))
74 # wrong format: ignore
79 raise PLCInvalidArgument("Cannot parse timestamp %r - not in any of %r formats"%(input,Timestamp.input_formats))
80 elif isinstance(input, (int, float)):
82 timestamp = int(input)
83 sql = time.strftime(output_format, time.gmtime(timestamp))
84 except Exception as e:
85 raise PLCInvalidArgument("Timestamp %r not recognized -- %r"%(input,e))
87 raise PLCInvalidArgument("Timestamp %r - unsupported type %r"%(input,type(input)))
89 if check_future and input < time.time():
90 raise PLCInvalidArgument("'%s' not in the future" % sql)
92 if Timestamp.debug: print('sql_validate, out:',sql)
96 def sql_validate_utc (timestamp):
97 "For convenience, return sql_validate(intput, timezone=True, check_future=False)"
98 return Timestamp.sql_validate (timestamp, timezone=True, check_future=False)
102 def cast_long(input):
104 Translates input timestamp as a unix timestamp.
106 Input may be a number (seconds since UNIX epoch, i.e., 1970-01-01
107 00:00:00 GMT), a string (in one of the supported input formats above).
111 print('cast_long, in:', input, end=' ')
112 if isinstance(input, str):
114 for time_format in Timestamp.input_formats:
116 result = calendar.timegm(time.strptime(input, time_format))
118 print('out:', result)
120 # wrong format: ignore
123 raise PLCInvalidArgument("Cannot parse timestamp %r - not in any of %r formats"%(input, Timestamp.input_formats))
124 elif isinstance(input, (int, float)):
130 raise PLCInvalidArgument("Timestamp %r - unsupported type %r"%(input,type(input)))
133 # utility for displaying durations
134 # be consistent in avoiding the datetime stuff
142 def to_string(duration):
145 (days, left) = divmod(left, Duration.DAY)
147 result.append("%d d)"%td.days)
148 (hours, left) = divmod (left, Duration.HOUR)
150 result.append("%d h"%hours)
151 (minutes, seconds) = divmod (left, Duration.MINUTE)
152 if minutes: result.append("%d m"%minutes)
153 if seconds: result.append("%d s"%seconds)
154 if not result: result = ['void']
155 return "-".join(result)
158 def validate (duration):
159 # support seconds only for now, works for int/long/str
163 raise PLCInvalidArgument("Could not parse duration %r"%duration)