// class_isodate_oc.h Copyright (c) Kari Laitinen // http://www.naturalprogramming.com // 2013-07-11 Objective-C version of this file was created. // 2013-07-15 Last modification. // This file contains the definition for class ISODate and // its instance methods. // ISO refers to International Standards Organization that // has provided a standard according to which date information // should be shown in the format YYYY-MM-DD. // Our Gregorian Calendar is built into the methods of // the ISODate class. This means that the methods automatically // handle things like leap years. // Note that this is a textbook program. Most programming // environments provide 'official' classes to calculate // information related to dates and time. Those official // classes should be used when more serious programs are written. // This class should, however, serve as an interesting // example of a somewhat larger class. #import @interface ISODate : NSObject { int this_year ; int this_month ; int this_day ; } - (id) init ; - (id) init_year: (int) given_year and_month: (int) given_month and_day: (int) given_day ; - (id) init_with_string: (NSString*) date_as_string ; - (int) year ; // getter methods for data fields (attributes) - (int) month ; - (int) day ; - (bool) is_last_day_of_month ; - (bool) this_is_a_leap_year ; - (bool) is_within_date : (ISODate*) earlier_date and_date: (ISODate*) later_date ; - (int) index_for_day_of_week ; - (NSString*) get_day_of_week ; - (void) increment ; - (void) decrement ; - (void) get_distance_to: (ISODate*) another_date years: (int*) years_of_distance months: (int*) months_of_distance days: (int*) days_of_distance ; - (int) get_week_number ; - (bool) is_equal_to: (ISODate*) another_date ; - (bool) is_not_equal_to: (ISODate*) another_date ; - (bool) is_earlier_than: (ISODate*) another_date ; - (bool) is_later_than: (ISODate*) another_date ; - (NSString*) description ; @end @implementation ISODate - (id) init { // This is the 'default constructor' that initializes // the date with the current computer date NSDate* current_system_date = [ NSDate date ] ; // NSDate method 'description' returns a string that contains // the NSDate date in the format YYYY-MM-DD HH:SS etc. // We'll take the first 10 characters of this string and we // have a date that is suitable for another constructor of this // class. NSString* current_date_as_string = [ [ current_system_date description ] substringToIndex: 10 ] ; [ self init_with_string: current_date_as_string ] ; } - (id) init_year: (int) given_year and_month: (int) given_month and_day: (int) given_day { self = [ super init ] ; if ( self ) { this_year = given_year ; this_month = given_month ; this_day = given_day ; } return self ; } - (id) init_with_string: (NSString*) date_as_string { // This constructor accepts date strings in three formats: // // YYYY-MM-DD is the ISO 8601 format // MM/DD/YYYY is the American format. // DD.MM.YYYY is the format used in Europe. self = [ super init ] ; if ( self ) { bool given_date_is_valid = true ; NSArray* date_components = [ date_as_string componentsSeparatedByString: @"-" ] ; if ( [ date_components count ] == 3 ) { // Character '-' was contained in the given string. // We'll suppose it is the YYYY-MM-DD format. this_year = [ [ date_components objectAtIndex: 0 ] intValue ] ; this_month = [ [ date_components objectAtIndex: 1 ] intValue ] ; this_day = [ [ date_components objectAtIndex: 2 ] intValue ] ; } else { date_components = [ date_as_string componentsSeparatedByString: @"/" ] ; if ( [ date_components count ] == 3 ) { // Character '/' is contained in the given date. // We'll think that it is a date in format MM/DD/YYYY this_year = [ [ date_components objectAtIndex: 2 ] intValue ] ; this_month = [ [ date_components objectAtIndex: 0 ] intValue ] ; this_day = [ [ date_components objectAtIndex: 1 ] intValue ] ; } else { date_components = [ date_as_string componentsSeparatedByString: @"." ] ; if ( [ date_components count ] == 3 ) { // Character '.' is contained in the given date. // We'll think that it is a date in format DD.MM.YYYY this_year = [ [ date_components objectAtIndex: 2 ] intValue ] ; this_month = [ [ date_components objectAtIndex: 1 ] intValue ] ; this_day = [ [ date_components objectAtIndex: 0 ] intValue ] ; } else { given_date_is_valid = false ; } } } if ( this_day < 1 || this_day > 31 || this_month < 1 || this_month > 12 ) { given_date_is_valid = false ; } if ( given_date_is_valid == false ) { printf( "\n\n Invalid date discovered. \n\n" ) ; } } return self ; } - (int) year { return this_year ; } - (int) month { return this_month ; } - (int) day { return this_day ; } - (bool) is_last_day_of_month { bool it_is_last_day_of_month = false ; if ( this_day > 27 ) { if ( this_day == 31 ) { it_is_last_day_of_month = true ; } else if ( ( this_day == 30 ) && ( this_month == 2 || this_month == 4 || this_month == 6 || this_month == 9 || this_month == 11 ) ) { it_is_last_day_of_month = true ; } else if ( this_day == 29 && this_month == 2 ) { it_is_last_day_of_month = true ; } else if ( this_day == 28 && this_month == 2 && ! [ self this_is_a_leap_year ] ) { it_is_last_day_of_month = true ; } } return it_is_last_day_of_month ; } - (bool) this_is_a_leap_year { bool return_code = false ; if ( this_year % 4 == 0 ) { // Years which are equally divisible by 4 and which // are not full centuries are leap years. Centuries // equally divisible by 4 are, however, leap years. if ( this_year % 100 == 0 ) { int century = this_year / 100 ; if ( century % 4 == 0 ) { return_code = true ; } } else { return_code = true ; } } return return_code ; } - (bool) is_within_date : (ISODate*) earlier_date and_date: (ISODate*) later_date { return (( [ self is_equal_to: earlier_date ] ) || ( [ self is_equal_to: later_date ] ) || ( [ self is_later_than: earlier_date ] && [ self is_earlier_than: later_date ] ) ) ; } - (int) index_for_day_of_week { // day_index will get a value in the range from 0 to 6, // 0 meaning Monday and 6 meaning Sunday. int day_index = 0 ; ISODate* known_date = [ [ ISODate alloc ] init_year: 1997 and_month: 10 and_day: 6 ] ; // Oct. 6, 1997 is Monday. if ( [ self is_earlier_than: known_date ] ) { while ( [ self is_not_equal_to: known_date ] ) { if ( day_index > 0 ) { day_index -- ; } else { day_index = 6 ; } [ known_date decrement ] ; } } else { while ( [ self is_not_equal_to: known_date ] ) { if ( day_index < 6 ) { day_index ++ ; } else { day_index = 0 ; } [ known_date increment ] ; } } return day_index ; } - (NSString*) get_day_of_week { NSArray* days_of_week = [ NSArray arrayWithObjects: @"Monday", @"Tuesday", @"Wednesday", @"Thursday", @"Friday", @"Saturday", @"Sunday", nil ] ; return [ days_of_week objectAtIndex: [ self index_for_day_of_week ] ] ; } - (void) increment { if ( [ self is_last_day_of_month ] ) { this_day = 1 ; if ( this_month < 12 ) { this_month ++ ; } else { this_month = 1 ; this_year ++ ; } } else { this_day ++ ; } } - (void) decrement { if ( this_day > 1 ) { this_day -- ; } else { if ( this_month == 5 || this_month == 7 || this_month == 10 || this_month == 12 ) { this_day = 30 ; this_month -- ; } else if ( this_month == 2 || this_month == 4 || this_month == 6 || this_month == 8 || this_month == 9 || this_month == 11 ) { this_day = 31 ; this_month -- ; } else if ( this_month == 1 ) { this_day = 31 ; this_month = 12 ; this_year -- ; } else if ( this_month == 3 ) { this_month = 2 ; if ( [ self this_is_a_leap_year ] ) { this_day = 29 ; } else { this_day = 28 ; } } } } - (void) get_distance_to: (ISODate*) another_date years: (int*) years_of_distance months: (int*) months_of_distance days: (int*) days_of_distance { ISODate* start_date ; ISODate* end_date ; int start_day, end_day ; if ( [ self is_earlier_than: another_date ] ) { start_date = self ; end_date = another_date ; } else { start_date = another_date ; end_date = self ; } // We will suppose that day 30 is the last day of every // month. This way we minimize calculation errors. if ( [ start_date is_last_day_of_month ] || ( [ start_date day ] == 28 && [ start_date month ] == 2 ) ) { start_day = 30 ; } else { start_day = [ start_date day ] ; } if ( [ end_date is_last_day_of_month ] || ( [ end_date day ] == 28 && [ end_date month ] == 2 ) ) { end_day = 30 ; } else { end_day = [ end_date day ] ; } *years_of_distance = [ end_date year ] - [ start_date year ] ; *months_of_distance = [ end_date month ] - [ start_date month ] ; *days_of_distance = end_day - start_day ; if ( *days_of_distance < 0 ) { *months_of_distance = *months_of_distance - 1 ; *days_of_distance = *days_of_distance + 30 ; } if ( *months_of_distance < 0 ) { // Note that the statement // *years_of_distance -- ; // cannot replace the following statement. // (I had to work several hours to discover this. // Again it was a programming mistake created by // the existence of C pointers) *years_of_distance = *years_of_distance - 1 ; *months_of_distance = *months_of_distance + 12 ; } } - (int) get_week_number { // January 1, 1883 was a Monday with week number 1. // This method works correctly only with dates that come // after that date. ISODate* date_to_increment = [ [ ISODate alloc ] init_year: 1883 and_month: 1 and_day: 1 ] ; int week_number = 1 ; int local_index_for_day_of_week = 0 ; // 0 means Monday while ( [ date_to_increment is_earlier_than: self ] ) { [ date_to_increment increment ] ; if ( local_index_for_day_of_week == 6 ) // 6 means Sunday { local_index_for_day_of_week = 0 ; // back to Monday if ( week_number < 52 ) { week_number ++ ; } else if ( week_number == 52 ) { if ( [ date_to_increment day ] <= 28 && [ date_to_increment month ] == 12 ) { week_number = 53 ; } else { week_number = 1 ; } } else // must be week_number 53 { week_number = 1 ; } } else { local_index_for_day_of_week ++ ; } } return week_number ; } - (bool) is_equal_to: (ISODate*) another_date { return ( this_day == [ another_date day ] && this_month == [ another_date month ] && this_year == [ another_date year ] ) ; } - (bool) is_not_equal_to: (ISODate*) another_date { return ( this_day != [ another_date day ] || this_month != [ another_date month ] || this_year != [ another_date year ] ) ; } - (bool) is_earlier_than: (ISODate*) another_date { return ( ( this_year < [ another_date year ] ) || ( ( this_year == [ another_date year ] ) && ( this_month < [ another_date month ] ) ) || ( ( this_year == [ another_date year ] ) && ( this_month == [ another_date month ] ) && ( this_day < [ another_date day ] ) ) ) ; } - (bool) is_later_than: (ISODate*) another_date { return ( ( this_year > [ another_date year ] ) || ( ( this_year == [ another_date year ] ) && ( this_month > [ another_date month ] ) ) || ( ( this_year == [ another_date year ] ) && ( this_month == [ another_date month ] ) && ( this_day > [ another_date day ] ) ) ) ; } // A method named 'description' is the toString() method of // Objective-C - (NSString*) description { // This method returns 'this' date as a string in the YYYY-MM-DD format. return [ NSString stringWithFormat: @"%d-%02d-%02d", this_year, this_month, this_day ] ; } @end // end of ISODate implementation