From: <as...@us...> - 2009-09-06 22:46:41
|
Revision: 7666 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=7666&view=rev Author: astraw Date: 2009-09-06 22:46:27 +0000 (Sun, 06 Sep 2009) Log Message: ----------- testing: move test/mplTest/units to lib/matplotlib/testing/jpl_units Added Paths: ----------- trunk/matplotlib/lib/matplotlib/testing/jpl_units/ trunk/matplotlib/lib/matplotlib/testing/jpl_units/Duration.py trunk/matplotlib/lib/matplotlib/testing/jpl_units/Epoch.py trunk/matplotlib/lib/matplotlib/testing/jpl_units/EpochConverter.py trunk/matplotlib/lib/matplotlib/testing/jpl_units/StrConverter.py trunk/matplotlib/lib/matplotlib/testing/jpl_units/UnitDbl.py trunk/matplotlib/lib/matplotlib/testing/jpl_units/UnitDblConverter.py trunk/matplotlib/lib/matplotlib/testing/jpl_units/UnitDblFormatter.py trunk/matplotlib/lib/matplotlib/testing/jpl_units/__init__.py Removed Paths: ------------- trunk/matplotlib/test/mplTest/units/Duration.py trunk/matplotlib/test/mplTest/units/Epoch.py trunk/matplotlib/test/mplTest/units/EpochConverter.py trunk/matplotlib/test/mplTest/units/StrConverter.py trunk/matplotlib/test/mplTest/units/UnitDbl.py trunk/matplotlib/test/mplTest/units/UnitDblConverter.py trunk/matplotlib/test/mplTest/units/UnitDblFormatter.py trunk/matplotlib/test/mplTest/units/__init__.py Copied: trunk/matplotlib/lib/matplotlib/testing/jpl_units/Duration.py (from rev 7665, trunk/matplotlib/test/mplTest/units/Duration.py) =================================================================== --- trunk/matplotlib/lib/matplotlib/testing/jpl_units/Duration.py (rev 0) +++ trunk/matplotlib/lib/matplotlib/testing/jpl_units/Duration.py 2009-09-06 22:46:27 UTC (rev 7666) @@ -0,0 +1,203 @@ +#=========================================================================== +# +# Duration +# +#=========================================================================== + +"""Duration module.""" + +#=========================================================================== +# Place all imports after here. +# +# +# Place all imports before here. +#=========================================================================== + +#=========================================================================== +class Duration: + """Class Duration in development. + """ + allowed = [ "ET", "UTC" ] + + #----------------------------------------------------------------------- + def __init__( self, frame, seconds ): + """Create a new Duration object. + + = ERROR CONDITIONS + - If the input frame is not in the allowed list, an error is thrown. + + = INPUT VARIABLES + - frame The frame of the duration. Must be 'ET' or 'UTC' + - seconds The number of seconds in the Duration. + """ + if frame not in self.allowed: + msg = "Input frame '%s' is not one of the supported frames of %s" \ + % ( frame, str( self.allowed ) ) + raise ValueError( msg ) + + self._frame = frame + self._seconds = seconds + + #----------------------------------------------------------------------- + def frame( self ): + """Return the frame the duration is in.""" + return self._frame + + #----------------------------------------------------------------------- + def __abs__( self ): + """Return the absolute value of the duration.""" + return Duration( self._frame, abs( self._seconds ) ) + + #----------------------------------------------------------------------- + def __neg__( self ): + """Return the negative value of this Duration.""" + return Duration( self._frame, -self._seconds ) + + #----------------------------------------------------------------------- + def seconds( self ): + """Return the number of seconds in the Duration.""" + return self._seconds + + #----------------------------------------------------------------------- + def __nonzero__( self ): + """Compare two Durations. + + = INPUT VARIABLES + - rhs The Duration to compare against. + + = RETURN VALUE + - Returns -1 if self < rhs, 0 if self == rhs, +1 if self > rhs. + """ + return self._seconds != 0 + + #----------------------------------------------------------------------- + def __cmp__( self, rhs ): + """Compare two Durations. + + = ERROR CONDITIONS + - If the input rhs is not in the same frame, an error is thrown. + + = INPUT VARIABLES + - rhs The Duration to compare against. + + = RETURN VALUE + - Returns -1 if self < rhs, 0 if self == rhs, +1 if self > rhs. + """ + self.checkSameFrame( rhs, "compare" ) + return cmp( self._seconds, rhs._seconds ) + + #----------------------------------------------------------------------- + def __add__( self, rhs ): + """Add two Durations. + + = ERROR CONDITIONS + - If the input rhs is not in the same frame, an error is thrown. + + = INPUT VARIABLES + - rhs The Duration to add. + + = RETURN VALUE + - Returns the sum of ourselves and the input Duration. + """ + # Delay-load due to circular dependencies. + import mplTest.units as U + + if isinstance( rhs, U.Epoch ): + return rhs + self + + self.checkSameFrame( rhs, "add" ) + return Duration( self._frame, self._seconds + rhs._seconds ) + + #----------------------------------------------------------------------- + def __sub__( self, rhs ): + """Subtract two Durations. + + = ERROR CONDITIONS + - If the input rhs is not in the same frame, an error is thrown. + + = INPUT VARIABLES + - rhs The Duration to subtract. + + = RETURN VALUE + - Returns the difference of ourselves and the input Duration. + """ + self.checkSameFrame( rhs, "sub" ) + return Duration( self._frame, self._seconds - rhs._seconds ) + + #----------------------------------------------------------------------- + def __mul__( self, rhs ): + """Scale a UnitDbl by a value. + + = INPUT VARIABLES + - rhs The scalar to multiply by. + + = RETURN VALUE + - Returns the scaled Duration. + """ + return Duration( self._frame, self._seconds * float( rhs ) ) + + #----------------------------------------------------------------------- + def __rmul__( self, lhs ): + """Scale a Duration by a value. + + = INPUT VARIABLES + - lhs The scalar to multiply by. + + = RETURN VALUE + - Returns the scaled Duration. + """ + return Duration( self._frame, self._seconds * float( lhs ) ) + + #----------------------------------------------------------------------- + def __div__( self, rhs ): + """Divide a Duration by a value. + + = INPUT VARIABLES + - rhs The scalar to divide by. + + = RETURN VALUE + - Returns the scaled Duration. + """ + return Duration( self._frame, self._seconds / float( rhs ) ) + + #----------------------------------------------------------------------- + def __rdiv__( self, rhs ): + """Divide a Duration by a value. + + = INPUT VARIABLES + - rhs The scalar to divide by. + + = RETURN VALUE + - Returns the scaled Duration. + """ + return Duration( self._frame, float( rhs ) / self._seconds ) + + #----------------------------------------------------------------------- + def __str__( self ): + """Print the Duration.""" + return "%g %s" % ( self._seconds, self._frame ) + + #----------------------------------------------------------------------- + def __repr__( self ): + """Print the Duration.""" + return "Duration( '%s', %g )" % ( self._frame, self._seconds ) + + #----------------------------------------------------------------------- + def checkSameFrame( self, rhs, func ): + """Check to see if frames are the same. + + = ERROR CONDITIONS + - If the frame of the rhs Duration is not the same as our frame, + an error is thrown. + + = INPUT VARIABLES + - rhs The Duration to check for the same frame + - func The name of the function doing the check. + """ + if self._frame != rhs._frame: + msg = "Cannot %s Duration's with different frames.\n" \ + "LHS: %s\n" \ + "RHS: %s" % ( func, self._frame, rhs._frame ) + raise ValueError( msg ) + +#=========================================================================== Copied: trunk/matplotlib/lib/matplotlib/testing/jpl_units/Epoch.py (from rev 7665, trunk/matplotlib/test/mplTest/units/Epoch.py) =================================================================== --- trunk/matplotlib/lib/matplotlib/testing/jpl_units/Epoch.py (rev 0) +++ trunk/matplotlib/lib/matplotlib/testing/jpl_units/Epoch.py 2009-09-06 22:46:27 UTC (rev 7666) @@ -0,0 +1,232 @@ +#=========================================================================== +# +# Epoch +# +#=========================================================================== + +"""Epoch module.""" + +#=========================================================================== +# Place all imports after here. +# +import math +import datetime as DT +from matplotlib.dates import date2num +# +# Place all imports before here. +#=========================================================================== + +#=========================================================================== +class Epoch: + # Frame conversion offsets in seconds + # t(TO) = t(FROM) + allowed[ FROM ][ TO ] + allowed = { + "ET" : { + "UTC" : +64.1839, + }, + "UTC" : { + "ET" : -64.1839, + }, + } + + #----------------------------------------------------------------------- + def __init__( self, frame, sec=None, jd=None, daynum=None, dt=None ): + """Create a new Epoch object. + + Build an epoch 1 of 2 ways: + + Using seconds past a Julian date: + # Epoch( 'ET', sec=1e8, jd=2451545 ) + + or using a matplotlib day number + # Epoch( 'ET', daynum=730119.5 ) + + + = ERROR CONDITIONS + - If the input units are not in the allowed list, an error is thrown. + + = INPUT VARIABLES + - frame The frame of the epoch. Must be 'ET' or 'UTC' + - sec The number of seconds past the input JD. + - jd The Julian date of the epoch. + - daynum The matplotlib day number of the epoch. + - dt A python datetime instance. + """ + if ( ( sec is None and jd is not None ) or + ( sec is not None and jd is None ) or + ( daynum is not None and ( sec is not None or jd is not None ) ) or + ( daynum is None and dt is None and ( sec is None or jd is None ) ) or + ( daynum is not None and dt is not None ) or + ( dt is not None and ( sec is not None or jd is not None ) ) or + ( (dt is not None) and not isinstance(dt, DT.datetime) ) ): + msg = "Invalid inputs. Must enter sec and jd together, " \ + "daynum by itself, or dt (must be a python datetime).\n" \ + "Sec = %s\nJD = %s\ndnum= %s\ndt = %s" \ + % ( str( sec ), str( jd ), str( daynum ), str( dt ) ) + raise ValueError( msg ) + + if frame not in self.allowed: + msg = "Input frame '%s' is not one of the supported frames of %s" \ + % ( frame, str( self.allowed.keys() ) ) + raise ValueError( msg ) + + self._frame = frame + + if dt is not None: + daynum = date2num( dt ) + + if daynum is not None: + # 1-JAN-0001 in JD = 1721425.5 + jd = float( daynum ) + 1721425.5 + self._jd = math.floor( jd ) + self._seconds = ( jd - self._jd ) * 86400.0 + + else: + self._seconds = float( sec ) + self._jd = float( jd ) + + # Resolve seconds down to [ 0, 86400 ) + deltaDays = int( math.floor( self._seconds / 86400.0 ) ) + self._jd += deltaDays + self._seconds -= deltaDays * 86400.0 + + #----------------------------------------------------------------------- + def convert( self, frame ): + if self._frame == frame: + return self + + offset = self.allowed[ self._frame ][ frame ] + + return Epoch( frame, self._seconds + offset, self._jd ) + + #----------------------------------------------------------------------- + def frame( self ): + return self._frame + + #----------------------------------------------------------------------- + def julianDate( self, frame ): + t = self + if frame != self._frame: + t = self.convert( frame ) + + return t._jd + t._seconds / 86400.0 + + #----------------------------------------------------------------------- + def secondsPast( self, frame, jd ): + t = self + if frame != self._frame: + t = self.convert( frame ) + + delta = t._jd - jd + return t._seconds + delta * 86400 + + #----------------------------------------------------------------------- + def __cmp__( self, rhs ): + """Compare two Epoch's. + + = INPUT VARIABLES + - rhs The Epoch to compare against. + + = RETURN VALUE + - Returns -1 if self < rhs, 0 if self == rhs, +1 if self > rhs. + """ + t = self + if self._frame != rhs._frame: + t = self.convert( rhs._frame ) + + if t._jd != rhs._jd: + return cmp( t._jd, rhs._jd ) + + return cmp( t._seconds, rhs._seconds ) + + #----------------------------------------------------------------------- + def __add__( self, rhs ): + """Add a duration to an Epoch. + + = INPUT VARIABLES + - rhs The Epoch to subtract. + + = RETURN VALUE + - Returns the difference of ourselves and the input Epoch. + """ + t = self + if self._frame != rhs.frame(): + t = self.convert( rhs._frame ) + + sec = t._seconds + rhs.seconds() + + return Epoch( t._frame, sec, t._jd ) + + #----------------------------------------------------------------------- + def __sub__( self, rhs ): + """Subtract two Epoch's or a Duration from an Epoch. + + Valid: + Duration = Epoch - Epoch + Epoch = Epoch - Duration + + = INPUT VARIABLES + - rhs The Epoch to subtract. + + = RETURN VALUE + - Returns either the duration between to Epoch's or the a new + Epoch that is the result of subtracting a duration from an epoch. + """ + # Delay-load due to circular dependencies. + import mplTest.units as U + + # Handle Epoch - Duration + if isinstance( rhs, U.Duration ): + return self + -rhs + + t = self + if self._frame != rhs._frame: + t = self.convert( rhs._frame ) + + days = t._jd - rhs._jd + sec = t._seconds - rhs._seconds + + return U.Duration( rhs._frame, days*86400 + sec ) + + #----------------------------------------------------------------------- + def __str__( self ): + """Print the Epoch.""" + return "%22.15e %s" % ( self.julianDate( self._frame ), self._frame ) + + #----------------------------------------------------------------------- + def __repr__( self ): + """Print the Epoch.""" + return str( self ) + + #----------------------------------------------------------------------- + def range( start, stop, step ): + """Generate a range of Epoch objects. + + Similar to the Python range() method. Returns the range [ + start, stop ) at the requested step. Each element will be a + Epoch object. + + = INPUT VARIABLES + - start The starting value of the range. + - stop The stop value of the range. + - step Step to use. + + = RETURN VALUE + - Returns a list contianing the requested Epoch values. + """ + elems = [] + + i = 0 + while True: + d = start + i * step + if d >= stop: + break + + elems.append( d ) + i += 1 + + return elems + + range = staticmethod( range ) + +#=========================================================================== Copied: trunk/matplotlib/lib/matplotlib/testing/jpl_units/EpochConverter.py (from rev 7665, trunk/matplotlib/test/mplTest/units/EpochConverter.py) =================================================================== --- trunk/matplotlib/lib/matplotlib/testing/jpl_units/EpochConverter.py (rev 0) +++ trunk/matplotlib/lib/matplotlib/testing/jpl_units/EpochConverter.py 2009-09-06 22:46:27 UTC (rev 7666) @@ -0,0 +1,160 @@ +#=========================================================================== +# +# EpochConverter +# +#=========================================================================== + +"""EpochConverter module containing class EpochConverter.""" + +#=========================================================================== +# Place all imports after here. +# +import matplotlib.units as units +import matplotlib.dates as date_ticker +from matplotlib.cbook import iterable +# +# Place all imports before here. +#=========================================================================== + +__all__ = [ 'EpochConverter' ] + +#=========================================================================== +class EpochConverter( units.ConversionInterface ): + """: A matplotlib converter class. Provides matplotlib conversion + functionality for Monte Epoch and Duration classes. + """ + + # julian date reference for "Jan 1, 0001" minus 1 day because + # matplotlib really wants "Jan 0, 0001" + jdRef = 1721425.5 - 1 + + #------------------------------------------------------------------------ + @staticmethod + def axisinfo( unit, axis ): + """: Returns information on how to handle an axis that has Epoch data. + + = INPUT VARIABLES + - unit The units to use for a axis with Epoch data. + + = RETURN VALUE + - Returns a matplotlib AxisInfo data structure that contains + minor/major formatters, major/minor locators, and default + label information. + """ + + majloc = date_ticker.AutoDateLocator() + majfmt = date_ticker.AutoDateFormatter( majloc ) + + return units.AxisInfo( majloc = majloc, + majfmt = majfmt, + label = unit ) + + #------------------------------------------------------------------------ + @staticmethod + def float2epoch( value, unit ): + """: Convert a matplotlib floating-point date into an Epoch of the + specified units. + + = INPUT VARIABLES + - value The matplotlib floating-point date. + - unit The unit system to use for the Epoch. + + = RETURN VALUE + - Returns the value converted to an Epoch in the sepcified time system. + """ + # Delay-load due to circular dependencies. + import mplTest.units as U + + secPastRef = value * 86400.0 * U.UnitDbl( 1.0, 'sec' ) + return U.Epoch( unit, secPastRef, EpochConverter.jdRef ) + + #------------------------------------------------------------------------ + @staticmethod + def epoch2float( value, unit ): + """: Convert an Epoch value to a float suitible for plotting as a + python datetime object. + + = INPUT VARIABLES + - value An Epoch or list of Epochs that need to be converted. + - unit The units to use for an axis with Epoch data. + + = RETURN VALUE + - Returns the value parameter converted to floats. + """ + return value.julianDate( unit ) - EpochConverter.jdRef + + #------------------------------------------------------------------------ + @staticmethod + def duration2float( value ): + """: Convert a Duration value to a float suitible for plotting as a + python datetime object. + + = INPUT VARIABLES + - value A Duration or list of Durations that need to be converted. + + = RETURN VALUE + - Returns the value parameter converted to floats. + """ + return value.days() + + #------------------------------------------------------------------------ + @staticmethod + def convert( value, unit, axis ): + """: Convert value using unit to a float. If value is a sequence, return + the converted sequence. + + = INPUT VARIABLES + - value The value or list of values that need to be converted. + - unit The units to use for an axis with Epoch data. + + = RETURN VALUE + - Returns the value parameter converted to floats. + """ + # Delay-load due to circular dependencies. + import mplTest.units as U + + isNotEpoch = True + isDuration = False + + if ( iterable(value) and not isinstance(value, str) ): + if ( len(value) == 0 ): + return [] + else: + return [ EpochConverter.convert( x, unit, axis ) for x in value ] + + if ( isinstance(value, U.Epoch) ): + isNotEpoch = False + elif ( isinstance(value, U.Duration) ): + isDuration = True + + if ( isNotEpoch and not isDuration and + units.ConversionInterface.is_numlike( value ) ): + return value + + if ( unit == None ): + unit = EpochConverter.default_units( value, axis ) + + if ( isDuration ): + return EpochConverter.duration2float( value ) + else: + return EpochConverter.epoch2float( value, unit ) + + #------------------------------------------------------------------------ + @staticmethod + def default_units( value, axis ): + """: Return the default unit for value, or None. + + = INPUT VARIABLES + - value The value or list of values that need units. + + = RETURN VALUE + - Returns the default units to use for value. + """ + frame = None + if ( iterable(value) and not isinstance(value, str) ): + return EpochConverter.default_units( value[0], axis ) + else: + frame = value.frame() + + return frame + Copied: trunk/matplotlib/lib/matplotlib/testing/jpl_units/StrConverter.py (from rev 7665, trunk/matplotlib/test/mplTest/units/StrConverter.py) =================================================================== --- trunk/matplotlib/lib/matplotlib/testing/jpl_units/StrConverter.py (rev 0) +++ trunk/matplotlib/lib/matplotlib/testing/jpl_units/StrConverter.py 2009-09-06 22:46:27 UTC (rev 7666) @@ -0,0 +1,158 @@ +#=========================================================================== +# +# StrConverter +# +#=========================================================================== + +"""StrConverter module containing class StrConverter.""" + +#=========================================================================== +# Place all imports after here. +# +import matplotlib.units as units +from matplotlib.cbook import iterable + +# Place all imports before here. +#=========================================================================== + +__all__ = [ 'StrConverter' ] + +#=========================================================================== +class StrConverter( units.ConversionInterface ): + """: A matplotlib converter class. Provides matplotlib conversion + functionality for string data values. + + Valid units for string are: + - 'indexed' : Values are indexed as they are specified for plotting. + - 'sorted' : Values are sorted alphanumerically. + - 'inverted' : Values are inverted so that the first value is on top. + - 'sorted-inverted' : A combination of 'sorted' and 'inverted' + """ + + #------------------------------------------------------------------------ + @staticmethod + def axisinfo( unit, axis ): + """: Returns information on how to handle an axis that has string data. + + = INPUT VARIABLES + - axis The axis using this converter. + - unit The units to use for a axis with string data. + + = RETURN VALUE + - Returns a matplotlib AxisInfo data structure that contains + minor/major formatters, major/minor locators, and default + label information. + """ + + return None + + #------------------------------------------------------------------------ + @staticmethod + def convert( value, unit, axis ): + """: Convert value using unit to a float. If value is a sequence, return + the converted sequence. + + = INPUT VARIABLES + - axis The axis using this converter. + - value The value or list of values that need to be converted. + - unit The units to use for a axis with Epoch data. + + = RETURN VALUE + - Returns the value parameter converted to floats. + """ + + if ( units.ConversionInterface.is_numlike( value ) ): + return value + + if ( value == [] ): + return [] + + # we delay loading to make matplotlib happy + ax = axis.axes + if axis is ax.get_xaxis(): + isXAxis = True + else: + isXAxis = False + + axis.get_major_ticks() + ticks = axis.get_ticklocs() + labels = axis.get_ticklabels() + + labels = [ l.get_text() for l in labels if l.get_text() ] + + if ( not labels ): + ticks = [] + labels = [] + + + if ( not iterable( value ) ): + value = [ value ] + + newValues = [] + for v in value: + if ( (v not in labels) and (v not in newValues) ): + newValues.append( v ) + + for v in newValues: + if ( labels ): + labels.append( v ) + else: + labels = [ v ] + + #DISABLED: This is disabled because matplotlib bar plots do not + #DISABLED: recalculate the unit conversion of the data values + #DISABLED: this is due to design and is not really a bug. + #DISABLED: If this gets changed, then we can activate the following + #DISABLED: block of code. Note that this works for line plots. + #DISABLED if ( unit ): + #DISABLED if ( unit.find( "sorted" ) > -1 ): + #DISABLED labels.sort() + #DISABLED if ( unit.find( "inverted" ) > -1 ): + #DISABLED labels = labels[ ::-1 ] + + # add padding (so they do not appear on the axes themselves) + labels = [ '' ] + labels + [ '' ] + ticks = range( len(labels) ) + ticks[0] = 0.5 + ticks[-1] = ticks[-1] - 0.5 + + axis.set_ticks( ticks ) + axis.set_ticklabels( labels ) + # we have to do the following lines to make ax.autoscale_view work + loc = axis.get_major_locator() + loc.set_bounds( ticks[0], ticks[-1] ) + + if ( isXAxis ): + ax.set_xlim( ticks[0], ticks[-1] ) + else: + ax.set_ylim( ticks[0], ticks[-1] ) + + result = [] + for v in value: + # If v is not in labels then something went wrong with adding new + # labels to the list of old labels. + errmsg = "This is due to a logic error in the StrConverter class. " + errmsg += "Please report this error and its message in bugzilla." + assert ( v in labels ), errmsg + result.append( ticks[ labels.index(v) ] ) + + ax.viewLim.ignore(-1) + return result + + #------------------------------------------------------------------------ + @staticmethod + def default_units( value, axis ): + """: Return the default unit for value, or None. + + = INPUT VARIABLES + - axis The axis using this converter. + - value The value or list of values that need units. + + = RETURN VALUE + - Returns the default units to use for value. + Return the default unit for value, or None. + """ + + # The default behavior for string indexing. + return "indexed" + Copied: trunk/matplotlib/lib/matplotlib/testing/jpl_units/UnitDbl.py (from rev 7665, trunk/matplotlib/test/mplTest/units/UnitDbl.py) =================================================================== --- trunk/matplotlib/lib/matplotlib/testing/jpl_units/UnitDbl.py (rev 0) +++ trunk/matplotlib/lib/matplotlib/testing/jpl_units/UnitDbl.py 2009-09-06 22:46:27 UTC (rev 7666) @@ -0,0 +1,287 @@ +#=========================================================================== +# +# UnitDbl +# +#=========================================================================== + +"""UnitDbl module.""" + +#=========================================================================== +# Place all imports after here. +# + +# +# Place all imports before here. +#=========================================================================== + + +#=========================================================================== +class UnitDbl: + """Class UnitDbl in development. + """ + #----------------------------------------------------------------------- + # Unit conversion table. Small subset of the full one but enough + # to test the required functions. First field is a scale factor to + # convert the input units to the units of the second field. Only + # units in this table are allowed. + allowed = { + "m" : ( 0.001, "km" ), + "km" : ( 1, "km" ), + "mile" : ( 1.609344, "km" ), + + "rad" : ( 1, "rad" ), + "deg" : ( 1.745329251994330e-02, "rad" ), + + "sec" : ( 1, "sec" ), + "min" : ( 60.0, "sec" ), + "hour" : ( 3600, "sec" ), + } + + _types = { + "km" : "distance", + "rad" : "angle", + "sec" : "time", + } + + #----------------------------------------------------------------------- + def __init__( self, value, units ): + """Create a new UnitDbl object. + + Units are internally converted to km, rad, and sec. The only + valid inputs for units are [ m, km, mile, rad, deg, sec, min, hour ]. + + The field UnitDbl.value will contain the converted value. Use + the convert() method to get a specific type of units back. + + = ERROR CONDITIONS + - If the input units are not in the allowed list, an error is thrown. + + = INPUT VARIABLES + - value The numeric value of the UnitDbl. + - units The string name of the units the value is in. + """ + self.checkUnits( units ) + + data = self.allowed[ units ] + self._value = float( value * data[0] ) + self._units = data[1] + + #----------------------------------------------------------------------- + def convert( self, units ): + """Convert the UnitDbl to a specific set of units. + + = ERROR CONDITIONS + - If the input units are not in the allowed list, an error is thrown. + + = INPUT VARIABLES + - units The string name of the units to convert to. + + = RETURN VALUE + - Returns the value of the UnitDbl in the requested units as a floating + point number. + """ + if self._units == units: + return self._value + + self.checkUnits( units ) + + data = self.allowed[ units ] + if self._units != data[1]: + msg = "Error trying to convert to different units.\n" \ + " Invalid conversion requested.\n" \ + " UnitDbl: %s\n" \ + " Units: %s\n" % ( str( self ), units ) + raise ValueError( msg ) + + return self._value / data[0] + + #----------------------------------------------------------------------- + def __abs__( self ): + """Return the absolute value of this UnitDbl.""" + return UnitDbl( abs( self._value ), self._units ) + + #----------------------------------------------------------------------- + def __neg__( self ): + """Return the negative value of this UnitDbl.""" + return UnitDbl( -self._value, self._units ) + + #----------------------------------------------------------------------- + def __nonzero__( self ): + """Test a UnitDbl for a non-zero value. + + = RETURN VALUE + - Returns true if the value is non-zero. + """ + return self._value.__nonzero__() + + #----------------------------------------------------------------------- + def __cmp__( self, rhs ): + """Compare two UnitDbl's. + + = ERROR CONDITIONS + - If the input rhs units are not the same as our units, + an error is thrown. + + = INPUT VARIABLES + - rhs The UnitDbl to compare against. + + = RETURN VALUE + - Returns -1 if self < rhs, 0 if self == rhs, +1 if self > rhs. + """ + self.checkSameUnits( rhs, "compare" ) + return cmp( self._value, rhs._value ) + + #----------------------------------------------------------------------- + def __add__( self, rhs ): + """Add two UnitDbl's. + + = ERROR CONDITIONS + - If the input rhs units are not the same as our units, + an error is thrown. + + = INPUT VARIABLES + - rhs The UnitDbl to add. + + = RETURN VALUE + - Returns the sum of ourselves and the input UnitDbl. + """ + self.checkSameUnits( rhs, "add" ) + return UnitDbl( self._value + rhs._value, self._units ) + + #----------------------------------------------------------------------- + def __sub__( self, rhs ): + """Subtract two UnitDbl's. + + = ERROR CONDITIONS + - If the input rhs units are not the same as our units, + an error is thrown. + + = INPUT VARIABLES + - rhs The UnitDbl to subtract. + + = RETURN VALUE + - Returns the difference of ourselves and the input UnitDbl. + """ + self.checkSameUnits( rhs, "subtract" ) + return UnitDbl( self._value - rhs._value, self._units ) + + #----------------------------------------------------------------------- + def __mul__( self, rhs ): + """Scale a UnitDbl by a value. + + = INPUT VARIABLES + - rhs The scalar to multiply by. + + = RETURN VALUE + - Returns the scaled UnitDbl. + """ + return UnitDbl( self._value * rhs, self._units ) + + #----------------------------------------------------------------------- + def __rmul__( self, lhs ): + """Scale a UnitDbl by a value. + + = INPUT VARIABLES + - lhs The scalar to multiply by. + + = RETURN VALUE + - Returns the scaled UnitDbl. + """ + return UnitDbl( self._value * lhs, self._units ) + + #----------------------------------------------------------------------- + def __div__( self, rhs ): + """Divide a UnitDbl by a value. + + = INPUT VARIABLES + - rhs The scalar to divide by. + + = RETURN VALUE + - Returns the scaled UnitDbl. + """ + return UnitDbl( self._value / rhs, self._units ) + + #----------------------------------------------------------------------- + def __str__( self ): + """Print the UnitDbl.""" + return "%g *%s" % ( self._value, self._units ) + + #----------------------------------------------------------------------- + def __repr__( self ): + """Print the UnitDbl.""" + return "UnitDbl( %g, '%s' )" % ( self._value, self._units ) + + #----------------------------------------------------------------------- + def type( self ): + """Return the type of UnitDbl data.""" + return self._types[ self._units ] + + #----------------------------------------------------------------------- + def range( start, stop, step=None ): + """Generate a range of UnitDbl objects. + + Similar to the Python range() method. Returns the range [ + start, stop ) at the requested step. Each element will be a + UnitDbl object. + + = INPUT VARIABLES + - start The starting value of the range. + - stop The stop value of the range. + - step Optional step to use. If set to None, then a UnitDbl of + value 1 w/ the units of the start is used. + + = RETURN VALUE + - Returns a list contianing the requested UnitDbl values. + """ + if step is None: + step = UnitDbl( 1, start._units ) + + elems = [] + + i = 0 + while True: + d = start + i * step + if d >= stop: + break + + elems.append( d ) + i += 1 + + return elems + + range = staticmethod( range ) + + #----------------------------------------------------------------------- + def checkUnits( self, units ): + """Check to see if some units are valid. + + = ERROR CONDITIONS + - If the input units are not in the allowed list, an error is thrown. + + = INPUT VARIABLES + - units The string name of the units to check. + """ + if units not in self.allowed.keys(): + msg = "Input units '%s' are not one of the supported types of %s" \ + % ( units, str( self.allowed.keys() ) ) + raise ValueError( msg ) + + #----------------------------------------------------------------------- + def checkSameUnits( self, rhs, func ): + """Check to see if units are the same. + + = ERROR CONDITIONS + - If the units of the rhs UnitDbl are not the same as our units, + an error is thrown. + + = INPUT VARIABLES + - rhs The UnitDbl to check for the same units + - func The name of the function doing the check. + """ + if self._units != rhs._units: + msg = "Cannot %s units of different types.\n" \ + "LHS: %s\n" \ + "RHS: %s" % ( func, self._units, rhs._units ) + raise ValueError( msg ) + +#=========================================================================== Copied: trunk/matplotlib/lib/matplotlib/testing/jpl_units/UnitDblConverter.py (from rev 7665, trunk/matplotlib/test/mplTest/units/UnitDblConverter.py) =================================================================== --- trunk/matplotlib/lib/matplotlib/testing/jpl_units/UnitDblConverter.py (rev 0) +++ trunk/matplotlib/lib/matplotlib/testing/jpl_units/UnitDblConverter.py 2009-09-06 22:46:27 UTC (rev 7666) @@ -0,0 +1,159 @@ +#=========================================================================== +# +# UnitDblConverter +# +#=========================================================================== + +"""UnitDblConverter module containing class UnitDblConverter.""" + +#=========================================================================== +# Place all imports after here. +# +import numpy as np +import matplotlib.units as units +import matplotlib.ticker as ticker +import matplotlib.projections.polar as polar +from matplotlib.cbook import iterable +# +# Place all imports before here. +#=========================================================================== + +__all__ = [ 'UnitDblConverter' ] + +#=========================================================================== + +# A special function for use with the matplotlib FuncFormatter class +# for formatting axes with radian units. +# This was copied from matplotlib example code. +def rad_fn(x, pos = None ): + """Radian function formatter.""" + n = int((x / np.pi) * 2.0 + 0.25) + if n == 0: + return str(x) + elif n == 1: + return r'$\pi/2$' + elif n == 2: + return r'$\pi$' + elif n % 2 == 0: + return r'$%s\pi$' % (n/2,) + else: + return r'$%s\pi/2$' % (n,) + +#=========================================================================== +class UnitDblConverter( units.ConversionInterface ): + """: A matplotlib converter class. Provides matplotlib conversion + functionality for the Monte UnitDbl class. + """ + + # default for plotting + defaults = { + "distance" : 'km', + "angle" : 'deg', + "time" : 'sec', + } + + #------------------------------------------------------------------------ + @staticmethod + def axisinfo( unit, axis ): + """: Returns information on how to handle an axis that has Epoch data. + + = INPUT VARIABLES + - unit The units to use for a axis with Epoch data. + + = RETURN VALUE + - Returns a matplotlib AxisInfo data structure that contains + minor/major formatters, major/minor locators, and default + label information. + """ + # Delay-load due to circular dependencies. + import mplTest.units as U + + # Check to see if the value used for units is a string unit value + # or an actual instance of a UnitDbl so that we can use the unit + # value for the default axis label value. + if ( unit ): + if ( isinstance( unit, str ) ): + label = unit + else: + label = unit.label() + else: + label = None + + if ( label == "rad" ): + # If the axis units are in radians, then use a special function for + # applying format control. + majfmt = ticker.FuncFormatter( rad_fn ) + elif ( label == "deg" ) and isinstance( axis.axes, polar.PolarAxes ): + # If we want degrees for a polar plot, use the PolarPlotFormatter + majfmt = polar.PolarAxes.ThetaFormatter() + else: + majfmt = U.UnitDblFormatter( useOffset = False ) + + return units.AxisInfo( majfmt = majfmt, label = label ) + + #------------------------------------------------------------------------ + @staticmethod + def convert( value, unit, axis ): + """: Convert value using unit to a float. If value is a sequence, return + the converted sequence. + + = INPUT VARIABLES + - value The value or list of values that need to be converted. + - unit The units to use for a axis with Epoch data. + + = RETURN VALUE + - Returns the value parameter converted to floats. + """ + # Delay-load due to circular dependencies. + import mplTest.units as U + + isNotUnitDbl = True + + if ( iterable(value) and not isinstance(value, str) ): + if ( len(value) == 0 ): + return [] + else: + return [ UnitDblConverter.convert( x, unit, axis ) for x in value ] + + # We need to check to see if the incoming value is actually a UnitDbl and + # set a flag. If we get an empty list, then just return an empty list. + if ( isinstance(value, U.UnitDbl) ): + isNotUnitDbl = False + + # If the incoming value behaves like a number, but is not a UnitDbl, + # then just return it because we don't know how to convert it + # (or it is already converted) + if ( isNotUnitDbl and units.ConversionInterface.is_numlike( value ) ): + return value + + # If no units were specified, then get the default units to use. + if ( unit == None ): + unit = UnitDblConverter.default_units( value, axis ) + + # Convert the incoming UnitDbl value/values to float/floats + if isinstance( axis.axes, polar.PolarAxes ) and (value.type() == "angle"): + # Guarantee that units are radians for polar plots. + return value.convert( "rad" ) + + return value.convert( unit ) + + #------------------------------------------------------------------------ + @staticmethod + def default_units( value, axis ): + """: Return the default unit for value, or None. + + = INPUT VARIABLES + - value The value or list of values that need units. + + = RETURN VALUE + - Returns the default units to use for value. + Return the default unit for value, or None. + """ + + # Determine the default units based on the user preferences set for + # default units when printing a UnitDbl. + if ( iterable(value) and not isinstance(value, str) ): + return UnitDblConverter.default_units( value[0], axis ) + else: + return UnitDblConverter.defaults[ value.type() ] + Copied: trunk/matplotlib/lib/matplotlib/testing/jpl_units/UnitDblFormatter.py (from rev 7665, trunk/matplotlib/test/mplTest/units/UnitDblFormatter.py) =================================================================== --- trunk/matplotlib/lib/matplotlib/testing/jpl_units/UnitDblFormatter.py (rev 0) +++ trunk/matplotlib/lib/matplotlib/testing/jpl_units/UnitDblFormatter.py 2009-09-06 22:46:27 UTC (rev 7666) @@ -0,0 +1,41 @@ +#=========================================================================== +# +# UnitDblFormatter +# +#=========================================================================== + +"""UnitDblFormatter module containing class UnitDblFormatter.""" + +#=========================================================================== +# Place all imports after here. +# +import matplotlib.ticker as ticker +# +# Place all imports before here. +#=========================================================================== + +__all__ = [ 'UnitDblFormatter' ] + +#=========================================================================== +class UnitDblFormatter( ticker.ScalarFormatter ): + """The formatter for UnitDbl data types. This allows for formatting + with the unit string. + """ + def __init__( self, *args, **kwargs ): + 'The arguments are identical to matplotlib.ticker.ScalarFormatter.' + ticker.ScalarFormatter.__init__( self, *args, **kwargs ) + + def __call__( self, x, pos = None ): + 'Return the format for tick val x at position pos' + if len(self.locs) == 0: + return '' + else: + return str(x) + + def format_data_short( self, value ): + "Return the value formatted in 'short' format." + return str(value) + + def format_data( self, value ): + "Return the value formatted into a string." + return str(value) Copied: trunk/matplotlib/lib/matplotlib/testing/jpl_units/__init__.py (from rev 7665, trunk/matplotlib/test/mplTest/units/__init__.py) =================================================================== --- trunk/matplotlib/lib/matplotlib/testing/jpl_units/__init__.py (rev 0) +++ trunk/matplotlib/lib/matplotlib/testing/jpl_units/__init__.py 2009-09-06 22:46:27 UTC (rev 7666) @@ -0,0 +1,82 @@ +#======================================================================= +""" +This is a sample set of units for use with testing unit conversion +of matplotlib routines. These are used because they use very strict +enforcement of unitized data which will test the entire spectrum of how +unitized data might be used (it is not always meaningful to convert to +a float without specific units given). + +UnitDbl is essentially a unitized floating point number. It has a +minimal set of supported units (enough for testing purposes). All +of the mathematical operation are provided to fully test any behaviour +that might occur with unitized data. Remeber that unitized data has +rules as to how it can be applied to one another (a value of distance +cannot be added to a value of time). Thus we need to guard against any +accidental "default" conversion that will strip away the meaning of the +data and render it neutered. + +Epoch is different than a UnitDbl of time. Time is something that can be +measured where an Epoch is a specific moment in time. Epochs are typically +referenced as an offset from some predetermined epoch. + +A difference of two epochs is a Duration. The distinction between a +Duration and a UnitDbl of time is made because an Epoch can have different +frames (or units). In the case of our test Epoch class the two allowed +frames are 'UTC' and 'ET' (Note that these are rough estimates provided for +testing purposes and should not be used in production code where accuracy +of time frames is desired). As such a Duration also has a frame of +reference and therefore needs to be called out as different that a simple +measurement of time since a delta-t in one frame may not be the same in another. +""" + +#======================================================================= +from mplTest.units.Duration import Duration +from mplTest.units.Epoch import Epoch +from mplTest.units.UnitDbl import UnitDbl + +from mplTest.units.StrConverter import StrConverter +from mplTest.units.EpochConverter import EpochConverter +from mplTest.units.UnitDblConverter import UnitDblConverter + +from mplTest.units.UnitDblFormatter import UnitDblFormatter + +#======================================================================= + +__version__ = "1.0" + +__all__ = [ + 'register', + 'Duration', + 'Epoch', + 'UnitDbl', + 'UnitDblFormatter', + ] + +#======================================================================= +def register(): + """Register the unit conversion classes with matplotlib.""" + import matplotlib.units as mplU + + mplU.registry[ str ] = StrConverter() + mplU.registry[ Epoch ] = EpochConverter() + mplU.registry[ UnitDbl ] = UnitDblConverter() + +#======================================================================= +# Some default unit instances + +# Distances +m = UnitDbl( 1.0, "m" ) +km = UnitDbl( 1.0, "km" ) +mile = UnitDbl( 1.0, "mile" ) + +# Angles +deg = UnitDbl( 1.0, "deg" ) +rad = UnitDbl( 1.0, "rad" ) + +# Time +sec = UnitDbl( 1.0, "sec" ) +min = UnitDbl( 1.0, "min" ) +hr = UnitDbl( 1.0, "hour" ) +day = UnitDbl( 24.0, "hour" ) +sec = UnitDbl( 1.0, "sec" ) + Deleted: trunk/matplotlib/test/mplTest/units/Duration.py =================================================================== --- trunk/matplotlib/test/mplTest/units/Duration.py 2009-09-06 22:46:00 UTC (rev 7665) +++ trunk/matplotlib/test/mplTest/units/Duration.py 2009-09-06 22:46:27 UTC (rev 7666) @@ -1,203 +0,0 @@ -#=========================================================================== -# -# Duration -# -#=========================================================================== - -"""Duration module.""" - -#=========================================================================== -# Place all imports after here. -# -# -# Place all imports before here. -#=========================================================================== - -#=========================================================================== -class Duration: - """Class Duration in development. - """ - allowed = [ "ET", "UTC" ] - - #----------------------------------------------------------------------- - def __init__( self, frame, seconds ): - """Create a new Duration object. - - = ERROR CONDITIONS - - If the input frame is not in the allowed list, an error is thrown. - - = INPUT VARIABLES - - frame The frame of the duration. Must be 'ET' or 'UTC' - - seconds The number of seconds in the Duration. - """ - if frame not in self.allowed: - msg = "Input frame '%s' is not one of the supported frames of %s" \ - % ( frame, str( self.allowed ) ) - raise ValueError( msg ) - - self._frame = frame - self._seconds = seconds - - #----------------------------------------------------------------------- - def frame( self ): - """Return the frame the duration is in.""" - return self._frame - - #----------------------------------------------------------------------- - def __abs__( self ): - """Return the absolute value of the duration.""" - return Duration( self._frame, abs( self._seconds ) ) - - #----------------------------------------------------------------------- - def __neg__( self ): - """Return the negative value of this Duration.""" - return Duration( self._frame, -self._seconds ) - - #----------------------------------------------------------------------- - def seconds( self ): - """Return the number of seconds in the Duration.""" - return self._seconds - - #----------------------------------------------------------------------- - def __nonzero__( self ): - """Compare two Durations. - - = INPUT VARIABLES - - rhs The Duration to compare against. - - = RETURN VALUE - - Returns -1 if self < rhs, 0 if self == rhs, +1 if self > rhs. - """ - return self._seconds != 0 - - #----------------------------------------------------------------------- - def __cmp__( self, rhs ): - """Compare two Durations. - - = ERROR CONDITIONS - - If the input rhs is not in the same frame, an error is thrown. - - = INPUT VARIABLES - - rhs The Duration to compare against. - - = RETURN VALUE - - Returns -1 if self < rhs, 0 if self == rhs, +1 if self > rhs. - """ - self.checkSameFrame( rhs, "compare" ) - return cmp( self._seconds, rhs._seconds ) - - #----------------------------------------------------------------------- - def __add__( self, rhs ): - """Add two Durations. - - = ERROR CONDITIONS - - If the input rhs is not in the same frame, an error is thrown. - - = INPUT VARIABLES - - rhs The Duration to add. - - = RETURN VALUE - - Returns the sum of ourselves and the input Duration. - """ - # Delay-load due to circular dependencies. - import mplTest.units as U - - if isinstance( rhs, U.Epoch ): - return rhs + self - - self.checkSameFrame( rhs, "add" ) - return Duration( self._frame, self._seconds + rhs._seconds ) - - #----------------------------------------------------------------------- - def __sub__( self, rhs ): - """Subtract two Durations. - - = ERROR CONDITIONS - - If the input rhs is not in the same frame, an error is thrown. - - = INPUT VARIABLES - - rhs The Duration to subtract. - - = RETURN VALUE - - Returns the difference of ourselves and the input Duration. - """ - self.checkSameFrame( rhs, "sub" ) - return Duration( self._frame, self._seconds - rhs._seconds ) - - #----------------------------------------------------------------------- - def __mul__( self, rhs ): - """Scale a UnitDbl by a value. - - = INPUT VARIABLES - - rhs The scalar to multiply by. - - = RETURN VALUE - - Returns the scaled Duration. - """ - return Duration( self._frame, self._seconds * float( rhs ) ) - - #----------------------------------------------------------------------- - def __rmul__( self, lhs ): - """Scale a Duration by a value. - - = INPUT VARIABLES - - lhs The scalar to multiply by. - - = RETURN VALUE - - Returns the scaled Duration. - """ - return Duration( self._frame, self._seconds * float( lhs ) ) - - #----------------------------------------------------------------------- - def __div__( self, rhs ): - """Divide a Duration by a value. - - = INPUT VARIABLES - - rhs The scalar to divide by. - - = RETURN VALUE - - Returns the scaled Duration. - """ - return Duration( self._frame, self._seconds / float( rhs ) ) - - #----------------------------------------------------------------------- - def __rdiv__( self, rhs ): - """Divide a Duration by a value. - - = INPUT VARIABLES - - rhs The scalar to divide by. - - = RETURN VALUE - - Returns the scaled Duration. - """ - return Duration( self._frame, float( rhs ) / self._seconds ) - - #----------------------------------------------------------------------- - def __str__( self ): - """Print the Duration.""" - return "%g %s" % ( self._seconds, self._frame ) - - #----------------------------------------------------------------------- - def __repr__( self ): - """Print the Duration.""" - return "Duration( '%s', %g )" % ( self._frame, self._seconds ) - - #----------------------------------------------------------------------- - def checkSameFrame( self, rhs, func ): - """Check to see if frames are the same. - - = ERROR CONDITIONS - - If the frame of the rhs Duration is not the same as our frame, - an error is thrown. - - = INPUT VARIABLES - - rhs The Duration to check for the same frame - - func The name of the function doing the check. - """ - if self._frame != rhs._frame: - msg = "Cannot %s Duration's with different frames.\n" \ - "LHS: %s\n" \ - "RHS: %s" % ( func, self._frame, rhs._frame ) - raise ValueError( msg ) - -#=========================================================================== Deleted: trunk/matplotlib/test/mplTest/units/Epoch.py =================================================================== --- trunk/matplotlib/test/mplTest/units/Epoch.py 2009-09-06 22:46:00 UTC (rev 7665) +++ trunk/matplotlib/test/mplTest/units/Epoch.py 2009-09-06 22:46:27 UTC (rev 7666) @@ -1,232 +0,0 @@ -#=========================================================================== -# -# Epoch -# -#=========================================================================== - -"""Epoch module.""" - -#=========================================================================== -# Place all imports after here. -# -import math -import datetime as DT -from matplotlib.dates import date2num -# -# Place all imports before here. -#=========================================================================== - -#=========================================================================== -class Epoch: - # Frame conversion offsets in seconds - # t(TO) = t(FROM) + allowed[ FROM ][ TO ] - allowed = { - "ET" : { - "UTC" : +64.1839, - }, - "UTC" : { - "ET" : -64.1839, - }, - } - - #----------------------------------------------------------------------- - def __init__( self, frame, sec=None, jd=None, daynum=None, dt=None ): - """Create a new Epoch object. - - Build an epoch 1 of 2 ways: - - Using seconds past a Julian date: - # Epoch( 'ET', sec=1e8, jd=2451545 ) - - or using a matplotlib day number - # Epoch( 'ET', daynum=730119.5 ) - - - = ERROR CONDITIONS - - If the input units are not in the allowed list, an error is thrown. - - = INPUT VARIABLES - - frame The frame of the epoch. Must be 'ET' or 'UTC' - - sec The number of seconds past the input JD. - - jd The Julian date of the epoch. - - daynum The matplotlib day number of the epoch. - - dt A python datetime instance. - """ - if ( ( sec is None and jd is not None ) or - ( sec is not None and jd is None ) or - ( daynum is not None and ( sec is not None or jd is not None ) ) or - ( daynum is None and dt is None and ( sec is None or jd is None ) ) or - ( daynum is not None and dt is not None ) or - ( dt is not None and ( sec is not None or jd is not None ) ) or - ( (dt is not None) and not isinstance(dt, DT.datetime) ) ): - msg = "Invalid inputs. Must enter sec and jd together, " \ - "daynum by itself, or dt (must be a python datetime).\n" \ - "Sec = %s\nJD = %s\ndnum= %s\ndt = %s" \ - % ( str( sec ), str( jd ), str( daynum ), str( dt ) ) - raise ValueError( msg ) - - if frame not in self.allowed: - msg = "Input frame '%s' is not one of the supported frames of %s" \ - % ( frame, str( self.allowed.keys() ) ) - raise ValueError( msg ) - - self._frame = frame - - if dt is not None: - daynum = date2num( dt ) - - if daynum is not None: - # 1-JAN-0001 in JD = 1721425.5 - jd = float( daynum ) + 1721425.5 - self._jd = math.floor( jd ) - self._seconds = ( jd - self._jd ) * 86400.0 - - else: - self._seconds = float( sec ) - self._jd = float( jd ) - - # Resolve seconds down to [ 0, 86400 ) - deltaDays = int( math.floor( self._seconds / 86400.0 ) ) - self._jd += deltaDays - self._seconds -= deltaDays * 86400.0 - - #----------------------------------------------------------------------- - def convert( self, frame ): - if self._frame == frame: - return self - - offset = self.allowed[ self._frame ][ frame ] - - return Epoch( frame, self._seconds + of... [truncated message content] |