I am normally pretty good with this, but I am having trouble with the NSDate object. I need a NSDate object set for tomorrow at 8am (relatively). How would I do this and what is the simplest method?
Here's how WWDC 2011 session 117 - Performing Calendar Calculations taught me:
NSDate* now = [NSDate date] ;
NSDateComponents* tomorrowComponents = [NSDateComponents new] ;
tomorrowComponents.day = 1 ;
NSCalendar* calendar = [NSCalendar currentCalendar] ;
NSDate* tomorrow = [calendar dateByAddingComponents:tomorrowComponents toDate:now options:0] ;
NSDateComponents* tomorrowAt8AMComponents = [calendar components:(NSEraCalendarUnit|NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit) fromDate:tomorrow] ;
tomorrowAt8AMComponents.hour = 8 ;
NSDate* tomorrowAt8AM = [calendar dateFromComponents:tomorrowAt8AMComponents] ;
Too bad iOS doesn't have [NSDate dateWithNaturalLanguageString:#"tomorrow at 8:00 am"]. Thanks, rmaddy, for pointing that out.
In Swift 2.1:
let now = NSDate()
let tomorrowComponents = NSDateComponents()
tomorrowComponents.day = 1
let calendar = NSCalendar.currentCalendar()
if let tomorrow = calendar.dateByAddingComponents(tomorrowComponents, toDate: now, options: NSCalendarOptions.MatchFirst) {
let flags: NSCalendarUnit = [.Era, .Year, .Month, .Day]
let tomorrowValidTime: NSDateComponents = calendar.components(flags, fromDate: tomorrow)
tomorrowValidTime.hour = 7
if let tomorrowMorning = calendar.dateFromComponents(tomorrowValidTime) {
return tomorrowMorning
}
}
Swift 3+
private func tomorrowMorning() -> Date? {
let now = Date()
var tomorrowComponents = DateComponents()
tomorrowComponents.day = 1
let calendar = Calendar.current
if let tomorrow = calendar.date(byAdding: tomorrowComponents, to: now) {
let components: Set<Calendar.Component> = [.era, .year, .month, .day]
var tomorrowValidTime = calendar.dateComponents(components, from: tomorrow)
tomorrowValidTime.hour = 7
if let tomorrowMorning = calendar.date(from: tomorrowValidTime) {
return tomorrowMorning
}
}
return nil
}
Related
What's the best way to get a total step count for every day recorded in HealthKit.
With HKSampleQuery's method initWithSampleType (see below) I can set a start and end date for the query using NSPredicate, but the method returns an array with many HKQuantitySamples per day.
- (instancetype)initWithSampleType:(HKSampleType *)sampleType
predicate:(NSPredicate *)predicate
limit:(NSUInteger)limit
sortDescriptors:(NSArray *)sortDescriptors
resultsHandler:(void (^)(HKSampleQuery *query,
NSArray *results,
NSError *error))resultsHandler
I guess I can query all recorded step counts and go through the array and calculate the total step count for each day, but I'm hoping for an easier solution as there will be thousands of HKSampleQuery objects. Is there a way to have initWithSampleType return a total step count per day?
You should use HKStatisticsCollectionQuery:
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *interval = [[NSDateComponents alloc] init];
interval.day = 1;
NSDateComponents *anchorComponents = [calendar components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear
fromDate:[NSDate date]];
anchorComponents.hour = 0;
NSDate *anchorDate = [calendar dateFromComponents:anchorComponents];
HKQuantityType *quantityType = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
// Create the query
HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:quantityType
quantitySamplePredicate:nil
options:HKStatisticsOptionCumulativeSum
anchorDate:anchorDate
intervalComponents:interval];
// Set the results handler
query.initialResultsHandler = ^(HKStatisticsCollectionQuery *query, HKStatisticsCollection *results, NSError *error) {
if (error) {
// Perform proper error handling here
NSLog(#"*** An error occurred while calculating the statistics: %# ***",error.localizedDescription);
}
NSDate *endDate = [NSDate date];
NSDate *startDate = [calendar dateByAddingUnit:NSCalendarUnitDay
value:-7
toDate:endDate
options:0];
// Plot the daily step counts over the past 7 days
[results enumerateStatisticsFromDate:startDate
toDate:endDate
withBlock:^(HKStatistics *result, BOOL *stop) {
HKQuantity *quantity = result.sumQuantity;
if (quantity) {
NSDate *date = result.startDate;
double value = [quantity doubleValueForUnit:[HKUnit countUnit]];
NSLog(#"%#: %f", date, value);
}
}];
};
[self.healthStore executeQuery:query];
Port to Swift with no dependency to SwiftDate library
let calendar = NSCalendar.current
let interval = NSDateComponents()
interval.day = 1
var anchorComponents = calendar.dateComponents([.day, .month, .year], from: NSDate() as Date)
anchorComponents.hour = 0
let anchorDate = calendar.date(from: anchorComponents)
// Define 1-day intervals starting from 0:00
let stepsQuery = HKStatisticsCollectionQuery(quantityType: stepsCount!, quantitySamplePredicate: nil, options: .cumulativeSum, anchorDate: anchorDate!, intervalComponents: interval as DateComponents)
// Set the results handler
stepsQuery.initialResultsHandler = {query, results, error in
let endDate = NSDate()
let startDate = calendar.date(byAdding: .day, value: -7, to: endDate as Date, wrappingComponents: false)
if let myResults = results{
myResults.enumerateStatistics(from: startDate!, to: endDate as Date) { statistics, stop in
if let quantity = statistics.sumQuantity(){
let date = statistics.startDate
let steps = quantity.doubleValue(for: HKUnit.count())
print("\(date): steps = \(steps)")
//NOTE: If you are going to update the UI do it in the main thread
DispatchQueue.main.async {
//update UI components
}
}
} //end block
} //end if let
}
healthStore?.execute(stepsQuery)
Modified #sebastianr's answer using core Swift classes, for just for testing I am returning only steps for just one day, once you have more days you can create a dictionary of Dates and step count and return it
func getStepCountPerDay(completion:#escaping (_ count: Double)-> Void){
guard let sampleType = HKObjectType.quantityType(forIdentifier: .stepCount)
else {
return
}
let calendar = Calendar.current
var dateComponents = DateComponents()
dateComponents.day = 1
var anchorComponents = calendar.dateComponents([.day, .month, .year], from: Date())
anchorComponents.hour = 0
let anchorDate = calendar.date(from: anchorComponents)
let stepsCumulativeQuery = HKStatisticsCollectionQuery(quantityType: sampleType, quantitySamplePredicate: nil, options: .cumulativeSum, anchorDate: anchorDate!, intervalComponents: dateComponents
)
// Set the results handler
stepsCumulativeQuery.initialResultsHandler = {query, results, error in
let endDate = Date()
let startDate = calendar.date(byAdding: .day, value: 0, to: endDate, wrappingComponents: false)
if let myResults = results{
myResults.enumerateStatistics(from: startDate!, to: endDate as Date) { statistics, stop in
if let quantity = statistics.sumQuantity(){
let date = statistics.startDate
let steps = quantity.doubleValue(for: HKUnit.count())
print("\(date): steps = \(steps)")
completion(steps)
//NOTE: If you are going to update the UI do it in the main thread
DispatchQueue.main.async {
//update UI components
}
}
} //end block
} //end if let
}
HKHealthStore().execute(stepsCumulativeQuery)
}
Here is a translation that currently works for Swift 2.0, using the SwiftDate library.
let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
let startDate = NSDate().beginningOfDay().oneWeekAgo()
let interval = NSDateComponents()
interval.day = 1
let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: NSDate(), options: .StrictStartDate)
let query = HKStatisticsCollectionQuery(quantityType: type!, quantitySamplePredicate: predicate, options: [.CumulativeSum], anchorDate: NSDate().begginingOfDay(), intervalComponents:interval)
query.initialResultsHandler = { query, results, error in
let endDate = NSDate()
let startDate = NSDate().beginningOfDay().oneWeekAgo()
if let myResults = results{
myResults.enumerateStatisticsFromDate(startDate, toDate: endDate) {
statistics, stop in
if let quantity = statistics.sumQuantity() {
let date = statistics.startDate
let steps = quantity.doubleValueForUnit(HKUnit.countUnit())
print("\(date): steps = \(steps)")
}
}
}
}
healthKitStore.executeQuery(query)
I wrapped mine in a completion block (objective -c). I found what was best was to set the startDate for the query to todays date at midnight. Hope this helps, feel free to copy/paste to get started
-(void)fetchHourlyStepsWithCompletionHandler:(void (^)(NSMutableArray *, NSError *))completionHandler {
NSMutableArray *mutArray = [NSMutableArray new];
NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
NSDate *startDate = [calendar dateBySettingHour:0 minute:0 second:0 ofDate:[NSDate date] options:0];
NSDate *endDate = [NSDate date]; // Whatever you need in your case
HKQuantityType *type = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
// Your interval: sum by hour
NSDateComponents *intervalComponents = [[NSDateComponents alloc] init];
intervalComponents.hour = 1;
// Example predicate
NSPredicate *predicate = [HKQuery predicateForSamplesWithStartDate:startDate endDate:endDate options:HKQueryOptionStrictStartDate];
HKStatisticsCollectionQuery *query = [[HKStatisticsCollectionQuery alloc] initWithQuantityType:type quantitySamplePredicate:predicate options:HKStatisticsOptionCumulativeSum anchorDate:startDate intervalComponents:intervalComponents];
query.initialResultsHandler = ^(HKStatisticsCollectionQuery *query, HKStatisticsCollection *results, NSError *error) {
[results enumerateStatisticsFromDate:startDate toDate:endDate
withBlock:^(HKStatistics *result, BOOL *stop) {
if (!result) {
if (completionHandler) {
completionHandler(nil, error);
}
return;
}
HKQuantity *quantity = result.sumQuantity;
NSDate *startDate = result.startDate;
NSDateFormatter *formatter = [[NSDateFormatter alloc]init];
formatter.dateFormat = #"h a";
NSString *dateString = [formatter stringFromDate:startDate];
double steps = [quantity doubleValueForUnit:[HKUnit countUnit]];
NSDictionary *dict = #{#"steps" : #(steps),
#"hour" : dateString
};
[mutArray addObject:dict];
}];
if (completionHandler) {
completionHandler(mutArray, error);
}
};
[self.healthStore executeQuery:query];
}
With Updated Swift 2.0 & SwiftDate library.
let type = HKSampleType.quantityTypeForIdentifier(HKQuantityTypeIdentifierStepCount)
let startDate = NSDate().beginningOfDay
let interval = NSDateComponents()
interval.day = 1
let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: NSDate(), options: .StrictStartDate)
let query = HKStatisticsCollectionQuery(quantityType: type!, quantitySamplePredicate: predicate, options: [.CumulativeSum], anchorDate: NSDate().beginningOfDay, intervalComponents:interval)
query.initialResultsHandler = { query, results, error in
let endDate = NSDate()
let startDate = NSDate().beginningOfDay
if let myResults = results{
myResults.enumerateStatisticsFromDate(startDate, toDate: endDate) {
statistics, stop in
if let quantity = statistics.sumQuantity() {
let date = statistics.startDate
let steps = quantity.doubleValueForUnit(HKUnit.countUnit())
print("\(date): steps = \(steps)")
}
}
}
}
healthKitStore.executeQuery(query)
How can I get all the dates that come in between 2 dates?
For example:
Start date = 2011/01/25
End date = 2011/02/03
1) Find the no of days between two dates. Then,
for(i=0;i<noofdays;i++)
{
//Find the next date
//add to the array
}
To find number of days
To find next date
NSCalendarUnit serves for defining the step between the dates & taking care of the dates being normalized.
iOS 8 API, Swift 2.0
func generateDates(calendarUnit: NSCalendarUnit, startDate: NSDate, endDate: NSDate) -> [NSDate] {
let calendar = NSCalendar.currentCalendar()
let normalizedStartDate = calendar.startOfDayForDate(startDate)
let normalizedEndDate = calendar.startOfDayForDate(endDate)
var dates = [normalizedStartDate]
var currentDate = normalizedStartDate
repeat {
currentDate = calendar.dateByAddingUnit(calendarUnit, value: 1, toDate: currentDate, options: NSCalendarOptions.MatchNextTime)!
dates.append(currentDate)
} while !calendar.isDate(currentDate, inSameDayAsDate: normalizedEndDate)
return dates
}
To find all days between two NSDates:
- (void)cerateDaysArray{
_daysArray = [NSMutableArray new];
NSCalendar *calendar = [[NSCalendaralloc]initWithCalendarIdentifier:NSGregorianCalendar];
[calendar setTimeZone:[NSTimeZone systemTimeZone]];
NSDate *startDate = [_minDate copy];
NSDateComponents *deltaDays = [NSDateComponents new];
[deltaDays setDay:1];
[_daysArray addObject:startDate];
while ([startDate compare:_maxDate] == NSOrderedAscending) {
startDate = [calendar dateByAddingComponents:deltaDays toDate:startDate options:0];
[_daysArray addObject:startDate];
}
}
I want to compare two dates: date1 and date2
2011-06-06 12:59:48.994 Project[419:707] firstDate:2011-06-06 10:59:21 +0000
2011-06-06 12:59:49.004 Project[419:707] selectedData:2011-06-06 10:59:17 +0000
but these dates have different time and when I use NSOrderedSame it don't work fine, how can I solve?
my code:
NSDate *firstDate = [[appDelegate.project objectAtIndex:i]objectAtIndex:3];
NSDate *secondDate = [[appDelegate.project objectAtIndex:i]objectAtIndex:4];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSInteger comps = (NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit);
NSDateComponents *date1Components = [calendar components:comps
fromDate:firstDate];
NSDateComponents *date2Components = [calendar components:comps
fromDate:secondDate];
NSDateComponents *date3Components = [calendar components:comps fromDate:appDelegate.selectedDate];
NSLog(#"firstDate:%#", [date1Components date]);
NSLog(#"secondDate:%#", [date2Components date]);
NSLog(#"selectedData:%#", [date3Components date]);
NSComparisonResult compareStart = [[date1Components date] compare: [date3Components date]];
NSComparisonResult compareEnd = [[date2Components date] compare: [date3Components date]];
if ((compareStart == NSOrderedAscending || compareStart == NSOrderedSame)
&& (compareEnd == NSOrderedDescending || compareEnd == NSOrderedSame))
{
NSLog(#"inside");
Then I want to compare my dates and entry inside the "if" when date1 <= selectedDate <= date2; now I understand because I have a warning: I should add this "[date1Components date]" and it work; the problem is that I have in the NSLog null values, why??
NSCalendar *calendar = [NSCalendar currentCalendar];
NSInteger comps = (NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear);
NSDateComponents *date1Components = [calendar components:comps
fromDate: date1];
NSDateComponents *date2Components = [calendar components:comps
fromDate: date2];
date1 = [calendar dateFromComponents:date1Components];
date2 = [calendar dateFromComponents:date2Components];
NSComparisonResult result = [date1 compare:date2];
if (result == NSOrderedAscending) {
} else if (result == NSOrderedDescending) {
} else {
//the same
}
There is another handy method to create for a given date the date that represents the start of a given unit: [aCalendar rangeOfUnit:startDate:interval:forDate:]
To illustrate how this method works, see this code, that easily creates the date for the beginning of the day, week, month and year for a given date (here: now).
NSDate *now = [NSDate date];
NSDate *startOfToday = nil;
NSDate *startOfThisWeek = nil;
NSDate *startOfThisMonth = nil;
NSDate *startOfThisYear = nil;
[[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit startDate:&startOfToday interval:NULL forDate:now];
[[NSCalendar currentCalendar] rangeOfUnit:NSWeekCalendarUnit startDate:&startOfThisWeek interval:NULL forDate:now];
[[NSCalendar currentCalendar] rangeOfUnit:NSMonthCalendarUnit startDate:&startOfThisMonth interval:NULL forDate:now];
[[NSCalendar currentCalendar] rangeOfUnit:NSYearCalendarUnit startDate:&startOfThisYear interval:NULL forDate:now];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateStyle:NSDateFormatterFullStyle];
[formatter setTimeStyle:NSDateFormatterFullStyle];
NSLog(#"%#", [formatter stringFromDate:now]);
NSLog(#"%#", [formatter stringFromDate:startOfToday]);
NSLog(#"%#", [formatter stringFromDate:startOfThisWeek]);
NSLog(#"%#", [formatter stringFromDate:startOfThisMonth]);
NSLog(#"%#", [formatter stringFromDate:startOfThisYear]);
result:
Thursday, July 12, 2012, 4:36:07 PM Central European Summer Time
Thursday, July 12, 2012, 12:00:00 AM Central European Summer Time
Sunday, July 8, 2012, 12:00:00 AM Central European Summer Time
Sunday, July 1, 2012, 12:00:00 AM Central European Summer Time
Sunday, January 1, 2012, 12:00:00 AM Central European Standard Time
this allows us to shorten the first code to:
[[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit startDate:&date1 interval:NULL forDate:date1];
[[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit startDate:&date2 interval:NULL forDate:date2];
NSComparisonResult result = [date1 compare:date2];
if (result == NSOrderedAscending) {
} else if (result == NSOrderedDescending) {
} else {
//the same
}
Note, that in this code, date1 and date2 will be overwritten. Alternatively you can pass in a reference to another NSDate pointer for startDate as shown in the code above, where now stays untouched.
I have used another method with NSDateFormatter and a string comparison, less smarter than
NSDate compare method but faster to write and flexible enough to do variety of comparison :
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"yyyy-MM-dd"];
if ([[dateFormat stringFromDate:date1] isEqualToString:[dateFormat stringFromDate:date2]])
{
//It's the same day
}
Okay, so it's a few years after the original question was asked, but it's probably worth mentioning that NSCalendar now has a number of methods that make certain date comparison questions much more straight-forward:
NSCalendar *currentCalendar = [NSCalendar currentCalendar];
Bool sameDay = [currentCalendar isDate:dateA inSameDayAsDate:dateB];
Swift Version , Comparing Dates and ignoring their time.
let dateExam1:NSDate = NSDate.init(timeIntervalSinceNow: 300)
let dateExam2:NSDate = NSDate.init(timeIntervalSinceNow: 10000)
let currCalendar = NSCalendar.currentCalendar()
let dateCompanent1:NSDateComponents = currCalendar.components([.Year,.Month,.Day], fromDate: dateExam1)
let dateCompanent2:NSDateComponents = currCalendar.components([.Year,.Month,.Day], fromDate: dateExam2)
let date1WithoutTime:NSDate? = currCalendar .dateFromComponents(dateCompanent1)
let date2WithoutTime:NSDate? = currCalendar .dateFromComponents(dateCompanent2)
if (date1WithoutTime != nil) && (date2WithoutTime != nil)
{
let dateCompResult:NSComparisonResult = date1WithoutTime!.compare(date2WithoutTime!)
if (dateCompResult == NSComparisonResult.OrderedSame)
{
print("Same Dates")
}
else
{
print("Different Dates")
}
}
Updating #LuAndre answer swift 5
let dateExam1 = Date(timeIntervalSinceNow: 300)
let dateExam2 = Date(timeIntervalSinceNow: 10000)
let currCalendar = Calendar.current
let dateCompanent1 = currCalendar.dateComponents([.year,.month,.day], from: dateExam1)
let dateCompanent2 = currCalendar.dateComponents([.year,.month,.day], from: dateExam2)
if let date1WithoutTime = currCalendar.date(from:dateCompanent1), let dateCompanent2 = currCalendar.date(from:dateCompanent2) {
let dateCompResult = date1WithoutTime.compare(dateCompanent2)
if (dateCompResult == ComparisonResult.orderedSame)
{
print("Same Dates")
}
else
{
print("Different Dates")
}
}
I would like to know if anyone can help me with my method. I have the following method, which will zero out the seconds value of a NSDate object:
- (NSDate *)dateWithZeroSeconds:(NSDate *)date {
NSTimeInterval time = round([date timeIntervalSinceReferenceDate] / 60.0) * 60.0;
return [NSDate dateWithTimeIntervalSinceReferenceDate:time];
}
The problem is when passed a date such as:
2011-03-16 18:21:43 +0000
it returns:
2011-03-16 18:22:00 +0000
I do not want this rounding to occur, as it is a user who is actually specifying the date, so it needs to be exact to the minute they request.
Any help is greatly appreciated.
Use floor instead of round:
- (NSDate *)dateWithZeroSeconds:(NSDate *)date
{
NSTimeInterval time = floor([date timeIntervalSinceReferenceDate] / 60.0) * 60.0;
return [NSDate dateWithTimeIntervalSinceReferenceDate:time];
}
Use NSCalendar and NSDateComponents to get the parts of the date. Set the seconds component to 0, then create a new date from that NSDateComponents.
To be complete, here is the code referenced to iOS SDK 8.1 using NSCalendar and NSDateComponents.
+ (NSDate *)truncateSecondsForDate:(NSDate *)fromDate;
{
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
NSCalendarUnit unitFlags = NSCalendarUnitEra | NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute;
NSDateComponents *fromDateComponents = [gregorian components:unitFlags fromDate:fromDate ];
return [gregorian dateFromComponents:fromDateComponents];
}
Note that as of iOS 8 the calendar unit names have changed.
You can get the start of any time unit — such as an minute — with rangeOfUnit:startDate:interval:forDate:
NSDate *startOfMinuteDate;
[[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitMinute
startDate:&startOfMinuteDate
interval:NULL
forDate:originalDate];
Swift 2.2 version of #Neil answer:
func truncateSecondsForDate(fromDate: NSDate) -> NSDate {
let calendar : NSCalendar = NSCalendar.currentCalendar()
let unitFlags : NSCalendarUnit = [.Era , .Year , .Month , .Day , .Hour , .Minute]
let fromDateComponents: NSDateComponents = calendar.components(unitFlags, fromDate: fromDate)
return calendar.dateFromComponents(fromDateComponents)!
}
Swift 4 version :
func truncateSecondsForDate(fromDate: Date) -> Date {
let calendar = Calendar.current
let fromDateComponents: DateComponents = calendar.dateComponents([.era , .year , .month , .day , .hour , .minute], from: fromDate as Date) as DateComponents
return calendar.date(from: fromDateComponents as DateComponents)! as Date
}
I'm trying to get the number of days in a current year.
When I try the solution on Number of days in the current month using iPhone SDK?, and replace NSMonthCalendarUnit by NSYearCalendarUnit, I still get the number of days for that month.
Does anyone know how I should do this?
Thanks in advance for your help.
Here's a super accurate NSCalendar extension in Swift 2:
extension NSCalendar {
func daysInYear(date: NSDate = NSDate()) -> Int? {
let year = components([NSCalendarUnit.Year], fromDate: date).year
return daysInYear(year)
}
func daysInYear(year: Int) -> Int? {
guard let begin = lastDayOfYear(year - 1), end = lastDayOfYear(year) else { return nil }
return components([NSCalendarUnit.Day], fromDate: begin, toDate: end, options: []).day
}
func lastDayOfYear(year: Int) -> NSDate? {
let components = NSDateComponents()
components.year = year
guard let years = dateFromComponents(components) else { return nil }
components.month = rangeOfUnit(NSCalendarUnit.Month, inUnit: NSCalendarUnit.Year, forDate: years).length
guard let months = dateFromComponents(components) else { return nil }
components.day = rangeOfUnit(NSCalendarUnit.Day, inUnit: NSCalendarUnit.Month, forDate: months).length
return dateFromComponents(components)
}
}
You can use it like this:
let calendar = NSCalendar.currentCalendar() // I'm using the Gregorian calendar
calendar.daysInYear() // 365 (since it's currently 2015)
calendar.daysInYear(2016) // 366 (leap year!)
This is super flexible since we don't assume anything about the length of the calendar:
let hebrew = NSCalendar(calendarIdentifier: NSCalendarIdentifierHebrew)
hebrew?.daysInYear(-7) // 354
hebrew?.daysInYear(-100) // 384
Enjoy.
If you're only going to use the Gregorian Calender, you can calculate it manually.
http://en.wikipedia.org/wiki/Leap_year#Algorithm
if year modulo 400 is 0 then leap
else if year modulo 100 is 0 then no_leap
else if year modulo 4 is 0 then leap
else no_leap
I finally came up with a solution that works. What I do is first calculate the number of months in the year and then for each month calculate the number of days for that month.
The code looks like this:
NSUInteger days = 0;
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *today = [NSDate date];
NSDateComponents *components = [calendar components:NSYearCalendarUnit fromDate:today];
NSUInteger months = [calendar rangeOfUnit:NSMonthCalendarUnit
inUnit:NSYearCalendarUnit
forDate:today].length;
for (int i = 1; i <= months; i++) {
components.month = i;
NSDate *month = [calendar dateFromComponents:components];
days += [calendar rangeOfUnit:NSDayCalendarUnit
inUnit:NSMonthCalendarUnit
forDate:month].length;
}
return days;
It is not as neat as I would have hoped for but it will work for any calendar such as the ordinary gregorian one or the islamic one.
Use the NSCalendar and NSDateComponent classes, like this:
long GetDaysInYear(int year) {
NSDateComponents* c = [[NSDateComponents alloc] init];
c.year = year;
NSCalendar* cal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDate* startDate = [cal dateFromComponents:c];
c.year += 1;
NSDate* endDate = [cal dateFromComponents:c];
return [cal components:NSDayCalendarUnit fromDate:startDate toDate:endDate options:0].day;
}
As example:
func daysInYear(year: Int) -> Int {
var calendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)
var b = NSDate.dateWithNaturalLanguageString("01.01.\(year)", locale: NSLocale.currentLocale()) as! NSDate
var e = NSDate.dateWithNaturalLanguageString("12.31.\(year)", locale: NSLocale.currentLocale()) as! NSDate
return calendar!.components(NSCalendarUnit.CalendarUnitDay, fromDate: b, toDate: e, options: nil).day + 1
}
But default days return 355 and 354 this caused (may be) that counting begin from zero :)