Robot Framework
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 re
17 import time
18 from datetime import timedelta
19 
20 from .normalizing import normalize
21 from .misc import plural_or_not
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 
38 
40  isecs = int(secs)
41  millis = round((secs - isecs) * 1000)
42  return (isecs, millis) if millis < 1000 else (isecs+1, 0)
43 
44 
45 
55 def timestr_to_secs(timestr, round_to=3, accept_plain_values=True):
56  if is_string(timestr) or is_number(timestr):
57  if accept_plain_values:
58  converters = [_number_to_secs, _timer_to_secs, _time_string_to_secs]
59  else:
60  converters = [_timer_to_secs, _time_string_to_secs]
61  for converter in converters:
62  secs = converter(timestr)
63  if secs is not None:
64  return secs if round_to is None else round(secs, round_to)
65  if isinstance(timestr, timedelta):
66  return timestr.total_seconds()
67  raise ValueError("Invalid time string '%s'." % timestr)
68 
69 
70 def _number_to_secs(number):
71  try:
72  return float(number)
73  except ValueError:
74  return None
75 
76 
77 def _timer_to_secs(number):
78  match = _timer_re.match(number)
79  if not match:
80  return None
81  prefix, hours, minutes, seconds, millis = match.groups()
82  seconds = float(minutes) * 60 + float(seconds)
83  if hours:
84  seconds += float(hours[:-1]) * 60 * 60
85  if millis:
86  seconds += float(millis[1:]) / 10**len(millis[1:])
87  if prefix == '-':
88  seconds *= -1
89  return seconds
90 
91 
92 def _time_string_to_secs(timestr):
93  timestr = _normalize_timestr(timestr)
94  if not timestr:
95  return None
96  nanos = micros = millis = secs = mins = hours = days = 0
97  if timestr[0] == '-':
98  sign = -1
99  timestr = timestr[1:]
100  else:
101  sign = 1
102  temp = []
103  for c in timestr:
104  try:
105  if c == 'n': nanos = float(''.join(temp)); temp = []
106  elif c == 'u': micros = float(''.join(temp)); temp = []
107  elif c == 'x': millis = float(''.join(temp)); temp = []
108  elif c == 's': secs = float(''.join(temp)); temp = []
109  elif c == 'm': mins = float(''.join(temp)); temp = []
110  elif c == 'h': hours = float(''.join(temp)); temp = []
111  elif c == 'd': days = float(''.join(temp)); temp = []
112  else: temp.append(c)
113  except ValueError:
114  return None
115  if temp:
116  return None
117  return sign * (nanos/1E9 + micros/1E6 + millis/1000 + secs +
118  mins*60 + hours*60*60 + days*60*60*24)
119 
120 
121 def _normalize_timestr(timestr):
122  timestr = normalize(timestr)
123  for specifier, aliases in [('n', ['nanosecond', 'ns']),
124  ('u', ['microsecond', 'us', 'μs']),
125  ('x', ['millisecond', 'millisec', 'millis',
126  'msec', 'ms']),
127  ('s', ['second', 'sec']),
128  ('m', ['minute', 'min']),
129  ('h', ['hour']),
130  ('d', ['day'])]:
131  plural_aliases = [a+'s' for a in aliases if not a.endswith('s')]
132  for alias in plural_aliases + aliases:
133  if alias in timestr:
134  timestr = timestr.replace(alias, specifier)
135  return timestr
136 
137 
138 
151 def secs_to_timestr(secs, compact=False):
152  return _SecsToTimestrHelper(secs, compact).get_value()
153 
154 
156 
157  def __init__(self, float_secs, compact):
158  self._compact_compact = compact
159  self._ret_ret = []
160  self._sign, millis, secs, mins, hours, days \
161  = self._secs_to_components_secs_to_components(float_secs)
162  self._add_item_add_item(days, 'd', 'day')
163  self._add_item_add_item(hours, 'h', 'hour')
164  self._add_item_add_item(mins, 'min', 'minute')
165  self._add_item_add_item(secs, 's', 'second')
166  self._add_item_add_item(millis, 'ms', 'millisecond')
167 
168  def get_value(self):
169  if len(self._ret_ret) > 0:
170  return self._sign + ' '.join(self._ret_ret)
171  return '0s' if self._compact_compact else '0 seconds'
172 
173  def _add_item(self, value, compact_suffix, long_suffix):
174  if value == 0:
175  return
176  if self._compact_compact:
177  suffix = compact_suffix
178  else:
179  suffix = ' %s%s' % (long_suffix, plural_or_not(value))
180  self._ret_ret.append('%d%s' % (value, suffix))
181 
182  def _secs_to_components(self, float_secs):
183  if float_secs < 0:
184  sign = '- '
185  float_secs = abs(float_secs)
186  else:
187  sign = ''
188  int_secs, millis = _float_secs_to_secs_and_millis(float_secs)
189  secs = int_secs % 60
190  mins = int_secs // 60 % 60
191  hours = int_secs // (60 * 60) % 24
192  days = int_secs // (60 * 60 * 24)
193  return sign, millis, secs, mins, hours, days
194 
195 
196 
207 def format_time(timetuple_or_epochsecs, daysep='', daytimesep=' ', timesep=':',
208  millissep=None):
209  if is_number(timetuple_or_epochsecs):
210  timetuple = _get_timetuple(timetuple_or_epochsecs)
211  else:
212  timetuple = timetuple_or_epochsecs
213  daytimeparts = ['%02d' % t for t in timetuple[:6]]
214  day = daysep.join(daytimeparts[:3])
215  time_ = timesep.join(daytimeparts[3:6])
216  millis = millissep and '%s%03d' % (millissep, timetuple[6]) or ''
217  return day + daytimesep + time_ + millis
218 
219 
220 
236 def get_time(format='timestamp', time_=None):
237  time_ = int(time.time() if time_ is None else time_)
238  format = format.lower()
239  # 1) Return time in seconds since epoc
240  if 'epoch' in format:
241  return time_
242  timetuple = time.localtime(time_)
243  parts = []
244  for i, match in enumerate('year month day hour min sec'.split()):
245  if match in format:
246  parts.append('%.2d' % timetuple[i])
247  # 2) Return time as timestamp
248  if not parts:
249  return format_time(timetuple, daysep='-')
250  # Return requested parts of the time
251  elif len(parts) == 1:
252  return parts[0]
253  else:
254  return parts
255 
256 
257 
271 def parse_time(timestr):
272  for method in [_parse_time_epoch,
273  _parse_time_timestamp,
274  _parse_time_now_and_utc]:
275  seconds = method(timestr)
276  if seconds is not None:
277  return int(seconds)
278  raise ValueError("Invalid time format '%s'." % timestr)
279 
280 
281 def _parse_time_epoch(timestr):
282  try:
283  ret = float(timestr)
284  except ValueError:
285  return None
286  if ret < 0:
287  raise ValueError("Epoch time must be positive (got %s)." % timestr)
288  return ret
289 
290 
292  try:
293  return timestamp_to_secs(timestr, (' ', ':', '-', '.'))
294  except ValueError:
295  return None
296 
297 
299  timestr = timestr.replace(' ', '').lower()
300  base = _parse_time_now_and_utc_base(timestr[:3])
301  if base is not None:
302  extra = _parse_time_now_and_utc_extra(timestr[3:])
303  if extra is not None:
304  return base + extra + _get_dst_difference(base, base + extra)
305  return None
306 
307 
309  now = time.time()
310  if base == 'now':
311  return now
312  if base == 'utc':
313  zone = time.altzone if time.localtime().tm_isdst else time.timezone
314  return now + zone
315  return None
316 
317 
319  if not extra:
320  return 0
321  if extra[0] not in ['+', '-']:
322  return None
323  return (1 if extra[0] == '+' else -1) * timestr_to_secs(extra[1:])
324 
325 
326 def _get_dst_difference(time1, time2):
327  time1_is_dst = time.localtime(time1).tm_isdst
328  time2_is_dst = time.localtime(time2).tm_isdst
329  if time1_is_dst is time2_is_dst:
330  return 0
331  difference = time.timezone - time.altzone
332  return difference if time1_is_dst else -difference
333 
334 
335 def get_timestamp(daysep='', daytimesep=' ', timesep=':', millissep='.'):
336  return TIMESTAMP_CACHE.get_timestamp(daysep, daytimesep, timesep, millissep)
337 
338 
339 def timestamp_to_secs(timestamp, seps=None):
340  try:
341  secs = _timestamp_to_millis(timestamp, seps) / 1000.0
342  except (ValueError, OverflowError):
343  raise ValueError("Invalid timestamp '%s'." % timestamp)
344  else:
345  return round(secs, 3)
346 
347 
348 def secs_to_timestamp(secs, seps=None, millis=False):
349  if not seps:
350  seps = ('', ' ', ':', '.' if millis else None)
351  ttuple = time.localtime(secs)[:6]
352  if millis:
353  millis = (secs - int(secs)) * 1000
354  ttuple = ttuple + (round(millis),)
355  return format_time(ttuple, *seps)
356 
357 
358 
359 def get_elapsed_time(start_time, end_time):
360  if start_time == end_time or not (start_time and end_time):
361  return 0
362  if start_time[:-4] == end_time[:-4]:
363  return int(end_time[-3:]) - int(start_time[-3:])
364  start_millis = _timestamp_to_millis(start_time)
365  end_millis = _timestamp_to_millis(end_time)
366  # start/end_millis can be long but we want to return int when possible
367  return int(end_millis - start_millis)
368 
369 
370 
374 def elapsed_time_to_string(elapsed, include_millis=True):
375  prefix = ''
376  if elapsed < 0:
377  prefix = '-'
378  elapsed = abs(elapsed)
379  if include_millis:
380  return prefix + _elapsed_time_to_string(elapsed)
381  return prefix + _elapsed_time_to_string_without_millis(elapsed)
382 
383 
385  secs, millis = divmod(round(elapsed), 1000)
386  mins, secs = divmod(secs, 60)
387  hours, mins = divmod(mins, 60)
388  return '%02d:%02d:%02d.%03d' % (hours, mins, secs, millis)
389 
390 
392  secs = round(elapsed, ndigits=-3) // 1000
393  mins, secs = divmod(secs, 60)
394  hours, mins = divmod(mins, 60)
395  return '%02d:%02d:%02d' % (hours, mins, secs)
396 
397 
398 def _timestamp_to_millis(timestamp, seps=None):
399  if seps:
400  timestamp = _normalize_timestamp(timestamp, seps)
401  Y, M, D, h, m, s, millis = _split_timestamp(timestamp)
402  secs = time.mktime((Y, M, D, h, m, s, 0, 0, -1))
403  return round(1000*secs + millis)
404 
405 
406 def _normalize_timestamp(ts, seps):
407  for sep in seps:
408  if sep in ts:
409  ts = ts.replace(sep, '')
410  ts = ts.ljust(17, '0')
411  return f'{ts[:8]} {ts[8:10]}:{ts[10:12]}:{ts[12:14]}.{ts[14:17]}'
412 
413 
414 def _split_timestamp(timestamp):
415  years = int(timestamp[:4])
416  mons = int(timestamp[4:6])
417  days = int(timestamp[6:8])
418  hours = int(timestamp[9:11])
419  mins = int(timestamp[12:14])
420  secs = int(timestamp[15:17])
421  millis = int(timestamp[18:21])
422  return years, mons, days, hours, mins, secs, millis
423 
424 
426 
427  def __init__(self):
428  self._previous_secs_previous_secs = None
429  self._previous_separators_previous_separators = None
430  self._previous_timestamp_previous_timestamp = None
431 
432  def get_timestamp(self, daysep='', daytimesep=' ', timesep=':', millissep='.'):
433  epoch = self._get_epoch_get_epoch()
434  secs, millis = _float_secs_to_secs_and_millis(epoch)
435  if self._use_cache_use_cache(secs, daysep, daytimesep, timesep):
436  return self._cached_timestamp_cached_timestamp(millis, millissep)
437  timestamp = format_time(epoch, daysep, daytimesep, timesep, millissep)
438  self._cache_timestamp_cache_timestamp(secs, timestamp, daysep, daytimesep, timesep, millissep)
439  return timestamp
440 
441  # Seam for mocking
442  def _get_epoch(self):
443  return time.time()
444 
445  def _use_cache(self, secs, *separators):
446  return self._previous_timestamp_previous_timestamp \
447  and self._previous_secs_previous_secs == secs \
448  and self._previous_separators_previous_separators == separators
449 
450  def _cached_timestamp(self, millis, millissep):
451  if millissep:
452  return self._previous_timestamp_previous_timestamp + millissep + format(millis, '03d')
453  return self._previous_timestamp_previous_timestamp
454 
455  def _cache_timestamp(self, secs, timestamp, daysep, daytimesep, timesep, millissep):
456  self._previous_secs_previous_secs = secs
457  self._previous_separators_previous_separators = (daysep, daytimesep, timesep)
458  self._previous_timestamp_previous_timestamp = timestamp[:-4] if millissep else timestamp
459 
460 
461 TIMESTAMP_CACHE = TimestampCache()
def get_timestamp(self, daysep='', daytimesep=' ', timesep=':', millissep='.')
Definition: robottime.py:432
def _use_cache(self, secs, *separators)
Definition: robottime.py:445
def _cached_timestamp(self, millis, millissep)
Definition: robottime.py:450
def _cache_timestamp(self, secs, timestamp, daysep, daytimesep, timesep, millissep)
Definition: robottime.py:455
def _add_item(self, value, compact_suffix, long_suffix)
Definition: robottime.py:173
def __init__(self, float_secs, compact)
Definition: robottime.py:157
def _secs_to_components(self, float_secs)
Definition: robottime.py:182
def plural_or_not(item)
Definition: misc.py:73
def normalize(string, ignore=(), caseless=True, spaceless=True)
Normalizes given string according to given spec.
Definition: normalizing.py:27
def get_time(format='timestamp', time_=None)
Return the given or current time in requested format.
Definition: robottime.py:236
def _normalize_timestr(timestr)
Definition: robottime.py:121
def _parse_time_epoch(timestr)
Definition: robottime.py:281
def _timer_to_secs(number)
Definition: robottime.py:77
def _elapsed_time_to_string(elapsed)
Definition: robottime.py:384
def _time_string_to_secs(timestr)
Definition: robottime.py:92
def format_time(timetuple_or_epochsecs, daysep='', daytimesep=' ', timesep=':', millissep=None)
Returns a timestamp formatted from given time using separators.
Definition: robottime.py:208
def get_elapsed_time(start_time, end_time)
Returns the time between given timestamps in milliseconds.
Definition: robottime.py:359
def _parse_time_now_and_utc(timestr)
Definition: robottime.py:298
def get_timestamp(daysep='', daytimesep=' ', timesep=':', millissep='.')
Definition: robottime.py:335
def _normalize_timestamp(ts, seps)
Definition: robottime.py:406
def _timestamp_to_millis(timestamp, seps=None)
Definition: robottime.py:398
def _split_timestamp(timestamp)
Definition: robottime.py:414
def secs_to_timestamp(secs, seps=None, millis=False)
Definition: robottime.py:348
def timestamp_to_secs(timestamp, seps=None)
Definition: robottime.py:339
def _parse_time_now_and_utc_base(base)
Definition: robottime.py:308
def _get_dst_difference(time1, time2)
Definition: robottime.py:326
def _float_secs_to_secs_and_millis(secs)
Definition: robottime.py:39
def _elapsed_time_to_string_without_millis(elapsed)
Definition: robottime.py:391
def secs_to_timestr(secs, compact=False)
Converts time in seconds to a string representation.
Definition: robottime.py:151
def _get_timetuple(epoch_secs=None)
Definition: robottime.py:31
def parse_time(timestr)
Parses the time string and returns its value as seconds since epoch.
Definition: robottime.py:271
def timestr_to_secs(timestr, round_to=3, accept_plain_values=True)
Parses time strings like '1h 10s', '01:00:10' and '42' and returns seconds.
Definition: robottime.py:55
def _parse_time_timestamp(timestr)
Definition: robottime.py:291
def _number_to_secs(number)
Definition: robottime.py:70
def _parse_time_now_and_utc_extra(extra)
Definition: robottime.py:318
def elapsed_time_to_string(elapsed, include_millis=True)
Converts elapsed time in milliseconds to format 'hh:mm:ss.mil'.
Definition: robottime.py:374