Skip to content

Commit 96e2640

Browse files
committed
2 parents 3815815 + 32d7301 commit 96e2640

File tree

7 files changed

+130
-15
lines changed

7 files changed

+130
-15
lines changed

doc/source/gotchas.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,3 +217,27 @@ passed in the index, thus finding the integers ``0`` and ``1``. While it would
217217
be possible to insert some logic to check whether a passed sequence is all
218218
contained in the index, that logic would exact a very high cost in large data
219219
sets.
220+
221+
Timestamp limitations
222+
---------------------
223+
224+
Minimum and maximum timestamps
225+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
226+
227+
Since pandas represents timestamps in nanosecond resolution, the timespan that
228+
can be represented using a 64-bit integer is limited to approximately 584 years:
229+
230+
.. ipython:: python
231+
232+
begin = Timestamp(-9223285636854775809L)
233+
begin
234+
end = Timestamp(np.iinfo(np.int64).max)
235+
end
236+
237+
If you need to represent time series data outside the nanosecond timespan, use
238+
PeriodIndex:
239+
240+
.. ipython:: python
241+
242+
span = period_range('1215-01-01', '1381-01-01', freq='D')
243+
span

pandas/src/datetime.pyx

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ def ints_to_pydatetime(ndarray[int64_t] arr, tz=None):
6565
return result
6666

6767

68+
6869
# Python front end to C extension type _Timestamp
6970
# This serves as the box for datetime64
7071
class Timestamp(_Timestamp):
@@ -101,10 +102,27 @@ class Timestamp(_Timestamp):
101102
return ts_base
102103

103104
def __repr__(self):
104-
result = self.strftime('<Timestamp: %Y-%m-%d %H:%M:%S%z')
105-
if self.tzinfo:
106-
result += self.strftime(' %%Z, tz=%s' % self.tzinfo.zone)
107-
return result + '>'
105+
result = '%d-%.2d-%.2d %.2d:%.2d:%.2d' % (self.year, self.month,
106+
self.day, self.hour,
107+
self.minute, self.second)
108+
109+
if self.nanosecond != 0:
110+
nanos = self.nanosecond + 1000 * self.microsecond
111+
result += '.%.9d' % nanos
112+
elif self.microsecond != 0:
113+
result += '.%.6d' % self.microsecond
114+
115+
try:
116+
result += self.strftime('%z')
117+
if self.tzinfo:
118+
result += self.strftime(' %%Z, tz=%s' % self.tzinfo.zone)
119+
except ValueError:
120+
year2000 = self.replace(year=2000)
121+
result += year2000.strftime('%z')
122+
if self.tzinfo:
123+
result += year2000.strftime(' %%Z, tz=%s' % self.tzinfo.zone)
124+
125+
return '<Timestamp: %s>' % result
108126

109127
@property
110128
def tz(self):
@@ -507,13 +525,17 @@ cpdef convert_to_tsobject(object ts, object tz=None):
507525
obj.tzinfo = ts.tzinfo
508526
if obj.tzinfo is not None:
509527
obj.value -= _delta_to_nanoseconds(obj.tzinfo._utcoffset)
528+
_check_dts_bounds(obj.value, &obj.dts)
510529
return obj
511530
elif PyDate_Check(ts):
512531
obj.value = _date_to_datetime64(ts, &obj.dts)
513532
else:
514533
raise ValueError("Could not construct Timestamp from argument %s" %
515534
type(ts))
516535

536+
if obj.value != NPY_NAT:
537+
_check_dts_bounds(obj.value, &obj.dts)
538+
517539
if tz is not None:
518540
if tz is pytz.utc:
519541
obj.tzinfo = tz
@@ -530,6 +552,20 @@ cpdef convert_to_tsobject(object ts, object tz=None):
530552

531553
return obj
532554

555+
cdef int64_t _NS_LOWER_BOUND = -9223285636854775809LL
556+
cdef int64_t _NS_UPPER_BOUND = -9223372036854775807LL
557+
558+
cdef inline _check_dts_bounds(int64_t value, pandas_datetimestruct *dts):
559+
cdef pandas_datetimestruct dts2
560+
if dts.year <= 1677 or dts.year >= 2262:
561+
pandas_datetime_to_datetimestruct(value, PANDAS_FR_ns, &dts2)
562+
if dts2.year != dts.year:
563+
fmt = '%d-%.2d-%.2d %.2d:%.2d:%.2d' % (dts.year, dts.month,
564+
dts.day, dts.hour,
565+
dts.min, dts.sec)
566+
567+
raise ValueError('Out of bounds nanosecond timestamp: %s' % fmt)
568+
533569
# elif isinstance(ts, _Timestamp):
534570
# tmp = ts
535571
# obj.value = (<_Timestamp> ts).value
@@ -613,8 +649,10 @@ def array_to_datetime(ndarray[object] values, raise_=False, dayfirst=False):
613649
iresult[i] = iNaT
614650
elif PyDateTime_Check(val):
615651
iresult[i] = _pydatetime_to_dts(val, &dts)
652+
_check_dts_bounds(iresult[i], &dts)
616653
elif PyDate_Check(val):
617654
iresult[i] = _date_to_datetime64(val, &dts)
655+
_check_dts_bounds(iresult[i], &dts)
618656
elif util.is_datetime64_object(val):
619657
iresult[i] = _get_datetime64_nanos(val)
620658
elif util.is_integer_object(val):
@@ -627,6 +665,9 @@ def array_to_datetime(ndarray[object] values, raise_=False, dayfirst=False):
627665
result[i] = parse(val, dayfirst=dayfirst)
628666
except Exception:
629667
raise TypeError
668+
pandas_datetime_to_datetimestruct(iresult[i], PANDAS_FR_ns,
669+
&dts)
670+
_check_dts_bounds(iresult[i], &dts)
630671
return result
631672
except TypeError:
632673
oresult = np.empty(n, dtype=object)

pandas/tools/plotting.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from pandas.core.series import Series
1111
from pandas.tseries.index import DatetimeIndex
1212
from pandas.tseries.period import PeriodIndex
13-
from pandas.tseries.frequencies import get_period_alias
13+
from pandas.tseries.frequencies import get_period_alias, get_base_alias
1414
from pandas.tseries.offsets import DateOffset
1515
import pandas.tseries.tools as datetools
1616

@@ -590,14 +590,19 @@ def __init__(self, data, **kwargs):
590590
def has_ts_index(self):
591591
from pandas.core.frame import DataFrame
592592
if isinstance(self.data, (Series, DataFrame)):
593-
if isinstance(self.data.index, (DatetimeIndex, PeriodIndex)):
594-
has_freq = (hasattr(self.data.index, 'freq') and
595-
self.data.index.freq is not None)
596-
has_inferred = (hasattr(self.data.index, 'inferred_freq') and
597-
self.data.index.inferred_freq is not None)
598-
return has_freq or has_inferred
593+
freq = (getattr(self.data.index, 'freq', None)
594+
or getattr(self.data.index, 'inferred_freq', None))
595+
return (freq is not None) and self._has_dynamic_index_freq(freq)
599596
return False
600597

598+
def _has_dynamic_index_freq(self, freq):
599+
if isinstance(freq, DateOffset):
600+
freq = freq.rule_code
601+
else:
602+
freq = get_base_alias(freq)
603+
freq = get_period_alias(freq)
604+
return freq is not None
605+
601606
def _make_plot(self):
602607
# this is slightly deceptive
603608
if self.use_index and self.has_ts_index:

pandas/tseries/frequencies.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,16 @@ def _get_freq_str(base, mult=1):
246246
'BA' : 'A',
247247
'AS' : 'A',
248248
'BAS' : 'A',
249-
'MS' : 'M'
249+
'MS' : 'M',
250+
'D' : 'D',
251+
'B' : 'B',
252+
'T' : 'T',
253+
'S' : 'S',
254+
'H' : 'H',
255+
'Q' : 'Q',
256+
'A' : 'A',
257+
'W' : 'W',
258+
'M' : 'M'
250259
}
251260

252261
need_suffix = ['QS', 'BQ', 'BQS', 'AS', 'BA', 'BAS']
@@ -257,9 +266,20 @@ def _get_freq_str(base, mult=1):
257266
_offset_to_period_map['%s-%s' % (prefix, m)] = \
258267
_offset_to_period_map[prefix]
259268

269+
months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP',
270+
'OCT', 'NOV', 'DEC']
271+
for prefix in ['A', 'Q']:
272+
for m in months:
273+
alias = '%s-%s' % (prefix, m)
274+
_offset_to_period_map[alias] = alias
275+
276+
_days = ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN']
277+
for d in _days:
278+
_offset_to_period_map['W-%s' % d] = 'W-%s' % d
279+
260280
def get_period_alias(offset_str):
261281
""" alias to closest period strings BQ->Q etc"""
262-
return _offset_to_period_map.get(offset_str, offset_str)
282+
return _offset_to_period_map.get(offset_str, None)
263283

264284
_rule_aliases = {
265285
# Legacy rules that will continue to map to their original values

pandas/tseries/plotting.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@ def _get_default_annual_spacing(nyears):
136136
(min_spacing, maj_spacing) = (factor * 20, factor * 100)
137137
return (min_spacing, maj_spacing)
138138

139-
140139
def period_break(dates, period):
141140
"""
142141
Returns the indices where the given period changes.

pandas/tseries/tests/test_plotting.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ def test_tsplot(self):
6868
ax = ts.plot(style='k')
6969
self.assert_((0., 0., 0.) == ax.get_lines()[0].get_color())
7070

71+
@slow
72+
def test_high_freq(self):
73+
freaks = ['ms', 'us']
74+
for freq in freaks:
75+
rng = date_range('1/1/2012', periods=100000, freq=freq)
76+
ser = Series(np.random.randn(len(rng)), rng)
77+
_check_plot_works(ser.plot)
78+
7179
def test_get_datevalue(self):
7280
from pandas.tseries.plotting import get_datevalue
7381
self.assert_(get_datevalue(None, 'D') is None)
@@ -268,6 +276,7 @@ def test_finder_monthly(self):
268276
@slow
269277
def test_finder_annual(self):
270278
import matplotlib.pyplot as plt
279+
plt.close('all')
271280
xp = [1987, 1988, 1990, 1990, 1995, 2020, 2070, 2170]
272281
for i, nyears in enumerate([5, 10, 19, 49, 99, 199, 599, 1001]):
273282
rng = period_range('1987', periods=nyears, freq='A')

pandas/tseries/tests/test_timeseries.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,24 @@ def test_timestamp_fields(self):
898898
self.assertEqual(idx.freq, Timestamp(idx[0], idx.freq).freq)
899899
self.assertEqual(idx.freqstr, Timestamp(idx[0], idx.freq).freqstr)
900900

901+
def test_timestamp_date_out_of_range(self):
902+
self.assertRaises(ValueError, Timestamp, '1676-01-01')
903+
self.assertRaises(ValueError, Timestamp, '2263-01-01')
904+
905+
# 1475
906+
self.assertRaises(ValueError, DatetimeIndex, ['1400-01-01'])
907+
self.assertRaises(ValueError, DatetimeIndex, [datetime(1400, 1, 1)])
908+
909+
def test_timestamp_repr(self):
910+
# pre-1900
911+
stamp = Timestamp('1850-01-01', tz='US/Eastern')
912+
repr(stamp)
913+
914+
iso8601 = '1850-01-01 01:23:45.012345'
915+
stamp = Timestamp(iso8601, tz='US/Eastern')
916+
result = repr(stamp)
917+
self.assert_(iso8601 in result)
918+
901919
def test_datetimeindex_integers_shift(self):
902920
rng = date_range('1/1/2000', periods=20)
903921

@@ -918,7 +936,6 @@ def test_astype_object(self):
918936

919937
self.assert_(np.array_equal(casted, exp_values))
920938

921-
922939
def test_catch_infinite_loop(self):
923940
offset = datetools.DateOffset(minute=5)
924941
# blow up, don't loop forever

0 commit comments

Comments
 (0)