0

I am using this method to convert a month and year to a date which equals the last day in the month of the year given.

+ (NSDate*)endOfMonthDateForMonth:(int)month year:(int)year
{
    NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    NSDateComponents *comps = [[NSDateComponents alloc] init];
    comps.year = year;
    comps.month = month;

    NSDate *monthYearDate = [calendar dateFromComponents:comps];

    // daysRange.length will contain the number of the last day of the endMonth:
    NSRange daysRange = [calendar rangeOfUnit:NSDayCalendarUnit inUnit:NSMonthCalendarUnit forDate:monthYearDate];
    comps.day = daysRange.length;
    comps.hour = 0;
    comps.minute = 0;
    comps.second = 0;
    [comps setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
    [calendar setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
    [calendar setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
    NSDate *endDate = [calendar dateFromComponents:comps];
    return endDate;
}

I want the date to have a time component of 00:00:00, thats why I have set the time zone to GMT 0 and the date components for minutes, hours and seconds to 0. The date returned from the method is correct and has a time component from 00:00:00.

This is how I save the date to Core Data:

NSDate *endDate = [IBEstPeriod endOfMonthDateForMonth:endMonth year:endCalYear];
[annualPeriod setEndDate:endDate];

After retrieving the data and NSLogging it to the debugger console, I get dates like 2008-12-30 23:00:00 +0000 with a time component != 0.

Why did the component change now? Shouldn't it stay at 00:00:00?

What did I code wrong here?

Thank you!!

AlexR
  • 5,514
  • 9
  • 75
  • 130

2 Answers2

3

You just need to set the calendar time zone after creating calendar.

Add this as the second line of your function:

calendar.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];

However, an easier way to do it would be this:

- (NSDate*)endOfMonthDateForMonth:(int)month year:(int)year
{
    NSCalendar *calendar    = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    calendar.timeZone       = [NSTimeZone timeZoneWithName:@"UTC"];

    NSDateComponents *comps = [[NSDateComponents alloc] init];
    comps.year              = year;
    comps.month             = month+1;
    comps.day               = 0;

    NSDate *monthYearDate   = [calendar dateFromComponents:comps];
    return monthYearDate;
}

Results:

NSLog(@"%@",[self endOfMonthDateForMonth:12 year:2010]);
// Output: 2012-07-10 12:30:05.999 Testing App[16310:fb03] 2010-12-31 00:00:00 +0000

This works by setting the date to the "0th" day of next month, which is the same as the last day of this month. (This does the same thing as subtracting one day from the first day of next month.) Note that this works because comps are allowed to "overflow" (or in this case "underflow") and dateFromComponents: does the math automatically.

lnafziger
  • 25,760
  • 8
  • 60
  • 101
  • I tried your code, but I still have the same issue: I get `2010-12-30 23:00:00 +0000` instead of `2010-12-31`. – AlexR Jul 10 '12 at 16:20
  • Are you sure that you aren't changing the dates somewhere else? I just updated my answer to show the actual results that I receive when I run it. – lnafziger Jul 10 '12 at 16:31
  • Is it possible that I need to set my local time zone differently (I am in my local time zone (GMT +2 = Germany) but had used GMT +0 when I created the date. – AlexR Jul 10 '12 at 16:37
  • Well, this function will take whatever month and year that you give it and give you a NSDate that is the last day of the month, in GMT +0. When you use it later, make sure that you aren't converting it to your local timezone! – lnafziger Jul 10 '12 at 19:10
  • It seems to work now: I am using a dateFormatter, in which I set my current timezone to format the NSLog output: `[dateFormatter setTimeZone:[NSTimeZone localTimeZone]];` However, I must admit that I don't fully understand why this works: I have a GMT +0 date, which is shown correctly (= unchanged) in my current time zone. This does not sound very logical to me. I am missing something here? – AlexR Jul 10 '12 at 19:18
  • Note that a date formatter won't affect a NSLog statement unless you are using the string that the date formatter creates. – lnafziger Jul 11 '12 at 13:59
1

Two things to look at:

  1. Try setting the time zone for comps before you set the time components. I haven't tested that, but it may be that NSDateComponents adjusts the hour to keep the same time relative to GMT when you set the time zone.

  2. Take a look at how you're interpreting the date when you read it back from your Core Data store.

Caleb
  • 124,013
  • 19
  • 183
  • 272
  • Thank you for your help, Caleb!! I tried your suggestion 1., unfortunately without success. Regarding 2.: I just do logging of the date with no conversion at all. It seems that the date stored in Core Data is being converted in my current time zone when it is being read (I am based in GMT +2:00). How can I avoid this conversion, which actually changes the day? I do not care about the time component, I just need the date, which must stay constant in all time zones. – AlexR Jul 10 '12 at 15:48
  • NSDate stores date information independent of time zone. To make sense of a NSDate, you should use a date formatter to display your dates so that you can specify the time zone yourself. I suspect that when you log a date, NSDate's `-description` method probably uses the current time zone, but the date itself won't change (dates are immutable). So, again, use a date formatter if you want to display your dates in GMT. – Caleb Jul 10 '12 at 16:22
  • I am not sure if NSDate's -description method really uses the current time zone because I use similar code to generate an NSDate: `NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]]; [dateFormatter setDateFormat:@"yyy-MM-dd"]; [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];NSDate *date = [dateFormatter dateFromString:[csvLineItems objectAtIndex:0]];` to parse from a string. – AlexR Jul 10 '12 at 16:28
  • ... In this case I always get correct values like 2002-04-03 00:00:00 +0000. I tried the same method (string parsing) instead of my code shown above and still have the same issue. – AlexR Jul 10 '12 at 16:29
  • As Caleb says, NSDate's are always stored in GMT, but the calendar/date formatter operations will convert to/from the timezone that they are set to when you create them or format them. – lnafziger Jul 10 '12 at 16:35
  • How would you recommend to set the time zone to GMT in NSLog to avoid this conversion? – AlexR Jul 10 '12 at 17:08
  • This should do it: `NSDateFormatter *df = [[NSDateFormatter alloc] init]; [df setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]; NSLog(@"the date is: %@", [df stringFromDate:someDate]);` – Caleb Jul 10 '12 at 17:33