Add a more instructive Event message to reconstruct what actually occurred as
[plcapi.git] / PLC / Timestamp.py
1 #
2 # Utilities to handle timestamps / durations from/to integers and strings
3 #
4 # $Id$
5 # $URL$
6 #
7
8 #
9 # datetime.{datetime,timedelta} are powerful tools, but these objects are not
10 # natively marshalled over xmlrpc
11 #
12
13 from types import StringTypes
14 import time, calendar
15 import datetime
16
17 from PLC.Faults import *
18 from PLC.Parameter import Parameter, Mixed
19
20 # a dummy class mostly used as a namespace
21 class Timestamp:
22
23     debug=False
24 #    debug=True
25
26     # this is how we expose times to SQL
27     sql_format = "%Y-%m-%d %H:%M:%S"
28     sql_format_utc = "%Y-%m-%d %H:%M:%S UTC"
29     # this one (datetime.isoformat) would work too but that's less readable - we support this input though
30     iso_format = "%Y-%m-%dT%H:%M:%S"
31     # sometimes it's convenient to understand more formats
32     input_formats = [ sql_format,
33                       sql_format_utc,
34                       iso_format,
35                       "%Y-%m-%d %H:%M",
36                       "%Y-%m-%d %H:%M UTC",
37                       ]
38
39     # for timestamps we usually accept either an int, or an ISO string,
40     # the datetime.datetime stuff can in general be used locally,
41     # but not sure it can be marshalled over xmlrpc though
42
43     @staticmethod
44     def Parameter (doc):
45         return Mixed (Parameter (int, doc + " (unix timestamp)"),
46                       Parameter (str, doc + " (formatted as %s)"%Timestamp.sql_format),
47                       )
48
49     @staticmethod
50     def sql_validate (input, timezone=False, check_future = False):
51         """
52         Validates the specified GMT timestamp, returns a
53         standardized string suitable for SQL input.
54
55         Input may be a number (seconds since UNIX epoch back in 1970,
56         or a string (in one of the supported input formats).
57
58         If timezone is True, the resulting string contains
59         timezone information, which is hard-wired as 'UTC'
60
61         If check_future is True, raises an exception if timestamp is in
62         the past.
63
64         Returns a GMT timestamp string suitable to feed SQL.
65         """
66
67         if not timezone: output_format = Timestamp.sql_format
68         else:            output_format = Timestamp.sql_format_utc
69
70         if Timestamp.debug: print 'sql_validate, in:',input,
71         if isinstance(input, StringTypes):
72             sql=''
73             # calendar.timegm() is the inverse of time.gmtime()
74             for time_format in Timestamp.input_formats:
75                 try:
76                     timestamp = calendar.timegm(time.strptime(input, time_format))
77                     sql = time.strftime(output_format, time.gmtime(timestamp))
78                     break
79                 # wrong format: ignore
80                 except ValueError: pass
81             # could not parse it
82             if not sql:
83                 raise PLCInvalidArgument, "Cannot parse timestamp %r - not in any of %r formats"%(input,Timestamp.input_formats)
84         elif isinstance (input,(int,long,float)):
85             try:
86                 timestamp = long(input)
87                 sql = time.strftime(output_format, time.gmtime(timestamp))
88             except Exception,e:
89                 raise PLCInvalidArgument, "Timestamp %r not recognized -- %r"%(input,e)
90         else:
91             raise PLCInvalidArgument, "Timestamp %r - unsupported type %r"%(input,type(input))
92
93         if check_future and input < time.time():
94             raise PLCInvalidArgument, "'%s' not in the future" % sql
95
96         if Timestamp.debug: print 'sql_validate, out:',sql
97         return sql
98
99     @staticmethod
100     def sql_validate_utc (timestamp):
101         "For convenience, return sql_validate(intput, timezone=True, check_future=False)"
102         return Timestamp.sql_validate (timestamp, timezone=True, check_future=False)
103
104
105     @staticmethod
106     def cast_long (input):
107         """
108         Translates input timestamp as a unix timestamp.
109
110         Input may be a number (seconds since UNIX epoch, i.e., 1970-01-01
111         00:00:00 GMT), a string (in one of the supported input formats above).
112
113         """
114         if Timestamp.debug: print 'cast_long, in:',input,
115         if isinstance(input, StringTypes):
116             timestamp=0
117             for time_format in Timestamp.input_formats:
118                 try:
119                     result=calendar.timegm(time.strptime(input, time_format))
120                     if Timestamp.debug: print 'out:',result
121                     return result
122                 # wrong format: ignore
123                 except ValueError: pass
124             raise PLCInvalidArgument, "Cannot parse timestamp %r - not in any of %r formats"%(input,Timestamp.input_formats)
125         elif isinstance (input,(int,long,float)):
126             result=long(input)
127             if Timestamp.debug: print 'out:',result
128             return result
129         else:
130             raise PLCInvalidArgument, "Timestamp %r - unsupported type %r"%(input,type(input))
131
132
133 # utility for displaying durations
134 # be consistent in avoiding the datetime stuff
135 class Duration:
136
137     MINUTE = 60
138     HOUR = 3600
139     DAY = 3600*24
140
141     @staticmethod
142     def to_string(duration):
143         result=[]
144         left=duration
145         (days,left) = divmod(left,Duration.DAY)
146         if days:    result.append("%d d)"%td.days)
147         (hours,left) = divmod (left,Duration.HOUR)
148         if hours:   result.append("%d h"%hours)
149         (minutes, seconds) = divmod (left, Duration.MINUTE)
150         if minutes: result.append("%d m"%minutes)
151         if seconds: result.append("%d s"%seconds)
152         if not result: result = ['void']
153         return "-".join(result)
154
155     @staticmethod
156     def validate (duration):
157         # support seconds only for now, works for int/long/str
158         try:
159             return long (duration)
160         except:
161             raise PLCInvalidArgument, "Could not parse duration %r"%duration