The above diagram provides a high-level overview of Objective-C’s date-handling capabilities. The
NSDate class represents a specific
point in time, which can be reduced to NSDateComponent’s
(e.g., days, weeks, years) by interpreting it in the context of an
NSCalendar object. The NSDateFormatter class provides
a human-readable version of an NSDate, and NSLocale
and NSTimeZone encapsulate essential localization information
for many calendrical operations.This module explains all of this in much more detail using several hands-on examples. By the end of this module, you should be able to perform any kind of date operation that you’ll ever need.
NSDate
AnNSDate object represents a specific point in time,
independent of any particular calendrical system, time zone, or locale.
Internally, it just records the number of seconds from an arbitrary reference
point (January 1st, 2001 GMT). For a date to be useful, it generally needs to
be interpreted in the context of an NSCalendar object.NSDate recording the
number of seconds from its reference pointNSDate class
only provides a few low-level methods for creating dates from scratch. The
date method returns an object representing the current date and
time, and dateWithTimeInterval:sinceDate: generates a relative
date using an NSTimeInterval, which is a double
storing the number of seconds from the specified date. As you can see in the
following example, a positive interval goes forward in time, and a negative one
goes backwards.NSDate *now = [NSDate date];
NSTimeInterval secondsInWeek = 7 * 24 * 60 * 60;
NSDate *lastWeek = [NSDate dateWithTimeInterval:-secondsInWeek
sinceDate:now];
NSDate *nextWeek = [NSDate dateWithTimeInterval:secondsInWeek
sinceDate:now];
NSLog(@"Last Week: %@", lastWeek);
NSLog(@"Right Now: %@", now);
NSLog(@"Next Week: %@", nextWeek);
2012-11-06 02:24:00 +0000. They contain the date (expressed as
year-month-date), the time of day (expressed as hours:minutes:seconds) and the
time zone (expressed as an offset from Greenwich Mean Time (GMT)).Aside from capturing an absolute point in time, the only real job of
NSDate is to facilitate comparisons. It defines
isEqualToDate: and compare: methods, which work just
like the ones provided by NSNumber. In
addition, the earlierDate: and laterDate: methods can
be used as a convenient shortcut.NSComparisonResult result = [now compare:nextWeek];
if (result == NSOrderedAscending) {
NSLog(@"now < nextWeek");
} else if (result == NSOrderedSame) {
NSLog(@"now == nextWeek");
} else if (result == NSOrderedDescending) {
NSLog(@"now > nextWeek");
}
NSDate *earlierDate = [now earlierDate:lastWeek];
NSDate *laterDate = [now laterDate:lastWeek];
NSLog(@"%@ is earlier than %@", earlierDate, laterDate);
NSDateComponents
TheNSDateComponents class is a simple data structure for
representing the various time periods used by a calendrical system (days,
weeks, months, years, etc). Unlike NSDate, the meaning of these
components are entirely dependent on how it’s used.Consider an
NSDateComponents object with its year
property set to 2012. This could be interpreted as the year
2012 by a Gregorian calendar, the year 1469 by a
Buddhist calendar, or two thousand and twelve years relative to some other
date.We’ll work more with date components in the next section, but as a simple example, here’s how you would create an
NSDateComponents instance that could be interpreted as November
4th, 2012:NSDateComponents *november4th2012 = [[NSDateComponents alloc] init];
[november4th2012 setYear:2012];
[november4th2012 setMonth:11];
[november4th2012 setDay:4];
NSLog(@"%@", november4th2012);
The complete list of component fields can be found below. Note that it’s perfectly legal to set only the properties you need and leave the rest as
NSUndefinedDateComponent, which is the default value for
all fields.era |
week |
year |
weekday |
month |
weekdayOrdinal |
day |
quarter |
hour |
weekOfMonth |
minute |
weekOfYear |
second |
yearForWeekOfYear |
NSCalendar
A calendrical system is a way of breaking down time into manageable units like years, months, weeks, etc. These units are represented asNSDateComponents objects; however, not all systems use the same
units or interpret them in the same way. So, an NSCalendar object
is required to give meaning to these components by defining the exact length of
a year/month/week/etc.This provides the necessary context for translating absolute
NSDate instances into NSDateComponents. This is a
very important ability, as it lets you work with dates on an intuitive,
cultural level instead of a mathematical one. That is, it’s much easier
to say, “November 4th, 2012” than “373698000 seconds after
January 1st, 2001.”NSCalendar
to convert between dates and date componentsNSCalendar: converting
NSDate objects to components, creating NSDate objects
from components, and performing calendrical calculations. We’ll also take
a brief look at NSCalendarUnit.Creating Calendars
NSCalendar’s initWithCalendarIdentifier:
initializer accepts an identifier that defines the calendrical system to use.
In addition, the currentCalendar class method returns the
user’s preferred calendar. For most applications, you should opt for
currentCalendar instead of manually defining one, since it
reflects the user’s device settings.NSCalendar *gregorian = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSCalendar *buddhist = [[NSCalendar alloc]
initWithCalendarIdentifier:NSBuddhistCalendar];
NSCalendar *preferred = [NSCalendar currentCalendar];
NSLog(@"%@", gregorian.calendarIdentifier);
NSLog(@"%@", buddhist.calendarIdentifier);
NSLog(@"%@", preferred.calendarIdentifier);
calendarIdentifier method lets you display the
string-representation of the calendar ID, but it is read-only (you can’t
change the calendrical system after instantiation). The rest of Cocoa’s
built-in calendar identifiers can be accessed via the following constants:NSGregorianCalendar |
NSBuddhistCalendar |
NSChineseCalendar |
NSHebrewCalendar |
NSIslamicCalendar |
NSIslamicCivilCalendar |
NSJapaneseCalendar |
NSRepublicOfChinaCalendar |
NSPersianCalendar |
NSIndianCalendar |
NSISO8601Calendar |
NSCalendar. If you change the identifier, you can see how
different calendars have different interpretations of date components.From Dates to Components
The following example shows you how to convert anNSDate into a
culturally significant NSDateComponents object.NSDate *now = [NSDate date];
NSCalendar *calendar = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSCalendarUnit units = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;
NSDateComponents *components = [calendar components:units fromDate:now];
NSLog(@"Day: %ld", [components day]);
NSLog(@"Month: %ld", [components month]);
NSLog(@"Year: %ld", [components year]);
NSCalendar object that represents a
Gregorian calendar. Then, it creates a bitmask defining the units to include in
the conversion (see NSCalendarUnits
for details). Finally, it passes these units and an NSDate to the
components:fromDate: method.From Components to Dates
A calendar can also convert in the other direction, which offers a much more intuitive way to createNSDate objects. It lets you define a date
using the components of your native calendrical system:NSCalendar *calendar = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setYear:2012];
[components setMonth:11];
[components setDay:4];
NSDate *november4th2012 = [calendar dateFromComponents:components];
NSLog(@"%0.0f seconds between Jan 1st, 2001 and Nov 4th, 2012",
[november4th2012 timeIntervalSinceReferenceDate]);
NSDateComponents object, populate it with
the desired components, and pass it to the dateFromComponents:
method.Remember that
NSDate represents an absolute point in time,
independent of any particular calendrical system. So, by calling
dateFromComponents: on different NSCalendar objects,
you can reliably compare dates from incompatible systems.Calendrical Calculations
The third job ofNSCalendar is to provide a high-level API for
date-related calculations. First, we’ll take a look at how calendars let
you add components to a given NSDate instance. The following
example generates a date object that is exactly one month away from the current
date.NSDate *now = [NSDate date];
NSCalendar *calendar = [[NSCalendar alloc]
initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setMonth:1];
NSDate *oneMonthFromNow = [calendar dateByAddingComponents:components
toDate:now
options:0];
NSLog(@"%@", oneMonthFromNow);
NSDateComponents object, record
the components you want to add, and pass it to
dateByAddingComponents:toDate:options:. This is a good example of
how the meaning of date components is entirely dependent on how it’s
used. Instead of representing a date, the above components represent a
duration.The
options argument should either be 0 to let
components overflow into higher units or NSWrapCalendarComponents
to prevent this behavior. For example, if you set month to
14, passing 0 as an option tells the calendar to
interpret it as 1 year and 2 months, whereas
NSWrapCalendarComponents interprets it as 2 months and completely
ignores the extra year.Also note that
dateByAddingComponents:toDate:options: is a
calendar-aware operation, so it compensates for 30 vs. 31 day months (in the
Gregorian calendar). This makes NSCalendar a more robust way of
adding dates than NSDate’s low-level
dateWithTimeInterval:sinceDate: method.The other major calendrical operation provided by
NSCalendar is
components:fromDate:toDate:options:, which calculates the interval
between two NSDate objects. For example, you can determine the
number of weeks since NSDate’s internal reference point as
follows:NSDate *start = [NSDate dateWithTimeIntervalSinceReferenceDate:0];
NSDate *end = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSCalendarUnit units = NSWeekCalendarUnit;
NSDateComponents *components = [calendar components:units
fromDate:start
toDate:end
options:0];
NSLog(@"It has been %ld weeks since January 1st, 2001",
[components week]);
NSCalendarUnit
The first argument ofcomponents:fromDate: and
components:fromDate:toDate:options: determine the properties that
will be populated on the resulting NSDateComponents object. The
possible values are defined by NSCalendarUnit, which enumerates
the following constants:NSEraCalendarUnit |
NSWeekdayCalendarUnit |
|||
NSYearCalendarUnit |
NSWeekdayOrdinalCalendarUnit |
|||
NSMonthCalendarUnit |
NSQuarterCalendarUnit |
|||
NSDayCalendarUnit |
NSWeekOfMonthCalendarUnit |
|||
NSHourCalendarUnit |
NSWeekOfYearCalendarUnit |
|||
NSMinuteCalendarUnit |
NSYearForWeekOfYearCalendarUnit |
|||
NSSecondCalendarUnit |
NSCalendarCalendarUnit |
|||
NSWeekCalendarUnit |
NSTimeZoneCalendarUnit | | | |
OR operator (|). For
example, to include the day, hour, and minute, you would use the following
value for the first parameter of components:fromDate:.NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit
NSDateFormatter
TheNSDateFormatter class makes it easy to work with the
human-readable form of a date. Whereas calendars decompose a date into an
NSDateComponents object, date formatters convert between
NSDate’s and NSString’s.NSDateFormatter to convert between dates and stringsLocalized Styles
Localized styles define how a date should look using abstract descriptions instead of specific date components. For example, instead of defining a date asMM/dd/yy, a style would say it should be displayed in its
“short” form. This lets the system adapt the representation to the
user’s language, region, and preferences while still offering a level of
control to the developer.The following snippet formats a date using the “short” style. As you can see, separate styles are used for the date and the time. The
stringFromDate: method formats an NSDate object
according to the provided styles.NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateStyle:NSDateFormatterShortStyle];
[formatter setTimeStyle:NSDateFormatterShortStyle];
NSDate *now = [NSDate date];
NSString *prettyDate = [formatter stringFromDate:now];
NSLog(@"%@", prettyDate);
11/4/2012 8:09 PM. If you have different default settings,
you’ll see the traditional date formatting used in your particular
language/region. We’ll take a closer look at this behavior in the NSLocale section. The complete list of formatting styles
are included below.NSDateFormatterNoStyle |
NSDateFormatterShortStyle |
NSDateFormatterMediumStyle |
NSDateFormatterLongStyle |
NSDateFormatterFullStyle |
NSDateFormatterNoStyle constant can be used to
omit the date or time from the output string.Custom Format Strings
Again, the styles discussed above are the preferred way to define user-visible dates. However, if you’re working with dates on a programmatic level, you may find thesetDateFormat: method useful.
This accepts a format string that defines precisely how the date will appear.
For example, you can use M.d.y to output the month, date, and year
separated by periods as follows:// Formatter configuration
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
NSLocale *posix = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
[formatter setLocale:posix];
[formatter setDateFormat:@"M.d.y"];
// Date to string
NSDate *now = [NSDate date];
NSString *prettyDate = [formatter stringFromDate:now];
NSLog(@"%@", prettyDate);
locale property
of the NSDateFormatter before using custom format strings. This
ensures that the user’s default locale won’t affect the output,
which would result in subtle, hard-to-reproduce bugs. In the above case, the
POSIX locale ensures that the date is displayed as 11.4.2012,
regardless of the user’s settings.Custom date formats also present an alternative way to create date objects. Using the above configuration, it’s possible to convert the string
11.4.2012 into an NSDate with the
dateFromString: method. If the string cant’t be parsed, it
will return nil.// String to date
NSString *dateString = @"11.4.2012";
NSDate *november4th2012 = [formatter dateFromString:dateString];
NSLog(@"%@", november4th2012);
M, d, and y, the Unicode
Technical Standard #35 defines a plethora of other date format
specifiers. This document contains a lot of information not really necessary to
use NSDateFormatter correctly, so you may find the following list
of sample format strings to be a more practical quick-reference. The date used
to generate the output string is November 4th, 2012 8:09 PM Central Standard
Time.| Format String | Output String |
|---|---|
M/d/y |
11/4/2012 |
MM/dd/yy |
11/04/12 |
MMM d, ''yy |
Nov 4, '12 |
MMMM |
November |
E |
Sun |
EEEE |
Sunday |
'Week' w 'of 52' |
Week 45 of 52 |
'Day' D 'of 365' |
Day 309 of 365 |
QQQ |
Q4 |
QQQQ |
4th quarter |
m 'minutes past' h |
9 minutes past 8 |
h:mm a |
8:09 PM |
HH:mm:ss's' |
20:09:00s |
HH:mm:ss:SS |
20:09:00:00 |
h:mm a zz |
8:09 PM CST |
h:mm a zzzz |
8:09 PM Central Standard Time |
yyyy-MM-dd HH:mm:ss Z |
2012-11-04 20:09:00 -0600 |
NSLocale
AnNSLocale object represents a set of conventions for a
particular language, region, or culture. Both NSCalendar and
NSDateFormatter rely on this information to localize many of their
core operations.The previous section already showed you how to create a custom locale via the
initWithLocaleIdentifier: initializer. Let’s take a look
at the impact a locale change can have on an NSDateFormatter by
running the same example using Egyptian Arabic instead of POSIX:NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
NSLocale *egyptianArabic = [[NSLocale alloc] initWithLocaleIdentifier:@"ar_EG"];
[formatter setLocale:egyptianArabic];
[formatter setDateFormat:@"M.d.y"];
NSDate *now = [NSDate date];
NSString *prettyDate = [formatter stringFromDate:now];
NSLog(@"%@", prettyDate);
11.4.2012, the date is now displayed with Arabic
digits: ١١.٤.٢٠١٢. This is why
setting the locale before using custom format strings is so important—if
you don’t, NSDateFormatter will use the system default, and
your format strings will return unexpected results for international users.Locale identifiers are composed of a language abbreviation followed by a country abbreviation. In the above example,
ar stands for Arabic,
and EG represents the Egyptian dialect. You can obtain a complete
list of available locale identifiers with the following (it’s a very long
list):NSLog(@"%@", [NSLocale availableLocaleIdentifiers]);
currentLocale class
method, as shown below. Note that this is the default used by
NSCalendar and NSDateFormatter.NSLocale *preferredLocale = [NSLocale currentLocale]);
NSLocale objects are usually only required for
specialized applications or testing purposes, but it’s still good to
understand how iOS and OS X applications automatically translate your data
for an international audience.NSTimeZone
It’s very important to understand that a string like11.4.2012
8:09 PM does not specify a precise point in time—8:09 PM
in Chicago occurs 8 hours after 8:09 PM in Cairo. The time zone is a required
piece of information for creating an absolute NSDate instance.
Accordingly, NSCalendar and NSDateFormatter both
require an NSTimeZone object to offset their calculations
appropriately.A time zone can be created explicitly with either its full name or its abbreviated one. The following example demonstrates both methods and shows how a time zone can alter the
NSDate produced by a formatter. The
generated dates are displayed in Greenwich Mean Time, and you can see that 8:09
PM in Chicago is indeed 8 hours after 8:09 PM in Cairo.NSTimeZone *centralStandardTime = [NSTimeZone timeZoneWithAbbreviation:@"CST"];
NSTimeZone *cairoTime = [NSTimeZone timeZoneWithName:@"Africa/Cairo"];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
NSLocale *posix = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
[formatter setLocale:posix];
[formatter setDateFormat:@"M.d.y h:mm a"];
NSString *dateString = @"11.4.2012 8:09 PM";
[formatter setTimeZone:centralStandardTime];
NSDate *eightPMInChicago = [formatter dateFromString:dateString];
NSLog(@"%@", eightPMInChicago); // 2012-11-05 02:09:00 +0000
[formatter setTimeZone:cairoTime];
NSDate *eightPMInCairo = [formatter dateFromString:dateString];
NSLog(@"%@", eightPMInCairo); // 2012-11-04 18:09:00 +0000
NSDateFormatter’s timeZone
property is a fallback, so format strings that contain one of the
z specifiers will use the parsed value as expected. You can use
NSCalendar’s timeZone property to the same
effect.Complete lists of time zone names and abbreviations can be accessed via
knownTimeZoneNames and abbreviationDictionary,
respectively. However, the latter does not necessarily provide values for
every time zone.NSLog(@"%@", [NSTimeZone knownTimeZoneNames]);
NSLog(@"%@", [NSTimeZone abbreviationDictionary]);
localTimeZone class method is the best option for accessing the
current time zone.NSTimeZone *preferredTimeZone = [NSTimeZone localTimeZone];
NSTimeZone objects are typically only
necessary for scheduling applications where time zones need to be explicitly
associated with individual events.
No comments:
Post a Comment