ca88yzc 140

日子体系ca88yzc,大概是最好用的iOS日期工具库

Posted by

ca88yzc 1

ca88yzc 2DateTools

本章节以及后续章节的源码,当然也可以从我的github下载,在源码中我自己加了一些中文注释。

项目简介

DateTools 用于提高Objective-C中日期和时间相关操作的效率.灵感来源于
DateTime.aspx)和Time
Period
Library.

  • 项目主页: DateTools
  • 最新示例:
    点击下载
  • 工程简议: 支持国际化,支持中文输出,真的很贴心!

准备:

DateTools_下载

ca88yzc 3DateTools

DateTools是Objective-C中简化日期和时间处理的工具.

CocoaPodspod 'DateTools'

手动安装DataTools所需的所有类包含在DataTools文件夹下,如下:

  • DateTools.h
  • NSDate+DateTools.{h,m}
  • DTConstants.h
  • DTError.{h,m}
  • DTTimePeriod.{h,m}
  • DTTimePeriodGroup.{h,m}
  • DTTimePeriodCollection.{h,m}
  • DTTimePeriodChain.{h,m}

如果你的工程想要支持国际化或者使用”TimeAgo”功能必须要导入bundle文件你可以在工程中Info下添加Localizations来添加支持的语言

  • DateTools.bundle

    ca88yzc 4

DateTools.h
包含了所有其他文件的头文件.在你的工程中导入DateTools.h头文件即可

在多个时间点观察或测量到的任何事物都可以形成一段时间序列。

安装

DateTools中包含3个子库
  • NSDate+DateTools
  • Time Periods
  • Time Period Groups

Time ago
就是将日期转变为相对日期的形式,即我们常用的“昨天、今天、明天、几天前,一周以后……”这样的表述方式。

NSDate *timeAgoDate = [NSDate dateWithTimeIntervalSinceNow:-4];NSLog(@"Time Ago: %@", timeAgoDate.timeAgoSinceNow);NSLog(@"Time Ago: %@", timeAgoDate.shortTimeAgoSinceNow);//输出://Time Ago: 4 seconds ago //Time Ago: 4s //设置好支持中文输出://Time Ago: 4秒钟前 //Time Ago: 4秒

DateTools提供了多达33种语言的支持如果想修改相对日期显示的文字,可以修改DateTools/Resources/DateTools.bundle下相应的文件下图以简体中文为例:

ca88yzc 5修改相对日期显示文字

获取日期的组成部分:

NSDate *date = [NSDate date];NSInteger year = date.year;NSInteger month = date.month;NSInteger day = date.day;NSInteger hour = date.hour;NSInteger minute = date.minute;NSInteger second = date.second; NSLog(@"year:%ld month:%ld day:%ld hour:%ld minute:%ld second:%ld", year, month, day, hour, minute, second);//输出: year:2016 month:5 day:23 hour:10 minute:54 second:1

DateTools默认日历为公历!!!使用其他日历:(如果想全局使用其他日历,修改DateTools默认日历,可以改写
NSDate+DateTools.m 中的 defaultCalendar 方法)

ca88yzc 6日历枚举

/** * 使用中国农历 */NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSChineseCalendar]; NSDate *date = [NSDate date];NSInteger year = [date yearWithCalendar:calendar];NSInteger month = [date monthWithCalendar:calendar];NSInteger day = [date dayWithCalendar:calendar];NSInteger hour = [date hourWithCalendar:calendar];NSInteger minute = [date minuteWithCalendar:calendar];NSInteger second = [date secondWithCalendar:calendar]; NSLog(@"农历: %ld-%ld-%ld %ld:%ld:%ld", year, month, day, hour, minute, second);NSLog(@"公历: %ld-%ld-%ld %ld:%ld:%ld", date.year, date.month, date.day, date.hour, date.minute, date.second);//输出: 农历: 33-4-17 11:41:51//输出: 公历: 2016-5-23 11:41:51

对日期进行编辑(年,月,日,星期,时,分,秒)增加/减少方法:

ca88yzc 7日期编辑

/** * 日期增加1年 */NSDate *date = [NSDate date];NSDate *newDate = [date dateByAddingYears:1]; NSLog(@"year:%ld newYear:%ld", date.year, newDate.year);//输出: year:2016 newYear:2017

比较两个日期大小返回一个布尔值:

ca88yzc 8日期比较

更多日期比较方法:(很容易的得到两个日期相差的年,月,星期,日,时,分,秒)

ca88yzc 9更多日期比较方法

formattedDateWithStyle:formattedDateWithFormat:

ca88yzc 10格式化日期字符串

/** * formattedDateWithStyle */NSDate *date = [NSDate date];NSString *dateStringNo = [date formattedDateWithStyle:NSDateFormatterNoStyle];NSString *dateStringShort = [date formattedDateWithStyle:NSDateFormatterShortStyle];NSString *dateStringMedium = [date formattedDateWithStyle:NSDateFormatterMediumStyle];NSString *dateStringLong = [date formattedDateWithStyle:NSDateFormatterLongStyle];NSString *dateStringFull = [date formattedDateWithStyle:NSDateFormatterFullStyle]; NSLog(@"No: %@", dateStringNo); //输出: No:NSLog(@"Short: %@", dateStringShort); //输出: Short: 16/5/23NSLog(@"Medium: %@", dateStringMedium); //输出: Medium: 2016年5月23日NSLog(@"Long: %@", dateStringLong); //输出: Long: 2016年5月23日NSLog(@"Full: %@", dateStringFull); //输出: Full: 2016年5月23日 星期一 /** * formattedDateWithFormat */NSString *dateStringCustom1 = [date formattedDateWithFormat:@"yyyy-MM-dd"];NSString *dateStringCustom2 = [date formattedDateWithFormat:@"yyyy年MM月dd日"];NSString *dateStringCustom3 = [date formattedDateWithFormat:@"yyyy-MM-dd HH:mm:ss"];NSString *dateStringCustom4 = [date formattedDateWithFormat:@"yyyy年MM月dd日 HH:mm:ss"];NSString *dateStringCustom5 = [date formattedDateWithFormat:@"yyyy年MM月dd日 HH点mm分ss秒"]; NSLog(@"Custom1: %@", dateStringCustom1); //输出: Custom1: 2016-05-23NSLog(@"Custom2: %@", dateStringCustom2); //输出: Custom2: 2016年05月23日NSLog(@"Custom3: %@", dateStringCustom3); //输出: Custom3: 2016-05-23 13:42:53NSLog(@"Custom4: %@", dateStringCustom4); //输出: Custom4: 2016年05月23日 13:42:53NSLog(@"Custom5: %@", dateStringCustom5); //输出: Custom5: 2016年05月23日 13点42分53秒
字符 说明
yy 年的后2位
yyyy 完整年
M 1~12 月
MM 1~12 月
MMM Jan/Feb/Mar/Apr/May/Jun/Jul/Aug/Sep/Oct/Nov/Dec
MMMM January/February/March/April/May/June/July/August/September/October/November/December
d 1~31 日
dd 1~31 日
EEE Sun/Mon/Tue/Wed/Thu/Fri/Sat
EEEE Sunday/Monday/Tuesday/Wednesday/Thursday/Friday/Saturday
aa AM/PM
H 0~23 时(24小时制,不带0)
HH 0~23 时
h 1~12 时(12小时制,不带0)
hh 1~12 时
m 0~59 分
mm 0~59 分
s 0~59 秒
ss 0~59 秒
S 毫秒

DateTools提供DTTimePeriod来简化时间段的操作

/** * 通过起始时间来初始化时间段 */NSDate *startDate = [NSDate date];NSDate *endDate = [startDate dateByAddingHours:2];DTTimePeriod *timePeriod =[[DTTimePeriod alloc] initWithStartDate:startDate endDate:endDate]; BOOL hasStartDate = timePeriod.hasStartDate; //是否有开始时间BOOL hasEndDate = timePeriod.hasEndDate; //是否有结束时间BOOL isMoment = timePeriod.isMoment; //是否开始时间和结束时间相同 double durationInYears = [timePeriod durationInYears]; //相差年double durationInWeeks = [timePeriod durationInWeeks]; //相差周double durationInDays = [timePeriod durationInDays]; //相差日double durationInHours = [timePeriod durationInHours]; //相差小时double durationInMinutes = [timePeriod durationInMinutes]; //相差分double durationInSeconds = [timePeriod durationInSeconds]; //相差秒 NSLog(@"%f年 %f周 %f日 %f小时 %f分 %f秒",durationInYears, durationInWeeks, durationInDays, durationInHours, durationInMinutes, durationInSeconds);//输出: 0.000000年 0.000000周 0.000000日 2.000000小时 120.000000分 7200.000000秒

更多初始化时间段方法:

ca88yzc 11更多初始化时间段方法

移动时间段:shiftEarlierWithSize 时间段整体前移

shiftLaterWithSize 时间段整体后移

/** * 移动时间段 */NSDate *startDate = [NSDate date];NSDate *endDate = [startDate dateByAddingDays:2];DTTimePeriod *timePeriod =[[DTTimePeriod alloc] initWithStartDate:startDate endDate:endDate]; NSLog(@"开始:%@", [timePeriod.StartDate formattedDateWithStyle:NSDateFormatterMediumStyle]);NSLog(@"结束:%@", [timePeriod.EndDate formattedDateWithStyle:NSDateFormatterMediumStyle]);//输出: 开始:2016年5月23日//输出: 结束:2016年5月25日 [timePeriod shiftEarlierWithSize:DTTimePeriodSizeDay]; //提前1天 NSLog(@"开始:%@", [timePeriod.StartDate formattedDateWithStyle:NSDateFormatterMediumStyle]);NSLog(@"结束:%@", [timePeriod.EndDate formattedDateWithStyle:NSDateFormatterMediumStyle]);//输出: 开始:2016年5月22日//输出: 结束:2016年5月24日[timePeriod shiftLaterWithSize:DTTimePeriodSizeDay amount:2]; //延后2天 NSLog(@"开始:%@", [timePeriod.StartDate formattedDateWithStyle:NSDateFormatterMediumStyle]);NSLog(@"结束:%@", [timePeriod.EndDate formattedDateWithStyle:NSDateFormatterMediumStyle]);//输出: 开始:2016年5月24日//输出: 结束:2016年5月26日

延长/缩短时间段:shortenWithAnchorDate
延长(开始时间/中间时间/结束时间)lengthenWithAnchorDate
缩短(开始时间/中间时间/结束时间)

/** * 时间段延长/缩短 */NSDate *startDate = [NSDate date];NSDate *endDate = [startDate dateByAddingDays:2];DTTimePeriod *timePeriod =[[DTTimePeriod alloc] initWithStartDate:startDate endDate:endDate]; NSLog(@"时长: %.2f天", [timePeriod durationInDays]);//输出: 时长: 2.00天[timePeriod lengthenWithAnchorDate:DTTimePeriodAnchorEnd size:DTTimePeriodSizeDay amount:1]; //延长1天NSLog(@"时长: %.2f天", [timePeriod durationInDays]);//输出: 时长: 3.00天[timePeriod shortenWithAnchorDate:DTTimePeriodAnchorStart size:DTTimePeriodSizeDay amount:4]; //缩短4天 NSLog(@"时长: %.2f天", [timePeriod durationInDays]);//输出: 时长: 0.00天

两个时间段关系相关方法:

ca88yzc 12时间段关系方法

两个时间段关系所有可能性:

ca88yzc 13时间段关系可能性

时间段关系枚举DTTimePeriodRelation:

/** * DTTimePeriodRelation时间段关系枚举 */typedef NS_ENUM(NSUInteger, DTTimePeriodRelation){ DTTimePeriodRelationAfter, DTTimePeriodRelationStartTouching, DTTimePeriodRelationStartInside, DTTimePeriodRelationInsideStartTouching, DTTimePeriodRelationEnclosingStartTouching, DTTimePeriodRelationEnclosing, DTTimePeriodRelationEnclosingEndTouching, DTTimePeriodRelationExactMatch, DTTimePeriodRelationInside, DTTimePeriodRelationInsideEndTouching, DTTimePeriodRelationEndInside, DTTimePeriodRelationEndTouching, DTTimePeriodRelationBefore, DTTimePeriodRelationNone };

DateTools提供两种时间段组类:DTTimePeriodCollection 时间段集合
(允许存储彼此有交集的时间段)DTTimePeriodChain 时间段链接
(不允许存储彼此有交集的时间段)

DTTimePeriodCollection和DTTimePeriodChain就像NSArray一样,你可以像操作数组一样对它们中的DTTimePeriod对象进行添加,插入,删除,不同之处在于如何处理时间段

一个规则相对宽松的时间段集合默认是无序的,但是支持通过调用方法排序有自己的属性,例如StartDate和EndDate属性是根据内部时间段推测出来的允许存储有重叠的时间段

ca88yzc 14时间段集合

//时间段集合DTTimePeriodCollection *timePeriodCollection = [DTTimePeriodCollection collection]; /** * 时间段 * * 当前时间为: 2016年5月24日 */DTTimePeriod *timePeriod1 = [DTTimePeriod timePeriodWithSize:DTTimePeriodSizeDay startingAt:[NSDate date]];DTTimePeriod *timePeriod2 = [DTTimePeriod timePeriodWithSize:DTTimePeriodSizeDay endingAt:[NSDate date]]; NSLog(@"开始:%@ 结束:%@", [timePeriod1.StartDate formattedDateWithStyle:NSDateFormatterMediumStyle], [timePeriod1.EndDate formattedDateWithStyle:NSDateFormatterMediumStyle]);NSLog(@"开始:%@ 结束:%@", [timePeriod2.StartDate formattedDateWithStyle:NSDateFormatterMediumStyle], [timePeriod2.EndDate formattedDateWithStyle:NSDateFormatterMediumStyle]);//输出: 开始:2016年5月24日 结束:2016年5月25日//输出: 开始:2016年5月23日 结束:2016年5月24日 /** * 时间段集合操作 *///添加[timePeriodCollection addTimePeriod:timePeriod1];//插入[timePeriodCollection insertTimePeriod:timePeriod2 atIndex:0];//移除[timePeriodCollection removeTimePeriodAtIndex:0]; /** * 时间段集合排序 *///按开始时间升序[timePeriodCollection sortByStartAscending];//按开始时间降序[timePeriodCollection sortByStartDescending];//按结束时间升序[timePeriodCollection sortByEndAscending];//按结束时间降序[timePeriodCollection sortByEndDescending];//按持续时间升序[timePeriodCollection sortByDurationAscending];//按持续时间降序[timePeriodCollection sortByDurationDescending]; //从时间段集合中获取时间段DTTimePeriod *firstTimePeriod = [timePeriodCollection objectAtIndexedSubscript:0];

ca88yzc 15时间段集合操作示意图

获取一个NSDate对象或一个DTTimePeriod对象与一个时间段集合的相对关系

ca88yzc 16时间段集合操作方法

一个紧密耦合的时间段集合通常依据开始时间和结束时间存储时间段对象有自己的属性,例如StartDate和EndDate属性是根据内部时间段推测出来的不允许存储有重叠的时间段

ca88yzc 17时间链

 //创建时间链DTTimePeriodChain *chain = [DTTimePeriodChain chain]; //创建时间段NSDateFormatter * dateFormatter = [[NSDateFormatter alloc]init];[dateFormatter setDateFormat: @"YYYY MM dd HH:mm:ss.SSS"]; DTTimePeriod *firstPeriod = [DTTimePeriod timePeriodWithStartDate:[dateFormatter dateFromString:@"2014 11 05 18:15:12.000"] endDate:[dateFormatter dateFromString:@"2015 11 05 18:15:12.000"]];DTTimePeriod *secondPeriod = [DTTimePeriod timePeriodWithStartDate:[dateFormatter dateFromString:@"2015 11 05 18:15:12.000"] endDate:[dateFormatter dateFromString:@"2016 11 05 18:15:12.000"]]; //添加[chain addTimePeriod:firstPeriod];[chain addTimePeriod:secondPeriod];//插入[chain insertTimePeriod:firstPeriod atInedx:0];//移除[chain removeTimePeriodAtIndex:0];//移除最晚时间段[chain removeLatestTimePeriod];//移除最早时间段[chain removeEarliestTimePeriod]; //获取集合中的元素.firstPeriod = chain[0];

新加入的时间段,时长不变,起始时间变为前一个时间段的结束时间,结束时间对应前移后后移.在非零位置新插入的时间,其后的时间段相应后移.在零位置插入的时间,集合的起始时间前移

ca88yzc 18时间链操作示意图

时间戳(timestamp):特定的时刻。

使用 Cocoapods 安装

pod 'DateTools'

固定时期:如2007年1月或2010年全年。

NSDate+DateTools

DateTools让NSDate功能更完整,可以让你更容易地去获取日期各个组件的信息,如年
月 日等.

时间间隔:由起始和结束时间戳表示。时期可以被看做间隔的特例。

获取字符串形式的时间间隔.

DateTools
可以让你获取距离一个过去的时间点距离当前时间的字符串表示.和Twitter中很像,这个时间字符串有完整形式和缩略形式两种.你可以像下面这样使用:

NSDate *timeAgoDate = [NSDate dateWithTimeIntervalSinceNow:-4];
NSLog(@"Time Ago: %@", timeAgoDate.timeAgoSinceNow);
NSLog(@"Time Ago: %@", timeAgoDate.shortTimeAgoSinceNow);

//输出:
//Time Ago: 4 seconds ago
//Time Ago: 4s

// 如果工程支持国际化,并且模拟器或真机环境设为简体中文,则会输出:
// Time Ago: 4秒钟前
// Time Ago: 4秒

如果你的工程支持国际化,DateTools现在会自动支持以下语言的本地化:

  • ar (Arabic)
  • bg (Bulgarian)
  • ca (Catalan)
  • zh_Hans (简体中文)
  • zh_Hant (繁体中文)
  • cs (Czech)
  • da (Danish)
  • nl (Dutch)
  • en (English)
  • fi (Finnish)
  • fr (French)
  • de (German)
  • gre (Greek)
  • gu (Gujarati)
  • he (Hebrew)
  • hi (Hindi)
  • hu (Hungarian)
  • is (Icelandic)
  • id (Indonesian)
  • it (Italian)
  • ja (Japanese)
  • ko (Korean)
  • lv (Latvian)
  • ms (Malay)
  • nb (Norwegian)
  • pl (Polish)
  • pt (Portuguese)
  • ro (Romanian)
  • ru (Russian)
  • sl (Slovenian)
  • es (Spanish)
  • sv (Swedish)
  • th (Thai)
  • tr (Turkish)
  • uk (Ukrainian)
  • vi (Vietnamese)
  • cy (Welsh)
  • hr (Croatian)

实验或过程时间:每个时间点都是相对于特定起始时间的一个度量。例如,从放入烤箱时起,每秒钟饼干的直径。

获取日期的某个组成部分,如年月周日时分秒等.

使用 DateTools 可以很容易地获取日期对象的某一组成部分:

NSDate * date = [NSDate date];
NSInteger year = date.year;
NSInteger month = date.month;
NSLog(@"year: %ld, month: %ld", (long)year, (long)month); // year: 2015, month: 9

如果你不想使用公历,可以这样做:

NSInteger day = [date dayWithCalendar:calendar];

如果你想改变 DateTools 使用的默认日历,可以改写 NSDate+DateTools.m 中的
defaultCalendar 方法.

pandas提供了许多内置的时间序列处理工具和数据算法。因此,你可以高效处理非常大的时间序列,轻松地进行切片/切块、聚合、对定期/不定期的时间序列进行重采样等。有些工具特别适合金融和经济应用,你当然也可以用它们来分析服务器日志数据。

日期编辑

可以使用 dateByAdding...dateBySubtractingYears...
进行日期按年/月/日/时分/秒等增加或减少:

NSDate * date = [NSDate date];
NSInteger oldYear = date.year;

NSDate *newDate = [date dateByAddingYears:1];
NSInteger newYear = newDate.year;

NSLog(@"oldYear: %ld newYear: %ld", (long)oldYear, (long)newYear); // 输出: oldYear: 2015 newYear: 2016

ca88yzc 19datetime模块中的数据类型

日期比较

DateTools 提供下列方法,比较两个日期的大小,返回结果为一个布尔值:

  • isEarlierThan
  • isEarlierThanOrEqualTo
  • isLaterThan
  • isLaterThanOrEqualTo

如果想获取两个日期具体的差值: 获取毫秒间隔可以使用 NSDate 提供的
timeIntervalSinceDate:timeIntervalSinceNow
方法;获取相差多少年/月/周/日/时/分/秒等,可以直接使用
DateTools的扩展方法.

NSInteger yearsApart = [firstDate yearsFrom:secondDate];

类似yearsFrom:用于日期比较的方法包括:

  • yearsFrom:, yearsUntil, yearsAgo, yearsEarlierThan:,
    yearsLaterThan:
  • monthsFrom:, monthsUntil, monthsAgo, monthsEarlierThan:,
    monthsLaterThan:
  • weeksFrom:, weeksUntil, weeksAgo, weeksEarlierThan:,
    weeksLaterThan:
  • daysFrom:, daysUntil, daysAgo, daysEarlierThan:,
    daysLaterThan:
  • hoursFrom:, hoursUntil, hoursAgo, hoursEarlierThan:,
    hoursLaterThan:
  • minutesFrom:, minutesUntil, minutesAgo, minutesEarlierThan:,
    minutesLaterThan:
  • secondsFrom:, secondsUntil, secondsAgo, secondsEarlierThan:,
    secondsLaterThan:

Python标准库包含用于日期和时间数据的数据类型,而且还有日历方面的功能。我们主要会用到datetime、time以及calendar模块。datetime.datetime(也可以简写为datetime)是用得最多的数据类型:

日期的格式化输出

可以使用 code>formattedDateWithStyle: 和 formattedDateWithFormat:
方法格式化输出日期:

NSDate * date = [NSDate date];
NSString * dateStr = [date formattedDateWithStyle: NSDateFormatterFullStyle];

// 此处输出的具体内容会根据你的手机或模拟器语言环境的不同而不同.
NSLog(@"%@", dateStr); // 输出: 2015年9月25日 星期五

dateStr =  [date formattedDateWithFormat:@"YYYY/MM/dd HH:mm:ss"];
NSLog(@"%@", dateStr); // 输出: 2015/09/25 15:19:23

ca88yzc 20

时间段

DateTools 通过 DTTimePeriod类来简化时间段相关的操作.

datetime以毫秒形式存储日期和时间。timedelta表示两个datetime对象之间的时间差:

初始化

已知开始和结束时间,可以使用下面的方法初始化时间段对象:

DTTimePeriod *timePeriod = [[DTTimePeriod alloc] initWithStartDate:startDate endDate:endDate];

或者,已知起始或结束时间,同时知道时间段的总时长,可以用类似下面的方法创建时间端对象:

// 创建一个时间段,从现在开始,共5个小时.
DTTimePeriod *timePeriod = [DTTimePeriod timePeriodWithSize:DTTimePeriodSizeHour amount:5 startingAt:[NSDate date]];

ca88yzc 21

时间段信息

可以通过 DTTimePeriod 的实例方法来获取时间段的相关信息:

  • hasStartDate – 返回YES,如果有起始时间.
  • hasEndDate – 返回YES,如果有结束时间.
  • isMoment – 返回YES,如果起始时间和结束时间相同.
  • durationIn.... – 返回指定单位下时间段的长度.

DTTimePeriod *timePeriod = [[DTTimePeriod alloc] initWithStartDate:date endDate: [date dateByAddingDays: 10]];

NSLog(@"相差 %g 天", [timePeriod durationInDays]); // 输出: 相差 10 天

可以给datetime对象加上一个或多个timedelta,这样会产生一个新对象:

操作

可以对时间段进行移动,延长或缩短的操作.

移动

当一个时间段被移动时,起始时间和结束时间会相应地同步迁移或推后.可以使用下面两个方法移动时间段:

  • shiftEarlierWithSize:amount: 时间段整体前移
  • shiftLaterWithSize:amount: 时间段整体推后

延长/缩短

可以通过保持起始点/中间时间点/结束时间点不变,然后改变开始或结束时间点,以得到延长或缩短时间段的目的:

// 通过前移起始时间,把时间段总时长从1分钟变为2分钟.
DTTimePeriod *timePeriod  = [DTTimePeriod timePeriodWithSize:DTTimePeriodSizeMinute endingAt:[NSDate date]];
[timePeriod lengthenWithAnchorDate:DTTimePeriodAnchorEnd size:DTTimePeriodSizeMinute amount:1];

ca88yzc 22

关系

可以使用
DTTimePeriod的关系操作相关的方法,来判断两个时间段的相互关系,如是否包含,是否是同一段时间等.

基础

下图表格列出了两个时间段所有可能的关系:
ca88yzc 23

可通过下列方法判断两个时间段的关系:

  • isEqualToPeriod:
  • isInside:
  • contains:
  • overlapsWith:
  • intersects:

你可以通过下面这个方法获取相对于另一个时间段的关系:

-(DTTimePeriodRelation)relationToPeriod:(DTTimePeriod *)period;

所有可能的时间段间的关系都列在了枚举 DTTimePeriodRelation 中了.

点击示例中 Time Periods
按钮,然后滑动滑块,可以更好地掌握时间段之间的相互关系

ca88yzc 24

利用str或strftime方法(传入一个格式化字符串),datetime对象和pandas的Timestamp对象可以被格式化为字符串:

时间段集合

DateTools 提供两种时间段集合类: DTTimePeriodCollection
DTTimePeriodChain.前者,允许存储彼此有交集的时间段;后者,不允许存储彼此有交集的时间段.

这两个时间段集合类,操作和 NSArray 很像.你可以添加,插入和移除
DTTimePeriod
对象,就像你在数组时的那样.唯一的不同是,两中集合存储时间段的方式.

DTTimePeriodCollection
DTTimePeriodChain,是为了简化基于多个时间段的逻辑处理.比如同一团队中,给不同的人设置任务的起始和结束时间,此时如果使用
DTTimePeriodCollection
来处理各个时间段,可以直接得到团队总任务的起始时间和结束时间.

ca88yzc 25

DTTimePeriodCollection

DTTimePeriodCollection
是一个规则相对宽松的集合.默认无序(指的是顺序和各个时间段的起止时间无关.),但支持手动排序;拥有自己的属性,比如基于内粗存储的时间段计算出的此集合的开始时间和结束时间.这个结合允许存储有交集的时间段.

ca88yzc 26

可以像下面这样创建新的DTTimePeriodCollection集合:

// 创建集合.
DTTimePeriodCollection *collection = [DTTimePeriodCollection collection];

// 创建时间段
NSDateFormatter * dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setDateFormat: @"YYYY MM dd HH:mm:ss.SSS"];
DTTimePeriod *firstPeriod = [DTTimePeriod timePeriodWithStartDate:[dateFormatter dateFromString:@"2014 11 05 18:15:12.000"] endDate:[dateFormatter dateFromString:@"2015 11 05 18:15:12.000"]];
DTTimePeriod *secondPeriod = [DTTimePeriod timePeriodWithStartDate:[dateFormatter dateFromString:@"2015 11 05 18:15:12.000"] endDate:[dateFormatter dateFromString:@"2016 11 05 18:15:12.000"]];

// 把时间段添加到集合中.
[collection addTimePeriod:firstPeriod];
[collection addTimePeriod:secondPeriod];

// 从集合中获取时间段.
firstPeriod = collection[0];

排序

有三类给集合内时间段排序的方法:

  • 根据起始时间排序sortByStartAscending,
    sortByStartDescending
  • 根据结束时间排序sortByEndAscending, sortByEndDescending
  • 根据时长排序sortByDurationAscending,
    sortByDurationDescending

操作

也可以去获取一个 NSDate 对象或一个 DTTimePeriod 对象与一个
时间段结合的相对关系.例如,你可以通过 periodsIntersectedByDate:
方法获取所有与某个时间有交集的时间段.这个方法会返回一个新的
DTTimePeriodCollection 对象,里面包含所有符合条件的时间段.

有许多类似的方法,如下图:

ca88yzc 27

全部的格式化编码:

DTTimePeriodChain

DTTimePeriodChain 以较为严格的方式存储时间段对象.
DTTimePeriodChain集合通常依据开始和结束时间存储时间段对象,并且有自己的属性,如
根据内部存储的时间段对象推断出来的此集合的开始时间和结束时间.
DTTimePeriodChain
内部存储的时间段对象不允许有交集.这种集合很适用于连续会议或约会等日程类事务的建模.

ca88yzc 28

创建一个新的 DTTimePeriodChain 集合:

// 创建集合.
DTTimePeriodChain *chain = [DTTimePeriodChain chain];

// 创建时间段
NSDateFormatter * dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setDateFormat: @"YYYY MM dd HH:mm:ss.SSS"];

DTTimePeriod *firstPeriod = [DTTimePeriod timePeriodWithStartDate:[dateFormatter dateFromString:@"2014 11 05 18:15:12.000"] endDate:[dateFormatter dateFromString:@"2015 11 05 18:15:12.000"]];
DTTimePeriod *secondPeriod = [DTTimePeriod timePeriodWithStartDate:[dateFormatter dateFromString:@"2015 11 05 18:15:12.000"] endDate:[dateFormatter dateFromString:@"2016 11 05 18:15:12.000"]];

// 添加时间段对象到集合中.
[chain addTimePeriod:firstPeriod];

// 如果后存入的时间和前一个存入的时间无法前后完全衔接,则后一个时间会适当前移或后移,以使前后时间段紧凑.
[chain addTimePeriod:secondPeriod];

// 获取集合中的元素.
firstPeriod = chain[0];

新加入的时间段,时长不变,起始时间变为前一个时间段的结束时间,结束时间对应前移后后移.在非零位置新插入的时间,其后的时间段相应后移.在零位置插入的时间,集合的起始时间前移.操作图解如下:

操作
像 DTTimePeriodCollection 一样, DTTimePeriodChain
也可以进行相等性比较,并且也可以前移后后移.其他执行的方法在下图列出:

ca88yzc 29

ca88yzc 30ca88yzc 31

datetime.strptime可以用这些格式化编码将字符串转换为日期:

ca88yzc 32

datetime.strptime是通过已知格式进行日期解析的最佳方式。但是每次都要编写格式定义是很麻烦的事情,尤其是对于一些常见的日期格式。这种情况下,你可以用dateutil这个第三方包中的parser.parse方法(pandas中已经自动安装好了):

ca88yzc 33

在国际通用的格式中,日出现在月的前面很普遍,传入dayfirst=True即可解决这个问题:

ca88yzc 34

pandas通常是用于处理成组日期的,不管这些日期是DataFrame的轴索引还是列。to_datetime方法可以解析多种不同的日期表示形式。

ca88yzc 35

还可以处理缺失值(None、空字符串等):

ca88yzc 36NaT(Not
a Time)是pandas中时间戳数据的null值。

pandas最基本的时间序列类型就是以时间戳(通常以Python字符串或datatime对象表示)为索引的Series:

ca88yzc 37

跟其他Series一样,不同索引的时间序列之间的算术运算会自动按日期对齐:

ca88yzc 38

pandas用NumPy的datetime64数据类型以纳秒形式存储时间戳:

ca88yzc 39

DatetimeIndex中的各个标量值是pandas的Timestamp对象:

ca88yzc 40

只要有需要,TimeStamp可以随时自动转换为datetime对象。

当你根据标签索引选取数据时,时间序列和其它的pandas.Series很像:

ca88yzc 41

传入一个可以被解释为日期的字符串:

ca88yzc 42

对于较长的时间序列,只需传入“年”或“年月”即可轻松选取数据的切片:

ca88yzc 43ca88yzc 44ca88yzc 45

datetime对象也可以进行切片:

ca88yzc 46

由于大部分时间序列数据都是按照时间先后排序的,因此你也可以用不存在于该时间序列中的时间戳对其进行切片:

ca88yzc 47

可以传入字符串日期、datetime或Timestamp。注意,这样切片所产生的是原时间序列的视图,跟NumPy数组的切片运算是一样的。这意味着,没有数据被复制,对切片进行修改会反映到原始数据上。

还有一个等价的实例方法也可以截取两个日期之间TimeSeries:

ca88yzc 48

这些操作对DataFrame也有效:

ca88yzc 49

在某些应用场景中,可能会存在多个观测数据落在同一个时间点上的情况。

ca88yzc 50

对具有非唯一时间戳的数据进行聚合。一个办法是使用groupby,并传入level=0:

ca88yzc 51

pandas中的原生时间序列一般被认为是不规则的,也就是说,它们没有固定的频率。对于大部分应用程序而言,这是无所谓的。但是,它常常需要以某种相对固定的频率进行分析,比如每日、每月、每15分钟等(这样自然会在时间序列中引入缺失值)。幸运的是,pandas有一整套标准时间序列频率以及用于重采样、频率推断、生成固定频率日期范围的工具。例如,我们可以将之前那个时间序列转换为一个具有固定频率的时间序列,只需调用resample即可:

ca88yzc 52字符串“D”是每天的意思

date_range会产生按天计算的时间点。如果只传入起始或结束日期,那就还得传入一个表示一段时间的数字:

ca88yzc 53

起始和结束日期定义了日期索引的严格边界。例如,如果你想要生成一个由每月最后一个工作日组成的日期索引,可以传入”BM”频率:

ca88yzc 54

基本的时间序列频率:

ca88yzc 55ca88yzc 56ca88yzc 57

date_range默认会保留起始和结束时间戳的时间信息:

ca88yzc 58

虽然起始和结束日期带有时间信息,但你希望产生一组被规范化(normalize)到午夜的时间戳。normalize选项即可实现该功能:

ca88yzc 59

pandas中的频率是由一个基础频率(base
frequency)和一个乘数组成的。基础频率通常以一个字符串别名表示,比如”M”表示每月,”H”表示每小时。对于每个基础频率,都有一个被称为日期偏移量(date
offset)的对象与之对应。例如,按小时计算的频率可以用Hour类表示:

ca88yzc 60

一般来说,无需明确创建这样的对象,只需使用诸如”H”或”4H”这样的字符串别名即可。在基础频率前面放上一个整数即可创建倍数:

ca88yzc 61

大部分偏移量对象都可通过加法进行连接:

ca88yzc 62

也可以传入频率字符串(如”2h30min”),这种字符串可以被高效地解析为等效的表达式:

ca88yzc 63

WOM(Week Of
Month)是一种非常实用的频率类,它以WOM开头。它使你能获得诸如“每月第3个星期五”之类的日期:

ca88yzc 64

移动指的是沿着时间轴将数据前移或后移。Series和DataFrame都有一个shift方法用于执行单纯的前移或后移操作,保持索引不变:

ca88yzc 65

shift通常用于计算一个时间序列或多个时间序列(如DataFrame的列)中的百分比变化。可以这样表达:

ca88yzc 66

由于单纯的移位操作不会修改索引,所以部分数据会被丢弃。因此,如果频率已知,则可以将其传给shift以便实现对时间戳进行位移而不是对数据进行简单位移:

ca88yzc 67

pandas的日期偏移量还可以用在datetime或Timestamp对象上:

ca88yzc 68

如果加的是锚点偏移量(比如MonthEnd),第一次增量会将原日期向前滚动到符合频率规则的下一个日期:

ca88yzc 69

通过锚点偏移量的rollforward和rollback方法,可明确地将日期向前或向后“滚动”:

ca88yzc 70

结合groupby使用这两个“滚动”方法:

ca88yzc 71

更简单、更快速地实现该功能的办法是使用resample:

ca88yzc 72

在Python中,时区信息来自第三方库pytz,它使Python可以使用Olson数据库(汇编了世界时区信息)。pandas包装了pytz的功能。

时区名可以在shell中看到,也可以通过文档查看:

ca88yzc 73

要从pytz中获取时区对象,使用pytz.timezone即可:

ca88yzc 74

pandas中的方法既可以接受时区名也可以接受这些对象。

默认情况下,pandas中的时间序列是单纯的时区。看看下面这个时间序列:

ca88yzc 75

可以用时区集生成日期范围:

ca88yzc 76

从单纯到本地化的转换是通过tz_localize方法处理的:

ca88yzc 77

一旦时间序列被本地化到某个特定时区,就可以用tz_convert将其转换到别的时区了:

ca88yzc 78

对于上面这种时间序列(它跨越了美国东部时区的夏令时转变期),我们可以将其本地化到EST,然后转换为UTC或柏林时间:

ca88yzc 79

tz_localize和tz_convert也是DatetimeIndex的实例方法:

ca88yzc 80

跟时间序列和日期范围差不多,独立的Timestamp对象也能被从单纯型本地化为时区意识型(time
zone-aware),并从一个时区转换到另一个时区:

ca88yzc 81

在创建Timestamp时,还可以传入一个时区信息:

ca88yzc 82

时区意识型Timestamp对象在内部保存了一个UTC时间戳值(自UNIX纪元(1970年1月1日)算起的纳秒数)。这个UTC值在时区转换过程中是不会发生变化的:

ca88yzc 83

当使用pandas的DateOffset对象执行时间算术运算时,运算过程会自动关注是否存在夏令时转变期。这里,我们创建了在DST转变之前的时间戳。首先,来看夏令时转变前的30分钟:

ca88yzc 84

夏令时转变前90分钟:

ca88yzc 85

如果两个时间序列的时区不同,在将它们合并到一起时,最终结果就会是UTC。由于时间戳其实是以UTC存储的,所以这是一个很简单的运算,并不需要发生任何转换:

ca88yzc 86

时期表示的是时间区间,比如数日、数月、数季、数年等。Period类所表示的就是这种数据类型,其构造函数需要用到一个字符串或整数,以及频率:

ca88yzc 87

这个Period对象表示的是从2007年1月1日到2007年12月31日之间的整段时间。只需对Period对象加上或减去一个整数即可达到根据其频率进行位移的效果:

ca88yzc 88

如果两个Period对象拥有相同的频率,则它们的差就是它们之间的单位数量:

ca88yzc 89

period_range函数可用于创建规则的时期范围:

ca88yzc 90

PeriodIndex类保存了一组Period,它可以在任何pandas数据结构中被用作轴索引:

ca88yzc 91

如果你有一个字符串数组,你也可以使用PeriodIndex类:

ca88yzc 92

Period和PeriodIndex对象都可以通过其asfreq方法被转换成别的频率。假设我们有一个年度时期,希望将其转换为当年年初或年末的一个月度时期。

ca88yzc 93

对于一个不以12月结束的财政年度,月度子时期的归属情况就不一样了:

ca88yzc 94ca88yzc 95Period(‘2011′,’A-DEC’)看做一个被划分为多个月度时期的时间段中的游标

在将高频率转换为低频率时,超时期(superperiod)是由子时期(subperiod)所属的位置决定的。例如,在A-JUN频率中,,月份“2007年8月”实际上是属于周期“2008年”的:

ca88yzc 96

完整的PeriodIndex或TimeSeries的频率转换方式也是如此:

ca88yzc 97ca88yzc 98

季度型数据在会计、金融等领域中很常见。许多季度型数据都会涉及“财年末”的概念,通常是一年12个月中某月的最后一个日历日或工作日。就这一点来说,时期”2012Q4″根据财年末的不同会有不同的含义。pandas支持12种可能的季度型频率,即Q-JAN到Q-DEC:

ca88yzc 99

在以1月结束的财年中,2012Q4是从11月到1月(将其转换为日型频率就明白了)

ca88yzc 100ca88yzc 101不同季度型频率之间的转换

因此,Period之间的算术运算会非常简单。例如,要获取该季度倒数第二个工作日下午4点的时间戳:

ca88yzc 102

period_range可用于生成季度型范围。季度型范围的算术运算也跟上面是一样的:

ca88yzc 103

通过使用to_period方法,可以将由时间戳索引的Series和DataFrame对象转换为以时期索引:

ca88yzc 104

由于时期指的是非重叠时间区间,因此对于给定的频率,一个时间戳只能属于一个时期。新PeriodIndex的频率默认是从时间戳推断而来的,你也可以指定任何别的频率。结果中允许存在重复时期:

ca88yzc 105

要转换回时间戳,使用to_timestamp即可:

ca88yzc 106

固定频率的数据集通常会将时间信息分开存放在多个列中。例如,在下面这个宏观经济数据集中,年度和季度就分别存放在不同的列中:

ca88yzc 107ca88yzc 108ca88yzc 109

通过将这些数组以及一个频率传入PeriodIndex,就可以将它们合并成DataFrame的一个索引:

ca88yzc 110

重采样(resampling)指的是将时间序列从一个频率转换到另一个频率的处理过程。将高频率数据聚合到低频率称为降采样(downsampling),而将低频率数据转换到高频率则称为升采样(upsampling)。并不是所有的重采样都能被划分到这两个大类中。例如,将W-WED转换为W-FRI既不是降采样也不是升采样。

pandas对象都带有一个resample方法,它是各种频率转换工作的主力函数。resample有一个类似于groupby的API,调用resample可以分组数据,然后会调用一个聚合函数:

ca88yzc 111ca88yzc 112

resample是一个灵活高效的方法,可用于处理非常大的时间序列。

ca88yzc 113
resample方法的参数

将数据聚合到规律的低频率是一件非常普通的时间序列处理任务。待聚合的数据不必拥有固定的频率,期望的频率会自动定义聚合的面元边界,这些面元用于将时间序列拆分为多个片段。例如,要转换到月度频率,数据需要被划分到多个单月时间段中。各时间段都是半开放的。一个数据点只能属于一个时间段,所有时间段的并集必须能组成整个时间帧。在用resample对数据进行降采样时,需要考虑两样东西:各区间哪边是闭合的、如何标记各个聚合面元,用区间的开头还是末尾。

看一些“1分钟”数据:

ca88yzc 114

通过求和的方式将这些数据聚合到“5分钟”块中。传入的频率将会以“5分钟”的增量定义面元边界。默认情况下,面元的右边界是包含的,因此00:00到00:05的区间中是包含00:05的。传入closed=’left’会让区间以左边界闭合:

ca88yzc 115

最终的时间序列是以各面元右边界的时间戳进行标记的。传入label=’right’即可用面元的邮编界对其进行标记:

ca88yzc 116ca88yzc 117各种closed、label约定的“5分钟”重采样演示

你可能希望对结果索引做一些位移,比如从右边界减去一秒以便更容易明白该时间戳到底表示的是哪个区间。只需通过loffset设置一个字符串或日期偏移量即可实现这个目的:

ca88yzc 118也可以通过调用结果对象的shift方法来实现该目的

金融领域中有一种无所不在的时间序列聚合方式,即计算各面元的四个值:第一个值、最后一个值、最大值以及最小值。传入how=’ohlc’即可得到一个含有这四种聚合值的DataFrame。整个过程很高效,只需一次扫描即可计算出结果:

ca88yzc 119

在将数据从低频率转换到高频率时,就不需要聚合了。看一个带有一些周型数据的DataFrame:

ca88yzc 120

对这个数据进行聚合,每组只有一个值,这样就会引入缺失值。我们使用asfreq方法转换成高频,不经过聚合:

ca88yzc 121

假设你想要用前面的周型值填充“非星期三”。resampling的填充和插值方式跟fillna和reindex的一样:

ca88yzc 122

也可以只填充指定的时期数(目的是限制前面的观测值的持续使用距离):

ca88yzc 123

新的日期索引完全没必要跟旧的重叠:

ca88yzc 124

对那些使用时期索引的数据进行重采样与时间戳很像:

ca88yzc 125

升采样要稍微麻烦一些,因为你必须决定在新频率中各区间的哪端用于放置原来的值,就像asfreq方法那样。convention参数默认为’start’,也可设置为’end’:

ca88yzc 126

由于时期指的是时间区间,所以升采样和降采样的规则就比较严格:在降采样中,目标频率必须是源频率的子时期(subperiod)。在升采样中,目标频率必须是源频率的超时期(superperiod)。

如果不满足这些条件,就会引发异常。这主要影响的是按季、年、周计算的频率。例如,由Q-MAR定义的时间区间只能升采样为A-MAR、A-JUN、A-SEP、A-DEC等:

ca88yzc 127

在移动窗口(可以带有指数衰减权数)上计算的各种统计函数也是一类常见于时间序列的数组变换。这样可以圆滑噪音数据或断裂数据。将它们称为移动窗口函数(moving
window
function),其中还包括那些窗口不定长的函数(如指数加权移动平均)。跟其他统计函数一样,移动窗口函数也会自动排除缺失值。

为了提升数据的准确性,将某个点的取值扩大到包含这个点的一段区间,用区间来进行判断,这个区间就是窗口。移动窗口就是窗口向一端滑行,默认是从右往左,每次滑行并不是区间整块的滑行,而是一个单位一个单位的滑行。给个例子好理解一点:

ca88yzc 128

首先我们设置的窗口window=3,也就是3个数取一个均值。index 0,1
为NaN,是因为它们前面都不够3个数,等到index2
的时候,它的值是怎么算的呢,就是(index0+index1+index2 )/3

index3 的值就是( index1+index2+index3)/ 3

rolling函数参数详解:

ca88yzc 129

加载一些时间序列数据,将其重采样为工作日频率:

ca88yzc 130

引入rolling运算符,它与resample和groupby很像。可以在TimeSeries或DataFrame以及一个window上调用它:

ca88yzc 131苹果公司股价的250日均线

表达式rolling与groupby很像,但不是对其进行分组,而是创建一个按照250天分组的滑动窗口对象。然后,我们就得到了苹果公司股价的250天的移动窗口。

默认情况下,rolling函数需要窗口中所有的值为非NA值。可以修改该行为以解决缺失数据的问题。其实,在时间序列开始处尚不足窗口期的那些数据就是个特例:

ca88yzc 132苹果公司250日每日回报标准差

要计算扩展窗口平均(expanding window
mean),可以使用expanding。“扩展”意味着,从时间序列的起始处开始窗口,增加窗口直到它超过所有的序列。apple_std250时间序列的扩展窗口平均如下所示:

ca88yzc 133

对DataFrame调用rolling_mean(以及与之类似的函数)会将转换应用到所有的列上:

ca88yzc 134各股价60日均线

rolling函数也可以接受一个指定固定大小时间补偿字符串,而不是一组时期。这样可以方便处理不规律的时间序列。这些字符串也可以传递给resample。例如,我们可以计算20天的滚动均值,如下所示:

ca88yzc 135

另一种使用固定大小窗口及相等权数观测值的办法是,定义一个衰减因子(decay
factor)常量,以便使近期的观测值拥有更大的权数。衰减因子的定义方式有很多,比较流行的是使用时间间隔,它可以使结果兼容于窗口大小等于时间间隔的简单移动窗口(simple
moving window)函数。

由于指数加权统计会赋予近期的观测值更大的权数,因此相对于等权统计,它能“适应”更快的变化。

除了rolling和expanding,pandas还有ewm运算符。下面这个例子对比了苹果公司股价的30日移动平均和span=30的指数加权移动平均:

ca88yzc 136
简单移动平均与指数加权移动平均

有些统计运算(如相关系数和协方差)需要在两个时间序列上执行。例如,金融分析师常常对某只股票对某个参考指数(如标准普尔500指数)的相关系数感兴趣。要进行说明,我们先计算我们感兴趣的时间序列的百分数变化:

ca88yzc 137

调用rolling之后,corr聚合函数开始计算与spx_rets滚动相关系数:

ca88yzc 138AAPL
6个月的回报与标准普尔500指数的相关系数

假设你想要一次性计算多只股票与标准普尔500指数的相关系数。虽然编写一个循环并新建一个DataFrame不是什么难事,但比较啰嗦。其实,只需传入一个TimeSeries和一个DataFrame,rolling_corr就会自动计算TimeSeries(本例中就是spx_rets)与DataFrame各列的相关系数:

ca88yzc 1393只股票6个月的回报与标准普尔500指数的相关系数

rolling_apply函数使你能够在移动窗口上应用自己设计的数组函数。唯一要求的就是:该函数要能从数组的各个片段中产生单个值。比如说,当我们用rolling.quantile计算样本分位数时,可能对样本中特定值的百分等级感兴趣。scipy.stats.percentileofscore函数就能达到这个目的:

ca88yzc 140AAPL
2%回报率的百分等级

快速学习:

第一节 NumPy基础

第二节 NumPy基础

第三节 Pandas入门基础

第四节 数据加载、存储

第五节 数据清洗

第六节 数据合并、重塑

第七节 数据聚合与分组运算

第八节 数据可视化

第九节 pandas高级应用

第十节 时间序列

第十一节 Python建模库

数据分析案例–1880-2010年间全美婴儿姓名的处理

数据分析案例–MovieLens 1M数据集

数据分析案例–USA.gov数据

数据分析案例–2012联邦选举委员会数据库

数据分析案例–USDA食品数据库

相关文章

Leave a Reply

电子邮件地址不会被公开。 必填项已用*标注