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