Setting tag plcapi-5.4-2
[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
128 # utility for displaying durations
129 # be consistent in avoiding the datetime stuff
130 class Duration:
131
132     MINUTE = 60
133     HOUR = 3600
134     DAY = 3600*24
135
136     @staticmethod
137     def to_string(duration):
138         result=[]
139         left=duration
140         (days,left) = divmod(left,Duration.DAY)
141         if days:    result.append("%d d)"%td.days)
142         (hours,left) = divmod (left,Duration.HOUR)
143         if hours:   result.append("%d h"%hours)
144         (minutes, seconds) = divmod (left, Duration.MINUTE)
145         if minutes: result.append("%d m"%minutes)
146         if seconds: result.append("%d s"%seconds)
147         if not result: result = ['void']
148         return "-".join(result)
149
150     @staticmethod
151     def validate (duration):
152         # support seconds only for now, works for int/long/str
153         try:
154             return long (duration)
155         except:
156             raise PLCInvalidArgument, "Could not parse duration %r"%duration