iOS NSCalendar 日历【转】

NSCalendar 对世界上现存的常用的历法进行了封装,既提供了不同历法的时间信息,又支持日历的计算。可以很方便的用来表示日期,获取日期的各种信息,包括年、月、日,时分秒。可以很方便的计算两个日期之前的关系。也可以很方便的根据一个日期获取另一个日期。

  • NSCalendar -- 日历类,它提供了大部分的日期计算接口,并且允许您在NSDate和NSDateComponents之间转换
  • NSTimeZone -- 时区信息
  • NSLocale -- 本地化信息
  • NSDate -- 表示一个绝对的时间点
  • NSDateComponents -- 一个封装了具体年月日、时秒分、周、季度等的类
  • NSDateFormatter -- 用来在日期和字符串之间转换

1. 日历的创建

根据提供的日历标示符初始化。
identifier 的范围可以是:

        NSCalendarIdentifierGregorian         公历
        NSCalendarIdentifierBuddhist          佛教日历
        NSCalendarIdentifierChinese           中国农历
        NSCalendarIdentifierHebrew            希伯来日历
        NSCalendarIdentifierIslamic           伊斯兰日历
        NSCalendarIdentifierIslamicCivil      伊斯兰教日历
        NSCalendarIdentifierJapanese          日本日历
        NSCalendarIdentifierRepublicOfChina   中华民国日历(台湾)
        NSCalendarIdentifierPersian           波斯历
        NSCalendarIdentifierIndian            印度日历
        NSCalendarIdentifierISO8601           ISO8601
// 使用用户手机设置的日期信息,有缓存,用户手机日历改变后不会变
@property (class, readonly, copy) NSCalendar *currentCalendar;  
// 使用用户手机设置的日期信息,并且用户改变之后会跟着改变
@property (class, readonly, strong) NSCalendar *autoupdatingCurrentCalendar API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0)); // tracks changes to user's preferred calendar identifier
// 使用指定的标识获取日期,比如农历、佛历,常用的是格里高利历(NSCalendarIdentifierGregorian)
+ (nullable NSCalendar *)calendarWithIdentifier:(NSCalendarIdentifier)calendarIdentifierConstant API_AVAILABLE(macos(10.9), ios(8.0), watchos(2.0), tvos(9.0));
- (nullable id)initWithCalendarIdentifier:(NSCalendarIdentifier)ident NS_DESIGNATED_INITIALIZER;
  • NSCalendar中有一个重要的概念NSCalendarUnit,这是一个位枚举,意味着作为参数可以采用位运算的方式传参。
  • 另外一个比较重要的类是NSDateComponents,上面是通过位参数获取每个单位(年月日)的信息,返回的结构可以看到是一个NSDateComponents,如果我要表示一个日期的信息,构建日期或者进行日期的计算,就少不了NSDateComponents,它可以将日期按照单位的形式封装起来,然后通过NSCalendar的方法进行计算
NSDateComponents *comps = [NSCalendar.currentCalendar components:NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay fromDate:[NSDate date]];

2. 日历的设置

NSCalendar *calendar = [NSCalendar currentCalendar];
    
    // 获取日历标示符
NSString *calendarIdentifier =  calendar.calendarIdentifier;
    
    // 获取地区信息
    /*
        语言地区
    */
NSString *localeIdentifier = calendar.locale.localeIdentifier;
NSString *localeIdentifier2 = [calendar.locale objectForKey:NSLocaleIdentifier];
        
    // 获取时区信息
    NSTimeZone *timeZone = calendar.timeZone;
        
    // 获取每周的第一天从星期几开始
    /*
        缺省为星期天
    */
    NSUInteger firstWeekday = calendar.firstWeekday;
        
    // 获取第一周必须包含的最少天数
    /*
        缺省为 1
    */
    NSUInteger minimumDaysInFirstWeek = calendar.minimumDaysInFirstWeek;

3. 日历信息的获取

1) 获取一个小的单位在一个大的单位里面的序数
NSUInteger count = [calendar ordinalityOfUnit:NSCalendarUnitWeekday
inUnit:NSCalendarUnitWeekOfMonth
forDate:[NSDate date]];

  • (NSUInteger)ordinalityOfUnit:(NSCalendarUnit)smaller inUnit:(NSCalendarUnit)larger forDate:(NSDate *)date;

    NSCalendarUnit包含的值有:

      NSCalendarUnitEra                 -- 纪元单位。对于 NSGregorianCalendar (公历)来说,只有公元前(BC)和公元(AD);
                                           而对于其它历法可能有很多,例如日本和历是以每一代君王统治来做计算。
      NSCalendarUnitYear                -- 年单位。值很大,相当于经历了多少年,未来多少年。
      NSCalendarUnitMonth               -- 月单位。范围为1-12
      NSCalendarUnitDay                 -- 天单位。范围为1-31
      NSCalendarUnitHour                -- 小时单位。范围为0-24
      NSCalendarUnitMinute              -- 分钟单位。范围为0-60
      NSCalendarUnitSecond              -- 秒单位。范围为0-60
      NSCalendarUnitWeekOfMonth / NSCalendarUnitWeekOfYear -- 周单位。范围为1-53
      NSCalendarUnitWeekday             -- 星期单位,每周的7天。范围为1-7
      NSCalendarUnitWeekdayOrdinal      -- 没完全搞清楚
      NSCalendarUnitQuarter             -- 几刻钟,也就是15分钟。范围为1-4
      NSCalendarUnitWeekOfMonth         -- 月包含的周数。最多为6个周
      NSCalendarUnitWeekOfYear          -- 年包含的周数。最多为53个周
      NSCalendarUnitYearForWeekOfYear   -- 没完全搞清楚
      NSCalendarUnitTimeZone            -- 没完全搞清楚
    

    <1>、当小单位为 NSCalendarUnitWeekday,大单位为 NSCalendarUnitWeekOfMonth / NSCalendarUnitWeekOfYear 时
    (即某个日期在这一周是第几天),根据 firstWeekday 属性不同,返回的结果也不同。具体说明如下:

      当 firstWeekday 被指定为星期天(即 = 1)时,它返回的值与星期几对应的数值保持一致。比如:
          fromDate 传入的参数是星期日,则函数返回 1
          fromDate 传入的参数是星期一,则函数返回 2
      当 firstWeekday 被指定为其它值时(即 <> 1)时,假设firstWeekday 被指定为星期一(即 = 2),那么:
          fromDate 传入的参数是星期一,则函数返回 1
          fromDate 传入的参数是星期二,则函数返回 2
          fromDate 传入的参数是星期日,则函数返回 7
    

    <2>、当小单位为 参数为 NSCalendarUnitWeekOfMonth / NSCalendarUnitWeekOfYear,大单位为 NSCalendarUnitYear 时
    (即某个日期在这一年中是第几周),minimumDaysInFirstWeek 属性影响它的返回值。具体说明如下:

          2005年1月
          日   一    二   三   四    五   六
          --------------------------------
                                        1
          2    3    4    5    6    7    8
          9    10   11   12   13   14   15
          16   17   18   19   20   21   22
          23   24   25   26   27   28   29
          30   31
    
          2005年1月第一周包括1号。
          a. 如果将 minimumDaysInFirstWeek 设定 = 1
          则 fromDate 传入1月1号,方法均返回1  ==> 满足 minimumDaysInFirstWeek 指定的天数(最少1天),所以方法将其归为
                                                2005年的第1周
          则 fromDate 传入1月2-8号,方法均返回2
          则 fromDate 传入1月9-15号,方法均返回3
          ......
    
          b. 如果将 minimumDaysInFirstWeek 设定为 > 1,比如2
          则 fromDate 传入1月1号,方法均返回53  ==> 不足2天,所以方法将其归为2004年的第53周
          则 fromDate 传入1月2-8号,方法均返回1
          则 fromDate 传入1月9-15号,方法均返回2
          ......
    
          2008年1月
          日   一    二   三   四    五   六
          ---------------------------------
                    1    2    3    4    5
          6    7    8    9    10   11   12
          13   14   15   16   17   18   19
          20   21   22   23   24   25   26
          27   28   29   30   31
    
          2005年1月第一周包括1-5号共5天。
          a. 如果将 minimumDaysInFirstWeek 设定为 <= 5时
          则 fromDate 传入1月1-5号,方法均返回1  ==> 满足 minimumDaysInFirstWeek 指定的天数,所以方法将其归为2008年的第1周
          则 fromDate 传入1月6-12号,方法均返回2
          则 fromDate 传入1月13-19号,方法均返回3
          ......
    
          b. 如果将 minimumDaysInFirstWeek 设定为 > 5,比如6
          则 fromDate 传入1月1-5号,方法均返回53  ==> 当周不足6天,所以方法将其归为2007年的第53周
          则 fromDate 传入1月2-8号,方法均返回1
          则 fromDate 传入1月9-15号,方法均返回2
          ......
    

    <3>、当小单位为 参数为 NSCalendarUnitWeekOfMonth / NSCalendarUnitWeekOfYear,大单位为 NSCalendarUnitMonth 时
    (即某个日期在这一个月中是第几周),minimumDaysInFirstWeek 属性影响它的返回值。具体说明如下:

          2008年4月
          日   一    二   三   四    五   六
          ---------------------------------
                    1    2    3    4    5
          6    7    8    9    10   11   12
          13   14   15   16   17   18   19
          20   21   22   23   24   25   26
          27   28   29   30
    
          2008年4月第一周包括1、2、3、4、5号。
          a. 如果将 minimumDaysInFirstWeek 设定为小于或等于5的数
          则 fromDate 传入4月1-5号,方法均返回1
          则 fromDate 传入4月6-12号,方法均返回2
          则 fromDate 传入4月13-19号,方法均返回3
          ....
    
          b. 如果将 minimumDaysInFirstWeek 设定为大于5的数
          则 fromDate 传入1-5号,方法均返回0
          则 fromDate 传入6-12号,方法均返回1
          则 fromDate 传入13-19号,方法均返回2
          ....
    

2)获取一个小的单位在一个大的单位里面的取值范围

NSRange range = [calendar rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:[NSDate date]];
NSLog(@"%zi -- %zi", range.location, range.length);
- (NSRange)rangeOfUnit:(NSCalendarUnit)smaller inUnit:(NSCalendarUnit)larger forDate:(NSDate *)date;

调用这个方法要明确一点,取得的是"范围"而不是"包含",下面是一些例子:

<1>、小单位是 NSCalendarUnitDay,大单位是 NSCalendarUnitYear,并不是要取这一年包含多少天,而是要取"天"(Day)这个单位
     在这一年(Year)的取值范围。其实不管你提供的日期是多少,返回的值都是"1--31"。

<2>、小单位是 NSCalendarUnitDay,大单位是 NSCalendarUnitMonth。要取得参数时间点所对应的月份下,"天"(Day)的取值范围。
     根据参数时间的月份不同,值也不同。例如2月是1--28、3月是 1--31、4月是1--30。

<3>、小单位是 NSCalendarUnitWeekOfMonth / NSCalendarUnitWeekOfYear,大单位是 NSCalendarUnitMonth。要取得参数
     时间点所对应的月份下,"周"(Week)的取值范围。需要注意的是结果会受到 minimumDaysInFirstWeek 属性的影响。在默认 
     minimumDaysInFirstWeek 情况下,取得的范围值一般是"1--5",从日历上可以看出来这个月包含5排,即5个周。

<4>、小单位是 NSCalendarUnitDay,大单位是 NSCalendarUnitWeekOfMonth / NSCalendarUnitWeekOfYear。要取得周所包含
     的"天"(Day)的取值范围。下面是一个示例日历图:

        2013年4月
        日   一    二   三   四    五   六
        ---------------------------------
             1    2    3    4    5    6    
        7    8    9    10   11   12  13   
        14   15   16   17   18   19  20   
        21   22   23   24   25   26  27   
        28   29   30

        在上图的日期条件下,假如提供的参数是4月1日--4月6日,那么对应的 week 就是1(第一个周),可以看到第一个周包含有6天,
    从1号开始,那么最终得到的范围值为1--6。

    假如提供的参数是4月18日,那么对应的 week 是3(第三个周),第三个周包含有7天,从14号开始,那么最终得到的范围值是14--7。

    假如提供的参数是4月30日,那么对应的 week 是5(第五个周),第五个周只包含3天,从28号开始,那么最终得到的范围值是28--3。

3)获取所在日历单位的开始时间及所在日历单位的总秒数

NSDate *startDate = nil;
NSTimeInterval intervalCount = 0;

BOOL bl = [calendar rangeOfUnit:NSCalendarUnitMonth 
                      startDate:&startDate 
                       interval:&intervalCount 
                        forDate:[NSDate date]];

if (bl) {

    // 得到本地时间,避免时区问题
    startDate = [startDate dateByAddingTimeInterval:[[NSTimeZone systemTimeZone] secondsFromGMTForDate:startDate]];

    NSLog(@"%@",startDate);
    NSLog(@"%f",intervalCount);
}
else {
    NSLog(@"无法计算");
}
//获取日期date对应月的第一天日期
+ (NSString *)getMonthFirstDayWithDate:(NSDate *)date format:(NSString *)aformat;
//获取日期date对应月的最后一天日期
+ (NSString *)getMonthLastDayWithDate:(NSDate *)date format:(NSString *)aformat;
//获取对应日期是周几
+ (NSInteger)getWeekDayFromDate:(NSDate *)date;


+ (NSString *)getMonthFirstDayWithDate:(NSDate *)date format:(NSString *)aformat{
    NSDate *newDate = date;
    double interval = 0;
    NSDate *firstDate = nil;
    NSCalendar *calendar = [NSCalendar currentCalendar];
    BOOL bl = [calendar rangeOfUnit:NSCalendarUnitMonth startDate:& firstDate interval:&interval forDate:newDate];
    if (bl) {
        NSDateFormatter *myDateFormatter = [[NSDateFormatter alloc] init];
        [myDateFormatter setDateFormat:aformat];
        NSString *firstString = [myDateFormatter stringFromDate: firstDate];
        return firstString;
    }
    return @"";
}
+ (NSString *)getMonthLastDayWithDate:(NSDate *)date format:(NSString *)aformat{
    NSDate *newDate = date;
    double interval = 0;
    NSDate *firstDate = nil;
    NSDate *lastDate = nil;
    NSCalendar *calendar = [NSCalendar currentCalendar];
    BOOL bl = [calendar rangeOfUnit:NSCalendarUnitMonth startDate:& firstDate interval:&interval forDate:newDate];
    if (bl) {
        lastDate = [firstDate dateByAddingTimeInterval:interval - 1];
        NSDateFormatter *myDateFormatter = [[NSDateFormatter alloc] init];
        [myDateFormatter setDateFormat:aformat];
        NSString *lastString = [myDateFormatter stringFromDate: lastDate];
        return lastString;
    }
    return @"";
}

+ (NSInteger)getWeekDayFromDate:(NSDate *)date{
    NSArray *tempWeek = @[@"7",@"1",@"2",@"3",@"4",@"5",@"6"];
    NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
    
    NSDateComponents *comps = [[NSDateComponents alloc] init];
    
    NSInteger unitFlags = NSCalendarUnitYear |NSCalendarUnitMonth | NSCalendarUnitDay |NSCalendarUnitWeekday | NSCalendarUnitHour |NSCalendarUnitMinute |NSCalendarUnitSecond;
    
    comps = [calendar components:unitFlags fromDate:date];
    //  1、2、3、4、5、6、7 分别对应 周日、周一、周二、周三、周四、周五、周六
    NSInteger week = [comps weekday];
    NSLog(@"---%ld",week);
    return  [tempWeek[week-1] integerValue] ;
    
}

参考资料:
QianChia NSCalendar - 日历

山有木枝壮 NSCalendar使用

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352