核心類速覽
| 類 | 職責 | 關鍵屬性/方法 |
NSDate | 絕對時間點(參考日期:2001-01-01 00:00:00 UTC) | `date`、`timeIntervalSince1970`、`timeIntervalSinceNow`、`dateByAddingTimeInterval:` |
NSTimeInterval | `typedef double`,以秒為單位的時間間隔 | 正數=未來,負數=過去 |
NSDateFormatter | NSDate ↔ NSString 雙向轉換 | `dateFormat`、`stringFromDate:`、`dateFromString:`、`timeZone`、`locale` |
NSTimeZone | 時區資訊(偏移量、夏令時規則) | `systemTimeZone`、`localTimeZone`、`secondsFromGMTForDate:` |
NSDate 基礎操作
// 當前時間 (UTC)
NSDate *now = [NSDate date];
NSLog(@"UTC: %@", now); // 輸出總是 UTC
// 獲取當前時區偏移秒數
NSTimeZone *tz = [NSTimeZone systemTimeZone];
NSInteger offset = [tz secondsFromGMTForDate:now];
// 本地時間 (UTC + 偏移)
NSDate *local = [NSDate dateWithTimeIntervalSinceNow:offset];
NSLog(@"本地: %@", local);
// 相對時間構建
NSDate *tomorrow = [NSDate dateWithTimeIntervalSinceNow:24*3600];
NSDate *yesterday = [NSDate dateWithTimeIntervalSinceNow:-24*3600];
NSDate *oneHourLater = [NSDate dateWithTimeIntervalSinceNow:3600];
// 時間間隔計算
NSTimeInterval diff = [tomorrow timeIntervalSinceDate:yesterday]; // 172800.0
NSTimeInterval since1970 = [now timeIntervalSince1970]; // Unix 時間戳
// 快速獲取本地時間 (一行代碼)
NSDate *localNow = [NSDate dateWithTimeIntervalSinceNow:[NSTimeZone localTimeZone].secondsFromGMT];
NSDateFormatter 格式化字符串規範
| 符號 | 含義 | 示例 |
| `y` | 年 | `yyyy` → 2024 |
| `M` | 月 | `MM` → 01, `MMM` → Jan |
| `d` | 日 | `dd` → 05 |
| `H` | 時 (24制) | `HH` → 14 |
| `h` | 時 (12制) | `hh` → 02 |
| `a` | AM/PM | `a` → PM |
| `m` | 分 | `mm` → 05 |
| `s` | 秒 | `ss` → 09 |
| `z` | 時區名 | `z` → GMT+08:00 |
| `E` | 星期 | `EEE` → Mon |
| `G` | 時代 | `G` → AD |
雙向轉換實戰
NSDate → NSString
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
[fmt setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
[fmt setTimeZone:[NSTimeZone systemTimeZone]]; // 關鍵:指定輸出時區
[fmt setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]]; // 避免 24/12 制歧義
NSString *str = [fmt stringFromDate:[NSDate date]];
NSLog(@"格式化: %@", str); // 2024-01-15 14:30:05
NSString → NSDate
NSString *input = @"2024-01-15 14:30:05";
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
[fmt setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
[fmt setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]]; // 輸入視為 UTC
NSDate *date = [fmt dateFromString:input];
// 若輸入為本地時間字符串,需手動加上時區偏移
NSDate *localDate = [date dateByAddingTimeInterval:[NSTimeZone localTimeZone].secondsFromGMT];
練習:中文自定義格式
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
[fmt setDateFormat:@"yyyy年MM月dd日 HH點mm分ss秒"];
NSString *chinese = @"2024年01月15日 14點30分05秒";
NSDate *date = [fmt dateFromString:chinese];
// 再轉為本地時間
date = [date dateByAddingTimeInterval:[NSTimeZone localTimeZone].secondsFromGMT];
常見坑點與最佳實踐
| 坑點 | 現象 | 修正 |
| NSDate 輸出總是 UTC | `NSLog(@"%@", date)` 顯示 2024-01-15 06:30:00 +0000 | 這是正常的!`description` 固定 UTC。用 `NSDateFormatter` 指定時區輸出 |
| 格式化器不指定時區 | 同一代碼在不同手機輸出不一致 | **始終** `[fmt setTimeZone:...]`,通常用 `systemTimeZone` 或 `timeZoneWithAbbreviation:@"UTC"` |
| 12/24 制歧義 | `hh` 配合 `a` (AM/PM) 使用;單用 `hh` 易混淆 | 統一用 `HH` (24制) 或 明確設置 `locale` 為 `en_US_POSIX` |
| 解析失敗返回 nil | `dateFromString:` 返回 nil 無報錯 | 檢查格式字符串完全匹配、時區正確、輸入字符串無隱形字符 |
| 頻繁創建 Formatter | 性能開銷大(解析格式字符串、創建內部緩存) | **靜態單例復用** 或持有屬性復用,避免循環中創建 |
| Unix 時間戳精度 | `timeIntervalSince1970` 返回 `double` 含毫秒 | 需整秒傳給後端時:`(long long)[date timeIntervalSince1970]` |
工程級封裝建議
// DateUtils.h
@interface DateUtils : NSObject
+ (NSString *)stringFromDate:(NSDate *)date format:(NSString *)fmt timeZone:(NSTimeZone *)tz;
+ (NSDate *)dateFromString:(NSString *)str format:(NSString *)fmt timeZone:(NSTimeZone *)tz;
+ (NSDate *)localDateFromUTCString:(NSString *)str format:(NSString *)fmt;
+ (NSString *)utcStringFromLocalDate:(NSDate *)date format:(NSString *)fmt;
+ (NSTimeInterval)timeIntervalSince1970:(NSDate *)date;
@end
// 內部持有靜態 NSDateFormatter 字典按格式緩存,線程安全用 GCD 鎖
總結
- NSDate = 絕對時間點 (UTC 參考),無時區概念
- NSDateFormatter = 格式化引擎,**必須顯式設置時區**
- NSTimeInterval = 秒級 `double`,支援負數、小數
- 最佳實踐:Formatter 復用、時區顯式、Locale 固定 `en_US_POSIX`、UTC 存儲/傳輸、本地顯示轉換