diff --git a/control/config.py b/control/config.py
index 800fe7f26..4d4512af7 100644
--- a/control/config.py
+++ b/control/config.py
@@ -15,7 +15,7 @@
 
 # Package level default values
 _control_defaults = {
-    # No package level defaults (yet)
+    'control.default_dt':0
 }
 defaults = dict(_control_defaults)
 
@@ -59,6 +59,9 @@ def reset_defaults():
     from .statesp import _statesp_defaults
     defaults.update(_statesp_defaults)
 
+    from .iosys import _iosys_defaults
+    defaults.update(_iosys_defaults)
+
 
 def _get_param(module, param, argval=None, defval=None, pop=False):
     """Return the default value for a configuration option.
@@ -208,8 +211,11 @@ def use_legacy_defaults(version):
     # Go backwards through releases and reset defaults
     #
 
-    # Version 0.9.0: switched to 'array' as default for state space objects
+    # Version 0.9.0:
     if major == 0 and minor < 9:
+        # switched to 'array' as default for state space objects
         set_defaults('statesp', use_numpy_matrix=True)
+        # switched to 0 (=continuous) as default timestep
+        set_defaults('control', default_dt=None)
 
     return (major, minor, patch)
diff --git a/control/dtime.py b/control/dtime.py
index 89f17c4af..725bcde47 100644
--- a/control/dtime.py
+++ b/control/dtime.py
@@ -53,21 +53,19 @@
 
 # Sample a continuous time system
 def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None):
-    """Convert a continuous time system to discrete time
-
-    Creates a discrete time system from a continuous time system by
-    sampling.  Multiple methods of conversion are supported.
+    """
+    Convert a continuous time system to discrete time by sampling
 
     Parameters
     ----------
-    sysc : linsys
+    sysc : LTI (StateSpace or TransferFunction)
         Continuous time system to be converted
-    Ts : real
+    Ts : real > 0
         Sampling period
     method : string
-        Method to use for conversion: 'matched', 'tustin', 'zoh' (default)
+        Method to use for conversion, e.g. 'bilinear', 'zoh' (default)
 
-    prewarp_frequency : float within [0, infinity)
+    prewarp_frequency : real within [0, infinity)
         The frequency [rad/s] at which to match with the input continuous-
         time system's magnitude and phase
 
@@ -78,13 +76,13 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None):
 
     Notes
     -----
-    See `TransferFunction.sample` and `StateSpace.sample` for
+    See :meth:`StateSpace.sample` or :meth:`TransferFunction.sample`` for
     further details.
 
     Examples
     --------
     >>> sysc = TransferFunction([1], [1, 2, 1])
-    >>> sysd = sample_system(sysc, 1, method='matched')
+    >>> sysd = sample_system(sysc, 1, method='bilinear')
     """
 
     # Make sure we have a continuous time system
@@ -95,35 +93,39 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None):
 
 
 def c2d(sysc, Ts, method='zoh', prewarp_frequency=None):
-    '''
-    Return a discrete-time system
+    """
+    Convert a continuous time system to discrete time by sampling
 
     Parameters
     ----------
-    sysc: LTI (StateSpace or TransferFunction), continuous
-        System to be converted
+    sysc : LTI (StateSpace or TransferFunction)
+        Continuous time system to be converted
+    Ts : real > 0
+        Sampling period
+    method : string
+        Method to use for conversion, e.g. 'bilinear', 'zoh' (default)
 
-    Ts: number
-        Sample time for the conversion
+    prewarp_frequency : real within [0, infinity)
+        The frequency [rad/s] at which to match with the input continuous-
+        time system's magnitude and phase
 
-    method: string, optional
-        Method to be applied,
-        'zoh'        Zero-order hold on the inputs (default)
-        'foh'        First-order hold, currently not implemented
-        'impulse'    Impulse-invariant discretization, currently not implemented
-        'tustin'     Bilinear (Tustin) approximation, only SISO
-        'matched'    Matched pole-zero method, only SISO
+    Returns
+    -------
+    sysd : linsys
+        Discrete time system, with sampling rate Ts
+
+    Notes
+    -----
+    See :meth:`StateSpace.sample` or :meth:`TransferFunction.sample`` for
+    further details.
 
-    prewarp_frequency : float within [0, infinity)
-            The frequency [rad/s] at which to match with the input continuous-
-            time system's magnitude and phase
+    Examples
+    --------
+    >>> sysc = TransferFunction([1], [1, 2, 1])
+    >>> sysd = sample_system(sysc, 1, method='bilinear')
+    """
 
-    '''
     #  Call the sample_system() function to do the work
     sysd = sample_system(sysc, Ts, method, prewarp_frequency)
 
-    # TODO: is this check needed?  If sysc is  StateSpace, sysd is too?
-    if isinstance(sysc, StateSpace) and not isinstance(sysd, StateSpace):
-        return _convertToStateSpace(sysd)       # pragma: no cover
-
     return sysd
diff --git a/control/iosys.py b/control/iosys.py
index a90b5193c..65d6c1228 100644
--- a/control/iosys.py
+++ b/control/iosys.py
@@ -38,12 +38,15 @@
 
 from .statesp import StateSpace, tf2ss
 from .timeresp import _check_convert_array
-from .lti import isctime, isdtime, _find_timebase
+from .lti import isctime, isdtime, common_timebase
+from . import config
 
 __all__ = ['InputOutputSystem', 'LinearIOSystem', 'NonlinearIOSystem',
            'InterconnectedSystem', 'input_output_response', 'find_eqpt',
            'linearize', 'ss2io', 'tf2io']
 
+# Define module default parameter values
+_iosys_defaults = {}
 
 class InputOutputSystem(object):
     """A class for representing input/output systems.
@@ -69,9 +72,11 @@ class for a set of subclasses that are used to implement specific
     states : int, list of str, or None
         Description of the system states.  Same format as `inputs`.
     dt : None, True or float, optional
-        System timebase.  None (default) indicates continuous time, True
-        indicates discrete time with undefined sampling time, positive number
-        is discrete time with specified sampling time.
+        System timebase. 0 (default) indicates continuous
+        time, True indicates discrete time with unspecified sampling
+        time, positive number is discrete time with specified
+        sampling time, None indicates unspecified timebase (either  
+        continuous or discrete time).
     params : dict, optional
         Parameter values for the systems.  Passed to the evaluation functions
         for the system as default values, overriding internal defaults.
@@ -87,9 +92,11 @@ class for a set of subclasses that are used to implement specific
         Dictionary of signal names for the inputs, outputs and states and the
         index of the corresponding array
     dt : None, True or float
-        System timebase.  None (default) indicates continuous time, True
-        indicates discrete time with undefined sampling time, positive number
-        is discrete time with specified sampling time.
+        System timebase. 0 (default) indicates continuous
+        time, True indicates discrete time with unspecified sampling
+        time, positive number is discrete time with specified
+        sampling time, None indicates unspecified timebase (either  
+        continuous or discrete time).
     params : dict, optional
         Parameter values for the systems.  Passed to the evaluation functions
         for the system as default values, overriding internal defaults.
@@ -118,7 +125,7 @@ def name_or_default(self, name=None):
         return name
 
     def __init__(self, inputs=None, outputs=None, states=None, params={},
-                 dt=None, name=None):
+                 name=None, **kwargs):
         """Create an input/output system.
 
         The InputOutputSystem contructor is used to create an input/output
@@ -143,10 +150,11 @@ def __init__(self, inputs=None, outputs=None, states=None, params={},
         states : int, list of str, or None
             Description of the system states.  Same format as `inputs`.
         dt : None, True or float, optional
-            System timebase.  None (default) indicates continuous
-            time, True indicates discrete time with undefined sampling
+            System timebase. 0 (default) indicates continuous
+            time, True indicates discrete time with unspecified sampling
             time, positive number is discrete time with specified
-            sampling time.
+            sampling time, None indicates unspecified timebase (either  
+            continuous or discrete time).
         params : dict, optional
             Parameter values for the systems.  Passed to the evaluation
             functions for the system as default values, overriding internal
@@ -162,9 +170,13 @@ def __init__(self, inputs=None, outputs=None, states=None, params={},
 
         """
         # Store the input arguments
-        self.params = params.copy()             # default parameters
-        self.dt = dt                            # timebase
-        self.name = self.name_or_default(name)  # system name
+
+        # default parameters
+        self.params = params.copy()
+        # timebase
+        self.dt = kwargs.get('dt', config.defaults['control.default_dt'])
+        # system name
+        self.name = self.name_or_default(name)
 
         # Parse and store the number of inputs, outputs, and states
         self.set_inputs(inputs)
@@ -210,9 +222,7 @@ def __mul__(sys2, sys1):
                              "inputs and outputs")
 
         # Make sure timebase are compatible
-        dt = _find_timebase(sys1, sys2)
-        if dt is False:
-            raise ValueError("System timebases are not compabile")
+        dt = common_timebase(sys1.dt, sys2.dt)
 
         inplist = [(0,i) for i in range(sys1.ninputs)]
         outlist = [(1,i) for i in range(sys2.noutputs)]
@@ -464,12 +474,11 @@ def feedback(self, other=1, sign=-1, params={}):
                              "inputs and outputs")
 
         # Make sure timebases are compatible
-        dt = _find_timebase(self, other)
-        if dt is False:
-            raise ValueError("System timebases are not compabile")
+        dt = common_timebase(self.dt, other.dt)
 
         inplist = [(0,i) for i in range(self.ninputs)]
         outlist = [(0,i) for i in range(self.noutputs)]
+
         # Return the series interconnection between the systems
         newsys = InterconnectedSystem((self, other), inplist=inplist, outlist=outlist,
                                       params=params, dt=dt)
@@ -580,10 +589,11 @@ def __init__(self, linsys, inputs=None, outputs=None, states=None,
         states : int, list of str, or None, optional
             Description of the system states.  Same format as `inputs`.
         dt : None, True or float, optional
-            System timebase.  None (default) indicates continuous
-            time, True indicates discrete time with undefined sampling
+            System timebase. 0 (default) indicates continuous
+            time, True indicates discrete time with unspecified sampling
             time, positive number is discrete time with specified
-            sampling time.
+            sampling time, None indicates unspecified timebase (either  
+            continuous or discrete time).
         params : dict, optional
             Parameter values for the systems.  Passed to the evaluation
             functions for the system as default values, overriding internal
@@ -650,7 +660,8 @@ class NonlinearIOSystem(InputOutputSystem):
 
     """
     def __init__(self, updfcn, outfcn=None, inputs=None, outputs=None,
-                 states=None, params={}, dt=None, name=None):
+                 states=None, params={},
+                 name=None, **kwargs):
         """Create a nonlinear I/O system given update and output functions.
 
         Creates an `InputOutputSystem` for a nonlinear system by specifying a
@@ -702,10 +713,10 @@ def __init__(self, updfcn, outfcn=None, inputs=None, outputs=None,
             operating in continuous or discrete time.  It can have the
             following values:
 
-            * dt = None       No timebase specified
-            * dt = 0          Continuous time system
-            * dt > 0          Discrete time system with sampling time dt
-            * dt = True       Discrete time with unspecified sampling time
+            * dt = 0: continuous time system (default)
+            * dt > 0: discrete time system with sampling period 'dt'
+            * dt = True: discrete time with unspecified sampling period
+            * dt = None: no timebase specified 
 
         name : string, optional
             System name (used for specifying signals). If unspecified, a generic
@@ -722,6 +733,7 @@ def __init__(self, updfcn, outfcn=None, inputs=None, outputs=None,
         self.outfcn = outfcn
 
         # Initialize the rest of the structure
+        dt = kwargs.get('dt', config.defaults['control.default_dt'])
         super(NonlinearIOSystem, self).__init__(
             inputs=inputs, outputs=outputs, states=states,
             params=params, dt=dt, name=name
@@ -871,10 +883,10 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
             operating in continuous or discrete time.  It can have the
             following values:
 
-            * dt = None       No timebase specified
-            * dt = 0          Continuous time system
-            * dt > 0          Discrete time system with sampling time dt
-            * dt = True       Discrete time with unspecified sampling time
+            * dt = 0: continuous time system (default)
+            * dt > 0: discrete time system with sampling period 'dt'
+            * dt = True: discrete time with unspecified sampling period
+            * dt = None: no timebase specified 
 
         name : string, optional
             System name (used for specifying signals). If unspecified, a generic
@@ -888,7 +900,6 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
         # Check to make sure all systems are consistent
         self.syslist = syslist
         self.syslist_index = {}
-        dt = None
         nstates = 0; self.state_offset = []
         ninputs = 0; self.input_offset = []
         noutputs = 0; self.output_offset = []
@@ -896,12 +907,7 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
         sysname_count_dct = {}
         for sysidx, sys in enumerate(syslist):
             # Make sure time bases are consistent
-            # TODO: Use lti._find_timebase() instead?
-            if dt is None and sys.dt is not None:
-                # Timebase was not specified; set to match this system
-                dt = sys.dt
-            elif dt != sys.dt:
-                raise TypeError("System timebases are not compatible")
+            dt = common_timebase(dt, sys.dt)
 
             # Make sure number of inputs, outputs, states is given
             if sys.ninputs is None or sys.noutputs is None or \
diff --git a/control/lti.py b/control/lti.py
index 8db14794b..e41fe416b 100644
--- a/control/lti.py
+++ b/control/lti.py
@@ -9,14 +9,16 @@
 isdtime()
 isctime()
 timebase()
-timebaseEqual()
+common_timebase()
 """
 
 import numpy as np
 from numpy import absolute, real
+from warnings import warn
 
-__all__ = ['issiso', 'timebase', 'timebaseEqual', 'isdtime', 'isctime',
-           'pole', 'zero', 'damp', 'evalfr', 'freqresp', 'dcgain']
+__all__ = ['issiso', 'timebase', 'common_timebase', 'timebaseEqual', 
+           'isdtime', 'isctime', 'pole', 'zero', 'damp', 'evalfr', 
+           'freqresp', 'dcgain']
 
 class LTI:
     """LTI is a parent class to linear time-invariant (LTI) system objects.
@@ -157,9 +159,59 @@ def timebase(sys, strict=True):
 
     return sys.dt
 
+def common_timebase(dt1, dt2):
+    """
+    Find the common timebase when interconnecting systems
+    
+    Parameters
+    ----------
+    dt1, dt2: number or system with a 'dt' attribute (e.g. TransferFunction 
+        or StateSpace system)
+
+    Returns
+    -------
+    dt: number
+        The common timebase of dt1 and dt2, as specified in 
+        :ref:`conventions-ref`. 
+        
+    Raises
+    ------
+    ValueError
+        when no compatible time base can be found
+    """
+    # explanation: 
+    # if either dt is None, they are compatible with anything
+    # if either dt is True (discrete with unspecified time base), 
+    #   use the timebase of the other, if it is also discrete
+    # otherwise both dts must be equal 
+    if hasattr(dt1, 'dt'):
+        dt1 = dt1.dt
+    if hasattr(dt2, 'dt'):
+        dt2 = dt2.dt
+
+    if dt1 is None: 
+        return dt2
+    elif dt2 is None: 
+        return dt1
+    elif dt1 is True: 
+        if dt2 > 0:
+            return dt2
+        else: 
+            raise ValueError("Systems have incompatible timebases")
+    elif dt2 is True: 
+        if dt1 > 0: 
+            return dt1
+        else: 
+            raise ValueError("Systems have incompatible timebases")
+    elif np.isclose(dt1, dt2):
+        return dt1
+    else: 
+        raise ValueError("Systems have incompatible timebases")
+
 # Check to see if two timebases are equal
 def timebaseEqual(sys1, sys2):
-    """Check to see if two systems have the same timebase
+    """
+    Check to see if two systems have the same timebase
 
     timebaseEqual(sys1, sys2)
 
@@ -168,7 +220,10 @@ def timebaseEqual(sys1, sys2):
     discrete or continuous timebase systems.  If two systems have a discrete
     timebase (dt > 0) then their timebases must be equal.
     """
-
+    warn("timebaseEqual will be deprecated in a future release of "
+         "python-control; use :func:`common_timebase` instead", 
+         PendingDeprecationWarning)
+        
     if (type(sys1.dt) == bool or type(sys2.dt) == bool):
         # Make sure both are unspecified discrete timebases
         return type(sys1.dt) == type(sys2.dt) and sys1.dt == sys2.dt
@@ -178,27 +233,6 @@ def timebaseEqual(sys1, sys2):
     else:
         return sys1.dt == sys2.dt
 
-# Find a common timebase between two or more systems
-def _find_timebase(sys1, *sysn):
-    """Find the common timebase between systems, otherwise return False"""
-
-    # Create a list of systems to check
-    syslist = [sys1]
-    syslist.append(*sysn)
-
-    # Look for a common timebase
-    dt = None
-
-    for sys in syslist:
-        # Make sure time bases are consistent
-        if (dt is None and sys.dt is not None) or \
-           (dt is True and isdiscrete(sys)):
-            # Timebase was not specified; set to match this system
-            dt = sys.dt
-        elif dt != sys.dt:
-            return False
-    return dt
-
 
 # Check to see if a system is a discrete time system
 def isdtime(sys, strict=False):
diff --git a/control/statesp.py b/control/statesp.py
index 0f6638881..2419b4e62 100644
--- a/control/statesp.py
+++ b/control/statesp.py
@@ -62,7 +62,7 @@
 from scipy.signal import cont2discrete
 from scipy.signal import StateSpace as signalStateSpace
 from warnings import warn
-from .lti import LTI, timebase, timebaseEqual, isdtime
+from .lti import LTI, common_timebase, isdtime
 from . import config
 from copy import deepcopy
 
@@ -72,7 +72,6 @@
 # Define module default parameter values
 _statesp_defaults = {
     'statesp.use_numpy_matrix': False,  # False is default in 0.9.0 and above
-    'statesp.default_dt': None,
     'statesp.remove_useless_states': True,
     'statesp.latex_num_format': '.3g',
     'statesp.latex_repr_type': 'partitioned',
@@ -178,14 +177,22 @@ class StateSpace(LTI):
     `numpy.ndarray` objects.  The :func:`~control.use_numpy_matrix` function
     can be used to set the storage type.
 
-    Discrete-time state space system are implemented by using the 'dt'
-    instance variable and setting it to the sampling period.  If 'dt' is not
-    None, then it must match whenever two state space systems are combined.
-    Setting dt = 0 specifies a continuous system, while leaving dt = None
-    means the system timebase is not specified.  If 'dt' is set to True, the
-    system will be treated as a discrete time system with unspecified sampling
-    time. The default value of 'dt' is None and can be changed by changing the
-    value of ``control.config.defaults['statesp.default_dt']``.
+    A discrete time system is created by specifying a nonzero 'timebase', dt
+    when the system is constructed:
+
+    * dt = 0: continuous time system (default)
+    * dt > 0: discrete time system with sampling period 'dt'
+    * dt = True: discrete time with unspecified sampling period
+    * dt = None: no timebase specified
+
+    Systems must have compatible timebases in order to be combined. A discrete
+    time system with unspecified sampling time (`dt = True`) can be combined
+    with a system having a specified sampling time; the result will be a
+    discrete time system with the sample time of the latter system. Similarly,
+    a system with timebase `None` can be combined with a system having any
+    timebase; the result will have the timebase of the latter system.
+    The default value of dt can be changed by changing the value of
+    ``control.config.defaults['control.default_dt']``.
 
     StateSpace instances have support for IPython LaTeX output,
     intended for pretty-printing in Jupyter notebooks.  The LaTeX
@@ -204,14 +211,13 @@ class StateSpace(LTI):
     `'partitioned'` or `'separate'`.  If `'partitioned'`, the A, B, C, D
     matrices are shown as a single, partitioned matrix; if
     `'separate'`, the matrices are shown separately.
-
     """
 
     # Allow ndarray * StateSpace to give StateSpace._rmul_() priority
     __array_priority__ = 11     # override ndarray and matrix types
 
 
-    def __init__(self, *args, **kw):
+    def __init__(self, *args, **kwargs):
         """
         StateSpace(A, B, C, D[, dt])
 
@@ -224,13 +230,13 @@ def __init__(self, *args, **kw):
         call StateSpace(sys), where sys is a StateSpace object.
 
         """
+        # first get A, B, C, D matrices
         if len(args) == 4:
             # The user provided A, B, C, and D matrices.
             (A, B, C, D) = args
-            dt = config.defaults['statesp.default_dt']
         elif len(args) == 5:
             # Discrete time system
-            (A, B, C, D, dt) = args
+            (A, B, C, D, _) = args
         elif len(args) == 1:
             # Use the copy constructor.
             if not isinstance(args[0], StateSpace):
@@ -240,16 +246,12 @@ def __init__(self, *args, **kw):
             B = args[0].B
             C = args[0].C
             D = args[0].D
-            try:
-                dt = args[0].dt
-            except NameError:
-                dt = config.defaults['statesp.default_dt']
         else:
-            raise ValueError("Needs 1 or 4 arguments; received %i." % len(args))
+            raise ValueError("Expected 1, 4, or 5 arguments; received %i." % len(args))
 
         # Process keyword arguments
-        remove_useless = kw.get('remove_useless',
-                                config.defaults['statesp.remove_useless_states'])
+        remove_useless = kwargs.get('remove_useless',
+                                    config.defaults['statesp.remove_useless_states'])
 
         # Convert all matrices to standard form
         A = _ssmatrix(A)
@@ -268,12 +270,33 @@ def __init__(self, *args, **kw):
         D = _ssmatrix(D)
 
         # TODO: use super here?
-        LTI.__init__(self, inputs=D.shape[1], outputs=D.shape[0], dt=dt)
+        LTI.__init__(self, inputs=D.shape[1], outputs=D.shape[0])
         self.A = A
         self.B = B
         self.C = C
         self.D = D
 
+        # now set dt
+        if len(args) == 4:
+            if 'dt' in kwargs:
+                dt = kwargs['dt']
+            elif self.is_static_gain():
+                dt = None
+            else:
+                dt = config.defaults['control.default_dt']
+        elif len(args) == 5:
+            dt = args[4]
+            if 'dt' in kwargs:
+                warn('received multiple dt arguments, using positional arg dt=%s'%dt)
+        elif len(args) == 1:
+            try:
+                dt = args[0].dt
+            except AttributeError:
+                if self.is_static_gain():
+                    dt = None
+                else:
+                    dt = config.defaults['control.default_dt']
+        self.dt = dt
         self.states = A.shape[1]
 
         if 0 == self.states:
@@ -507,14 +530,7 @@ def __add__(self, other):
                     (self.outputs != other.outputs)):
                 raise ValueError("Systems have different shapes.")
 
-            # Figure out the sampling time to use
-            if self.dt is None and other.dt is not None:
-                dt = other.dt       # use dt from second argument
-            elif (other.dt is None and self.dt is not None) or \
-                    (timebaseEqual(self, other)):
-                dt = self.dt        # use dt from first argument
-            else:
-                raise ValueError("Systems have different sampling times")
+            dt = common_timebase(self.dt, other.dt)
 
             # Concatenate the various arrays
             A = concatenate((
@@ -563,16 +579,8 @@ def __mul__(self, other):
             # Check to make sure the dimensions are OK
             if self.inputs != other.outputs:
                 raise ValueError("C = A * B: A has %i column(s) (input(s)), \
-but B has %i row(s)\n(output(s))." % (self.inputs, other.outputs))
-
-            # Figure out the sampling time to use
-            if (self.dt == None and other.dt != None):
-                dt = other.dt       # use dt from second argument
-            elif (other.dt == None and self.dt != None) or \
-                    (timebaseEqual(self, other)):
-                dt = self.dt        # use dt from first argument
-            else:
-                raise ValueError("Systems have different sampling times")
+                    but B has %i row(s)\n(output(s))." % (self.inputs, other.outputs))
+            dt = common_timebase(self.dt, other.dt)
 
             # Concatenate the various arrays
             A = concatenate(
@@ -644,9 +652,8 @@ def _evalfr(self, omega):
         """Evaluate a SS system's transfer function at a single frequency"""
         # Figure out the point to evaluate the transfer function
         if isdtime(self, strict=True):
-            dt = timebase(self)
-            s = exp(1.j * omega * dt)
-            if omega * dt > math.pi:
+            s = exp(1.j * omega * self.dt)
+            if omega * self.dt > math.pi:
                 warn("_evalfr: frequency evaluation above Nyquist frequency")
         else:
             s = omega * 1.j
@@ -703,9 +710,8 @@ def freqresp(self, omega):
         # axis (continuous time) or unit circle (discrete time).
         omega.sort()
         if isdtime(self, strict=True):
-            dt = timebase(self)
-            cmplx_freqs = exp(1.j * omega * dt)
-            if max(np.abs(omega)) * dt > math.pi:
+            cmplx_freqs = exp(1.j * omega * self.dt)
+            if max(np.abs(omega)) * self.dt > math.pi:
                 warn("freqresp: frequency evaluation above Nyquist frequency")
         else:
             cmplx_freqs = omega * 1.j
@@ -808,14 +814,7 @@ def feedback(self, other=1, sign=-1):
         if (self.inputs != other.outputs) or (self.outputs != other.inputs):
                 raise ValueError("State space systems don't have compatible inputs/outputs for "
                                  "feedback.")
-
-        # Figure out the sampling time to use
-        if self.dt is None and other.dt is not None:
-            dt = other.dt       # use dt from second argument
-        elif other.dt is None and self.dt is not None or timebaseEqual(self, other):
-            dt = self.dt        # use dt from first argument
-        else:
-            raise ValueError("Systems have different sampling times")
+        dt = common_timebase(self.dt, other.dt)
 
         A1 = self.A
         B1 = self.B
@@ -885,14 +884,7 @@ def lft(self, other, nu=-1, ny=-1):
         # dimension check
         # TODO
 
-        # Figure out the sampling time to use
-        if (self.dt == None and other.dt != None):
-            dt = other.dt       # use dt from second argument
-        elif (other.dt == None and self.dt != None) or \
-                timebaseEqual(self, other):
-            dt = self.dt        # use dt from first argument
-        else:
-            raise ValueError("Systems have different time bases")
+        dt = common_timebase(self.dt, other.dt)
 
         # submatrices
         A = self.A
@@ -1035,8 +1027,7 @@ def append(self, other):
         if not isinstance(other, StateSpace):
             other = _convertToStateSpace(other)
 
-        if self.dt != other.dt:
-            raise ValueError("Systems must have the same time step")
+        self.dt = common_timebase(self.dt, other.dt)
 
         n = self.states + other.states
         m = self.inputs + other.inputs
@@ -1151,9 +1142,10 @@ def dcgain(self):
         return np.squeeze(gain)
 
     def is_static_gain(self):
-         """True if and only if the system has no dynamics, that is, 
-         if A and B are zero. """
-         return not np.any(self.A) and not np.any(self.B)
+        """True if and only if the system has no dynamics, that is,
+        if A and B are zero. """
+        return not np.any(self.A) and not np.any(self.B)
+
 
 # TODO: add discrete time check
 def _convertToStateSpace(sys, **kw):
@@ -1470,8 +1462,7 @@ def _mimo2simo(sys, input, warn_conversion=False):
 
     return sys
 
-
-def ss(*args):
+def ss(*args, **kwargs):
     """ss(A, B, C, D[, dt])
 
     Create a state space system.
@@ -1516,8 +1507,7 @@ def ss(*args):
         Output matrix
     D: array_like or string
         Feed forward matrix
-    dt: If present, specifies the sampling period and a discrete time
-        system is created
+    dt: If present, specifies the timebase of the system
 
     Returns
     -------
@@ -1548,7 +1538,7 @@ def ss(*args):
     """
 
     if len(args) == 4 or len(args) == 5:
-        return StateSpace(*args)
+        return StateSpace(*args, **kwargs)
     elif len(args) == 1:
         from .xferfcn import TransferFunction
         sys = args[0]
@@ -1560,7 +1550,7 @@ def ss(*args):
             raise TypeError("ss(sys): sys must be a StateSpace or \
 TransferFunction object.  It is %s." % type(sys))
     else:
-        raise ValueError("Needs 1 or 4 arguments; received %i." % len(args))
+        raise ValueError("Needs 1, 4, or 5 arguments; received %i." % len(args))
 
 
 def tf2ss(*args):
diff --git a/control/tests/config_test.py b/control/tests/config_test.py
index 3b2a11f12..0e68ec8a7 100644
--- a/control/tests/config_test.py
+++ b/control/tests/config_test.py
@@ -234,17 +234,14 @@ def test_legacy_defaults(self):
     @pytest.mark.parametrize("dt", [0, None])
     def test_change_default_dt(self, dt):
         """Test that system with dynamics uses correct default dt"""
-        ct.set_defaults('statesp', default_dt=dt)
+        ct.set_defaults('control', default_dt=dt)
         assert ct.ss(1, 0, 0, 1).dt == dt
-        ct.set_defaults('xferfcn', default_dt=dt)
         assert ct.tf(1, [1, 1]).dt == dt
+        nlsys = ct.iosys.NonlinearIOSystem(
+            lambda t, x, u: u * x * x,
+            lambda t, x, u: x, inputs=1, outputs=1)
+        assert nlsys.dt == dt
 
-        # nlsys = ct.iosys.NonlinearIOSystem(
-        #     lambda t, x, u: u * x * x,
-        #     lambda t, x, u: x, inputs=1, outputs=1)
-        # assert nlsys.dt == dt
-
-    @pytest.mark.skip("implemented in gh-431")
     def test_change_default_dt_static(self):
         """Test that static gain systems always have dt=None"""
         ct.set_defaults('control', default_dt=0)
diff --git a/control/tests/discrete_test.py b/control/tests/discrete_test.py
index 7aee216d4..3dcbb7f3b 100644
--- a/control/tests/discrete_test.py
+++ b/control/tests/discrete_test.py
@@ -6,9 +6,10 @@
 import numpy as np
 import pytest
 
-from control import StateSpace, TransferFunction, feedback, step_response, \
-    isdtime, timebase, isctime, sample_system, bode, impulse_response, \
-    evalfr, timebaseEqual, forced_response, rss
+from control import (StateSpace, TransferFunction, bode, common_timebase,
+                     evalfr, feedback, forced_response, impulse_response,
+                     isctime, isdtime, rss, sample_system, step_response,
+                     timebase)
 
 
 class TestDiscrete:
@@ -51,13 +52,21 @@ class Tsys:
 
         return T
 
-    def testTimebaseEqual(self, tsys):
-        """Test for equal timebases and not so equal ones"""
-        assert timebaseEqual(tsys.siso_ss1, tsys.siso_tf1)
-        assert timebaseEqual(tsys.siso_ss1, tsys.siso_ss1c)
-        assert not timebaseEqual(tsys.siso_ss1d, tsys.siso_ss1c)
-        assert not timebaseEqual(tsys.siso_ss1d, tsys.siso_ss2d)
-        assert not timebaseEqual(tsys.siso_ss1d, tsys.siso_ss3d)
+    def testCompatibleTimebases(self, tsys):
+        """test that compatible timebases don't throw errors and vice versa"""
+        common_timebase(tsys.siso_ss1.dt, tsys.siso_tf1.dt)
+        common_timebase(tsys.siso_ss1.dt, tsys.siso_ss1c.dt)
+        common_timebase(tsys.siso_ss1d.dt, tsys.siso_ss1.dt)
+        common_timebase(tsys.siso_ss1.dt, tsys.siso_ss1d.dt)
+        common_timebase(tsys.siso_ss1.dt, tsys.siso_ss1d.dt)
+        common_timebase(tsys.siso_ss1d.dt, tsys.siso_ss3d.dt)
+        common_timebase(tsys.siso_ss3d.dt, tsys.siso_ss1d.dt)
+        with pytest.raises(ValueError):
+            # cont + discrete
+            common_timebase(tsys.siso_ss1d.dt, tsys.siso_ss1c.dt)
+        with pytest.raises(ValueError):
+            # incompatible discrete
+            common_timebase(tsys.siso_ss1d.dt, tsys.siso_ss2d.dt)
 
     def testSystemInitialization(self, tsys):
         # Check to make sure systems are discrete time with proper variables
@@ -75,6 +84,18 @@ def testSystemInitialization(self, tsys):
         assert tsys.siso_tf2d.dt == 0.2
         assert tsys.siso_tf3d.dt is True
 
+        # keyword argument check
+        # dynamic systems
+        assert TransferFunction(1, [1, 1], dt=0.1).dt == 0.1
+        assert TransferFunction(1, [1, 1], 0.1).dt == 0.1
+        assert StateSpace(1,1,1,1, dt=0.1).dt == 0.1
+        assert StateSpace(1,1,1,1, 0.1).dt == 0.1
+        # static gain system, dt argument should still override default dt
+        assert TransferFunction(1, [1,], dt=0.1).dt == 0.1
+        assert TransferFunction(1, [1,], 0.1).dt == 0.1
+        assert StateSpace(0,0,1,1, dt=0.1).dt == 0.1
+        assert StateSpace(0,0,1,1, 0.1).dt == 0.1
+
     def testCopyConstructor(self, tsys):
         for sys in (tsys.siso_ss1, tsys.siso_ss1c, tsys.siso_ss1d):
             newsys = StateSpace(sys)
@@ -114,6 +135,7 @@ def test_timebase_conversions(self, tsys):
         assert timebase(tf1*tf2) == timebase(tf2)
         assert timebase(tf1*tf3) == timebase(tf3)
         assert timebase(tf1*tf4) == timebase(tf4)
+        assert timebase(tf3*tf4) == timebase(tf4)
         assert timebase(tf2*tf1) == timebase(tf2)
         assert timebase(tf3*tf1) == timebase(tf3)
         assert timebase(tf4*tf1) == timebase(tf4)
@@ -128,33 +150,36 @@ def test_timebase_conversions(self, tsys):
 
         # Make sure discrete time without sampling is converted correctly
         assert timebase(tf3*tf3) == timebase(tf3)
+        assert timebase(tf3*tf4) == timebase(tf4)
         assert timebase(tf3+tf3) == timebase(tf3)
+        assert timebase(tf3+tf4) == timebase(tf4)
         assert timebase(feedback(tf3, tf3)) == timebase(tf3)
+        assert timebase(feedback(tf3, tf4)) == timebase(tf4)
 
         # Make sure all other combinations are errors
-        with pytest.raises(ValueError, match="different sampling times"):
+        with pytest.raises(ValueError, match="incompatible timebases"):
             tf2 * tf3
-        with pytest.raises(ValueError, match="different sampling times"):
+        with pytest.raises(ValueError, match="incompatible timebases"):
             tf3 * tf2
-        with pytest.raises(ValueError, match="different sampling times"):
+        with pytest.raises(ValueError, match="incompatible timebases"):
             tf2 * tf4
-        with pytest.raises(ValueError, match="different sampling times"):
+        with pytest.raises(ValueError, match="incompatible timebases"):
             tf4 * tf2
-        with pytest.raises(ValueError, match="different sampling times"):
+        with pytest.raises(ValueError, match="incompatible timebases"):
             tf2 + tf3
-        with pytest.raises(ValueError, match="different sampling times"):
+        with pytest.raises(ValueError, match="incompatible timebases"):
             tf3 + tf2
-        with pytest.raises(ValueError, match="different sampling times"):
+        with pytest.raises(ValueError, match="incompatible timebases"):
             tf2 + tf4
-        with pytest.raises(ValueError, match="different sampling times"):
+        with pytest.raises(ValueError, match="incompatible timebases"):
             tf4 + tf2
-        with pytest.raises(ValueError, match="different sampling times"):
+        with pytest.raises(ValueError, match="incompatible timebases"):
             feedback(tf2, tf3)
-        with pytest.raises(ValueError, match="different sampling times"):
+        with pytest.raises(ValueError, match="incompatible timebases"):
             feedback(tf3, tf2)
-        with pytest.raises(ValueError, match="different sampling times"):
+        with pytest.raises(ValueError, match="incompatible timebases"):
             feedback(tf2, tf4)
-        with pytest.raises(ValueError, match="different sampling times"):
+        with pytest.raises(ValueError, match="incompatible timebases"):
             feedback(tf4, tf2)
 
     def testisdtime(self, tsys):
@@ -212,13 +237,12 @@ def testAddition(self, tsys):
         sys = tsys.siso_ss1c + tsys.siso_ss1c
         sys = tsys.siso_ss1d + tsys.siso_ss1d
         sys = tsys.siso_ss3d + tsys.siso_ss3d
+        sys = tsys.siso_ss1d + tsys.siso_ss3d
 
         with pytest.raises(ValueError):
             StateSpace.__add__(tsys.mimo_ss1c, tsys.mimo_ss1d)
         with pytest.raises(ValueError):
             StateSpace.__add__(tsys.mimo_ss1d, tsys.mimo_ss2d)
-        with pytest.raises(ValueError):
-            StateSpace.__add__(tsys.siso_ss1d, tsys.siso_ss3d)
 
         # Transfer function addition
         sys = tsys.siso_tf1 + tsys.siso_tf1d
@@ -228,13 +252,12 @@ def testAddition(self, tsys):
         sys = tsys.siso_tf1c + tsys.siso_tf1c
         sys = tsys.siso_tf1d + tsys.siso_tf1d
         sys = tsys.siso_tf2d + tsys.siso_tf2d
+        sys = tsys.siso_tf1d + tsys.siso_tf3d
 
         with pytest.raises(ValueError):
             TransferFunction.__add__(tsys.siso_tf1c, tsys.siso_tf1d)
         with pytest.raises(ValueError):
             TransferFunction.__add__(tsys.siso_tf1d, tsys.siso_tf2d)
-        with pytest.raises(ValueError):
-            TransferFunction.__add__(tsys.siso_tf1d, tsys.siso_tf3d)
 
         # State space + transfer function
         sys = tsys.siso_ss1c + tsys.siso_tf1c
@@ -252,13 +275,12 @@ def testMultiplication(self, tsys):
         sys = tsys.siso_ss1d * tsys.siso_ss1
         sys = tsys.siso_ss1c * tsys.siso_ss1c
         sys = tsys.siso_ss1d * tsys.siso_ss1d
+        sys = tsys.siso_ss1d * tsys.siso_ss3d
 
         with pytest.raises(ValueError):
             StateSpace.__mul__(tsys.mimo_ss1c, tsys.mimo_ss1d)
         with pytest.raises(ValueError):
             StateSpace.__mul__(tsys.mimo_ss1d, tsys.mimo_ss2d)
-        with pytest.raises(ValueError):
-            StateSpace.__mul__(tsys.siso_ss1d, tsys.siso_ss3d)
 
         # Transfer function multiplication
         sys = tsys.siso_tf1 * tsys.siso_tf1d
@@ -267,13 +289,12 @@ def testMultiplication(self, tsys):
         sys = tsys.siso_tf1d * tsys.siso_tf1
         sys = tsys.siso_tf1c * tsys.siso_tf1c
         sys = tsys.siso_tf1d * tsys.siso_tf1d
+        sys = tsys.siso_tf1d * tsys.siso_tf3d
 
         with pytest.raises(ValueError):
             TransferFunction.__mul__(tsys.siso_tf1c, tsys.siso_tf1d)
         with pytest.raises(ValueError):
             TransferFunction.__mul__(tsys.siso_tf1d, tsys.siso_tf2d)
-        with pytest.raises(ValueError):
-            TransferFunction.__mul__(tsys.siso_tf1d, tsys.siso_tf3d)
 
         # State space * transfer function
         sys = tsys.siso_ss1c * tsys.siso_tf1c
@@ -293,13 +314,12 @@ def testFeedback(self, tsys):
         sys = feedback(tsys.siso_ss1d, tsys.siso_ss1)
         sys = feedback(tsys.siso_ss1c, tsys.siso_ss1c)
         sys = feedback(tsys.siso_ss1d, tsys.siso_ss1d)
+        sys = feedback(tsys.siso_ss1d, tsys.siso_ss3d)
 
         with pytest.raises(ValueError):
             feedback(tsys.mimo_ss1c, tsys.mimo_ss1d)
         with pytest.raises(ValueError):
             feedback(tsys.mimo_ss1d, tsys.mimo_ss2d)
-        with pytest.raises(ValueError):
-            feedback(tsys.siso_ss1d, tsys.siso_ss3d)
 
         # Transfer function feedback
         sys = feedback(tsys.siso_tf1, tsys.siso_tf1d)
@@ -308,13 +328,12 @@ def testFeedback(self, tsys):
         sys = feedback(tsys.siso_tf1d, tsys.siso_tf1)
         sys = feedback(tsys.siso_tf1c, tsys.siso_tf1c)
         sys = feedback(tsys.siso_tf1d, tsys.siso_tf1d)
+        sys = feedback(tsys.siso_tf1d, tsys.siso_tf3d)
 
         with pytest.raises(ValueError):
             feedback(tsys.siso_tf1c, tsys.siso_tf1d)
         with pytest.raises(ValueError):
             feedback(tsys.siso_tf1d, tsys.siso_tf2d)
-        with pytest.raises(ValueError):
-            feedback(tsys.siso_tf1d, tsys.siso_tf3d)
 
         # State space, transfer function
         sys = feedback(tsys.siso_ss1c, tsys.siso_tf1c)
diff --git a/control/tests/iosys_test.py b/control/tests/iosys_test.py
index 740416507..faed39e07 100644
--- a/control/tests/iosys_test.py
+++ b/control/tests/iosys_test.py
@@ -29,17 +29,17 @@ class TSys:
         """Return some test systems"""
         # Create a single input/single output linear system
         T.siso_linsys = ct.StateSpace(
-            [[-1, 1], [0, -2]], [[0], [1]], [[1, 0]], [[0]], 0)
+            [[-1, 1], [0, -2]], [[0], [1]], [[1, 0]], [[0]])
 
         # Create a multi input/multi output linear system
         T.mimo_linsys1 = ct.StateSpace(
             [[-1, 1], [0, -2]], [[1, 0], [0, 1]],
-            [[1, 0], [0, 1]], np.zeros((2, 2)), 0)
+            [[1, 0], [0, 1]], np.zeros((2, 2)))
 
         # Create a multi input/multi output linear system
         T.mimo_linsys2 = ct.StateSpace(
             [[-1, 1], [0, -2]], [[0, 1], [1, 0]],
-            [[1, 0], [0, 1]], np.zeros((2, 2)), 0)
+            [[1, 0], [0, 1]], np.zeros((2, 2)))
 
         # Create simulation parameters
         T.T = np.linspace(0, 10, 100)
@@ -281,7 +281,7 @@ def test_algebraic_loop(self, tsys):
         linsys = tsys.siso_linsys
         lnios = ios.LinearIOSystem(linsys)
         nlios =  ios.NonlinearIOSystem(None, \
-            lambda t, x, u, params: u*u, inputs=1, outputs=1, dt=0)
+            lambda t, x, u, params: u*u, inputs=1, outputs=1)
         nlios1 = nlios.copy()
         nlios2 = nlios.copy()
 
@@ -310,7 +310,7 @@ def test_algebraic_loop(self, tsys):
         iosys = ios.InterconnectedSystem(
             (lnios, nlios),         # linear system w/ nonlinear feedback
             ((1,),                  # feedback interconnection (sig to 0)
-              (0, (1, 0, -1))),
+             (0, (1, 0, -1))),
             0,                      # input to linear system
             0                       # output from linear system
         )
@@ -331,7 +331,7 @@ def test_algebraic_loop(self, tsys):
 
         # Algebraic loop due to feedthrough term
         linsys = ct.StateSpace(
-            [[-1, 1], [0, -2]], [[0], [1]], [[1, 0]], [[1]], 0)
+            [[-1, 1], [0, -2]], [[0], [1]], [[1, 0]], [[1]])
         lnios = ios.LinearIOSystem(linsys)
         iosys = ios.InterconnectedSystem(
             (nlios, lnios),         # linear system w/ nonlinear feedback
@@ -374,7 +374,7 @@ def test_rmul(self, tsys):
         # Also creates a nested interconnected system
         ioslin = ios.LinearIOSystem(tsys.siso_linsys)
         nlios =  ios.NonlinearIOSystem(None, \
-            lambda t, x, u, params: u*u, inputs=1, outputs=1, dt=0)
+            lambda t, x, u, params: u*u, inputs=1, outputs=1)
         sys1 = nlios * ioslin
         sys2 = ios.InputOutputSystem.__rmul__(nlios, sys1)
 
@@ -414,7 +414,7 @@ def test_feedback(self, tsys):
         # Linear system with constant feedback (via "nonlinear" mapping)
         ioslin = ios.LinearIOSystem(tsys.siso_linsys)
         nlios =  ios.NonlinearIOSystem(None, \
-            lambda t, x, u, params: u, inputs=1, outputs=1, dt=0)
+            lambda t, x, u, params: u, inputs=1, outputs=1)
         iosys = ct.feedback(ioslin, nlios)
         linsys = ct.feedback(tsys.siso_linsys, 1)
 
@@ -740,7 +740,7 @@ def test_named_signals(self, tsys):
             inputs = ('u[0]', 'u[1]'),
             outputs = ('y[0]', 'y[1]'),
             states = tsys.mimo_linsys1.states,
-            name = 'sys1', dt=0)
+            name = 'sys1')
         sys2 = ios.LinearIOSystem(tsys.mimo_linsys2,
             inputs = ('u[0]', 'u[1]'),
             outputs = ('y[0]', 'y[1]'),
@@ -1015,7 +1015,7 @@ def test_duplicates(self, tsys):
         nlios = ios.NonlinearIOSystem(lambda t, x, u, params: x,
                                       lambda t, x, u, params: u * u,
                                       inputs=1, outputs=1, states=1,
-                                      name="sys", dt=0)
+                                      name="sys")
 
         # Duplicate objects
         with pytest.warns(UserWarning, match="Duplicate object"):
@@ -1033,10 +1033,10 @@ def test_duplicates(self, tsys):
         iosys_siso = ct.LinearIOSystem(tsys.siso_linsys)
         nlios1 = ios.NonlinearIOSystem(None,
                                        lambda t, x, u, params: u * u,
-                                       inputs=1, outputs=1, name="sys", dt=0)
+                                       inputs=1, outputs=1, name="sys")
         nlios2 = ios.NonlinearIOSystem(None,
                                        lambda t, x, u, params: u * u,
-                                       inputs=1, outputs=1, name="sys", dt=0)
+                                       inputs=1, outputs=1, name="sys")
 
         with pytest.warns(UserWarning, match="Duplicate name"):
             ct.InterconnectedSystem((nlios1, iosys_siso, nlios2),
@@ -1045,10 +1045,10 @@ def test_duplicates(self, tsys):
         # Same system, different names => everything should be OK
         nlios1 = ios.NonlinearIOSystem(None,
                                        lambda t, x, u, params:  u * u,
-                                       inputs=1, outputs=1, name="nlios1", dt=0)
+                                       inputs=1, outputs=1, name="nlios1")
         nlios2 = ios.NonlinearIOSystem(None,
                                        lambda t, x, u, params: u * u,
-                                       inputs=1, outputs=1, name="nlios2", dt=0)
+                                       inputs=1, outputs=1, name="nlios2")
         with pytest.warns(None) as record:
             ct.InterconnectedSystem((nlios1, iosys_siso, nlios2),
                                     inputs=0, outputs=0, states=0)
diff --git a/control/tests/lti_test.py b/control/tests/lti_test.py
index 762e1435a..ee9d95a09 100644
--- a/control/tests/lti_test.py
+++ b/control/tests/lti_test.py
@@ -4,7 +4,7 @@
 import pytest
 
 from control import c2d, tf, tf2ss, NonlinearIOSystem
-from control.lti import (LTI, damp, dcgain, isctime, isdtime,
+from control.lti import (LTI, common_timebase, damp, dcgain, isctime, isdtime,
                          issiso, pole, timebaseEqual, zero)
 from control.tests.conftest import slycotonly
 
@@ -72,3 +72,84 @@ def test_dcgain(self):
         sys = tf(84, [1, 2])
         np.testing.assert_equal(sys.dcgain(), 42)
         np.testing.assert_equal(dcgain(sys), 42)
+
+    @pytest.mark.parametrize("dt1, dt2, expected",
+                             [(None, None, True),
+                              (None, 0, True),
+                              (None, 1, True),
+                              pytest.param(None, True, True,
+                                           marks=pytest.mark.xfail(
+                                               reason="returns false")),
+                              (0, 0, True),
+                              (0, 1, False),
+                              (0, True, False),
+                              (1, 1, True),
+                              (1, 2, False),
+                              (1, True, False),
+                              (True, True, True)])
+    def test_timebaseEqual_deprecated(self, dt1, dt2, expected):
+        """Test that timbaseEqual throws a warning and returns as documented"""
+        sys1 = tf([1], [1, 2, 3], dt1)
+        sys2 = tf([1], [1, 4, 5], dt2)
+
+        print(sys1.dt)
+        print(sys2.dt)
+
+        with pytest.deprecated_call():
+            assert timebaseEqual(sys1, sys2) is expected
+        # Make sure behaviour is symmetric
+        with pytest.deprecated_call():
+            assert timebaseEqual(sys2, sys1) is expected
+
+    @pytest.mark.parametrize("dt1, dt2, expected",
+                             [(None, None, None),
+                              (None, 0, 0),
+                              (None, 1, 1),
+                              (None, True, True),
+                              (True, True, True),
+                              (True, 1, 1),
+                              (1, 1, 1),
+                              (0, 0, 0),
+                              ])
+    @pytest.mark.parametrize("sys1", [True, False])
+    @pytest.mark.parametrize("sys2", [True, False])
+    def test_common_timebase(self, dt1, dt2, expected, sys1, sys2):
+        """Test that common_timbase adheres to :ref:`conventions-ref`"""
+        i1 = tf([1], [1, 2, 3], dt1) if sys1 else dt1
+        i2 = tf([1], [1, 4, 5], dt2) if sys2 else dt2
+        assert common_timebase(i1, i2) == expected
+        # Make sure behaviour is symmetric
+        assert common_timebase(i2, i1) == expected
+
+    @pytest.mark.parametrize("i1, i2",
+                             [(True, 0),
+                              (0, 1),
+                              (1, 2)])
+    def test_common_timebase_errors(self, i1, i2):
+        """Test that common_timbase throws errors on invalid combinations"""
+        with pytest.raises(ValueError):
+            common_timebase(i1, i2)
+        # Make sure behaviour is symmetric
+        with pytest.raises(ValueError):
+            common_timebase(i2, i1)
+
+    @pytest.mark.parametrize("dt, ref, strictref",
+                             [(None, True, False),
+                              (0, False, False),
+                              (1, True, True),
+                              (True, True, True)])
+    @pytest.mark.parametrize("objfun, arg",
+                             [(LTI, ()),
+                              (NonlinearIOSystem, (lambda x: x, ))])
+    def test_isdtime(self, objfun, arg, dt, ref, strictref):
+        """Test isdtime and isctime functions to follow convention"""
+        obj = objfun(*arg, dt=dt)
+
+        assert isdtime(obj) == ref
+        assert isdtime(obj, strict=True) == strictref
+
+        if dt is not None:
+            ref = not ref
+            strictref = not strictref
+        assert isctime(obj) == ref
+        assert isctime(obj, strict=True) == strictref
diff --git a/control/tests/matlab_test.py b/control/tests/matlab_test.py
index 6c7f6f14f..3a15a5aff 100644
--- a/control/tests/matlab_test.py
+++ b/control/tests/matlab_test.py
@@ -736,20 +736,20 @@ def testCombi01(self):
         margin command should remove the solution for w = nearly zero.
         """
         # Example is a concocted two-body satellite with flexible link
-        Jb = 400;
-        Jp = 1000;
-        k = 10;
-        b = 5;
+        Jb = 400
+        Jp = 1000
+        k = 10
+        b = 5
 
         # can now define an "s" variable, to make TF's
-        s = tf([1, 0], [1]);
-        hb1 = 1/(Jb*s);
-        hb2 = 1/s;
-        hp1 = 1/(Jp*s);
-        hp2 = 1/s;
+        s = tf([1, 0], [1])
+        hb1 = 1/(Jb*s)
+        hb2 = 1/s
+        hp1 = 1/(Jp*s)
+        hp2 = 1/s
 
         # convert to ss and append
-        sat0 = append(ss(hb1), ss(hb2), k, b, ss(hp1), ss(hp2));
+        sat0 = append(ss(hb1), ss(hb2), k, b, ss(hp1), ss(hp2))
 
         # connection of the elements with connect call
         Q = [[1, -3, -4],  # link moment (spring, damper), feedback to body
@@ -758,9 +758,9 @@ def testCombi01(self):
              [4,  1, -5],  # damper input
              [5,  3,  4],  # link moment, acting on payload
              [6,  5,  0]]
-        inputs = [1];
-        outputs = [1, 2, 5, 6];
-        sat1 = connect(sat0, Q, inputs, outputs);
+        inputs = [1]
+        outputs = [1, 2, 5, 6]
+        sat1 = connect(sat0, Q, inputs, outputs)
 
         # matched notch filter
         wno = 0.19
diff --git a/control/tests/statesp_test.py b/control/tests/statesp_test.py
index 3fcf5b45b..a69672d36 100644
--- a/control/tests/statesp_test.py
+++ b/control/tests/statesp_test.py
@@ -81,13 +81,16 @@ def sys623(self):
 
     @pytest.mark.parametrize(
         "dt",
-        [(None, ), (0, ), (1, ), (0.1, ), (True, )],
+        [(), (None, ), (0, ), (1, ), (0.1, ), (True, )],
         ids=lambda i: "dt " + ("unspec" if len(i) == 0 else str(i[0])))
     @pytest.mark.parametrize(
         "argfun",
         [pytest.param(
             lambda ABCDdt: (ABCDdt, {}),
             id="A, B, C, D[, dt]"),
+         pytest.param(
+            lambda ABCDdt: (ABCDdt[:4], {'dt': dt_ for dt_ in ABCDdt[4:]}),
+            id="A, B, C, D[, dt=dt]"),
          pytest.param(
              lambda ABCDdt: ((StateSpace(*ABCDdt), ), {}),
              id="sys")
@@ -107,7 +110,7 @@ def test_constructor(self, sys322ABCD, dt, argfun):
     @pytest.mark.parametrize("args, exc, errmsg",
                              [((True, ), TypeError,
                                "(can only take in|sys must be) a StateSpace"),
-                              ((1, 2), ValueError, "1 or 4 arguments"),
+                              ((1, 2), ValueError, "1, 4, or 5 arguments"),
                               ((np.ones((3, 2)), np.ones((3, 2)),
                                 np.ones((2, 2)), np.ones((2, 2))),
                                ValueError, "A must be square"),
@@ -131,6 +134,16 @@ def test_constructor_invalid(self, args, exc, errmsg):
         with pytest.raises(exc, match=errmsg):
             ss(*args)
 
+    def test_constructor_warns(self, sys322ABCD):
+        """Test ambiguos input to StateSpace() constructor"""
+        with pytest.warns(UserWarning, match="received multiple dt"):
+            sys = StateSpace(*(sys322ABCD + (0.1, )), dt=0.2)
+            np.testing.assert_almost_equal(sys.A, sys322ABCD[0])
+        np.testing.assert_almost_equal(sys.B, sys322ABCD[1])
+        np.testing.assert_almost_equal(sys.C, sys322ABCD[2])
+        np.testing.assert_almost_equal(sys.D, sys322ABCD[3])
+        assert sys.dt == 0.1
+
     def test_copy_constructor(self):
         """Test the copy constructor"""
         # Create a set of matrices for a simple linear system
@@ -152,6 +165,19 @@ def test_copy_constructor(self):
         linsys.A[0, 0] = -3
         np.testing.assert_array_equal(cpysys.A, [[-1]])  # original value
 
+    def test_copy_constructor_nodt(self, sys322):
+        """Test the copy constructor when an object without dt is passed"""
+        sysin = sample_system(sys322, 1.)
+        del sysin.dt
+        sys = StateSpace(sysin)
+        assert sys.dt == defaults['control.default_dt']
+
+        # test for static gain
+        sysin = StateSpace([], [], [], [[1, 2], [3, 4]], 1.)
+        del sysin.dt
+        sys = StateSpace(sysin)
+        assert sys.dt is None
+
     def test_matlab_style_constructor(self):
         """Use (deprecated) matrix-style construction string"""
         with pytest.deprecated_call():
@@ -354,7 +380,6 @@ def test_freq_resp(self):
         np.testing.assert_almost_equal(phase, true_phase)
         np.testing.assert_equal(omega, true_omega)
 
-    @pytest.mark.skip("is_static_gain is introduced in gh-431")
     def test_is_static_gain(self):
         A0 = np.zeros((2,2))
         A1 = A0.copy()
diff --git a/control/tests/xferfcn_test.py b/control/tests/xferfcn_test.py
index 62c4bfb23..c0b5e227f 100644
--- a/control/tests/xferfcn_test.py
+++ b/control/tests/xferfcn_test.py
@@ -13,6 +13,7 @@
 from control.tests.conftest import slycotonly, nopython2, matrixfilter
 from control.lti import isctime, isdtime
 from control.dtime import sample_system
+from control.config import defaults
 
 
 class TestXferFcn:
@@ -85,6 +86,28 @@ def test_constructor_zero_denominator(self):
             TransferFunction([[[1.], [2., 3.]], [[-1., 4.], [3., 2.]]],
                              [[[1., 0.], [0.]], [[0., 0.], [2.]]])
 
+    def test_constructor_nodt(self):
+        """Test the constructor when an object without dt is passed"""
+        sysin = TransferFunction([[[0., 1.], [2., 3.]]],
+                                 [[[5., 2.], [3., 0.]]])
+        del sysin.dt
+        sys = TransferFunction(sysin)
+        assert sys.dt == defaults['control.default_dt']
+
+        # test for static gain
+        sysin = TransferFunction([[[2.], [3.]]],
+                                 [[[1.], [.1]]])
+        del sysin.dt
+        sys = TransferFunction(sysin)
+        assert sys.dt is None
+
+    def test_constructor_double_dt(self):
+        """Test that providing dt as arg and kwarg prefers arg with warning"""
+        with pytest.warns(UserWarning, match="received multiple dt.*"
+                                             "using positional arg"):
+            sys = TransferFunction(1, [1, 2, 3], 0.1, dt=0.2)
+        assert sys.dt == 0.1
+
     def test_add_inconsistent_dimension(self):
         """Add two transfer function matrices of different sizes."""
         sys1 = TransferFunction([[[1., 2.]]], [[[4., 5.]]])
@@ -409,7 +432,6 @@ def test_evalfr_siso(self, dt, omega, resp):
                                            resp,
                                            atol=1e-3)
 
-    @pytest.mark.skip("is_static_gain is introduced in gh-431")
     def test_is_static_gain(self):
         numstatic = 1.1
         denstatic = 1.2
diff --git a/control/xferfcn.py b/control/xferfcn.py
index 4077080e3..93743deb1 100644
--- a/control/xferfcn.py
+++ b/control/xferfcn.py
@@ -63,18 +63,16 @@
 from warnings import warn
 from itertools import chain
 from re import sub
-from .lti import LTI, timebaseEqual, timebase, isdtime
+from .lti import LTI, common_timebase, isdtime
 from . import config
 
 __all__ = ['TransferFunction', 'tf', 'ss2tf', 'tfdata']
 
 
 # Define module default parameter values
-_xferfcn_defaults = {
-    'xferfcn.default_dt': None}
+_xferfcn_defaults = {}
 
 class TransferFunction(LTI):
-
     """TransferFunction(num, den[, dt])
 
     A class for representing transfer functions
@@ -90,13 +88,22 @@ class TransferFunction(LTI):
     means that the numerator of the transfer function from the 6th input to the
     3rd output is set to s^2 + 4s + 8.
 
-    Discrete-time transfer functions are implemented by using the 'dt'
-    instance variable and setting it to something other than 'None'.  If 'dt'
-    has a non-zero value, then it must match whenever two transfer functions
-    are combined.  If 'dt' is set to True, the system will be treated as a
-    discrete time system with unspecified sampling time. The default value of
-    'dt' is None and can be changed by changing the value of
-    ``control.config.defaults['xferfcn.default_dt']``.
+    A discrete time transfer function is created by specifying a nonzero
+    'timebase' dt when the system is constructed:
+
+    * dt = 0: continuous time system (default)
+    * dt > 0: discrete time system with sampling period 'dt'
+    * dt = True: discrete time with unspecified sampling period
+    * dt = None: no timebase specified
+
+    Systems must have compatible timebases in order to be combined. A discrete
+    time system with unspecified sampling time (`dt = True`) can be combined
+    with a system having a specified sampling time; the result will be a
+    discrete time system with the sample time of the latter system. Similarly,
+    a system with timebase `None` can be combined with a system having any
+    timebase; the result will have the timebase of the latter system.
+    The default value of dt can be changed by changing the value of
+    ``control.config.defaults['control.default_dt']``.
 
     The TransferFunction class defines two constants ``s`` and ``z`` that
     represent the differentiation and delay operators in continuous and
@@ -105,9 +112,9 @@ class TransferFunction(LTI):
 
     >>> s = TransferFunction.s
     >>> G  = (s + 1)/(s**2 + 2*s + 1)
-
     """
-    def __init__(self, *args):
+
+    def __init__(self, *args, **kwargs):
         """TransferFunction(num, den[, dt])
 
         Construct a transfer function.
@@ -125,7 +132,6 @@ def __init__(self, *args):
         if len(args) == 2:
             # The user provided a numerator and a denominator.
             (num, den) = args
-            dt = config.defaults['xferfcn.default_dt']
         elif len(args) == 3:
             # Discrete time transfer function
             (num, den, dt) = args
@@ -137,11 +143,6 @@ def __init__(self, *args):
                                 % type(args[0]))
             num = args[0].num
             den = args[0].den
-            # TODO: not sure this can ever happen since dt is always present
-            try:
-                dt = args[0].dt
-            except NameError:   # pragma: no coverage
-                dt = config.defaults['xferfcn.default_dt']
         else:
             raise ValueError("Needs 1, 2 or 3 arguments; received %i."
                              % len(args))
@@ -199,12 +200,37 @@ def __init__(self, *args):
                 if zeronum:
                     den[i][j] = ones(1)
 
-        LTI.__init__(self, inputs, outputs, dt)
+        LTI.__init__(self, inputs, outputs)
         self.num = num
         self.den = den
 
         self._truncatecoeff()
 
+        # get dt
+        if len(args) == 2:
+            # no dt given in positional arguments
+            if 'dt' in kwargs:
+                dt = kwargs['dt']
+            elif self.is_static_gain():
+                dt = None
+            else:
+                dt = config.defaults['control.default_dt']
+        elif len(args) == 3:
+            # Discrete time transfer function
+            if 'dt' in kwargs:
+                warn('received multiple dt arguments, '
+                     'using positional arg dt=%s' % dt)
+        elif len(args) == 1:
+            # TODO: not sure this can ever happen since dt is always present
+            try:
+                dt = args[0].dt
+            except AttributeError:
+                if self.is_static_gain():
+                    dt = None
+                else:
+                    dt = config.defaults['control.default_dt']
+        self.dt = dt
+
     def __call__(self, s):
         """Evaluate the system's transfer function for a complex variable
 
@@ -370,14 +396,7 @@ def __add__(self, other):
                 "The first summand has %i output(s), but the second has %i."
                 % (self.outputs, other.outputs))
 
-        # Figure out the sampling time to use
-        if self.dt is None and other.dt is not None:
-            dt = other.dt       # use dt from second argument
-        elif (other.dt is None and self.dt is not None) or \
-             (timebaseEqual(self, other)):
-            dt = self.dt        # use dt from first argument
-        else:
-            raise ValueError("Systems have different sampling times")
+        dt = common_timebase(self.dt, other.dt)
 
         # Preallocate the numerator and denominator of the sum.
         num = [[[] for j in range(self.inputs)] for i in range(self.outputs)]
@@ -421,14 +440,7 @@ def __mul__(self, other):
         inputs = other.inputs
         outputs = self.outputs
 
-        # Figure out the sampling time to use
-        if self.dt is None and other.dt is not None:
-            dt = other.dt       # use dt from second argument
-        elif (other.dt is None and self.dt is not None) or \
-             (self.dt == other.dt):
-            dt = self.dt        # use dt from first argument
-        else:
-            raise ValueError("Systems have different sampling times")
+        dt = common_timebase(self.dt, other.dt)
 
         # Preallocate the numerator and denominator of the sum.
         num = [[[0] for j in range(inputs)] for i in range(outputs)]
@@ -472,14 +484,7 @@ def __rmul__(self, other):
         inputs = self.inputs
         outputs = other.outputs
 
-        # Figure out the sampling time to use
-        if self.dt is None and other.dt is not None:
-            dt = other.dt       # use dt from second argument
-        elif (other.dt is None and self.dt is not None) \
-                or (self.dt == other.dt):
-            dt = self.dt        # use dt from first argument
-        else:
-            raise ValueError("Systems have different sampling times")
+        dt = common_timebase(self.dt, other.dt)
 
         # Preallocate the numerator and denominator of the sum.
         num = [[[0] for j in range(inputs)] for i in range(outputs)]
@@ -519,14 +524,7 @@ def __truediv__(self, other):
                 "TransferFunction.__truediv__ is currently \
                 implemented only for SISO systems.")
 
-        # Figure out the sampling time to use
-        if self.dt is None and other.dt is not None:
-            dt = other.dt       # use dt from second argument
-        elif (other.dt is None and self.dt is not None) or \
-             (self.dt == other.dt):
-            dt = self.dt        # use dt from first argument
-        else:
-            raise ValueError("Systems have different sampling times")
+        dt = common_timebase(self.dt, other.dt)
 
         num = polymul(self.num[0][0], other.den[0][0])
         den = polymul(self.den[0][0], other.num[0][0])
@@ -626,9 +624,8 @@ def _evalfr(self, omega):
         # TODO: implement for discrete time systems
         if isdtime(self, strict=True):
             # Convert the frequency to discrete time
-            dt = timebase(self)
-            s = exp(1.j * omega * dt)
-            if np.any(omega * dt > pi):
+            s = exp(1.j * omega * self.dt)
+            if np.any(omega * self.dt > pi):
                 warn("_evalfr: frequency evaluation above Nyquist frequency")
         else:
             s = 1.j * omega
@@ -692,9 +689,8 @@ def freqresp(self, omega):
         # Figure out the frequencies
         omega.sort()
         if isdtime(self, strict=True):
-            dt = timebase(self)
-            slist = np.array([exp(1.j * w * dt) for w in omega])
-            if max(omega) * dt > pi:
+            slist = np.array([exp(1.j * w * self.dt) for w in omega])
+            if max(omega) * self.dt > pi:
                 warn("freqresp: frequency evaluation above Nyquist frequency")
         else:
             slist = np.array([1j * w for w in omega])
@@ -737,15 +733,7 @@ def feedback(self, other=1, sign=-1):
             raise NotImplementedError(
                 "TransferFunction.feedback is currently only implemented "
                 "for SISO functions.")
-
-        # Figure out the sampling time to use
-        if self.dt is None and other.dt is not None:
-            dt = other.dt       # use dt from second argument
-        elif (other.dt is None and self.dt is not None) or \
-             (self.dt == other.dt):
-            dt = self.dt        # use dt from first argument
-        else:
-            raise ValueError("Systems have different sampling times")
+        dt = common_timebase(self.dt, other.dt)
 
         num1 = self.num[0][0]
         den1 = self.den[0][0]
@@ -1048,7 +1036,7 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None):
 
         Returns
         -------
-        sysd : StateSpace system
+        sysd : TransferFunction system
             Discrete time system, with sampling rate Ts
 
         Notes
@@ -1115,16 +1103,16 @@ def _dcgain_cont(self):
         return np.squeeze(gain)
 
     def is_static_gain(self):
-         """returns True if and only if all of the numerator and denominator 
-         polynomials of the (possibly MIMO) transfer function are zeroth order, 
+         """returns True if and only if all of the numerator and denominator
+         polynomials of the (possibly MIMO) transfer function are zeroth order,
          that is, if the system has no dynamics. """
-         for list_of_polys in self.num, self.den: 
+         for list_of_polys in self.num, self.den:
              for row in list_of_polys:
                  for poly in row:
-                     if len(poly) > 1: 
+                     if len(poly) > 1:
                          return False
          return True
-         
+
 # c2d function contributed by Benjamin White, Oct 2012
 def _c2d_matched(sysC, Ts):
     # Pole-zero match method of continuous to discrete time conversion
@@ -1335,7 +1323,7 @@ def _convert_to_transfer_function(sys, **kw):
     raise TypeError("Can't convert given type to TransferFunction system.")
 
 
-def tf(*args):
+def tf(*args, **kwargs):
     """tf(num, den[, dt])
 
     Create a transfer function system. Can create MIMO systems.
@@ -1425,7 +1413,7 @@ def tf(*args):
     """
 
     if len(args) == 2 or len(args) == 3:
-        return TransferFunction(*args)
+        return TransferFunction(*args, **kwargs)
     elif len(args) == 1:
         # Look for special cases defining differential/delay operator
         if args[0] == 's':
@@ -1446,7 +1434,7 @@ def tf(*args):
         raise ValueError("Needs 1 or 2 arguments; received %i." % len(args))
 
 
-def ss2tf(*args):
+def ss2tf(*args, **kwargs):
     """ss2tf(sys)
 
     Transform a state space system to a transfer function.
@@ -1511,7 +1499,7 @@ def ss2tf(*args):
     from .statesp import StateSpace
     if len(args) == 4 or len(args) == 5:
         # Assume we were given the A, B, C, D matrix and (optional) dt
-        return _convert_to_transfer_function(StateSpace(*args))
+        return _convert_to_transfer_function(StateSpace(*args, **kwargs))
 
     elif len(args) == 1:
         sys = args[0]
@@ -1597,7 +1585,6 @@ def _clean_part(data):
 
     return data
 
-
 # Define constants to represent differentiation, unit delay
 TransferFunction.s = TransferFunction([1, 0], [1], 0)
 TransferFunction.z = TransferFunction([1, 0], [1], True)
diff --git a/doc/conventions.rst b/doc/conventions.rst
index 99789bc9e..4a3d78926 100644
--- a/doc/conventions.rst
+++ b/doc/conventions.rst
@@ -80,27 +80,24 @@ Discrete time systems
 A discrete time system is created by specifying a nonzero 'timebase', dt.
 The timebase argument can be given when a system is constructed:
 
-* dt = None: no timebase specified (default)
-* dt = 0: continuous time system
+* dt = 0: continuous time system (default)
 * dt > 0: discrete time system with sampling period 'dt'
 * dt = True: discrete time with unspecified sampling period
+* dt = None: no timebase specified 
 
 Only the :class:`StateSpace`, :class:`TransferFunction`, and
 :class:`InputOutputSystem` classes allow explicit representation of
 discrete time systems.
 
-Systems must have compatible timebases in order to be combined.  A system
-with timebase `None` can be combined with a system having a specified
-timebase; the result will have the timebase of the latter system.
-Similarly, a discrete time system with unspecified sampling time (`dt =
-True`) can be combined with a system having a specified sampling time;
-the result will be a discrete time system with the sample time of the latter
-system.  For continuous time systems, the :func:`sample_system` function or
-the :meth:`StateSpace.sample` and :meth:`TransferFunction.sample` methods
+Systems must have compatible timebases in order to be combined. A discrete time 
+system with unspecified sampling time (`dt = True`) can be combined with a system 
+having a specified sampling time; the result will be a discrete time system with the sample time of the latter
+system.  Similarly, a system with timebase `None` can be combined with a system having a specified
+timebase; the result will have the timebase of the latter system. For continuous 
+time systems, the :func:`sample_system` function or the :meth:`StateSpace.sample` and :meth:`TransferFunction.sample` methods
 can be used to create a discrete time system from a continuous time system.
 See :ref:`utility-and-conversions`. The default value of 'dt' can be changed by
-changing the values of ``control.config.defaults['statesp.default_dt']`` and 
-``control.config.defaults['xferfcn.default_dt']``.
+changing the value of ``control.config.defaults['control.default_dt']``.
 
 Conversion between representations
 ----------------------------------