Skip to content

Instantly share code, notes, and snippets.

@drunknbass
Forked from d-ronnqvist/gist:6600444
Created September 19, 2013 16:38
Show Gist options
  • Save drunknbass/6626126 to your computer and use it in GitHub Desktop.
Save drunknbass/6626126 to your computer and use it in GitHub Desktop.
// 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