Repository: pandas-dev/pandas

Issue:
Timestamp should have alternate constructor for UTC millisecond timestamps
Was surprised at how difficult it was to convert a unix timestamp to a pandas Timestamp recently



Discussion:
I think an API like:

```
pd.to_datetime(values, unit='s')
Timestamp(value,unit='s')
```

might be all that we need, in that the unit is a hint that I am giving  you something that this is in these units

e.g. right now the default is (so no conversion from int needed)

```
In [9]: Timestamp(1368431149000000000, unit='ns')
Out[9]: <Timestamp: 2013-05-13 07:45:49>
```

http://stackoverflow.com/questions/16517240/pandas-using-unix-epoch-timestamp-as-datetime-index

```
In [3]: np.array([1368431149, 1368431150]).astype('datetime64[s]')
Out[3]: array([2013-05-13 07:45:49, 2013-05-13 07:45:50], dtype=datetime64[s])

In [4]: pd.to_datetime(np.array([1368431149, 1368431150])*int(1e6))
Out[4]: 
<class 'pandas.tseries.index.DatetimeIndex'>
[1970-01-16 20:07:11.149000, 1970-01-16 20:07:11.150000]
Length: 2, Freq: None, Timezone: None

In [5]: pd.to_datetime(np.array([1368431149, 1368431150])*int(1e9))
Out[5]: 
<class 'pandas.tseries.index.DatetimeIndex'>
[2013-05-13 07:45:49, 2013-05-13 07:45:50]
Length: 2, Freq: None, Timezone: None

```

@hayd what do u think about this API?

I think `unit` is descriptive, I like it.


Resolving patch:
diff --git a/pandas/tseries/index.py b/pandas/tseries/index.py
--- a/pandas/tseries/index.py
+++ b/pandas/tseries/index.py
@@ -1204,6 +1204,9 @@ def slice_indexer(self, start=None, end=None, step=None):
         if isinstance(start, time) or isinstance(end, time):
             raise KeyError('Cannot mix time and non-time slice keys')
 
+        if isinstance(start, float) or isinstance(end, float):
+            raise TypeError('Cannot index datetime64 with float keys')
+
         return Index.slice_indexer(self, start, end, step)
 
     def slice_locs(self, start=None, end=None):
diff --git a/pandas/tseries/tools.py b/pandas/tseries/tools.py
--- a/pandas/tseries/tools.py
+++ b/pandas/tseries/tools.py
@@ -50,7 +50,7 @@ def _maybe_get_tz(tz):
 
 
 def to_datetime(arg, errors='ignore', dayfirst=False, utc=None, box=True,
-                format=None, coerce=False):
+                format=None, coerce=False, unit='ns'):
     """
     Convert argument to datetime
 
@@ -69,6 +69,8 @@ def to_datetime(arg, errors='ignore', dayfirst=False, utc=None, box=True,
     format : string, default None
         strftime to parse time, eg "%d/%m/%Y"
     coerce : force errors to NaT (False by default)
+    unit : unit of the arg (s,ms,us,ns) denote the unit in epoch
+        (e.g. a unix timestamp), which is an integer/float number
 
     Returns
     -------
@@ -86,7 +88,7 @@ def _convert_f(arg):
             else:
                 result = tslib.array_to_datetime(arg, raise_=errors == 'raise',
                                                  utc=utc, dayfirst=dayfirst,
-                                                 coerce=coerce)
+                                                 coerce=coerce, unit=unit)
             if com.is_datetime64_dtype(result) and box:
                 result = DatetimeIndex(result, tz='utc' if utc else None)
             return result


Test patch:
diff --git a/pandas/tseries/tests/test_timeseries.py b/pandas/tseries/tests/test_timeseries.py
--- a/pandas/tseries/tests/test_timeseries.py
+++ b/pandas/tseries/tests/test_timeseries.py
@@ -38,6 +38,7 @@
 import pandas.util.py3compat as py3compat
 from pandas.core.datetools import BDay
 import pandas.core.common as com
+from pandas import concat
 
 from numpy.testing.decorators import slow
 
@@ -171,7 +172,6 @@ def test_indexing_over_size_cutoff(self):
     def test_indexing_unordered(self):
 
         # GH 2437
-        from pandas import concat
         rng = date_range(start='2011-01-01', end='2011-01-15')
         ts  = Series(randn(len(rng)), index=rng)
         ts2 = concat([ts[0:4],ts[-4:],ts[4:-4]])
@@ -593,6 +593,34 @@ def test_frame_add_datetime64_col_other_units(self):
 
             self.assert_((tmp['dates'].values == ex_vals).all())
 
+    def test_to_datetime_unit(self):
+
+        epoch = 1370745748
+        s = Series([ epoch + t for t in range(20) ])
+        result = to_datetime(s,unit='s')
+        expected = Series([ Timestamp('2013-06-09 02:42:28') + timedelta(seconds=t) for t in range(20) ])
+        assert_series_equal(result,expected)
+
+        s = Series([ epoch + t for t in range(20) ]).astype(float)
+        result = to_datetime(s,unit='s')
+        expected = Series([ Timestamp('2013-06-09 02:42:28') + timedelta(seconds=t) for t in range(20) ])
+        assert_series_equal(result,expected)
+
+        s = Series([ epoch + t for t in range(20) ] + [iNaT])
+        result = to_datetime(s,unit='s')
+        expected = Series([ Timestamp('2013-06-09 02:42:28') + timedelta(seconds=t) for t in range(20) ] + [NaT])
+        assert_series_equal(result,expected)
+
+        s = Series([ epoch + t for t in range(20) ] + [iNaT]).astype(float)
+        result = to_datetime(s,unit='s')
+        expected = Series([ Timestamp('2013-06-09 02:42:28') + timedelta(seconds=t) for t in range(20) ] + [NaT])
+        assert_series_equal(result,expected)
+
+        s = concat([Series([ epoch + t for t in range(20) ]).astype(float),Series([np.nan])],ignore_index=True)
+        result = to_datetime(s,unit='s')
+        expected = Series([ Timestamp('2013-06-09 02:42:28') + timedelta(seconds=t) for t in range(20) ] + [NaT])
+        assert_series_equal(result,expected)
+
     def test_series_ctor_datetime64(self):
         rng = date_range('1/1/2000 00:00:00', '1/1/2000 1:59:50',
                          freq='10s')
@@ -2691,6 +2719,61 @@ def test_basics_nanos(self):
         self.assert_(stamp.microsecond == 0)
         self.assert_(stamp.nanosecond == 500)
 
+    def test_unit(self):
+        def check(val,unit=None,s=1,us=0):
+            stamp = Timestamp(val, unit=unit)
+            self.assert_(stamp.year == 2000)
+            self.assert_(stamp.month == 1)
+            self.assert_(stamp.day == 1)
+            self.assert_(stamp.hour == 1)
+            self.assert_(stamp.minute == 1)
+            self.assert_(stamp.second == s)
+            self.assert_(stamp.microsecond == us)
+            self.assert_(stamp.nanosecond == 0)
+
+        val = Timestamp('20000101 01:01:01').value
+
+        check(val)
+        check(val/1000L,unit='us')
+        check(val/1000000L,unit='ms')
+        check(val/1000000000L,unit='s')
+
+        # using truediv, so these are like floats
+        if py3compat.PY3:
+            check((val+500000)/1000000000L,unit='s',us=500)
+            check((val+500000000)/1000000000L,unit='s',us=500000)
+            check((val+500000)/1000000L,unit='ms',us=500)
+
+        # get chopped in py2
+        else:
+            check((val+500000)/1000000000L,unit='s')
+            check((val+500000000)/1000000000L,unit='s')
+            check((val+500000)/1000000L,unit='ms')
+
+        # ok
+        check((val+500000)/1000L,unit='us',us=500)
+        check((val+500000000)/1000000L,unit='ms',us=500000)
+
+        # floats
+        check(val/1000.0 + 5,unit='us',us=5)
+        check(val/1000.0 + 5000,unit='us',us=5000)
+        check(val/1000000.0 + 0.5,unit='ms',us=500)
+        check(val/1000000.0 + 0.005,unit='ms',us=5)
+        check(val/1000000000.0 + 0.5,unit='s',us=500000)
+
+        # nan
+        result = Timestamp(np.nan)
+        self.assert_(result is NaT)
+        
+        result = Timestamp(None)
+        self.assert_(result is NaT)
+        
+        result = Timestamp(iNaT)
+        self.assert_(result is NaT)
+
+        result = Timestamp(NaT)
+        self.assert_(result is NaT)
+
     def test_comparison(self):
         # 5-18-2012 00:00:00.000
         stamp = 1337299200000000000L