Skip to content

Commit 6732306

Browse files
committed
BUG: Timestamp cannot parse nanosecond from string
1 parent db8533f commit 6732306

File tree

7 files changed

+282
-94
lines changed

7 files changed

+282
-94
lines changed

doc/source/v0.15.0.txt

+4-1
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ previously results in ``Exception`` or ``TypeError`` (:issue:`7812`)
166166
- ``DataFrame.tz_localize`` and ``DataFrame.tz_convert`` now accepts an optional ``level`` argument
167167
for localizing a specific level of a MultiIndex (:issue:`7846`)
168168

169+
- ``Timestamp.__repr__`` displays ``dateutil.tz.tzoffset`` info (:issue:`7907`)
170+
169171
.. _whatsnew_0150.dt:
170172

171173
.dt accessor
@@ -443,7 +445,8 @@ Bug Fixes
443445
- Bug in ``Series.str.cat`` with an index which was filtered as to not include the first item (:issue:`7857`)
444446

445447

446-
448+
- Bug in ``Timestamp`` cannot parse ``nanosecond`` from string (:issue:`7878`)
449+
- Bug in ``Timestamp`` with string offset and ``tz`` results incorrect (:issue:`7833`)
447450

448451
- Bug in ``tslib.tz_convert`` and ``tslib.tz_convert_single`` may return different results (:issue:`7798`)
449452
- Bug in ``DatetimeIndex.intersection`` of non-overlapping timestamps with tz raises ``IndexError`` (:issue:`7880`)

pandas/src/datetime.pxd

+9-6
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ cdef extern from "datetime/np_datetime_strings.h":
109109

110110
int parse_iso_8601_datetime(char *str, int len, PANDAS_DATETIMEUNIT unit,
111111
NPY_CASTING casting, pandas_datetimestruct *out,
112-
npy_bool *out_local, PANDAS_DATETIMEUNIT *out_bestunit,
112+
int *out_local, int *out_tzoffset,
113+
PANDAS_DATETIMEUNIT *out_bestunit,
113114
npy_bool *out_special)
114115

115116
int make_iso_8601_datetime(pandas_datetimestruct *dts, char *outstr, int outlen,
@@ -123,29 +124,31 @@ cdef extern from "datetime/np_datetime_strings.h":
123124

124125

125126

126-
cdef inline _string_to_dts(object val, pandas_datetimestruct* dts):
127+
cdef inline _string_to_dts(object val, pandas_datetimestruct* dts,
128+
int* out_local, int* out_tzoffset):
127129
cdef int result
128130
cdef char *tmp
129131

130132
if PyUnicode_Check(val):
131133
val = PyUnicode_AsASCIIString(val);
132134

133135
tmp = val
134-
result = _cstring_to_dts(tmp, len(val), dts)
136+
result = _cstring_to_dts(tmp, len(val), dts, out_local, out_tzoffset)
135137

136138
if result == -1:
137139
raise ValueError('Unable to parse %s' % str(val))
138140

139141
cdef inline int _cstring_to_dts(char *val, int length,
140-
pandas_datetimestruct* dts):
142+
pandas_datetimestruct* dts,
143+
int* out_local, int* out_tzoffset):
141144
cdef:
142-
npy_bool islocal, special
145+
npy_bool special
143146
PANDAS_DATETIMEUNIT out_bestunit
144147
int result
145148

146149
result = parse_iso_8601_datetime(val, length, PANDAS_FR_ns,
147150
NPY_UNSAFE_CASTING,
148-
dts, &islocal, &out_bestunit, &special)
151+
dts, out_local, out_tzoffset, &out_bestunit, &special)
149152
return result
150153

151154

pandas/src/datetime/np_datetime_strings.c

+10-19
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,9 @@ convert_datetimestruct_local_to_utc(pandas_datetimestruct *out_dts_utc,
363363
* to be cast to the 'unit' parameter.
364364
*
365365
* 'out' gets filled with the parsed date-time.
366-
* 'out_local' gets set to 1 if the parsed time was in local time,
366+
* 'out_local' gets whether returned value contains timezone. 0 for UTC, 1 for local time.
367+
* 'out_tzoffset' gets set to timezone offset by minutes
368+
* if the parsed time was in local time,
367369
* to 0 otherwise. The values 'now' and 'today' don't get counted
368370
* as local, and neither do UTC +/-#### timezone offsets, because
369371
* they aren't using the computer's local timezone offset.
@@ -381,7 +383,8 @@ parse_iso_8601_datetime(char *str, int len,
381383
PANDAS_DATETIMEUNIT unit,
382384
NPY_CASTING casting,
383385
pandas_datetimestruct *out,
384-
npy_bool *out_local,
386+
int *out_local,
387+
int *out_tzoffset,
385388
PANDAS_DATETIMEUNIT *out_bestunit,
386389
npy_bool *out_special)
387390
{
@@ -778,19 +781,6 @@ parse_iso_8601_datetime(char *str, int len,
778781
if (sublen == 0) {
779782
// Unlike NumPy, treating no time zone as naive
780783
goto finish;
781-
782-
/*
783-
if (convert_datetimestruct_local_to_utc(out, out) < 0) {
784-
goto error;
785-
}
786-
787-
// Since neither "Z" nor a time-zone was specified, it's local
788-
if (out_local != NULL) {
789-
*out_local = 1;
790-
}
791-
792-
goto finish;
793-
*/
794784
}
795785

796786
/* UTC specifier */
@@ -816,9 +806,6 @@ parse_iso_8601_datetime(char *str, int len,
816806
* Since "local" means local with respect to the current
817807
* machine, we say this is non-local.
818808
*/
819-
if (out_local != NULL) {
820-
*out_local = 0;
821-
}
822809

823810
if (*substr == '-') {
824811
offset_neg = 1;
@@ -872,7 +859,11 @@ parse_iso_8601_datetime(char *str, int len,
872859
offset_hour = -offset_hour;
873860
offset_minute = -offset_minute;
874861
}
875-
add_minutes_to_datetimestruct(out, -60 * offset_hour - offset_minute);
862+
if (out_local != NULL) {
863+
*out_local = 1;
864+
// Unlike NumPy, do not change internal value to local time
865+
*out_tzoffset = 60 * offset_hour - offset_minute;
866+
}
876867
}
877868

878869
/* Skip trailing whitespace */

pandas/src/datetime/np_datetime_strings.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
* to be cast to the 'unit' parameter.
2828
*
2929
* 'out' gets filled with the parsed date-time.
30-
* 'out_local' gets set to 1 if the parsed time was in local time,
30+
* 'out_local' gets whether returned value contains timezone. 0 for UTC, 1 for local time.
31+
* 'out_tzoffset' gets set to timezone offset by minutes
32+
* if the parsed time was in local time,
3133
* to 0 otherwise. The values 'now' and 'today' don't get counted
3234
* as local, and neither do UTC +/-#### timezone offsets, because
3335
* they aren't using the computer's local timezone offset.
@@ -45,7 +47,8 @@ parse_iso_8601_datetime(char *str, int len,
4547
PANDAS_DATETIMEUNIT unit,
4648
NPY_CASTING casting,
4749
pandas_datetimestruct *out,
48-
npy_bool *out_local,
50+
int *out_local,
51+
int *out_tzoffset,
4952
PANDAS_DATETIMEUNIT *out_bestunit,
5053
npy_bool *out_special);
5154

pandas/tseries/tests/test_timeseries.py

+23-2
Original file line numberDiff line numberDiff line change
@@ -2173,10 +2173,31 @@ def test_constructor_coverage(self):
21732173
def test_constructor_datetime64_tzformat(self):
21742174
# GH 6572
21752175
tm._skip_if_no_pytz()
2176+
import pytz
2177+
# ISO 8601 format results in pytz.FixedOffset
2178+
for freq in ['AS', 'W-SUN']:
2179+
idx = date_range('2013-01-01T00:00:00-05:00', '2016-01-01T23:59:59-05:00', freq=freq)
2180+
expected = date_range('2013-01-01T00:00:00', '2016-01-01T23:59:59',
2181+
freq=freq, tz=pytz.FixedOffset(-300))
2182+
tm.assert_index_equal(idx, expected)
2183+
# Unable to use `US/Eastern` because of DST
2184+
expected_i8 = date_range('2013-01-01T00:00:00', '2016-01-01T23:59:59',
2185+
freq=freq, tz='America/Lima')
2186+
self.assert_numpy_array_equal(idx.asi8, expected_i8.asi8)
2187+
2188+
idx = date_range('2013-01-01T00:00:00+09:00', '2016-01-01T23:59:59+09:00', freq=freq)
2189+
expected = date_range('2013-01-01T00:00:00', '2016-01-01T23:59:59',
2190+
freq=freq, tz=pytz.FixedOffset(540))
2191+
tm.assert_index_equal(idx, expected)
2192+
expected_i8 = date_range('2013-01-01T00:00:00', '2016-01-01T23:59:59',
2193+
freq=freq, tz='Asia/Tokyo')
2194+
self.assert_numpy_array_equal(idx.asi8, expected_i8.asi8)
2195+
21762196
tm._skip_if_no_dateutil()
21772197
from dateutil.tz import tzoffset
2198+
# Non ISO 8601 format results in dateutil.tz.tzoffset
21782199
for freq in ['AS', 'W-SUN']:
2179-
idx = date_range('2013-01-01T00:00:00-05:00', '2016-01-01T23:59:59-05:00', freq=freq)
2200+
idx = date_range('2013/1/1 0:00:00-5:00', '2016/1/1 23:59:59-5:00', freq=freq)
21802201
expected = date_range('2013-01-01T00:00:00', '2016-01-01T23:59:59',
21812202
freq=freq, tz=tzoffset(None, -18000))
21822203
tm.assert_index_equal(idx, expected)
@@ -2185,7 +2206,7 @@ def test_constructor_datetime64_tzformat(self):
21852206
freq=freq, tz='America/Lima')
21862207
self.assert_numpy_array_equal(idx.asi8, expected_i8.asi8)
21872208

2188-
idx = date_range('2013-01-01T00:00:00+09:00', '2016-01-01T23:59:59+09:00', freq=freq)
2209+
idx = date_range('2013/1/1 0:00:00+9:00', '2016/1/1 23:59:59+09:00', freq=freq)
21892210
expected = date_range('2013-01-01T00:00:00', '2016-01-01T23:59:59',
21902211
freq=freq, tz=tzoffset(None, 32400))
21912212
tm.assert_index_equal(idx, expected)

0 commit comments

Comments
 (0)