Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save coderChrisLee/e710e0de275fd27506fd5b7606945512 to your computer and use it in GitHub Desktop.
Save coderChrisLee/e710e0de275fd27506fd5b7606945512 to your computer and use it in GitHub Desktop.
日期和日历计算常用方法—代码示例参考自苹果官方文档
// Created by Chris Lee on 2018/5/25.
#import "DateAndTimeCalculateTools.h"
@interface DateAndTimeCalculateTools ()
//不要频繁创建日历对象。
@property(nonatomic, strong) NSCalendar *gregorian;
@end
@implementation DateAndTimeCalculateTools
- (instancetype)init {
if (self = [super init]) {
_gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
}
return self;
}
#pragma mark - 0.根据 时间间隔(秒) 去计算间隔多少的时间
- (NSDate *)calculateDateWithDate:(NSDate *)date andTimeIntervals:(NSTimeInterval)timeInterval {
NSTimeInterval secondsPerDay = 24 * 60 * 60;
// NSDate *tomorrow = [[NSDate alloc] initWithTimeIntervalSinceNow:secondsPerDay];
NSDate *yesterday = [[NSDate alloc] initWithTimeIntervalSinceNow:-secondsPerDay];
//等价算法
NSDate *today = [[NSDate alloc] init];
NSDate *tomorrow1, *yesterday1;
tomorrow1 = [today dateByAddingTimeInterval:secondsPerDay];
yesterday1 = [today dateByAddingTimeInterval:-secondsPerDay];
return yesterday;
}
//比较日期是否同一天
//isEqualToDate:, compare:, laterDate:, and earlierDate: 很精确比较到 sub-second
//如果比较两个日期不用太精确 一分钟之内 within a minute 则用
- (BOOL)compareOneDate:(NSDate *)date1 andDate2:(NSDate *)date2 {
if (fabs([date2 timeIntervalSinceDate:date1]) < 60) {
// …
return YES;
}
return NO;
}
//来源苹果官方文档 https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/DatesAndTimes/Articles/dtCalendars.html
////////强大的 NSCalendar(由 NSLocale决定是哪种日历) + NSDateComponents 去计算任何与年月日周时分秒相关的日期和时间。
//An NSDateComponents object can hold either absolute values or quantities of units。
//Day, week, weekday, month, and year numbers are generally 1-based,Ordinal numbers, where they occur, are 1-based.
#pragma mark - 1. 创建calendar对象
- (NSCalendar *)createCalendar{
//1. NSCalendar *currentCalendar = [NSCalendar currentCalendar];//the calendar for the user's preferred locale
//2. NSCalendar *japaneseCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierJapanese];
//the default calendar
NSCalendar *usersCalendar = [[NSLocale currentLocale] objectForKey:NSLocaleCalendar];
return usersCalendar;
//Here, usersCalendar and currentCalendar are equal, although they are different objects.
}
#pragma mark - 2.创建 a date components object
- (void)createDateComponents {
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setDay:6];
[components setMonth:5];
[components setYear:2004];
NSInteger weekday = [components weekday]; // Undefined (== NSUndefinedDateComponent)
NSLog(@"weekday:%ld", weekday);
//1.use to create the date 创建一个 年单位为2004、月单位为5,日单位为6的日期 供继续使用(in the Gregorian calendar this is May 6th, 2004)
//2.也可以use it to add 2004 year units, 5 month units, and 6 day units to an existing date.
}
#pragma mark - 3.date 和 dateComponents 的互转
//3.1 将一个日期分解成 dateComponents元素 用 NSCalendar的方法 components:fromDate:.但必须指明你感兴趣想获取的Calendar Units
- (void)onlyGetDayAndWeekday { //仅获取感兴趣的元素 今天是几号 是周几
NSDate *today = [NSDate date];
NSDateComponents *weekdayComponents = [_gregorian components:(NSCalendarUnitDay | NSCalendarUnitWeekday) fromDate:today];
NSInteger day = [weekdayComponents day];
NSInteger weekday = [weekdayComponents weekday];
NSLog(@"day:%ld, weekday:%ld",day, weekday);
//通过此方法 gives you the absolute components for a date.
}
/*3.2 创建一个希望得到的日期 通过配置 configure an instance of NSDateComponents to specify the components of a date
加上 NSCalendar 的方法 dateFromComponents: to create the corresponding date
// You can provide as many components as you need (or choose to). When there is incomplete information to compute an absolute time, default values such as 0 and 1 are usually chosen by a calendar, but this is a calendar-specific choice.
如果提供不一致有矛盾的信息 日历会消歧 去掉一些参数。
*/
// create a date object to represent (in the Gregorian calendar) the first Monday in May, 2008.
- (NSDate *)createOneDateFromComponents { // 创建2008年5月第一个周一 对应的日期。
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setWeekday:2]; // Monday
[components setWeekdayOrdinal:1]; // The first Monday in the month 第一个周一、
[components setMonth:5]; // May
[components setYear:2008];
NSDate *date = [_gregorian dateFromComponents:components];
return date;
//必须保证components有意义 you must make sure that the components used make sense for the calendar.
//Specifying “out of bounds” components—such as a day value of -6 or February 30th in the Gregorian calendar—produce undefined behavior.
}
- (NSDate *)creatingAYearlessDate { //创建一个不带年的日期 比如生日。
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setMonth:11];
[components setDay:7];
NSDate *birthday = [_gregorian dateFromComponents:components];
return birthday;
//注意:birthday in this instance has the default value for the year, which in this case is 1 CE。
//如果需要使用 NSDateFormatter object to display it,make sure to not use the year value
//可以通过 NSDateFormatter dateFormatFromTemplate:options:locale: method to create a yearless date formatter that adjusts to the users locale.
}
//警告:如果混合 week-based calendar constants (NSWeekOfMonthCalendarUnit, NSWeekOfYearCalendarUnit, and NSYearForWeekOfYearCalendarUnit) with other week-oriented calendar constants will result in errors.
#pragma mark - 4.日历互转 Converting from One Calendar to Another
/*To convert components of a date from one calendar to another—for example, from the Gregorian calendar to the Hebrew calendar—you first create a date object from the components using the first calendar, then you decompose the date into components using the second calendar. 示例代码如下:
*/
- (void)convertOneDateComponentToAnother { //将一个日历转化为另一个日历
NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setDay:6];
[comps setMonth:5];
[comps setYear:2004];
NSDate *date = [_gregorian dateFromComponents:comps];
NSCalendar *hebrew = [[NSCalendar alloc]
initWithCalendarIdentifier:NSCalendarIdentifierHebrew];
NSUInteger unitFlags = NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear;
NSDateComponents *components = [hebrew components:unitFlags fromDate:date];
NSInteger day = [components day]; // 15
NSInteger month = [components month]; // 9
NSInteger year = [components year]; // 5764
NSLog(@"day:%ld, month:%ld, year: %ld",day, month, year);
}
#pragma mark - 5.日历算法 Performing Calendar Calculations
/* NSDate提供一个绝对的时间点,可以渲染到一个特定的日历中作日历计算或者显示。作日历计算,通常需要获取某个date的日历元素(如 年、月、日),利用日历计算提供的方法,因为日历算法考虑了 边角案例、临界情况(夏令时daylight savings、 闰年leap years、闰月等)
NSDate provides the absolute scale and epoch for dates and times, which can then be rendered into a particular calendar for calendrical calculations or user display. To perform calendar calculations, you typically need to get the component elements of a date, such as the year, the month, and the day. You should use the provided methods for dealing with calendrical calculations because they take into account corner cases like daylight savings time starting or ending and leap years.
*/
//5.1 Adding Components to a Date
//使用方法此增加若干月、若干小时到一个指定date。dateByAddingComponents:toDate:options: method to add components of a date (such as hours or months) to an existing date
//Components 支持负号。Components to add can be negative.
- (NSDate *)addingOneHourAndHalfToNow { //得到一个半小时后的时间。
NSDate *today = [[NSDate alloc] init];
NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
[offsetComponents setHour:1];
[offsetComponents setMinute:30];
// Calculate when, according to Tom Lehrer, World War III will end
NSDate *endOfWorldWar3 = [_gregorian dateByAddingComponents:offsetComponents toDate:today options:0];
return endOfWorldWar3; //An hour and a half from now
}
- (NSDate *)gettingSundayInCurrentWeek { //计算当前周的周日
NSDate *today = [[NSDate alloc] init];
// Get the weekday component of the current date 获得当前是周几
NSDateComponents *weekdayComponents = [_gregorian components:NSCalendarUnitWeekday fromDate:today];
/*创建一个新的date components代表 需要从当前日期减去的天数。
Create a date components to represent the number of days to subtract from the current date.
The weekday value for Sunday in the Gregorian calendar is 1, so subtract 1 from the number of days to subtract from the date in question. (If today is Sunday, subtract 0 days.)
*/
NSDateComponents *componentsToSubtract = [[NSDateComponents alloc] init];
[componentsToSubtract setDay:(0 - ([weekdayComponents weekday] - 1))]; // 负号 表示根据当前周几 向前减几天 得到周日。
NSDate *beginningOfWeek = [_gregorian dateByAddingComponents:componentsToSubtract toDate:today options:0];
/*
Optional step: 可选操作
beginningOfWeek now has the same hour, minute, and second as the original date (today).
To normalize to midnight, extract the year, month, and day components and create a new date from those components.
*/
NSDateComponents *components = [_gregorian components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:beginningOfWeek];
beginningOfWeek = [_gregorian dateFromComponents:components];
return beginningOfWeek;
}
//Sunday is not the beginning of the week in all locales. how you can calculate the first moment of the week (as defined by the calendar's locale):周日并非是所有本地设置日历的第一天,用下面方法根据日历计算周的开始。
- (NSDate *)gettingTheBeginningDateOfOneWeek:(NSDate *)weekDate {
NSDate *today = [[NSDate alloc] init];
NSDate *beginningOfWeekOfMonth = nil; //月的 周单位
NSDate *beginningOfWeekOfYear = nil; //年的 周单位
BOOL ok = [_gregorian rangeOfUnit:NSCalendarUnitWeekOfMonth startDate:&beginningOfWeekOfMonth interval:NULL forDate:today];
ok = [_gregorian rangeOfUnit:NSCalendarUnitWeekOfYear startDate:&beginningOfWeekOfYear interval:NULL forDate:today];
return beginningOfWeekOfMonth;
}
//5.2 计算时间的差别、间隔 Determining Temporal Differences
// Since Cocoa implements time according to the NTP standard, these methods ignore leap seconds in the calculation.
/*
a.如果以秒计算差别 use the NSDate method timeIntervalSinceDate:
b.以其他单位计算差别 use components:fromDate:toDate:options: to determine the temporal difference between two dates in units other than seconds
*/
//how to get the number of months and days between two dates using a Gregorian calendar.
- (void)gettingTheDifferenceBetweenOneDate:(NSDate *)startDate anotherDate:(NSDate *)endDate { //计算两个日期间隔多少月多少天
NSUInteger unitFlags = NSCalendarUnitMonth | NSCalendarUnitDay;
NSDateComponents *components = [_gregorian components:unitFlags fromDate:startDate toDate:endDate options:0];
NSInteger months = [components month];
NSInteger days = [components day];
NSLog(@"两日期间隔 months:%ld, days:%ld",months, days);
//This method handles overflow as you may expect.
// If the fromDate: and toDate: parameters are a year and 3 days apart and you ask for only the days between, it returns an NSDateComponents object with a value of 368 (or 369 in a leap year) for the day component.
//However, this method truncates the results of the calculation to the smallest unit supplied.
// For instance, if the fromDate: parameter corresponds to Jan 14, 2010 at 11:30 PM and the toDate: parameter corresponds to Jan 15, 2010 at 8:00 AM, there are only 8.5 hours between the two dates. If you ask for the number of days, you get 0, because 8.5 hours is less than 1 day. There may be situations where this should be 1 day.
}
//5.3 判断日期是否在一个范围 Checking When a Date Falls
//判断一个日期是否在本周或者其他单位范围内。If you need to determine if a date falls within the current week (or any unit for that matter) you can make use of the NSCalendar method rangeOfUnit:startDate:interval:forDate:.
//shows a method that determines if a given date falls within this week. The week in this case is defined as the period between Sunday at midnight to the following Saturday just before midnight (in the Gregorian calendar).
- (BOOL)isDateThisWeek:(NSDate *)date {
NSDate *start;
NSTimeInterval extends;
NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
NSDate *today = [NSDate date];
BOOL success = [calendar rangeOfUnit:NSCalendarUnitWeekOfMonth startDate:&start interval:&extends forDate:today];
if(!success) {
return NO;
}
NSTimeInterval dateInSecs = [date timeIntervalSinceReferenceDate];
NSTimeInterval dayStartInSecs = [start timeIntervalSinceReferenceDate];
return dateInSecs > dayStartInSecs && dateInSecs < (dayStartInSecs + extends);
}
/*
Week-Based Calendars
A week-based calendar is defined by the weeks of a year. Instead of the year, month, and day of a date, a week-based calendar is defined by the week-year, the week number, and a weekday.
However, this can be complicated when the first week of the calendar overlaps(重叠) the last week of the previous year’s calendar. In this case there are two important properties of the calendar:
1. What is the first day of the week?
2. How many days does a week near the beginning of the year have to have within the ordinary calendar year for it to be considered the first week in the week-based calendar year?
A week-based calendar's first day of the year is on the first day of the week. The first week is preferred to be the week containing Jan 1 if that week satisfies the defined answer for the second point above.
For example, suppose the first day of the week is defined as Monday, in a week-based calendar interpretation of the Gregorian calendar. Consider the 2009/2010 transition shown in Table 1 and Table 2:
//Table 1 December 2009 Calendar
Sunday Monday Tuesday Wednesday Thursday Friday Saturday
20 21 22 23 24 25 26
27 28 29 30 31
Table 2 January 2010 Calendar
Sunday Monday Tuesday Wednesday Thursday Friday Saturday
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
Since the first day of the week is Monday, the 2010 week-based calendar year can begin either December 28 or January 4. That is, December 30, 2009 (ordinary) could be December 30, 2010 (week-based).
To choose between these two possibilities, there is the second criterion. Week Dec 28 - Jan 3 has 3 days in 2010. Week Jan 4-Jan 10 has 7 days in 2010.
If the minimum number of days in a first week is defined as 1 or 2 or 3, the week of Dec 28 satisfies the first week criteria and would be week 1 of the week-based calendar year 2010. Otherwise, the week of Jan 4 is the first week.
As another example, suppose you wanted to define a week-based calendar such that the first week of the calendar year begins with the first occurrence of a specific weekday.
In Table 2 Monday January 4 is the first Monday of the ordinary year, so the week-based calendar begins on that day. What you are requesting then is that the first week of your week-based calendar is entirely within the new ordinary year or that the minimum number of days in first week is 7.
The NSYearForWeekOfYearCalendarUnit is the year number of a week-based calendar interpretation of the calendar you're working with, where the two properties of the week-based calendar discussed in above correspond to these two NSCalendar properties: firstWeekday and minimumDaysInFirstWeek.
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/DatesAndTimes/Articles/dtCalendricalCalculations.html
*/
@end
//If you do need to have a calculation that returns the number of days, calculated by the number of midnights between the two dates
@implementation DateAndTimeCalculateTools (MyDateBetweensCalculate)
// Days between two dates, as the number of midnights between
- (NSInteger)daysWithinEraFromDate:(NSDate *)startDate toDate:(NSDate *)endDate { //计算两个日期 相差多少天
NSInteger startDay = [_gregorian ordinalityOfUnit:NSCalendarUnitDay inUnit: NSCalendarUnitEra forDate:startDate];
NSInteger endDay = [_gregorian ordinalityOfUnit:NSCalendarUnitDay inUnit: NSCalendarUnitEra forDate:endDate];
return endDay - startDay;
//Do not use this method for comparing second differences because it overflows NSInteger on 32-bit platforms.
//该方法不能跨世纪计算 This method is only valid if you stay within the same era (in the Gregorian Calendar this means that both dates must be CE or both must be BCE).
}
@end
//If you do need to compare dates across an era boundary you can use something similar to the category
@implementation DateAndTimeCalculateTools (MyAcrossEraCalculate)
- (NSInteger)daysFromDate:(NSDate *)startDate toDate:(NSDate *)endDate {
NSCalendarUnit units = NSCalendarUnitEra | NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay;
NSDateComponents *comp1 = [_gregorian components:units fromDate:startDate];
NSDateComponents *comp2 = [_gregorian components:units fromDate:endDate];
[comp1 setHour:12];
[comp2 setHour:12];
NSDate *date1 = [_gregorian dateFromComponents: comp1];
NSDate *date2 = [_gregorian dateFromComponents: comp2];
//支持跨世纪日期 计算间隔多少天
return [[_gregorian components:NSCalendarUnitDay fromDate:date1 toDate:date2 options:0] day];
//This method creates components from the given dates, and then normalizes the time and compares the two dates. This calculation is more expensive than comparing dates within an era. 如果非跨世纪计算间隔 用上面的MyDateBetweensCalculate类别方法。
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment