Xcode, Objective-C / 残り日数(あと何日?)の計算で困った

ある特定の日まで,あと何日残されているか?を計算しようとしたところ,タイムゾーンとか,今日を取得すると時刻もついてきてしまうので,「感覚的に」違和感が出るとのご指摘.

ということで「特定の日」が”yyyy-MM-dd”(2023-08-20形式)で与えられた場合の残り日数の計算結果の違和感の解消を目指します.

課題

  • 「特定の日」には,時刻もなければタイムゾーンも無い.漠然と「今いるタイムゾーンの,その日,しかもNSStringで渡す.」という解釈・条件である.
  • 「今」を取得した時,タイムゾーンがGMT(標準時)のものが返って来た.「特定の日」が日本時間で設定されていると9時間の差がある.
  • なので15時を境に1日増えたり減ったりする結果になっていたため違和感があった.

どうしたか

  • 「特定の日」も「今」も,日付だけを抽出しよう
  • どちらも時刻成分は取り除いてGMTで扱おう
  • たぶん日本時間で”00:00:00″はGMTだと”前日の15:00:00″になるけど「残り何日」を計算するので時差のことは気にしなくてよくなる

成果物

/// Get remaining days from today to specified date without considerting time components
/// @param expilation date in "yyyy-MM-dd" format(without time components)
/// @return remaining days
- (int) getRemainingDays:(NSString *)expilationDate
{
    // Get NSDate of "yyyy/MM/dd" format
    NSDateFormatter *formatter = [[NSDateFormatter alloc] initWithDateFormat:@"%Y/%m/%d" allowNaturalLanguage:YES];

    // Get today and extract year/month/day only with format "yyyy-MM-dd"
    NSCalendar  *cal = [NSCalendar currentCalendar];
    unsigned    unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;
    NSDateComponents *comp = [cal components:unitFlags fromDate:[NSDate date]];
    NSString    *todayDate = [[NSString alloc] initWithFormat:@"%04ld-%02ld-%02ld", (long)comp.year, comp.month, comp.day];

    // Get NSDates without time components
    NSDate      *today      = [formatter dateFromString:todayDate];
    NSDate      *target     = [formatter dateFromString:expilationDate];
    
    // Get the STARTING TIME of specified date (e.g. "2022-07-31 00:00:00" for "2022-07-31 12:34:56")
    NSDate      *fromDate;
    NSDate      *toDate;
    NSCalendar  *calendar   = [NSCalendar currentCalendar];
    [calendar rangeOfUnit:NSCalendarUnitDay startDate:&fromDate interval:NULL forDate:today];
    [calendar rangeOfUnit:NSCalendarUnitDay startDate:&toDate interval:NULL forDate:target];

    // Calculate differene in days
    NSDateComponents *difference
            = [calendar components:NSCalendarUnitDay
                          fromDate:fromDate
                            toDate:toDate
                           options:0];
    return (int)[difference day];
}