From cfb9c98ec2a039ec74d6c9f44bd132ad18f21bf7 Mon Sep 17 00:00:00 2001 From: Alex Rothberg Date: Fri, 13 Dec 2013 19:41:00 -0500 Subject: [PATCH] ENH Datetime64Formatter and Timedelta64Formatter now limit precision. For Datetime this means that only the date is shown when for all values there is no timezone and time is midnight and for Timedelta this means that the deltas are all whole days (GH3401). str(NaT) fixed to be "NaT" (GH5708). --- doc/source/release.rst | 4 + doc/source/v0.13.1.txt | 21 +++ pandas/core/format.py | 126 +++++++++---- pandas/tests/test_format.py | 240 +++++++++++++++++++++++-- pandas/tseries/index.py | 52 +++--- pandas/tseries/tests/test_timezones.py | 2 +- pandas/tslib.pyx | 77 ++++++-- 7 files changed, 439 insertions(+), 83 deletions(-) diff --git a/doc/source/release.rst b/doc/source/release.rst index fc9f18279087b..6d550d4f0b588 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -76,6 +76,8 @@ Improvements to existing features - support ``dtypes`` on ``Panel`` - extend ``Panel.apply`` to allow arbitrary functions (rather than only ufuncs) (:issue:`1148`) allow multiple axes to be used to operate on slabs of a ``Panel`` + - The ``ArrayFormatter``s for ``datetime`` and ``timedelta64`` now intelligently + limit precision based on the values in the array (:issue:`3401`) .. _release.bug_fixes-0.13.1: @@ -99,6 +101,8 @@ Bug Fixes - Bug in creating an empty DataFrame, copying, then assigning (:issue:`5932`) - Bug in DataFrame.tail with empty frame (:issue:`5846`) - Bug in propogating metadata on ``resample`` (:issue:`5862`) + - Fixed string-representation of ``NaT`` to be "NaT" (:issue:`5708`) + - Fixed string-representation for Timestamp to show nanoseconds if present (:issue:`5912`) pandas 0.13.0 ------------- diff --git a/doc/source/v0.13.1.txt b/doc/source/v0.13.1.txt index 76b915c519440..31004d24e56a6 100644 --- a/doc/source/v0.13.1.txt +++ b/doc/source/v0.13.1.txt @@ -83,6 +83,27 @@ Enhancements result result.loc[:,:,'ItemA'] +- The ``ArrayFormatter``s for ``datetime`` and ``timedelta64`` now intelligently + limit precision based on the values in the array (:issue:`3401`) + + Previously output might look like: + + .. code-block:: python + + age today diff + 0 2001-01-01 00:00:00 2013-04-19 00:00:00 4491 days, 00:00:00 + 1 2004-06-01 00:00:00 2013-04-19 00:00:00 3244 days, 00:00:00 + + Now the output looks like: + + .. ipython:: python + + df = DataFrame([ Timestamp('20010101'), + Timestamp('20040601') ], columns=['age']) + df['today'] = Timestamp('20130419') + df['diff'] = df['today']-df['age'] + df + Experimental ~~~~~~~~~~~~ diff --git a/pandas/core/format.py b/pandas/core/format.py index 47745635bbc39..24b0554755ead 100644 --- a/pandas/core/format.py +++ b/pandas/core/format.py @@ -14,11 +14,13 @@ from pandas.core.config import get_option, set_option, reset_option import pandas.core.common as com import pandas.lib as lib +from pandas.tslib import iNaT import numpy as np import itertools import csv +from datetime import time from pandas.tseries.period import PeriodIndex, DatetimeIndex @@ -1609,7 +1611,7 @@ def format_array(values, formatter, float_format=None, na_rep='NaN', if digits is None: digits = get_option("display.precision") - fmt_obj = fmt_klass(values, digits, na_rep=na_rep, + fmt_obj = fmt_klass(values, digits=digits, na_rep=na_rep, float_format=float_format, formatter=formatter, space=space, justify=justify) @@ -1704,7 +1706,7 @@ def _val(x, threshold): fmt_values = [_val(x, threshold) for x in self.values] return _trim_zeros(fmt_values, self.na_rep) - def get_result(self): + def _format_strings(self): if self.formatter is not None: fmt_values = [self.formatter(x) for x in self.values] else: @@ -1732,64 +1734,124 @@ def get_result(self): fmt_str = '%% .%de' % (self.digits - 1) fmt_values = self._format_with(fmt_str) - return _make_fixed_width(fmt_values, self.justify) + return fmt_values class IntArrayFormatter(GenericArrayFormatter): - def get_result(self): - if self.formatter: - formatter = self.formatter - else: - formatter = lambda x: '% d' % x + def _format_strings(self): + formatter = self.formatter or (lambda x: '% d' % x) fmt_values = [formatter(x) for x in self.values] - return _make_fixed_width(fmt_values, self.justify) + return fmt_values class Datetime64Formatter(GenericArrayFormatter): + def __init__(self, values, nat_rep='NaT', date_format=None, **kwargs): + super(Datetime64Formatter, self).__init__(values, **kwargs) + self.nat_rep = nat_rep + self.date_format = date_format - def get_result(self): - if self.formatter: - formatter = self.formatter - else: - formatter = _format_datetime64 + def _format_strings(self): + formatter = self.formatter or _get_format_datetime64_from_values( + self.values, + nat_rep=self.nat_rep, + date_format=self.date_format) fmt_values = [formatter(x) for x in self.values] - return _make_fixed_width(fmt_values, self.justify) + return fmt_values -def _format_datetime64(x, tz=None): - if isnull(x): - return 'NaT' - stamp = lib.Timestamp(x, tz=tz) - return stamp._repr_base +def _format_datetime64(x, tz=None, nat_rep='NaT'): + if x is None or lib.checknull(x): + return nat_rep + if tz is not None or not isinstance(x, lib.Timestamp): + x = lib.Timestamp(x, tz=tz) -class Timedelta64Formatter(Datetime64Formatter): + return str(x) - def get_result(self): - if self.formatter: - formatter = self.formatter - else: - formatter = _format_timedelta64 +def _format_datetime64_dateonly(x, nat_rep='NaT', date_format=None): + if x is None or lib.checknull(x): + return nat_rep + + if not isinstance(x, lib.Timestamp): + x = lib.Timestamp(x) + + if date_format: + return x.strftime(date_format) + else: + return x._date_repr + + +def _is_dates_only(values): + for d in values: + if isinstance(d, np.datetime64): + d = lib.Timestamp(d) + + if d is not None and not lib.checknull(d) and d._has_time_component(): + return False + return True + + +def _get_format_datetime64(is_dates_only, nat_rep='NaT', date_format=None): + + if is_dates_only: + return lambda x, tz=None: _format_datetime64_dateonly(x, + nat_rep=nat_rep, + date_format=date_format) + else: + return lambda x, tz=None: _format_datetime64(x, tz=tz, nat_rep=nat_rep) + + +def _get_format_datetime64_from_values(values, + nat_rep='NaT', + date_format=None): + is_dates_only = _is_dates_only(values) + return _get_format_datetime64(is_dates_only=is_dates_only, + nat_rep=nat_rep, + date_format=date_format) + + +class Timedelta64Formatter(GenericArrayFormatter): + + def _format_strings(self): + formatter = self.formatter or _get_format_timedelta64(self.values) fmt_values = [formatter(x) for x in self.values] - return _make_fixed_width(fmt_values, self.justify) + return fmt_values + + +def _get_format_timedelta64(values): + values_int = values.astype(np.int64) -def _format_timedelta64(x): - if isnull(x): - return 'NaT' + consider_values = values_int != iNaT - return lib.repr_timedelta64(x) + one_day_in_nanos = (86400 * 1e9) + even_days = np.logical_and(consider_values, values_int % one_day_in_nanos != 0).sum() == 0 + all_sub_day = np.logical_and(consider_values, np.abs(values_int) >= one_day_in_nanos).sum() == 0 + + format_short = even_days or all_sub_day + format = "short" if format_short else "long" + + def impl(x): + if x is None or lib.checknull(x): + return 'NaT' + elif format_short and x == 0: + return "0 days" if even_days else "00:00:00" + else: + return lib.repr_timedelta64(x, format=format) + + return impl def _make_fixed_width(strings, justify='right', minimum=None, truncated=False): - if len(strings) == 0: + + if len(strings) == 0 or justify == 'all': return strings _strlen = _strlen_func() diff --git a/pandas/tests/test_format.py b/pandas/tests/test_format.py index f66c59fade2c1..a9855c4e73c6e 100644 --- a/pandas/tests/test_format.py +++ b/pandas/tests/test_format.py @@ -13,15 +13,17 @@ from numpy.random import randn import numpy as np -from pandas import DataFrame, Series, Index +from pandas import DataFrame, Series, Index, _np_version_under1p7, Timestamp import pandas.core.format as fmt import pandas.util.testing as tm from pandas.util.terminal import get_terminal_size import pandas +import pandas.tslib as tslib import pandas as pd from pandas.core.config import (set_option, get_option, option_context, reset_option) +from datetime import datetime _frame = DataFrame(tm.getSeriesData()) @@ -55,6 +57,17 @@ def has_expanded_repr(df): return True return False +def skip_if_np_version_under1p7(): + if _np_version_under1p7: + import nose + + raise nose.SkipTest('numpy >= 1.7 required') + +def _skip_if_no_pytz(): + try: + import pytz + except ImportError: + raise nose.SkipTest("pytz not installed") class TestDataFrameFormatting(tm.TestCase): _multiprocess_can_split_ = True @@ -770,11 +783,11 @@ def test_wide_repr(self): with option_context('mode.sim_interactive', True): col = lambda l, k: [tm.rands(k) for _ in range(l)] max_cols = get_option('display.max_columns') - df = DataFrame([col(max_cols-1, 25) for _ in range(10)]) + df = DataFrame([col(max_cols - 1, 25) for _ in range(10)]) set_option('display.expand_frame_repr', False) rep_str = repr(df) - print(rep_str) - assert "10 rows x %d columns" % (max_cols-1) in rep_str + + assert "10 rows x %d columns" % (max_cols - 1) in rep_str set_option('display.expand_frame_repr', True) wide_repr = repr(df) self.assert_(rep_str != wide_repr) @@ -1749,7 +1762,7 @@ def test_float_trim_zeros(self): def test_datetimeindex(self): - from pandas import date_range, NaT, Timestamp + from pandas import date_range, NaT index = date_range('20130102',periods=6) s = Series(1,index=index) result = s.to_string() @@ -1779,32 +1792,33 @@ def test_timedelta64(self): # adding NaTs y = s-s.shift(1) result = y.to_string() - self.assertTrue('1 days, 00:00:00' in result) + self.assertTrue('1 days' in result) + self.assertTrue('00:00:00' not in result) self.assertTrue('NaT' in result) # with frac seconds o = Series([datetime(2012,1,1,microsecond=150)]*3) y = s-o result = y.to_string() - self.assertTrue('-00:00:00.000150' in result) + self.assertTrue('-0 days, 00:00:00.000150' in result) # rounding? o = Series([datetime(2012,1,1,1)]*3) y = s-o result = y.to_string() - self.assertTrue('-01:00:00' in result) + self.assertTrue('-0 days, 01:00:00' in result) self.assertTrue('1 days, 23:00:00' in result) o = Series([datetime(2012,1,1,1,1)]*3) y = s-o result = y.to_string() - self.assertTrue('-01:01:00' in result) + self.assertTrue('-0 days, 01:01:00' in result) self.assertTrue('1 days, 22:59:00' in result) o = Series([datetime(2012,1,1,1,1,microsecond=150)]*3) y = s-o result = y.to_string() - self.assertTrue('-01:01:00.000150' in result) + self.assertTrue('-0 days, 01:01:00.000150' in result) self.assertTrue('1 days, 22:58:59.999850' in result) # neg time @@ -2039,6 +2053,212 @@ class TestFloatArrayFormatter(tm.TestCase): def test_misc(self): obj = fmt.FloatArrayFormatter(np.array([], dtype=np.float64)) result = obj.get_result() + self.assertTrue(len(result) == 0) + + def test_format(self): + obj = fmt.FloatArrayFormatter(np.array([12, 0], dtype=np.float64)) + result = obj.get_result() + self.assertEqual(result[0], " 12") + self.assertEqual(result[1], " 0") + + +class TestRepr_timedelta64(tm.TestCase): + @classmethod + def setUpClass(cls): + skip_if_np_version_under1p7() + + def test_legacy(self): + delta_1d = pd.to_timedelta(1, unit='D') + delta_0d = pd.to_timedelta(0, unit='D') + delta_1s = pd.to_timedelta(1, unit='s') + delta_500ms = pd.to_timedelta(500, unit='ms') + + self.assertEqual(tslib.repr_timedelta64(delta_1d), "1 days, 00:00:00") + self.assertEqual(tslib.repr_timedelta64(-delta_1d), "-1 days, 00:00:00") + self.assertEqual(tslib.repr_timedelta64(delta_0d), "00:00:00") + self.assertEqual(tslib.repr_timedelta64(delta_1s), "00:00:01") + self.assertEqual(tslib.repr_timedelta64(delta_500ms), "00:00:00.500000") + self.assertEqual(tslib.repr_timedelta64(delta_1d + delta_1s), "1 days, 00:00:01") + self.assertEqual(tslib.repr_timedelta64(delta_1d + delta_500ms), "1 days, 00:00:00.500000") + + def test_short(self): + delta_1d = pd.to_timedelta(1, unit='D') + delta_0d = pd.to_timedelta(0, unit='D') + delta_1s = pd.to_timedelta(1, unit='s') + delta_500ms = pd.to_timedelta(500, unit='ms') + + self.assertEqual(tslib.repr_timedelta64(delta_1d, format='short'), "1 days") + self.assertEqual(tslib.repr_timedelta64(-delta_1d, format='short'), "-1 days") + self.assertEqual(tslib.repr_timedelta64(delta_0d, format='short'), "00:00:00") + self.assertEqual(tslib.repr_timedelta64(delta_1s, format='short'), "00:00:01") + self.assertEqual(tslib.repr_timedelta64(delta_500ms, format='short'), "00:00:00.500000") + self.assertEqual(tslib.repr_timedelta64(delta_1d + delta_1s, format='short'), "1 days, 00:00:01") + self.assertEqual(tslib.repr_timedelta64(delta_1d + delta_500ms, format='short'), "1 days, 00:00:00.500000") + + def test_long(self): + delta_1d = pd.to_timedelta(1, unit='D') + delta_0d = pd.to_timedelta(0, unit='D') + delta_1s = pd.to_timedelta(1, unit='s') + delta_500ms = pd.to_timedelta(500, unit='ms') + + self.assertEqual(tslib.repr_timedelta64(delta_1d, format='long'), "1 days, 00:00:00") + self.assertEqual(tslib.repr_timedelta64(-delta_1d, format='long'), "-1 days, 00:00:00") + self.assertEqual(tslib.repr_timedelta64(delta_0d, format='long'), "0 days, 00:00:00") + self.assertEqual(tslib.repr_timedelta64(delta_1s, format='long'), "0 days, 00:00:01") + self.assertEqual(tslib.repr_timedelta64(delta_500ms, format='long'), "0 days, 00:00:00.500000") + self.assertEqual(tslib.repr_timedelta64(delta_1d + delta_1s, format='long'), "1 days, 00:00:01") + self.assertEqual(tslib.repr_timedelta64(delta_1d + delta_500ms, format='long'), "1 days, 00:00:00.500000") + + +class TestTimedelta64Formatter(tm.TestCase): + @classmethod + def setUpClass(cls): + skip_if_np_version_under1p7() + + def test_mixed(self): + x = pd.to_timedelta(list(range(5)) + [pd.NaT], unit='D') + y = pd.to_timedelta(list(range(5)) + [pd.NaT], unit='s') + result = fmt.Timedelta64Formatter(x + y).get_result() + self.assertEqual(result[0].strip(), "0 days, 00:00:00") + self.assertEqual(result[1].strip(), "1 days, 00:00:01") + + def test_mixed_neg(self): + x = pd.to_timedelta(list(range(5)) + [pd.NaT], unit='D') + y = pd.to_timedelta(list(range(5)) + [pd.NaT], unit='s') + result = fmt.Timedelta64Formatter(-(x + y)).get_result() + self.assertEqual(result[0].strip(), "0 days, 00:00:00") + self.assertEqual(result[1].strip(), "-1 days, 00:00:01") + + def test_days(self): + x = pd.to_timedelta(list(range(5)) + [pd.NaT], unit='D') + result = fmt.Timedelta64Formatter(x).get_result() + self.assertEqual(result[0].strip(), "0 days") + self.assertEqual(result[1].strip(), "1 days") + + result = fmt.Timedelta64Formatter(x[1:2]).get_result() + self.assertEqual(result[0].strip(), "1 days") + + def test_days_neg(self): + x = pd.to_timedelta(list(range(5)) + [pd.NaT], unit='D') + result = fmt.Timedelta64Formatter(-x).get_result() + self.assertEqual(result[0].strip(), "0 days") + self.assertEqual(result[1].strip(), "-1 days") + + def test_subdays(self): + y = pd.to_timedelta(list(range(5)) + [pd.NaT], unit='s') + result = fmt.Timedelta64Formatter(y).get_result() + self.assertEqual(result[0].strip(), "00:00:00") + self.assertEqual(result[1].strip(), "00:00:01") + + def test_subdays_neg(self): + y = pd.to_timedelta(list(range(5)) + [pd.NaT], unit='s') + result = fmt.Timedelta64Formatter(-y).get_result() + self.assertEqual(result[0].strip(), "00:00:00") + self.assertEqual(result[1].strip(), "-00:00:01") + + def test_zero(self): + x = pd.to_timedelta(list(range(1)) + [pd.NaT], unit='D') + result = fmt.Timedelta64Formatter(x).get_result() + self.assertEqual(result[0].strip(), "0 days") + + x = pd.to_timedelta(list(range(1)), unit='D') + result = fmt.Timedelta64Formatter(x).get_result() + self.assertEqual(result[0].strip(), "0 days") + + +class TestDatetime64Formatter(tm.TestCase): + def test_mixed(self): + x = pd.Series([datetime(2013, 1, 1), datetime(2013, 1, 1, 12), pd.NaT]) + result = fmt.Datetime64Formatter(x).get_result() + self.assertEqual(result[0].strip(), "2013-01-01 00:00:00") + self.assertEqual(result[1].strip(), "2013-01-01 12:00:00") + + def test_dates(self): + x = pd.Series([datetime(2013, 1, 1), datetime(2013, 1, 2), pd.NaT]) + result = fmt.Datetime64Formatter(x).get_result() + self.assertEqual(result[0].strip(), "2013-01-01") + self.assertEqual(result[1].strip(), "2013-01-02") + + def test_date_nanos(self): + x = pd.Series([Timestamp(200)]) + result = fmt.Datetime64Formatter(x).get_result() + self.assertEqual(result[0].strip(), "1970-01-01 00:00:00.000000200") + + +class TestNaTFormatting(tm.TestCase): + def test_repr(self): + self.assertEqual(repr(pd.NaT), "NaT") + + def test_str(self): + self.assertEqual(str(pd.NaT), "NaT") + + +class TestDatetimeIndexFormat(tm.TestCase): + def test_datetime(self): + formatted = pd.to_datetime([datetime(2003, 1, 1, 12), pd.NaT]).format() + self.assertEqual(formatted[0], "2003-01-01 12:00:00") + self.assertEqual(formatted[1], "NaT") + + def test_date(self): + formatted = pd.to_datetime([datetime(2003, 1, 1), pd.NaT]).format() + self.assertEqual(formatted[0], "2003-01-01") + self.assertEqual(formatted[1], "NaT") + + def test_date_tz(self): + formatted = pd.to_datetime([datetime(2013,1,1)], utc=True).format() + self.assertEqual(formatted[0], "2013-01-01 00:00:00+00:00") + + formatted = pd.to_datetime([datetime(2013,1,1), pd.NaT], utc=True).format() + self.assertEqual(formatted[0], "2013-01-01 00:00:00+00:00") + + def test_date_explict_date_format(self): + formatted = pd.to_datetime([datetime(2003, 2, 1), pd.NaT]).format(date_format="%m-%d-%Y", na_rep="UT") + self.assertEqual(formatted[0], "02-01-2003") + self.assertEqual(formatted[1], "UT") + + +class TestDatetimeIndexUnicode(tm.TestCase): + def test_dates(self): + text = str(pd.to_datetime([datetime(2013,1,1), datetime(2014,1,1)])) + self.assertTrue("[2013-01-01," in text) + self.assertTrue(", 2014-01-01]" in text) + + def test_mixed(self): + text = str(pd.to_datetime([datetime(2013,1,1), datetime(2014,1,1,12), datetime(2014,1,1)])) + self.assertTrue("[2013-01-01 00:00:00," in text) + self.assertTrue(", 2014-01-01 00:00:00]" in text) + + +class TestStringRepTimestamp(tm.TestCase): + def test_no_tz(self): + dt_date = datetime(2013, 1, 2) + self.assertEqual(str(dt_date), str(Timestamp(dt_date))) + + dt_datetime = datetime(2013, 1, 2, 12, 1, 3) + self.assertEqual(str(dt_datetime), str(Timestamp(dt_datetime))) + + dt_datetime_us = datetime(2013, 1, 2, 12, 1, 3, 45) + self.assertEqual(str(dt_datetime_us), str(Timestamp(dt_datetime_us))) + + ts_nanos_only = Timestamp(200) + self.assertEqual(str(ts_nanos_only), "1970-01-01 00:00:00.000000200") + + ts_nanos_micros = Timestamp(1200) + self.assertEqual(str(ts_nanos_micros), "1970-01-01 00:00:00.000001200") + + def test_tz(self): + _skip_if_no_pytz() + + import pytz + + dt_date = datetime(2013, 1, 2, tzinfo=pytz.utc) + self.assertEqual(str(dt_date), str(Timestamp(dt_date))) + + dt_datetime = datetime(2013, 1, 2, 12, 1, 3, tzinfo=pytz.utc) + self.assertEqual(str(dt_datetime), str(Timestamp(dt_datetime))) + + dt_datetime_us = datetime(2013, 1, 2, 12, 1, 3, 45, tzinfo=pytz.utc) + self.assertEqual(str(dt_datetime_us), str(Timestamp(dt_datetime_us))) if __name__ == '__main__': import nose diff --git a/pandas/tseries/index.py b/pandas/tseries/index.py index 6779e1a61c081..e6115a7c0e95d 100644 --- a/pandas/tseries/index.py +++ b/pandas/tseries/index.py @@ -529,8 +529,16 @@ def _mpl_repr(self): _na_value = tslib.NaT """The expected NA value to use with this index.""" + @cache_readonly + def _is_dates_only(self): + from pandas.core.format import _is_dates_only + return _is_dates_only(self.values) + def __unicode__(self): - from pandas.core.format import _format_datetime64 + from pandas.core.format import _get_format_datetime64 + + formatter = _get_format_datetime64(is_dates_only=self._is_dates_only) + values = self.values freq = None @@ -539,15 +547,15 @@ def __unicode__(self): summary = str(self.__class__) if len(self) == 1: - first = _format_datetime64(values[0], tz=self.tz) + first = formatter(values[0], tz=self.tz) summary += '\n[%s]' % first elif len(self) == 2: - first = _format_datetime64(values[0], tz=self.tz) - last = _format_datetime64(values[-1], tz=self.tz) + first = formatter(values[0], tz=self.tz) + last = formatter(values[-1], tz=self.tz) summary += '\n[%s, %s]' % (first, last) elif len(self) > 2: - first = _format_datetime64(values[0], tz=self.tz) - last = _format_datetime64(values[-1], tz=self.tz) + first = formatter(values[0], tz=self.tz) + last = formatter(values[-1], tz=self.tz) summary += '\n[%s, ..., %s]' % (first, last) tagline = '\nLength: %d, Freq: %s, Timezone: %s' @@ -630,30 +638,14 @@ def __contains__(self, key): def _format_with_header(self, header, **kwargs): return header + self._format_native_types(**kwargs) - def _format_native_types(self, na_rep=u('NaT'), date_format=None, **kwargs): - data = list(self) - - # tz formatter or time formatter - zero_time = time(0, 0) - if date_format is None: - for d in data: - if d.time() != zero_time or d.tzinfo is not None: - return [u('%s') % x for x in data] - - values = np.array(data, dtype=object) - mask = isnull(self.values) - values[mask] = na_rep - - imask = -mask - - if date_format is None: - date_formatter = lambda x: u('%d-%.2d-%.2d' % (x.year, x.month, x.day)) - else: - date_formatter = lambda x: u(x.strftime(date_format)) - - values[imask] = np.array([date_formatter(dt) for dt in values[imask]]) - - return values.tolist() + def _format_native_types(self, na_rep=u('NaT'), + date_format=None, **kwargs): + data = self._get_object_index() + from pandas.core.format import Datetime64Formatter + return Datetime64Formatter(values=data, + nat_rep=na_rep, + date_format=date_format, + justify='all').get_result() def isin(self, values): """ diff --git a/pandas/tseries/tests/test_timezones.py b/pandas/tseries/tests/test_timezones.py index d82f91767d413..8f0c817d33a2b 100644 --- a/pandas/tseries/tests/test_timezones.py +++ b/pandas/tseries/tests/test_timezones.py @@ -427,7 +427,7 @@ def test_index_with_timezone_repr(self): rng_eastern = rng.tz_localize('US/Eastern') - rng_repr = repr(rng) + rng_repr = repr(rng_eastern) self.assert_('2010-04-13 00:00:00' in rng_repr) def test_index_astype_asobject_tzinfos(self): diff --git a/pandas/tslib.pyx b/pandas/tslib.pyx index bda6625f3c3ad..0bac159404e34 100644 --- a/pandas/tslib.pyx +++ b/pandas/tslib.pyx @@ -125,6 +125,8 @@ def _is_fixed_offset(tz): except AttributeError: return True +_zero_time = datetime_time(0, 0) + # Python front end to C extension type _Timestamp # This serves as the box for datetime64 class Timestamp(_Timestamp): @@ -203,13 +205,17 @@ class Timestamp(_Timestamp): pass zone = "'%s'" % zone if zone else 'None' - return "Timestamp('%s', tz=%s)" % (result,zone) + return "Timestamp('%s', tz=%s)" % (result, zone) @property - def _repr_base(self): - result = '%d-%.2d-%.2d %.2d:%.2d:%.2d' % (self.year, self.month, - self.day, self.hour, - self.minute, self.second) + def _date_repr(self): + # Ideal here would be self.strftime("%Y-%m-%d"), but + # the datetime strftime() methods require year >= 1900 + return '%d-%.2d-%.2d' % (self.year, self.month, self.day) + + @property + def _time_repr(self): + result = '%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second) if self.nanosecond != 0: nanos = self.nanosecond + 1000 * self.microsecond @@ -219,6 +225,10 @@ class Timestamp(_Timestamp): return result + @property + def _repr_base(self): + return '%s %s' % (self._date_repr, self._time_repr) + @property def tz(self): """ @@ -338,6 +348,32 @@ class Timestamp(_Timestamp): ts.dts.hour, ts.dts.min, ts.dts.sec, ts.dts.us, ts.tzinfo) + def isoformat(self, sep='T'): + base = super(_Timestamp, self).isoformat(sep=sep) + if self.nanosecond == 0: + return base + + if self.tzinfo is not None: + base1, base2 = base[:-6], base[-6:] + else: + base1, base2 = base, "" + + if self.microsecond != 0: + base1 += "%.3d" % self.nanosecond + else: + base1 += ".%.9d" % self.nanosecond + + return base1 + base2 + + def _has_time_component(self): + """ + Returns if the Timestamp has a time component + in addition to the date part + """ + return (self.time() != _zero_time + or self.tzinfo is not None + or self.nanosecond != 0) + _nat_strings = set(['NaT','nat','NAT','nan','NaN','NAN']) class NaTType(_NaT): @@ -355,6 +391,9 @@ class NaTType(_NaT): def __repr__(self): return 'NaT' + def __str__(self): + return 'NaT' + def __hash__(self): return iNaT @@ -1140,8 +1179,21 @@ def array_to_timedelta64(ndarray[object] values, coerce=True): return result -def repr_timedelta64(object value): - """ provide repr for timedelta64 """ + +def repr_timedelta64(object value, format=None): + """ + provide repr for timedelta64 + + Parameters + ---------- + value : timedelta64 + format : None|"short"|"long" + + Returns + ------- + converted : Timestamp + + """ ivalue = value.view('i8') @@ -1178,19 +1230,24 @@ def repr_timedelta64(object value): seconds_pretty = "%02d" % seconds else: sp = abs(round(1e6*frac)) - seconds_pretty = "%02d.%06d" % (seconds,sp) + seconds_pretty = "%02d.%06d" % (seconds, sp) if sign < 0: sign_pretty = "-" else: sign_pretty = "" - if days: - return "%s%d days, %02d:%02d:%s" % (sign_pretty, days, hours, minutes, + if days or format == 'long': + if (hours or minutes or seconds or frac) or format != 'short': + return "%s%d days, %02d:%02d:%s" % (sign_pretty, days, hours, minutes, seconds_pretty) + else: + return "%s%d days" % (sign_pretty, days) + return "%s%02d:%02d:%s" % (sign_pretty, hours, minutes, seconds_pretty) + def array_strptime(ndarray[object] values, object fmt, coerce=False): cdef: Py_ssize_t i, n = len(values)