Robot Framework Integrated Development Environment (RIDE)
robottime.py
Go to the documentation of this file.
1 # Copyright 2008-2015 Nokia Networks
2 # Copyright 2016- Robot Framework Foundation
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 import datetime
17 import time
18 import re
19 
20 from .normalizing import normalize
21 from .misc import plural_or_not, roundup
22 from .robottypes import is_number, is_string
23 
24 
25 
28 _timer_re = re.compile(r'^([+-])?(\d+:)?(\d+):(\d+)(\.\d+)?$')
29 
30 
31 def _get_timetuple(epoch_secs=None):
32  if epoch_secs is None: # can also be 0 (at least in unit tests)
33  epoch_secs = time.time()
34  secs, millis = _float_secs_to_secs_and_millis(epoch_secs)
35  timetuple = time.localtime(secs)[:6] # from year to secs
36  return timetuple + (millis,)
37 
39  isecs = int(secs)
40  millis = roundup((secs - isecs) * 1000)
41  return (isecs, millis) if millis < 1000 else (isecs+1, 0)
42 
43 
44 
45 def timestr_to_secs(timestr, round_to=3):
46  if is_string(timestr) or is_number(timestr):
47  for converter in _number_to_secs, _timer_to_secs, _time_string_to_secs:
48  secs = converter(timestr)
49  if secs is not None:
50  return secs if round_to is None else roundup(secs, round_to)
51  raise ValueError("Invalid time string '%s'." % timestr)
52 
53 def _number_to_secs(number):
54  try:
55  return float(number)
56  except ValueError:
57  return None
58 
59 def _timer_to_secs(number):
60  match = _timer_re.match(number)
61  if not match:
62  return None
63  prefix, hours, minutes, seconds, millis = match.groups()
64  seconds = float(minutes) * 60 + float(seconds)
65  if hours:
66  seconds += float(hours[:-1]) * 60 * 60
67  if millis:
68  seconds += float(millis[1:]) / 10**len(millis[1:])
69  if prefix == '-':
70  seconds *= -1
71  return seconds
72 
73 def _time_string_to_secs(timestr):
74  timestr = _normalize_timestr(timestr)
75  if not timestr:
76  return None
77  millis = secs = mins = hours = days = 0
78  if timestr[0] == '-':
79  sign = -1
80  timestr = timestr[1:]
81  else:
82  sign = 1
83  temp = []
84  for c in timestr:
85  try:
86  if c == 'x': millis = float(''.join(temp)); temp = []
87  elif c == 's': secs = float(''.join(temp)); temp = []
88  elif c == 'm': mins = float(''.join(temp)); temp = []
89  elif c == 'h': hours = float(''.join(temp)); temp = []
90  elif c == 'd': days = float(''.join(temp)); temp = []
91  else: temp.append(c)
92  except ValueError:
93  return None
94  if temp:
95  return None
96  return sign * (millis/1000 + secs + mins*60 + hours*60*60 + days*60*60*24)
97 
98 def _normalize_timestr(timestr):
99  timestr = normalize(timestr)
100  for specifier, aliases in [('x', ['millisecond', 'millisec', 'millis',
101  'msec', 'ms']),
102  ('s', ['second', 'sec']),
103  ('m', ['minute', 'min']),
104  ('h', ['hour']),
105  ('d', ['day'])]:
106  plural_aliases = [a+'s' for a in aliases if not a.endswith('s')]
107  for alias in plural_aliases + aliases:
108  if alias in timestr:
109  timestr = timestr.replace(alias, specifier)
110  return timestr
111 
112 
113 
126 def secs_to_timestr(secs, compact=False):
127  return _SecsToTimestrHelper(secs, compact).get_value()
128 
129 
131 
132  def __init__(self, float_secs, compact):
133  self._compact_compact = compact
134  self._ret_ret = []
135  self._sign, millis, secs, mins, hours, days \
136  = self._secs_to_components_secs_to_components(float_secs)
137  self._add_item_add_item(days, 'd', 'day')
138  self._add_item_add_item(hours, 'h', 'hour')
139  self._add_item_add_item(mins, 'min', 'minute')
140  self._add_item_add_item(secs, 's', 'second')
141  self._add_item_add_item(millis, 'ms', 'millisecond')
142 
143  def get_value(self):
144  if len(self._ret_ret) > 0:
145  return self._sign + ' '.join(self._ret_ret)
146  return '0s' if self._compact_compact else '0 seconds'
147 
148  def _add_item(self, value, compact_suffix, long_suffix):
149  if value == 0:
150  return
151  if self._compact_compact:
152  suffix = compact_suffix
153  else:
154  suffix = ' %s%s' % (long_suffix, plural_or_not(value))
155  self._ret_ret.append('%d%s' % (value, suffix))
156 
157  def _secs_to_components(self, float_secs):
158  if float_secs < 0:
159  sign = '- '
160  float_secs = abs(float_secs)
161  else:
162  sign = ''
163  int_secs, millis = _float_secs_to_secs_and_millis(float_secs)
164  secs = int_secs % 60
165  mins = int_secs // 60 % 60
166  hours = int_secs // (60 * 60) % 24
167  days = int_secs // (60 * 60 * 24)
168  return sign, millis, secs, mins, hours, days
169 
170 
171 
182 def format_time(timetuple_or_epochsecs, daysep='', daytimesep=' ', timesep=':',
183  millissep=None):
184  if is_number(timetuple_or_epochsecs):
185  timetuple = _get_timetuple(timetuple_or_epochsecs)
186  else:
187  timetuple = timetuple_or_epochsecs
188  daytimeparts = ['%02d' % t for t in timetuple[:6]]
189  day = daysep.join(daytimeparts[:3])
190  time_ = timesep.join(daytimeparts[3:6])
191  millis = millissep and '%s%03d' % (millissep, timetuple[6]) or ''
192  return day + daytimesep + time_ + millis
193 
194 
195 
211 def get_time(format='timestamp', time_=None):
212  time_ = int(time_ or time.time())
213  format = format.lower()
214  # 1) Return time in seconds since epoc
215  if 'epoch' in format:
216  return time_
217  timetuple = time.localtime(time_)
218  parts = []
219  for i, match in enumerate('year month day hour min sec'.split()):
220  if match in format:
221  parts.append('%.2d' % timetuple[i])
222  # 2) Return time as timestamp
223  if not parts:
224  return format_time(timetuple, daysep='-')
225  # Return requested parts of the time
226  elif len(parts) == 1:
227  return parts[0]
228  else:
229  return parts
230 
231 
232 
246 def parse_time(timestr):
247  for method in [_parse_time_epoch,
248  _parse_time_timestamp,
249  _parse_time_now_and_utc]:
250  seconds = method(timestr)
251  if seconds is not None:
252  return int(seconds)
253  raise ValueError("Invalid time format '%s'." % timestr)
254 
255 def _parse_time_epoch(timestr):
256  try:
257  ret = float(timestr)
258  except ValueError:
259  return None
260  if ret < 0:
261  raise ValueError("Epoch time must be positive (got %s)." % timestr)
262  return ret
263 
265  try:
266  return timestamp_to_secs(timestr, (' ', ':', '-', '.'))
267  except ValueError:
268  return None
269 
271  timestr = timestr.replace(' ', '').lower()
272  base = _parse_time_now_and_utc_base(timestr[:3])
273  if base is not None:
274  extra = _parse_time_now_and_utc_extra(timestr[3:])
275  if extra is not None:
276  return base + extra
277  return None
278 
280  now = time.time()
281  if base == 'now':
282  return now
283  if base == 'utc':
284  zone = time.altzone if time.localtime().tm_isdst else time.timezone
285  return now + zone
286  return None
287 
289  if not extra:
290  return 0
291  if extra[0] not in ['+', '-']:
292  return None
293  return (1 if extra[0] == '+' else -1) * timestr_to_secs(extra[1:])
294 
295 
296 def get_timestamp(daysep='', daytimesep=' ', timesep=':', millissep='.'):
297  return TIMESTAMP_CACHE.get_timestamp(daysep, daytimesep, timesep, millissep)
298 
299 
300 def timestamp_to_secs(timestamp, seps=None):
301  try:
302  secs = _timestamp_to_millis(timestamp, seps) / 1000.0
303  except (ValueError, OverflowError):
304  raise ValueError("Invalid timestamp '%s'." % timestamp)
305  else:
306  return roundup(secs, 3)
307 
308 
309 def secs_to_timestamp(secs, seps=None, millis=False):
310  if not seps:
311  seps = ('', ' ', ':', '.' if millis else None)
312  ttuple = time.localtime(secs)[:6]
313  if millis:
314  millis = (secs - int(secs)) * 1000
315  ttuple = ttuple + (roundup(millis),)
316  return format_time(ttuple, *seps)
317 
318 
319 
320 def get_elapsed_time(start_time, end_time):
321  if start_time == end_time or not (start_time and end_time):
322  return 0
323  if start_time[:-4] == end_time[:-4]:
324  return int(end_time[-3:]) - int(start_time[-3:])
325  start_millis = _timestamp_to_millis(start_time)
326  end_millis = _timestamp_to_millis(end_time)
327  # start/end_millis can be long but we want to return int when possible
328  return int(end_millis - start_millis)
329 
330 
331 
335 def elapsed_time_to_string(elapsed, include_millis=True):
336  prefix = ''
337  if elapsed < 0:
338  prefix = '-'
339  elapsed = abs(elapsed)
340  if include_millis:
341  return prefix + _elapsed_time_to_string(elapsed)
342  return prefix + _elapsed_time_to_string_without_millis(elapsed)
343 
345  secs, millis = divmod(roundup(elapsed), 1000)
346  mins, secs = divmod(secs, 60)
347  hours, mins = divmod(mins, 60)
348  return '%02d:%02d:%02d.%03d' % (hours, mins, secs, millis)
349 
351  secs = roundup(elapsed, ndigits=-3) // 1000
352  mins, secs = divmod(secs, 60)
353  hours, mins = divmod(mins, 60)
354  return '%02d:%02d:%02d' % (hours, mins, secs)
355 
356 
357 def _timestamp_to_millis(timestamp, seps=None):
358  if seps:
359  timestamp = _normalize_timestamp(timestamp, seps)
360  Y, M, D, h, m, s, millis = _split_timestamp(timestamp)
361  secs = time.mktime(datetime.datetime(Y, M, D, h, m, s).timetuple())
362  return roundup(1000*secs + millis)
363 
364 def _normalize_timestamp(ts, seps):
365  for sep in seps:
366  if sep in ts:
367  ts = ts.replace(sep, '')
368  ts = ts.ljust(17, '0')
369  return '%s%s%s %s:%s:%s.%s' % (ts[:4], ts[4:6], ts[6:8], ts[8:10],
370  ts[10:12], ts[12:14], ts[14:17])
371 
372 def _split_timestamp(timestamp):
373  years = int(timestamp[:4])
374  mons = int(timestamp[4:6])
375  days = int(timestamp[6:8])
376  hours = int(timestamp[9:11])
377  mins = int(timestamp[12:14])
378  secs = int(timestamp[15:17])
379  millis = int(timestamp[18:21])
380  return years, mons, days, hours, mins, secs, millis
381 
382 
384 
385  def __init__(self):
386  self._previous_secs_previous_secs = None
387  self._previous_separators_previous_separators = None
388  self._previous_timestamp_previous_timestamp = None
389 
390  def get_timestamp(self, daysep='', daytimesep=' ', timesep=':', millissep='.'):
391  epoch = self._get_epoch_get_epoch()
392  secs, millis = _float_secs_to_secs_and_millis(epoch)
393  if self._use_cache_use_cache(secs, daysep, daytimesep, timesep):
394  return self._cached_timestamp_cached_timestamp(millis, millissep)
395  timestamp = format_time(epoch, daysep, daytimesep, timesep, millissep)
396  self._cache_timestamp_cache_timestamp(secs, timestamp, daysep, daytimesep, timesep, millissep)
397  return timestamp
398 
399  # Seam for mocking
400  def _get_epoch(self):
401  return time.time()
402 
403  def _use_cache(self, secs, *separators):
404  return self._previous_timestamp_previous_timestamp \
405  and self._previous_secs_previous_secs == secs \
406  and self._previous_separators_previous_separators == separators
407 
408  def _cached_timestamp(self, millis, millissep):
409  if millissep:
410  return self._previous_timestamp_previous_timestamp + millissep + format(millis, '03d')
411  return self._previous_timestamp_previous_timestamp
412 
413  def _cache_timestamp(self, secs, timestamp, daysep, daytimesep, timesep, millissep):
414  self._previous_secs_previous_secs = secs
415  self._previous_separators_previous_separators = (daysep, daytimesep, timesep)
416  self._previous_timestamp_previous_timestamp = timestamp[:-4] if millissep else timestamp
417 
418 
419 TIMESTAMP_CACHE = TimestampCache()
def _cached_timestamp(self, millis, millissep)
Definition: robottime.py:408
def _use_cache(self, secs, *separators)
Definition: robottime.py:403
def get_timestamp(self, daysep='', daytimesep=' ', timesep=':', millissep='.')
Definition: robottime.py:390
def _cache_timestamp(self, secs, timestamp, daysep, daytimesep, timesep, millissep)
Definition: robottime.py:413
def _add_item(self, value, compact_suffix, long_suffix)
Definition: robottime.py:148
def roundup(number, ndigits=0, return_type=None)
Rounds number to the given number of digits.
Definition: misc.py:34
def normalize(string, ignore=(), caseless=True, spaceless=True)
Normalizes given string according to given spec.
Definition: normalizing.py:30
def _elapsed_time_to_string_without_millis(elapsed)
Definition: robottime.py:350
def secs_to_timestamp(secs, seps=None, millis=False)
Definition: robottime.py:309
def timestamp_to_secs(timestamp, seps=None)
Definition: robottime.py:300
def elapsed_time_to_string(elapsed, include_millis=True)
Converts elapsed time in milliseconds to format 'hh:mm:ss.mil'.
Definition: robottime.py:335
def get_timestamp(daysep='', daytimesep=' ', timesep=':', millissep='.')
Definition: robottime.py:296
def get_time(format='timestamp', time_=None)
Return the given or current time in requested format.
Definition: robottime.py:211
def get_elapsed_time(start_time, end_time)
Returns the time between given timestamps in milliseconds.
Definition: robottime.py:320
def secs_to_timestr(secs, compact=False)
Converts time in seconds to a string representation.
Definition: robottime.py:126
def _get_timetuple(epoch_secs=None)
Definition: robottime.py:31
def format_time(timetuple_or_epochsecs, daysep='', daytimesep=' ', timesep=':', millissep=None)
Returns a timestamp formatted from given time using separators.
Definition: robottime.py:183
def parse_time(timestr)
Parses the time string and returns its value as seconds since epoch.
Definition: robottime.py:246
def _timestamp_to_millis(timestamp, seps=None)
Definition: robottime.py:357
def timestr_to_secs(timestr, round_to=3)
Parses time like '1h 10s', '01:00:10' or '42' and returns seconds.
Definition: robottime.py:45