-
-
Save drunknbass/6626126 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The purpose of this gist is to provide examples of many real life date computations | |
// This is uploaded as a gist so that it can be copy and pasted into either a OS X or | |
// iOS project. You can either take it all "[Cmd]+[A], [Cmd]+[C]" or just a small piece. | |
// The results are fairly well formattes as log output and I recommend that you run this | |
// code to see the actual results yourself. Change some of the values and see what happens. | |
#pragma mark - Setup | |
NSString *line = @"–––––––––––––––––––––––––––––––––––––––––––––––––––––––"; | |
// So that you will get the same components as I do when you run this I will create a | |
// date representing today (or to be more precise when the blog post is published) | |
NSDateComponents *blogPostPublicationDateComponents = [NSDateComponents new]; | |
blogPostPublicationDateComponents.year = 2013; | |
blogPostPublicationDateComponents.month = 9; | |
blogPostPublicationDateComponents.day = 18; | |
blogPostPublicationDateComponents.hour = 20; | |
blogPostPublicationDateComponents.minute = 10; | |
blogPostPublicationDateComponents.second = 33; | |
// I'm also creating a specific calendar (Gregorian) so that the results are reproducable. | |
// Generally you would use `currentCalendar` or `autoupdatingCurrentCalendar` in real life. | |
NSCalendar *calendar = [NSCalendar calendarWithIdentifier:NSGregorianCalendar]; | |
calendar.locale = [NSLocale localeWithLocaleIdentifier:@"se"]; | |
// This is representing today. | |
NSDate *today = [calendar dateFromComponents:blogPostPublicationDateComponents]; | |
#pragma mark - Formatting dates | |
NSLog(@" "); | |
NSLog(@"%@", line); | |
NSLog(@"Formatting dates"); | |
NSLog(@"%@", line); | |
// This part is all about date formatters. It shows some of the more common ways to write | |
// custom date formats to convert dates to string and vice versa. | |
// Through out all the formatting examples I'm using the same date (it makes it easier to | |
// notice patterns. | |
NSDateComponents *birthDayComponents = [NSDateComponents new]; | |
birthDayComponents.year = 1987; | |
birthDayComponents.month = 8; | |
birthDayComponents.day = 27; | |
// Time is actually wrong but I wanted an hour >12 to show difference between `HH` and `hh` | |
birthDayComponents.hour = 15; | |
birthDayComponents.minute = 24; | |
birthDayComponents.second = 3; // Only one digit. Pay attention to `ss` and `s` | |
NSDate *formattingExampleDate = [calendar dateFromComponents:birthDayComponents]; | |
NSDateFormatter *singleComponentFormatter = [NSDateFormatter new]; | |
singleComponentFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"us"]; | |
// I'll be enumerating all these single component formats | |
NSArray *singleFormats = @[@"yy", | |
@"yyyy", | |
@"M", | |
@"MM", | |
@"MMM", | |
@"MMMM", | |
@"dd", | |
@"HH", | |
@"hh", | |
@"h", | |
@"a", | |
@"mm", | |
@"m", | |
@"ss", | |
@"s"]; | |
NSLog(@" "); | |
NSLog(@" format | result"); | |
NSLog(@"––––––––|––––––––"); | |
for (NSString *singleFormat in singleFormats) { | |
singleComponentFormatter.dateFormat = singleFormat; | |
NSString *result = [singleComponentFormatter stringFromDate:formattingExampleDate]; | |
NSLog(@" %@ | %@", | |
[singleFormat stringByPaddingToLength:6 withString:@" " startingAtIndex:0], | |
result); | |
} | |
// In this part I'm making two complete date format strings that both include date and time. | |
NSDateFormatter *resonableFormatter = [NSDateFormatter new]; | |
resonableFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"us"]; | |
resonableFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss"; | |
NSDateFormatter *doNotDoThisFormatter = [NSDateFormatter new]; | |
doNotDoThisFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"us"]; | |
doNotDoThisFormatter.dateFormat = @"h:mm a dd/MMM (yy)"; | |
NSLog(@" "); | |
NSLog(@" format | result"); | |
NSLog(@"–––––––––––––––––––––––|––––––––––––––––––––––"); | |
NSLog(@" %@ | %@", | |
[resonableFormatter.dateFormat stringByPaddingToLength:21 withString:@" " startingAtIndex:0], | |
[resonableFormatter stringFromDate:formattingExampleDate]); | |
NSLog(@" %@ | %@", | |
[doNotDoThisFormatter.dateFormat stringByPaddingToLength:21 withString:@" " startingAtIndex:0], | |
[doNotDoThisFormatter stringFromDate:formattingExampleDate]); | |
NSLog(@" "); | |
NSLog(@"Parsing a string"); | |
// This time a date is converted from one format to another | |
NSString *todaysDateAsString = @"8:10 PM 18/Sep (13)"; | |
// Use the correct formatter to create a date | |
NSDate *dateFromString = [doNotDoThisFormatter dateFromString:todaysDateAsString]; | |
NSLog(@"The string \"%@\" is converted to: \"%@\"", | |
todaysDateAsString, | |
[resonableFormatter stringFromDate:dateFromString]); // Use the other formatter to make a new string | |
#pragma mark - Adding time | |
NSLog(@" "); | |
NSLog(@"%@", line); | |
NSLog(@"Adding time"); | |
NSLog(@"%@", line); | |
// These examples show how you can add "one day" to another day. The point is to show that | |
// adding time in smaller components does not equal one larger component. | |
// It is a common misconception that 1 day == 24 h. This does however not work with DLS (day light savings) | |
// Create components corresponding to "1 day" | |
NSDateComponents *oneDay = [NSDateComponents new]; | |
oneDay.day = 1; | |
NSDate *now = [NSDate date]; // Not a typo. I'm fine with this varying as you run the code on any day | |
NSDate *sameTimeTomorrow = [calendar dateByAddingComponents:oneDay | |
toDate:now | |
options:0]; | |
NSLog(@"%@ plus \"one day\" is %@", | |
[resonableFormatter stringFromDate:now], | |
[resonableFormatter stringFromDate:sameTimeTomorrow]); | |
NSLog(@" "); | |
NSLog(@"Day lights saving"); | |
// In the locale of this calendar day light savings ends at Oct 27 03:00 | |
// Adding 1 day and 24 hours will give different results. | |
// For completions sake I've also included 60*60*24 seconds. Please never do that in real life. | |
// A little piece of my dies everytime I see someone recommend that to someone else. | |
// This date is just hours before the end of DLS | |
NSDateComponents *beforeDayLightSavingsEndComponents = [NSDateComponents new]; | |
beforeDayLightSavingsEndComponents.year = 2013; | |
beforeDayLightSavingsEndComponents.month = 10; | |
beforeDayLightSavingsEndComponents.day = 26; | |
beforeDayLightSavingsEndComponents.hour = 12; | |
beforeDayLightSavingsEndComponents.minute = 34; | |
beforeDayLightSavingsEndComponents.second = 56; | |
NSDate *beforeDayLightSavingsEnd = [calendar dateFromComponents:beforeDayLightSavingsEndComponents]; | |
NSDateComponents *twentyFourHours = [NSDateComponents new]; | |
twentyFourHours.hour = 24; | |
NSTimeInterval manySeconds = 60*60*24; // Again: please don't use this as "one day". It is wrong. | |
NSLog(@"Before adding: %@", [resonableFormatter stringFromDate:beforeDayLightSavingsEnd]); | |
NSDate *plusOneDay = [calendar dateByAddingComponents:oneDay toDate:beforeDayLightSavingsEnd options:0]; | |
NSLog(@"plus one day: %@", [resonableFormatter stringFromDate:plusOneDay]); | |
NSDate *plus24Hours = [calendar dateByAddingComponents:twentyFourHours toDate:beforeDayLightSavingsEnd options:0]; | |
NSLog(@"plus 24 hours: %@", [resonableFormatter stringFromDate:plus24Hours]); | |
NSDate *plusSeconds = [beforeDayLightSavingsEnd dateByAddingTimeInterval:manySeconds]; | |
NSLog(@"plus 24*60*60 sec: %@", [resonableFormatter stringFromDate:plusSeconds]); | |
NSLog(@" "); | |
NSLog(@"Two months != one month + one month"); | |
// This is ment to show that it's not as simple as 1+1 = 2 when working with calendars. | |
// January and March have 31 days but February doesn't. | |
NSDateComponents *oneMonth = [NSDateComponents new]; | |
oneMonth.month = 1; | |
NSDateComponents *twoMonths = [NSDateComponents new]; | |
twoMonths.month = 2; | |
NSDateComponents *endOfJanuaryComponents = [NSDateComponents new]; | |
endOfJanuaryComponents.year = 2013; | |
endOfJanuaryComponents.month = 1; | |
endOfJanuaryComponents.day = 31; | |
endOfJanuaryComponents.hour = 12; | |
NSDate *endOfJanuary = [calendar dateFromComponents:endOfJanuaryComponents]; | |
NSDate *plusTwoMonths = [calendar dateByAddingComponents:twoMonths toDate:endOfJanuary options:0]; | |
NSLog(@"plus two months at once: %@", [resonableFormatter stringFromDate:plusTwoMonths]); | |
NSDate *plusOneMonth = [calendar dateByAddingComponents:oneMonth toDate:endOfJanuary options:0]; | |
NSDate *plusOneMonthAgain = [calendar dateByAddingComponents:oneMonth toDate:plusOneMonth options:0]; | |
NSLog(@"plus one month twice: %@", [resonableFormatter stringFromDate:plusOneMonthAgain]); | |
#pragma mark - Checking if it is today | |
NSLog(@" "); | |
NSLog(@"%@", line); | |
NSLog(@"Checking if it is today"); | |
NSLog(@"%@", line); | |
// There are many wats to check if a date is sometime today. Here I am using 2 | |
// 1. checking relevant components | |
// 2. checking the begining and end | |
// First create one date that should be today and one that shouldn't | |
NSDateComponents *shouldBeTodayComponents = [NSDateComponents new]; | |
shouldBeTodayComponents.year = 2013; | |
shouldBeTodayComponents.month = 9; | |
shouldBeTodayComponents.day = 18; | |
shouldBeTodayComponents.hour = 7; | |
NSDate *shouldBeToday = [calendar dateFromComponents:shouldBeTodayComponents]; | |
NSDateComponents *notTodayComponents = [NSDateComponents new]; | |
notTodayComponents.year = 2013; | |
notTodayComponents.month = 7; | |
notTodayComponents.day = 8; | |
notTodayComponents.hour = 16; | |
NSDate *notToday = [calendar dateFromComponents:notTodayComponents]; | |
NSLog(@"Checking components"); | |
// This is how you can check the relevant components | |
// What you expect.. | |
NSInteger expectedYear = 2013; | |
NSInteger expectedMonth = 9; | |
NSInteger expectedDay = 18; | |
// The compoents to get... | |
NSUInteger yearMonthDay = NSYearCalendarUnit | | |
NSMonthCalendarUnit | | |
NSDayCalendarUnit; | |
for (NSDate *date in @[notToday, shouldBeToday]) { | |
// Get the components | |
NSDateComponents *components = | |
[calendar components:yearMonthDay | |
fromDate:date]; | |
// If the all match it is a match | |
if (components.year == expectedYear && | |
components.month == expectedMonth && | |
components.day == expectedDay) { | |
// is today ... | |
NSLog(@"%@ is sometime today", [resonableFormatter stringFromDate:date]); | |
} else { | |
NSLog(@"%@ is NOT sometime today", [resonableFormatter stringFromDate:date]); | |
} | |
} | |
NSLog(@"Between beginning and end of today"); | |
NSLog(@"%@", line); | |
// This method created two dates for the beginning and end of today. | |
// Everything between these will be sometime today. This is very efficient when | |
// filtering large sets of dates. | |
// beginning of today | |
NSDate *beginDate = nil; | |
// remove everything after the day (i.e. hour, minute, second) (refer to blog post for explanation (http://ronnqvi.st/working-with-dates/)) | |
[calendar rangeOfUnit:NSDayCalendarUnit | |
startDate:&beginDate | |
interval:NULL | |
forDate:today]; | |
NSLog(@"Begin date: %@ (after removing minute and secods from today)", [resonableFormatter stringFromDate:beginDate]); | |
// oneDay created above | |
NSDate *endDate = [calendar dateByAddingComponents:oneDay | |
toDate:beginDate | |
options:0]; | |
NSLog(@"End date: %@ (one day after begin date)", [resonableFormatter stringFromDate:endDate]); | |
// Note that the lower boundry is included in this predicate. | |
NSPredicate *todayPredicate = [NSPredicate predicateWithFormat:@"(SELF >= %@) AND (SELF < %@)", | |
beginDate, | |
endDate]; | |
NSLog(@"Checking one by one"); | |
for (NSDate *date in @[notToday, shouldBeToday]) { | |
// Even though it's a loop you can think of it as checking a single date with the predicate | |
BOOL isSometimeToday = [todayPredicate evaluateWithObject:date]; | |
NSLog(@"%@ is%@sometime today", | |
[resonableFormatter stringFromDate:date], | |
isSometimeToday?@" ":@" NOT "); // I'm to lazy sometimes (will be "NOT" when false) | |
} | |
NSLog(@"Filtering an array"); | |
NSArray *manyDates = @[notToday, shouldBeToday]; | |
NSArray *datesThatAreToday = [manyDates filteredArrayUsingPredicate:todayPredicate]; | |
NSLog(@"Out of %@, these dates are today: %@", | |
manyDates, | |
datesThatAreToday); | |
#pragma mark - Counting time between two dates | |
NSLog(@" "); | |
NSLog(@"%@", line); | |
NSLog(@"Counting time between two dates"); | |
NSLog(@"%@", line); | |
// This part shows how to get the time between two dates as different components. | |
// The first example is "how many X until Christmas Eve"? | |
NSDateComponents *christmasEveComponents = [NSDateComponents new]; | |
christmasEveComponents.year = 2013; | |
christmasEveComponents.month = 12; // December | |
christmasEveComponents.day = 24; // the Eve is on 24 | |
christmasEveComponents.hour = 21; | |
NSDate *christmasEve = [calendar dateFromComponents:christmasEveComponents]; | |
// This example gets the time as both weeks and days | |
// The result will do as many of the bigger (weeks) before doing the rest as the smaller (days) | |
// Note also that I'm using "WeekOfYear" instead of "Week". | |
// Week has ambiguities to it and since iOS 5 and OS X 10.7 it is recommended to use either | |
// WeekOfYear or WeekOfMonth instead. | |
NSDateComponents *untilChristmasEve = | |
[calendar components:NSWeekOfYearCalendarUnit|NSDayCalendarUnit | |
fromDate:today | |
toDate:christmasEve | |
options:0]; | |
NSLog(@"There are %zd weeks and %zd days until Christmas Eve.", | |
untilChristmasEve.weekOfYear, untilChristmasEve.day); | |
// Compare that with when you only get the days | |
NSDateComponents *daysUntilChristmasEve = | |
[calendar components:NSDayCalendarUnit | |
fromDate:today | |
toDate:christmasEve | |
options:0]; | |
NSLog(@"There are %zd days until Christmas Eve.", | |
daysUntilChristmasEve.day); | |
// This example is just to illustrate that you can work backwards in time as well and do funny things. | |
// Wouldn't your kid like to know how many: years, months, weeks and days old he is? I would :) | |
NSDate *birthDay = [calendar dateFromComponents:birthDayComponents]; | |
NSDateComponents *myAgeInMonhts = [calendar components:NSMonthCalendarUnit | |
fromDate:birthDay | |
toDate:today | |
options:0]; | |
NSLog(@"I am %zd months old", myAgeInMonhts.month); | |
#pragma mark - Absolute and relative dates with weekdays | |
NSLog(@" "); | |
NSLog(@"%@", line); | |
NSLog(@"Absolute and relative dates with weekdays"); | |
NSLog(@"%@", line); | |
// Weekdays can be tricky. For one thing not every locale have the same first day of the week. | |
// However 1 is still Sunday and 2 is still Monday in NSGregorianCalendar. | |
// Create a special formatter that prints out the name of the weekday. | |
NSDateFormatter *formatterWithWeekday = [NSDateFormatter new]; | |
formatterWithWeekday.locale = [NSLocale localeWithLocaleIdentifier:@"us"]; | |
formatterWithWeekday.dateFormat = @"MMMM d yyyy (EEEE)"; | |
NSLog(@"The first monday in May"); | |
// It is easy to miss or missunderstand `weekdayOrdinal`. Is is used to get the Nth (here) Monday of a month. | |
NSDateComponents *components = [NSDateComponents new]; | |
components.weekdayOrdinal = 1; // first | |
components.weekday = 2; // Monday | |
components.month = 5; // May | |
components.year = 2013; // (this year) | |
NSDate *firstMondayInMay = [calendar dateFromComponents:components]; | |
NSLog(@"The first monday in May is: \"%@\"", [formatterWithWeekday stringFromDate:firstMondayInMay]); | |
NSLog(@"Next Wednesday"); | |
// The next or previous weekday relative to another day has some things to consider. | |
// This example shows one approach that is not to complicated and yet (as far as I know) correct. | |
// Of course this is written specificly for "the next Wednesday" and would have to be adapted | |
// to fit other cases. I don't *just* want this to be copy and pasted. It's here to learn from. | |
NSUInteger componentsFromToday = NSWeekdayCalendarUnit | | |
NSWeekdayOrdinalCalendarUnit | | |
NSMonthCalendarUnit | | |
NSYearCalendarUnit; | |
NSLog(@"Today is: %@", [formatterWithWeekday stringFromDate:today]); | |
NSDateComponents *todayComponents = | |
[calendar components:componentsFromToday | |
fromDate:today]; | |
todayComponents.weekday = 4; // Wednesday; | |
todayComponents.hour = 12; // (noon is good practice when you don't care about time) | |
NSDate *nextWednesday = [calendar dateFromComponents:todayComponents]; | |
NSLog(@"Changing the weekday to Wednesday gives us: %@", [formatterWithWeekday stringFromDate:nextWednesday]); | |
// Since today is a Wednesday we get the same day. That is why we need to make sure | |
// that the Wednesday we get is in fact after today. Both "Same" and "Ascending" are | |
// the wrong date. | |
if ([nextWednesday compare:today] != NSOrderedDescending) { | |
NSLog(@"That date was not after today"); | |
// Ascending is the previous occurrence | |
// Same is not the "next" occurrence | |
NSDateComponents *oneWeek = [NSDateComponents new]; | |
oneWeek.week = 1; | |
nextWednesday = [calendar dateByAddingComponents:oneWeek | |
toDate:nextWednesday | |
options:0]; | |
// now it's definitely the next Wednesday | |
} | |
NSLog(@"Next Wednesday is: \"%@\"", [formatterWithWeekday stringFromDate:nextWednesday]); | |
NSLog(@"%@", line); | |
NSLog(@"Weeks that don't have all weekdays"); | |
// This is just a very small example to show what that NSCalendar is quite smart and will | |
// manage changing the weekday, even when it creates what could seem an invalid combination. | |
NSDateComponents *newYearsEveComponents = [NSDateComponents new]; | |
newYearsEveComponents.year = 2013; | |
newYearsEveComponents.month = 12; // December | |
newYearsEveComponents.day = 31; | |
newYearsEveComponents.hour = 12; | |
NSDate *newYearsEve = [calendar dateFromComponents:newYearsEveComponents]; | |
NSLog(@"New Years Eve is: \"%@\"", [formatterWithWeekday stringFromDate:newYearsEve]); | |
NSDateComponents *componentsFromNewYearsEve = [calendar components:componentsFromToday fromDate:newYearsEve]; | |
componentsFromNewYearsEve.weekday = 4; | |
NSDate *wednesDayAfterNewYearsEve = [calendar dateFromComponents:componentsFromNewYearsEve]; | |
NSLog(@"Wednesday after New Years Eve is: \"%@\"", [formatterWithWeekday stringFromDate:wednesDayAfterNewYearsEve]); | |
// Hope that you learnt to use what is already there in Foundation to better deal with dates | |
// in your code. The "Date and Time Programming Guide" is a great resouce if you want to read more. | |
// There is also (as of 2013-09-18) a blog post acompanying these examples over at http://ronnqvi.st/working-with-dates/ | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment