1"""aiding.py constants and basic functions 2 3""" 4from __future__ import absolute_import, division, print_function 5 6import os 7import time 8import random 9import datetime 10 11 12# Import ioflo libs 13from .sixing import * 14from ..base import excepting 15 16from .consoling import getConsole 17console = getConsole() 18 19TIME1970 = 2208988800 #offset secs between SNTP epoch=1900 & unix epoch=1970 20 21 22def totalSeconds(td): 23 """ Compute total seconds for datetime.timedelta object 24 needed for python 2.6 25 """ 26 return ((td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6) 27 28TotalSeconds = totalSeconds 29 30def timestamp(dt=None): 31 """ 32 Returns float posix timestamp at dt if given else now 33 Only TZ aware in python 3.2+ 34 """ 35 if dt is None: 36 if hasattr(datetime, "timezone"): 37 dt = datetime.datetime.now(datetime.timezone.utc) # make it aware 38 else: 39 dt = datetime.datetime.utcnow() # not aware 40 41 if hasattr(dt, "timestamp"): # only in python3.3+ 42 return dt.timestamp() 43 elif hasattr(datetime, "timezone"): # only in python3.2+ 44 return (dt - datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)).total_seconds() 45 else: # not tz aware 46 return (dt - datetime.datetime(1970, 1, 1)).total_seconds() 47 48def iso8601(dt=None, aware=False): 49 """ 50 Returns string datetime stamp in iso 8601 format from datetime object dt 51 If dt is missing and aware then use now(timezone.utc) else utcnow() naive 52 YYYY-MM-DDTHH:MM:SS.mmmmmm which is strftime '%Y-%m-%dT%H:%M:%S.%f' 53 Only TZ aware in python 3.2+ 54 """ 55 if dt is None: 56 if aware and hasattr(datetime, "timezone"): 57 dt = datetime.datetime.now(datetime.timezone.utc) # make it aware 58 else: # naive 59 dt = datetime.datetime.utcnow() # naive 60 61 return(dt.isoformat()) 62 63def tuuid(stamp=None, prefix=None): 64 """ 65 Returns lexocographically sortable TUUID (Time Universal Unique Identifier) 66 that is hex formated string of length 24 67 stamp is float time since unix epoch (1970-1-1) as in time.time() 68 If stamp not provided uses current system time UTC 69 70 prefix is optional prefix string that is prepended to tuid with '_' 71 for example if prefix is 'm' then tuiid looks like 72 'm_0000014ddf1f2f9c_5e36738' 73 The length of the tuuid is now 24 + len(prefix) +1 74 75 Format of of TUUID is hex string of form stamphex_randombyteshex 76 Example: 77 '0000014ddf1f2f9c_5e36738' 78 79 Stamp is time since unix epoch (1970-1-1) in milliseconds, for example 80 1422658890197 81 82 Unix time is 32 bit integer in seconds which rolls over on 2038-1-19 83 To represent unix epoch in seconds need at least 32 bits 84 To represent unix epoch in microseconds use 64 bits (8 bytes, 16 hex characters) 85 86 Adding separator character adds 1 hex char for 17 hex digits 87 Adding 7 hex char of random data brings the total to 24 hex bytes 88 89 28 bits (3.5 bytes, 7 hex characters) the random bytes or 24 characters total 90 91 16 chars (8 bytes) stamp + 1 char underscore + 7 chars (3.5 bytes) random = 92 24 chars (12 bytes) 93 94 Uses random.SystemRandom which is crytographically random 95 """ 96 parts = [] 97 if prefix is not None: 98 parts.append(prefix) 99 100 stamp = stamp if stamp is not None else timestamp() 101 stamp = int(stamp * 1000000) 102 stamp = "{0:016x}".format(stamp)[-16:] 103 parts.append(stamp) 104 randomized = random.SystemRandom().randint(0, 0xFFFFFFF) 105 randomized = "{0:07x}".format(randomized)[-7:] 106 parts.append(randomized) 107 return ("_".join(parts)) 108 109 110class Timer(object): 111 """ Class to manage real elaspsed time. needs time module 112 attributes: 113 .duration = time duration of timer start to stop 114 .start = time started 115 .stop = time when timer expires 116 117 properties: 118 .elaspsed = time elasped since start 119 .remaining = time remaining until stop 120 .expired = True if expired, False otherwise 121 122 methods: 123 .extend() = extends/shrinks timer duration 124 .repeat() = restarts timer at last .stop so no time lost 125 .restart() = restarts timer 126 """ 127 128 def __init__(self, duration = 0.0): 129 """ Initialization method for instance. 130 duration in seconds (fractional) 131 """ 132 self.restart(start=time.time(), duration=duration) 133 134 def getElapsed(self): #for property 135 """ Computes elapsed time in seconds (fractional) since start. 136 if zero then hasn't started yet 137 """ 138 return max(0.0, time.time() - self.start) 139 elapsed = property(getElapsed, doc='Elapsed time.') 140 141 def getRemaining(self):# for property 142 """ Returns time remaining in seconds (fractional) before expires. 143 returns zero if it has already expired 144 """ 145 return max(0.0, self.stop - time.time()) 146 remaining = property(getRemaining, doc='Remaining time.') 147 148 def getExpired(self): 149 if (time.time() >= self.stop): 150 return True 151 else: 152 return False 153 expired = property(getExpired, doc='True if expired, False otherwise') 154 155 def restart(self,start=None, duration=None): 156 """ Starts timer at start time secs for duration secs. 157 (fractional from epoc) 158 If start arg is missing then restarts at current time 159 If duration arg is missing then restarts for current duration 160 """ 161 if start is not None: 162 self.start = abs(start) #must be non negative 163 else: #use current time 164 self.start = time.time() 165 166 if duration is not None: 167 self.duration = abs(duration) #must be non negative 168 #Otherwise keep old duration 169 170 self.stop = self.start + self.duration 171 172 return (self.start, self.stop) 173 174 def repeat(self): 175 """ Restarts timer at stop so no time lost 176 177 """ 178 return self.restart(start=self.stop) 179 180 def extend(self, extension=None): 181 """ Extends timer duration for additional extension seconds (fractional). 182 Useful so as not to lose time when need more/less time on timer 183 184 If extension negative then shortens existing duration 185 If extension arg missing then extends for the existing duration 186 effectively doubling the time 187 188 """ 189 if extension is None: #otherwise extend by .duration or double 190 extension = self.duration 191 192 duration = self.duration + extension 193 194 return self.restart(start=self.start, duration=duration) 195 196class MonoTimer(object): 197 """ Class to manage real elaspsed time with monotonic guarantee. 198 If the system clock is retrograded (moved back in time) 199 while the timer is running then time.time() could move 200 to before the start time. 201 A MonoTimer detects this retrograde and if adjust is True then 202 shifts the timer back otherwise it raises a TimerRetroError 203 exception. 204 This timer is not able to detect a prograded clock 205 (moved forward in time) 206 207 Needs time module 208 attributes: 209 .duration = time duration of timer start to stop 210 .start = time started 211 .stop = time when timer expires 212 .retro = automaticall shift timer if retrograded clock detected 213 214 properties: 215 .elaspsed = time elasped since start 216 .remaining = time remaining until stop 217 .expired = True if expired, False otherwise 218 219 methods: 220 .extend() = extends/shrinks timer duration 221 .repeat() = restarts timer at last .stop so no time lost 222 .restart() = restarts timer 223 """ 224 225 def __init__(self, duration = 0.0, retro=False): 226 """ Initialization method for instance. 227 duration in seconds (fractional) 228 """ 229 self.retro = True if retro else False 230 self.start = None 231 self.stop = None 232 self.latest = time.time() # last time checked current time 233 self.restart(start=self.latest, duration=duration) 234 235 def update(self): 236 ''' 237 Updates .latest to current time. 238 Checks for retrograde movement of system time.time() and either 239 raises a TimerRetroErrorexception or adjusts the timer attributes to compensate. 240 ''' 241 delta = time.time() - self.latest # current time - last time checked 242 if delta < 0: # system clock has retrograded 243 if not self.retro: 244 raise excepting.TimerRetroError("Timer retrograded by {0} " 245 "seconds\n".format(delta)) 246 self.start = self.start + delta 247 self.stop = self.stop + delta 248 249 self.latest += delta 250 251 def getElapsed(self): #for property 252 """ Computes elapsed time in seconds (fractional) since start. 253 if zero then hasn't started yet 254 """ 255 self.update() 256 return max(0.0, self.latest - self.start) 257 elapsed = property(getElapsed, doc='Elapsed time.') 258 259 def getRemaining(self):# for property 260 """ Returns time remaining in seconds (fractional) before expires. 261 returns zero if it has already expired 262 """ 263 self.update() 264 return max(0.0, self.stop - self.latest) 265 remaining = property(getRemaining, doc='Remaining time.') 266 267 def getExpired(self): 268 self.update() 269 if (self.latest >= self.stop): 270 return True 271 else: 272 return False 273 expired = property(getExpired, doc='True if expired, False otherwise') 274 275 def restart(self,start=None, duration=None): 276 """ Starts timer at start time secs for duration secs. 277 (fractional from epoc) 278 If start arg is missing then restarts at current time 279 If duration arg is missing then restarts for current duration 280 """ 281 self.update() 282 if start is not None: 283 self.start = abs(start) #must be non negative 284 else: #use current time 285 self.start = self.latest 286 287 if duration is not None: 288 self.duration = abs(duration) #must be non negative 289 #Otherwise keep old duration 290 291 self.stop = self.start + self.duration 292 293 return (self.start, self.stop) 294 295 def repeat(self): 296 """ Restarts timer at stop so no time lost 297 298 """ 299 return self.restart(start=self.stop) 300 301 def extend(self, extension=None): 302 """ Extends timer duration for additional extension seconds (fractional). 303 Useful so as not to lose time when need more/less time on timer 304 305 If extension negative then shortens existing duration 306 If extension arg missing then extends for the existing duration 307 effectively doubling the time 308 309 """ 310 if extension is None: #otherwise extend by .duration or double 311 extension = self.duration 312 313 duration = self.duration + extension 314 315 return self.restart(start=self.start, duration=duration) 316 317class StoreTimer(object): 318 """ Class to manage relative Store based time. 319 Uses Store instance .stamp attribute as current time 320 Attributes: 321 .duration = time duration of timer start to stop 322 .start = time started 323 .stop = time when timer expires 324 325 properties: 326 .elaspsed = time elasped since start 327 .remaining = time remaining until stop 328 .expired = True if expired, False otherwise 329 330 methods: 331 .extend() = extends/shrinks timer duration 332 .repeat() = restarts timer at last .stop so no time lost 333 .restart() = restarts timer 334 """ 335 336 def __init__(self, store, duration = 0.0): 337 """ Initialization method for instance. 338 store is reference to Store instance 339 duration in seconds (fractional) 340 """ 341 self.store = store 342 start = self.store.stamp if self.store.stamp is not None else 0.0 343 self.restart(start=start, duration=duration) 344 345 def getElapsed(self): #for property 346 """ Computes elapsed time in seconds (fractional) since start. 347 if zero then hasn't started yet 348 """ 349 return max(0.0, self.store.stamp - self.start) 350 elapsed = property(getElapsed, doc='Elapsed time.') 351 352 def getRemaining(self):# for property 353 """ Returns time remaining in seconds (fractional) before expires. 354 returns zero if it has already expired 355 """ 356 return max(0.0, self.stop - self.store.stamp) 357 remaining = property(getRemaining, doc='Remaining time.') 358 359 def getExpired(self): 360 if (self.store.stamp is not None and self.store.stamp >= self.stop): 361 return True 362 else: 363 return False 364 expired = property(getExpired, doc='True if expired, False otherwise') 365 366 def restart(self, start=None, duration=None): 367 """ Starts timer at start time secs for duration secs. 368 (fractional from epoc) 369 If start arg is missing then restarts at current time 370 If duration arg is missing then restarts for current duration 371 """ 372 if start is not None: 373 self.start = abs(start) #must be non negative 374 else: #use current time 375 self.start = self.store.stamp 376 377 if duration is not None: 378 self.duration = abs(duration) #must be non negative 379 #Otherwise keep old duration 380 381 self.stop = self.start + self.duration 382 383 return (self.start, self.stop) 384 385 def repeat(self): 386 """ Restarts timer at stop so no time lost 387 388 """ 389 return self.restart(start = self.stop) 390 391 def extend(self, extension=None): 392 """ Extends timer duration for additional extension seconds (fractional). 393 Useful so as not to lose time when need more/less time on timer 394 395 If extension negative then shortens existing duration 396 If extension arg missing then extends for the existing duration 397 effectively doubling the time 398 399 """ 400 if extension is None: #otherwise extend by .duration or double 401 extension = self.duration 402 403 duration = self.duration + extension 404 405 return self.restart(start=self.start, duration=duration) 406 407 408class Stamper(object): 409 """ 410 Provides a relative time stamp that is advanced with method 411 Models the protocol for time stamps used by the Store class 412 Use this to provide matching interface to Store for relative time stamp 413 414 Attributes: 415 stamp is relative time stamp 416 417 """ 418 def __init__(self, stamp=None): 419 """ 420 Initialize instance 421 """ 422 self.stamp = float(stamp) if stamp is not None else 0.0 423 424 def change(self, stamp): 425 """ 426 change time stamp 427 """ 428 self.stamp = float(stamp) 429 430 changeStamp = change # alias 431 432 def advance(self, delta): 433 """ 434 Advance time stamp by delta 435 """ 436 self.stamp += float(delta) 437 438 advanceStamp = advance # alias 439 440 441 442