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