API/CLN: consolidate truncate into NDFrame (panel had a separate method)
diff --git a/doc/source/release.rst b/doc/source/release.rst
index af9611bb98fae..d331dc7e164fc 100644
--- a/doc/source/release.rst
+++ b/doc/source/release.rst
@@ -311,6 +311,7 @@ API Changes
   - Provide __dir__ method (and local context) for tab completion / remove ipython completers code
     (:issue:`4501`)
   - Support non-unique axes in a Panel via indexing operations (:issue:`4960`)
+  - ``.truncate`` will raise a ``ValueError`` if invalid before and afters dates are given (:issue:`5242`)
 
 Internal Refactoring
 ~~~~~~~~~~~~~~~~~~~~
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
index 16f4118d5d1df..266253e05ed61 100644
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -2813,7 +2813,7 @@ def tshift(self, periods=1, freq=None, axis=0, **kwds):
 
         return self._constructor(new_data).__finalize__(self)
 
-    def truncate(self, before=None, after=None, copy=True):
+    def truncate(self, before=None, after=None, axis=None, copy=True):
         """Truncates a sorted NDFrame before and/or after some particular
         dates.
 
@@ -2823,28 +2823,38 @@ def truncate(self, before=None, after=None, copy=True):
             Truncate before date
         after : date
             Truncate after date
+        axis : the truncation axis, defaults to the stat axis
+        copy : boolean, default is True,
+            return a copy of the truncated section
 
         Returns
         -------
         truncated : type of caller
         """
 
+        if axis is None:
+            axis = self._stat_axis_number
+        axis = self._get_axis_number(axis)
+        ax = self._get_axis(axis)
+
         # if we have a date index, convert to dates, otherwise
         # treat like a slice
-        if self.index.is_all_dates:
+        if ax.is_all_dates:
             from pandas.tseries.tools import to_datetime
             before = to_datetime(before)
             after = to_datetime(after)
 
         if before is not None and after is not None:
             if before > after:
-                raise AssertionError('Truncate: %s must be after %s' %
-                                     (after, before))
+                raise ValueError('Truncate: %s must be after %s' %
+                                 (after, before))
 
-        result = self.ix[before:after]
+        slicer = [ slice(None, None) ] * self._AXIS_LEN
+        slicer[axis] = slice(before,after)
+        result = self.ix[tuple(slicer)]
 
-        if isinstance(self.index, MultiIndex):
-            result.index = self.index.truncate(before, after)
+        if isinstance(ax, MultiIndex):
+            setattr(result,self._get_axis_name(axis),ax.truncate(before, after))
 
         if copy:
             result = result.copy()
diff --git a/pandas/core/panel.py b/pandas/core/panel.py
index 87e9121b2dffc..a86c186e26b53 100644
--- a/pandas/core/panel.py
+++ b/pandas/core/panel.py
@@ -998,30 +998,6 @@ def shift(self, lags, freq=None, axis='major'):
     def tshift(self, periods=1, freq=None, axis='major', **kwds):
         return super(Panel, self).tshift(periods, freq, axis, **kwds)
 
-    def truncate(self, before=None, after=None, axis='major'):
-        """Function truncates a sorted Panel before and/or after some
-        particular values on the requested axis
-
-        Parameters
-        ----------
-        before : date
-            Left boundary
-        after : date
-            Right boundary
-        axis : {'major', 'minor', 'items'}
-
-        Returns
-        -------
-        Panel
-        """
-        axis = self._get_axis_name(axis)
-        index = self._get_axis(axis)
-
-        beg_slice, end_slice = index.slice_locs(before, after)
-        new_index = index[beg_slice:end_slice]
-
-        return self.reindex(**{axis: new_index})
-
     def join(self, other, how='left', lsuffix='', rsuffix=''):
         """
         Join items with other Panel either on major and minor axes column
diff --git a/pandas/sparse/panel.py b/pandas/sparse/panel.py
index 74bca7de89bcc..86dcf97c8bd3d 100644
--- a/pandas/sparse/panel.py
+++ b/pandas/sparse/panel.py
@@ -187,6 +187,15 @@ def _ixs(self, i, axis=0):
 
         return self.xs(key, axis=axis)
 
+    def _slice(self, slobj, axis=0, raise_on_error=False, typ=None):
+        """
+        for compat as we don't support Block Manager here
+        """
+        axis = self._get_axis_name(axis)
+        index = self._get_axis(axis)
+
+        return self.reindex(**{axis: index[slobj]})
+
     def _get_item_cache(self, key):
         return self._frames[key]
 
diff --git a/pandas/tests/test_frame.py b/pandas/tests/test_frame.py
index fe0f9244c31a3..d74ea8a5d2ffc 100644
--- a/pandas/tests/test_frame.py
+++ b/pandas/tests/test_frame.py
@@ -7731,6 +7731,10 @@ def test_truncate(self):
         truncated = ts.truncate(after=end_missing)
         assert_frame_equal(truncated, expected)
 
+        self.assertRaises(ValueError, ts.truncate,
+                          before=ts.index[-1] - 1,
+                          after=ts.index[0] +1)
+
     def test_truncate_copy(self):
         index = self.tsframe.index
         truncated = self.tsframe.truncate(index[5], index[10])
diff --git a/pandas/tests/test_series.py b/pandas/tests/test_series.py
index 3715de6dffeb9..645533d5629d2 100644
--- a/pandas/tests/test_series.py
+++ b/pandas/tests/test_series.py
@@ -3899,7 +3899,7 @@ def test_truncate(self):
         truncated = ts.truncate(before=self.ts.index[-1] + offset)
         assert(len(truncated) == 0)
 
-        self.assertRaises(Exception, ts.truncate,
+        self.assertRaises(ValueError, ts.truncate,
                           before=self.ts.index[-1] + offset,
                           after=self.ts.index[0] - offset)
 

API: truncate error if before > after is now a ValueError (rather than AssertionError)
(related #5242)
