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)