Python时间戳、时间类型转换、日期时间子特征的提取及合并

本文详细介绍了Python中时间、时间戳的概念,包括Unix时间戳的原理和局限,如2038年问题。同时,文章讨论了Python中时间类型的相互转换,如时间戳转datetime、时间转时间戳等,还提到了pandas的pd.to_datetime方法在时间处理中的重要作用。此外,文章还讲解了日期时间子特征的提取与合并,并提供了课后练习题,帮助读者深入理解Python时间处理的相关知识。

一、时间、时间戳

时间,是物质的运动、变化的持续性、顺序性的表现,包含时刻和时段两个概念。时间是人类用以描述物质运动过程或事件发生过程的一个参数,确定时间,是靠不受外界影响的物质周期变化的规律。
时间戳(timestamp),一个能表示一份数据在某个特定时间之前已经存在的、 完整的、 可验证的数据,通常是一个字符序列,唯一地标识某一刻的时间。

Unix时间戳(Unix timestamp),或称Unix时间(Unix time)、POSIX时间(POSIX time),是一种时间表示方式,定义为从格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总毫秒数(在python中默认计算总秒数)。

由于时区的存在,世界上的时间并不完全统一。北京时间比格林威治时间提前8个小时,比美国东部时间提前13个小时。

倒时差:一个人晚上9点从国内坐飞机到伦敦,假设飞了8个小时,他在飞机上睡了8个小时。可是当他下飞机后,他会发现机场大厅的时钟还是晚上9点。由于他刚刚睡了8个小时,所以他到酒店后晚上失眠了,第二天白天了,他开始睡了,从而生物钟被打乱,可能几天缓不过劲来。

时间戳的不一致性:数据中的日期字段date中如果纪录的是一个整数或者浮点数,数据类型极有可能是时间戳,由于存储数据的计算机可能和分析数据的计算机位于不同时区,所以面对同一个时间戳,极有可能存在理解差异,需要分析该时间戳是相对于哪个时刻的时间戳。

2038年问题是指在使用POSIX时间的32位计算机应用程序上,格林尼治时间2038年1月19日凌晨03:14:07(北京时间:2038年1月19日中午11:14:07)之后无法正常工作。
计算机通常用一个10位的整数来描述时间戳对应的总秒数。10位的十进制数最大数为9999999999,一年(365天)的总秒数为31536000,所以9999999999/31536000=317.0979198059361,由于有闰年,时间戳最多可以表示到1970年后的316年左右,即到2286/11/21 01:46:40 会变成11位(10000000000)。但在32位计算机中,通常用一个32位的有符号整数来存储这个时间戳,这样时间戳的二进制数最大就是(01111111 11111111 11111111 11111111)。其后一秒,二进制数字会变为10000000 00000000 00000000 00000000,发生溢出错误,造成系统将时间误解为1901年12月13日20时45分52秒。这很可能会引起软件故障,甚至是系统瘫痪。使用64位二进制数字表示时间的系统(最多可以使用到格林威治时间292,277,026,596年12月04日15时30分08秒)则基本不会遇到这类溢出问题。
现在用Python来验证一下:

from datetime import datetime
int('01111111111111111111111111111111', 2) #将二进制值转换为十进制,输出2147483647
datetime.utcfromtimestamp(2147483647)  #datetime.datetime(2038, 1, 19, 3, 14, 7)

可以看到,受计算机存储能力限制,32位有符号二进制整数对应的十进制数最大不是9999999999,而是2147483647,对应时刻为datetime(2038, 1, 19, 3, 14, 7)

二、Python世界中的时间
对于一个时间(时刻),可以有两种视角,一种是计算其距离某一时刻,比如1970年1月1日零时零分经过的秒数总和,即格林威治时间时间戳。另个一种是比较直观的datetime形式(年,月,日,时,分,秒,微秒,时区)。python提供了一个函数:
datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]]),
此函数可以创建datetime对象,它同时包含date和time对象的所有特性。
其中microsecond为6位数字表示的微秒值,tzinfo参数代表时区。可表示的时间最大值和最小值如下:
max = datetime(9999, 12, 31, 23, 59, 59, 999999)
min = datetime(1, 1, 1, 0, 0)
通常情况下,我们只关注自己的时间即可,比如你手机或手表上的时间,这个时间称之为localtime,比如北京时间早八点整,你不用操心英国、美国现在是几点,他们是睡觉了还是吃饭了。

import pandas as pd
import time
from datetime import datetime
time.time()  #获得localtime的时间戳
datetime.now()  #datetime.now() 获得当前的localtime,注意不是格林威治时间

pandas中的Timestamp类型与python的datetime类型基本等价:

pd.Timestamp.now()

创建任意时间点:

a = pd.Timestamp('2018-03-16 21:01:34')
b = pd.datetime(2018,3,16,21,1,34)
c = datetime(2018,3,16,21,1,34)
print(a == b == c)  #True
print(type(b))  #<class 'datetime.datetime'>

三、时间类型的相互转换

1. 时间戳转datetime
常用场景:分析时需要将数据date字段存储的数字转为直观形式

datetime.fromtimestamp(1580572800.0) #输出:datetime.datetime(2020, 2, 2, 0, 0)
datetime.utcfromtimestamp(1580572800.0) #输出: datetime.datetime(2020, 2, 1, 16, 0)

可以发现同一个时间戳数据1580572800.0,fromtimestamp是在localtime背景下,将1970年01月01日08时00分00秒视为起点,将1580572800.0视为总秒数,算出来是2020, 2, 2, 0, 0。utcfromtimestamp是将1970年01月01日00时00分00秒视为起点,所以转换后比上一个时间晚8个小时。

datetime.fromtimestamp(9999999999) #datetime.datetime(2286, 11, 21, 1, 46, 39)

扩展知识:time.ctime函数可以把一个时间戳(按秒计算的浮点数)转化为time.asctime()形式的str。

time.ctime(1580572800.0)  #输出'Sun Feb  2 00:00:00 2020'

2. 时间转时间戳
常用场景:需要将一个时间存储为时间戳,然后存储在数据表中。
(1)获得localtime的时间戳,最简单方法:time.time()
(2)把一个任意的datetime时间转时间戳float:
具体过程:datetime–time.struct_time–float
time.mktime方法将struct_time时间转为时间戳,需要将datetime时间先通过timetuple()函数转为struct_time类型

s = time.mktime(datetime(2020,2,2).timetuple())
print(s)

问题:Python为什么需要struct_time类型做转换中介?
datetime时间虽然直观,但不能subscriptable(通过index或下标访问),并且datetime object is not iterable。所以当要取一个时刻的年、月、日等子特征时就比较麻烦。如果将其转换为time.struct_time类型后,就可以很方便根据索引或下标形式访问时间子特征。

datetime.now().timetuple().tm_year  #当前时刻所在年
datetime.now().timetuple()[1]  #当前时刻所在月份

struct_time包括的9个属性:
tm_year 年
tm_mon 月,范围1~12
tm_mday 日,范围1~31
tm_hour 小时,范围0~23
tm_min 分钟,范围0~59
tm_sec 秒,范围0~61(60或61是闰秒)
tm_wday 星期,范围0~6(周一为0)
tm_yday 一年内第几天,范围0~366
tm_isdst 夏时令,-1,0,1

mktime的逆函数为localtime和gmtime,既然是逆方法,所以返回值为struct_time类型。

s = time.mktime(datetime(2020,2,2).timetuple()) #获取时间戳
time.localtime(s) #时间戳转local时间
time.gmtime(s) #时间戳转格林威治时间
time.gmtime(s)[0] #获得tm_year子特征

3.time_struct转datetime
一种方法是先通过time.mktime转为时间戳,然后通过datetime.fromtimestamp转为datetime。更简便的方法如下:

structTime = time.localtime()  #time.localtime()返回time_struct格式的当前时间
datetime(*structTime[:6])

time_struct类似于tuple,tuple是可以切片的,structTime[:6]的type是tuple。星号出现在函数的参数structTime[:6]前面,代表解包。即在列表、元组、集合、字典及其他可迭代对象作为实参,并在前面加*时,系统自动进行解包然后传递给函数的多个单变量参数。

而如果:

datetime(*structTime)

会提示:TypeError: function takes at most 8 positional arguments (9 given),思考一下为什么会出现异常?

4. pandas的pd.to_datetime方法【重点】
(1)函数定义
pandas.to_datetime(arg, errors=‘ignore’, dayfirst=False, utc=None, box=True, format=None, exact=True, coerce=False, unit=‘ns’, infer_datetime_format=False)
该函数常用于原始数据中date字段的转换。函数的返回值依据于输入数据的类型,输入输出对应关系如下:
list-like: DatetimeIndex
Series: Series of datetime64 dtype
scalar: Timestamp

(2)转换实例
pandas读取源数据时,比如csv文件,有可能将日期时间特征在定义为object类型。
举例1:

s = pd.Series('01/02/1965',dtype='object')
d = pd.to_datetime(s,format='%m/%d/%Y')
print(d)

运行结果:
0 1965-01-02
dtype: datetime64[ns]
这个例子中,format=’%m/%d/%Y’参数不写也可。

s = pd.Series('01/02/1965',dtype='object')
d = pd.to_datetime(s)
print(d)

这是因为pandas能猜测出来其为1965年1月2日,但如果日期时间的格式不太符合常理或pandas猜不准情况下就会产生异常。这时需要程序员指定日期时间的格式。

举例2:

data = pd.DataFrame({'date':['02/Apr/2013:23:55:00 +0530']})
pd.to_datetime(data['date'],format='%d/%b/%Y:%H:%M:%S %z')

源数据中的时间’02/Apr/2013:23:55:00 +0530’,如果直接不指定format参数,pandas将无法进行转换为时间。其中的+0530为时区信息,表示相对格林威治时间提前5小时30分钟,即19800秒。

如果提示错误:‘z’ is a bad directive in format ‘%d/%b/%Y:%H:%M:%S %z’,请升级pandas版本至0.24.2及以上版本,对应命令为:‘conda update pandas’。

(3)两个坑:
坑1:原数据如果是时间戳,注意参数unit默认为ns,需要根据情况判断是否将其改为s。
坑2:转换后的时间极有可能不包含时区属性,如果原数据集是我们国内的数据集,分析时直接用pd.to_datetime转换后,这个时间可能要比我们自认为的时间要晚8个小时。所以用pd.DataFrame转换时间戳时,除非这是一份来自英国这种使用格林威治时间国家的数据,一般都需要用Timestamp.tz_localize方法为没有时区的时间序列赋予时区,然后调用tz_convert函数将其转换到原数据集对应的时区。

t = pd.to_datetime(1580572800.0,unit='s') #Timestamp('2020-02-01 16:00:00')
t = t.tz_localize('UTC') #为没有时区的时间序列赋予时区,UTC代表协调世界时,又称为世界统一时间
t.tz_convert('Asia/Shanghai') #指定到特定时区后,可以用tz_convert将其转换到其它时区
#上述三行代码可简化为:
t = pd.to_datetime(1580572800.0,unit='s',utc=True) #utc默认值为None
t.tz_convert('Asia/Shanghai')  #转换为北京时间

其它时区,可import pytz后,通过pytz.common_timezones来查看

import pytz
pytz.common_timezones[-6:]

之前的datetime.now(),默认创建localtime时间,如果要创建格林威治时间,可以如下操作:

datetime.now(pytz.utc) #输出:datetime.datetime(2020, 2, 9, 14, 22, 25, 565437, tzinfo=<UTC>) 参数写为pytz.UTC也可。

(4)扩展阅读:
协调世界时(英:Coordinated Universal Time ,法:Temps Universel Coordonné),又称世界统一时间,世界标准时间,国际协调时间。英文(CUT)和法文(TUC)的缩写不同,作为妥协,简称UTC。
世界标准时间UTC:GMT+0
GMT:格林尼治平时
即格林威治时间
CST时间:也就是北京时间
北京时间CST:GMT+8

5. 将pandas中的 Timestamp转换为datetime
Timestamp是从Python标准库的datetime类继承过来的,表示时间轴上的一个时刻。分辨率为纳秒,64位整数存储,其最小时刻和最大时刻如下:

pd.Timestamp.min
#Timestamp('1677-09-21 00:12:43.145225')
pd.Timestamp.max
#Timestamp('2262-04-11 23:47:16.854775807')

所以如下代码会报错:

pd.to_datetime('13000101', format='%Y%m%d')

解决方案为:

pd.to_datetime('13000101', format='%Y%m%d', errors='ignore')
#datetime.datetime(1300, 1, 1, 0, 0)

或:

pd.to_datetime('13000101', format='%Y%m%d', errors='coerce')
#NaT

pd.to_pydatetime方法可以Convert a Timestamp object to a native Python datetime object.

 pd.Timestamp.to_pydatetime(pd.to_datetime('2020/2/3'))

6.字符串到时间类型的转换
(1)字符串到struct_time
调用方法: time.strptime(string[, format])
python中日期时间格式化符号:
%y 两位数的年份表示(00-99)
%Y 四位数的年份表示(000-9999)
%m 月份(01-12)
%d 月内中的一天(0-31)
%H 24小时制小时数(0-23)
%I 12小时制小时数(01-12)
%M 分钟数(00=59)
%S 秒(00-59)
%a 本地简化星期名称
%A 本地完整星期名称
%b 本地简化的月份名称
%B 本地完整的月份名称
%c 本地相应的日期表示和时间表示
%j 年内的一天(001-366)
%p 本地A.M.或P.M.的等价符
%U 一年中的星期数(00-53)星期天为星期的开始
%w 星期(0-6),星期天为星期的开始
%W 一年中的星期数(00-53)星期一为星期的开始
%x 本地相应的日期表示
%X 本地相应的时间表示
%Z 当前时区的名称
%% %号本身

time.strptime('2020-02-05 13:45:20','%Y-%m-%d %H:%M:%S')

结果为:
time.struct_time(tm_year=2020, tm_mon=2, tm_mday=5, tm_hour=13, tm_min=45, tm_sec=20, tm_wday=2, tm_yday=36, tm_isdst=-1)

(2)struct_time到字符串

t = time.strptime('2020-02-05 13:45:20','%Y-%m-%d %H:%M:%S')
s = time.strftime('%Y-%m-%d',t)
print(s)  #输出2020-02-05

各种时间类型转换示意图如下图所示。
在这里插入图片描述
上图中,其实datetime和字符串之间其实还可以画出来两条线,datetime.strptime和datetime.strftime,功能自行百度。

四、日期时间子特征的提取及合并

(1)日期时间子特征的提取
方法1:用pd.DatetimeIndex将时间分解为子特征的索引
数据集如下:

days = pd.DataFrame([pd.datetime(2020,2,1),datetime.fromtimestamp(1580572800.0),pd.to_datetime('2020/2/3')])
print(days)

在这里插入图片描述
现需要将时间特征分解为年、月、日三个子特征,pandas提供了pd.DatetimeIndex函数。

timeIndex = pd.DatetimeIndex(days.iloc[:,0]) #参数指定为第0列
print(timeIndex.year) #Int64Index([2020, 2020, 2020], dtype='int64', name=0)
print(timeIndex.month) #Int64Index([2, 2, 2], dtype='int64', name=0)
print(timeIndex.day) #Int64Index([1, 2, 3], dtype='int64', name=0)
days['year'] = timeIndex.year
days['month'] = timeIndex.month
days['day'] = timeIndex.day
days.drop([0], axis=1, inplace=True)
 #days.drop(columns = 0, axis=1, inplace=True)
print(days)

运行结果:
在这里插入图片描述
2.方法2:通过Series.dt
如果不用DatetimeIndex方法,通过访问对象pandas.Series.dt也可完成相应操作。
dt的作用是:Accessor object for datetimelike properties of the Series values.
其 Returns a Series indexed like the original Series. Raises TypeError if the Series does not contain datetimelike values.
翻译过来的意思就是一个Series,如果它看上去像是日期时间字段,则可通过访问对象dt的子属性来获取时间的子特征序列,如果这个Series不包含看上去是日期时间的值,则抛出异常。

days = pd.DataFrame([pd.datetime(2020,2,1),datetime.fromtimestamp(1580572800.0),pd.to_datetime('2020/2/3')])
print(days)
days['year'] = days[0].dt.year
days['month'] = days[0].dt.month
days['day'] = days[0].dt.day
days.drop([0], axis=1, inplace=True)
print(days)

(2)时间子特征的合并
将多个时间子特征的Series合并为一个Series,然后创建或合并到新的DateFrame即可。

days = pd.DataFrame(pd.to_datetime(days),columns=['date'])

运行结果:
在这里插入图片描述

五、课后练习题

  1. 试解释 pd.to_datetime(1580572800)的输出结果为什么是:Timestamp(‘1970-01-01 00:00:01.580572800’)
    答:pd.to_datetime方法在将时间戳转换为Timestamp类型时,参数unit默认为’ns’,即其将1580572800视为纳秒,而不是秒或微秒,1秒=1000000000纳秒,所以1580572800对应于1秒580572800纳秒,对应于1970-01-01 00:00:01.580572800。

  2. time.mktime(datetime(1970,1,1).timetuple()) 为什么会产生异常:OverflowError: mktime argument out of range
    答:首先要明确1970,1,1是北京时间还是格林威治时间,datetime.now()或者datetime(具体时间) 表示的是localtime,如北京时间。而北京时间因为比格林威治时间早8个小时,所以时间戳表示的北京时间最早是1970年01月01日08时00分00秒。time.mktime(datetime(1970,1,1,8,0,0).timetuple()) 结果为0.0。
    直接用time.mktime(datetime(1970,1,1).timetuple()) 求的是北京时间1970年1月1日零时零分对应的时间戳,这个值是负数,所以会产生OverflowError异常。

  3. 一个人在伦敦,和一个人在中国同时用Python语言书写如下语句:
    time.time(),请问运行结果相差多少?
    答:时间戳定义为从格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。试想,两个人,一个人北京时间1970年01月01日08时00分00秒,另外一个人在伦敦,他的时间是1970年01月01日00时00分00秒,如果此时累计时间戳就像两个人分别在数数,每秒加1,这两个时间戳应该是近乎一样的。

  4. 一份来自美国东部华盛顿某银行的数据(美国东部时区比北京时间晚13个小时),某条记录的date字段的值为1581261699.8974593,假设是通过Python程序的time.time()函数产生该值。假设我们现在获得了这份数据,并且需要对该数据进行分析与处理,需要把时间戳转换为具体的美国时间,应该如何进行转换?
    答:如果我们直接通过 pd.to_datetime(1581261699.8974593,unit=‘s’)转换2020-02-09 15:21:39.897459269,显然这不是时间真实值。所以可以如下操作:

t = pd.to_datetime(1581261699.8974593,unit='s',utc=True)
t.tz_convert('US/Eastern')

结果为:Timestamp(‘2020-02-09 10:21:39.897459269-0500’, tz=‘US/Eastern’)

分析运行结果:pd.to_datetime() 把时间戳转换为时间默认不带时区,即对美国人来说,这个转换后的时间15:21:39,要比美国东部时间提前5个小时。所以需要再通过.tz_convert(‘US/Eastern’)将其转换为美国东部时间,即往后调5个小时,所以应该是10:21:39。
换一种形式来验证一下刚才的判断:

import pytz
ts = 1581261699.8974593
tz = pytz.timezone('America/New_York')
dt = pytz.datetime.datetime.fromtimestamp(ts, tz)
dt.strftime('%Y-%m-%d %H:%M:%S')

运行结果为:‘2020-02-09 10:21:39’

5.将时间戳转换为时间时,datetime.fromtimestamp()和pd.to_datetime()的区别
答:
(1)参数要求不同, datetime.fromtimestamp()的参数直接传递一个时间戳即可;pd.to_datetime()除了传时间戳外,通常必须指定参数 unit=‘s’,指定时间戳以秒为单位。
(2)作用范围不同,datetime.fromtimestamp()用途单一,只能用于时间戳到datetime类型的转换;pd.to_datetime()可以将任何形式的原始数据(比如object、字符串、整数、浮点数等)转换为时间。所以在数据分析与挖掘时,更多会用到pd.to_datetime()来转换时间。
(3)默认时区不同,datetime.fromtimestamp()默认时区为localtime所在的时区;而pd.to_datetime()默认时区为None,可以指定utc=True参数,再使用tz_convert函数进行时区转换。
(4)处理数据的类型不同,datetime.fromtimestamp()针对标量,pd.to_datetime()往往针对向量,比如pd.Series对象进行处理。datetime.fromtimestamp()如果直接传入Series对象,会产生异常:
TypeError: cannot convert the series to <class ‘int’>。
(5)再来一组选择题
1)下列哪个函数不能将时间戳转换为其它时间类型( )
A. datetime.fromtimestamp()
B. time.gtime()
C. time.localtime()
D. time.strftime()
2)下列哪个参数不是pandas.to_datetime()的参数( )
A. format
B. utc
C. inplace
D. unit
3) 下列哪个函数不能将时间类型转换为字符串( )
A. datetime.strftime()
B. time.strptime()
C. time.ctime()
D. time.asctime()
4) 一个struct_time类型的变量,不包含以下哪个信息( )
A. 年
B. 一年内的第几天
C. 毫秒
D. 星期
5) 在日期时间格式化符号集中,哪一个符号代表四位数的年份(000-9999)( )
A. %Y
B. %y
C. %yyyy
D.%Year
6) data = pd.DataFrame({‘date’:[‘05/Jun/2020:15:22:13 +0200’]}),现在需要将data[‘date’]字段的类型通过pd.to_datetime函数改为日期时间类型,下列哪种方法是正确的( )
A. pd.to_datetime(data[‘date’],format=’%D/%b/%Y:%H:%M:%S %z’)
B. pd.to_datetime(data[‘date’],format=’%d/%b/%y:%h:%m:%s %z’)
C. pd.to_datetime(data[‘date’],format=’%D/%A/%Y:%H:%M:%S %z’)
D. pd.to_datetime(data[‘date’],format=’%d/%b/%Y:%H:%M:%S %z’)

参考答案 D C B C A D

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值