添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

本文翻译自 maddysoft.com

作为 StackOverflow 的活跃用户,经常解答 iOS、Swift、Objective-C 的相关问题。我发现时间,日期,它们的格式以及相关操作是经常被问起的。因此,本文的目的就在于帮助开发者理清概念,消除疑虑,熟悉用法。

Swift(3及以后)通过 Date Calendar DateFormatter DateComponents Timezone Locale 表示时间和日期相关概念。相应的 Objective-C 类为 NSDate NSCalendar NSDateFormatter NSDateComponents NSTimezone NSLocale 。2 及以前的 Swift 版本同 OC 一致。

本文基于 Foundation 框架,使用 Swift 3/4 和 Objective-C 作为示例语言,

Date

Date 是一切的起点,代表某个时刻。必须指出 Date 不包含时区信息。试想一下“现在”就是一个 Date ,就是此时此刻。不论我们身处何地,“现在”都一样,对每个人来说都是相同的。尽管不同地区的当地时间不尽相同,但这并不影响“现在”的意义。

我们只需在两种情况下考虑时区影响:1) 将字符串转换为 Date ;2) 显示 Date 给用户看(或者我们自己 debug 时) 。

如何创建一个代表“现在”的 Date 对象呢?操作如下:

1
2
3
4
5
// Swift
let now = Date()

// Objective-C
NSDate *now = [NSDate date];

很简单吧,但即使这样许多开发者依然感到困惑。因为如果我们 debug 这个对象,把它打印到控制台,就会发现:(假设我们在纽约,当前时间为2018年3月1日下午2点42分)

1
2
3
4
5
// Swift
print(now)

// Objective-C
NSLog(@"%@", now);

结果如下:

2018-03-01 19:42:00 +0000

明明是下午2点42,为什么时间变成 19:42 ? 而 +0000 又是几个意思?

让我们迅速补充一下背景知识。打印 Date (或者在控制台查看) 实际调用 description (或 debugDescription )。下面两行代码作用相同:

1
2
3
4
5
6
7
// Swift
print(now)
print(now.description)

// Objective-C
NSLog(@"%@", now);
NSLog(@"%@", now.description);

description 返回 Date 的字符串表示。还记得之前我们说过需要担心时区的情况吗?这就是其中之一。不巧的目前它使用24小时制,UTC 时区实现。

回到正题, +0000 代表基于 UTC 偏移的时区,前两位对应小时,后两位对应分钟,+ 表示早于。由于默认使用 UTC 时区,所有没有偏移,即 +0000。纽约比 UTC 晚5个小时,所以当地时间2018年3月1日下午2点42分换算为 UTC 时间就是17点42分。

换一种方式思考:假设你在纽约,正在与身处 UTC 时区的朋友打电话。纽约时间下午2点42分,你说“现在”;与此同时你的朋友看到的时间是19点42分(24小时制)。这两个时间对于你们来说都是“现在”,可以用同一个 Date 表示。尽管时钟的显示不同,但它们指向同一时刻。打印 Date 对应“看表”这个动作,只不过你看的是朋友的表,位于 UTC 时区。这并不会改变 Date 的意义,只不过是基于特定时区展示时间而已。

你肯定会说这的确很容易使人感到困惑。是的,开发者明明只想看自己时区的时间。所以除了 description Date 还提供另一个函数 description(with:) ,参数是一个 Locale 对象,稍后介绍。目前我们只需填入 .current 。如此就可以看到本地时间啦。

下面代码根据当前时区和地区显示时间,如果不想用 UTC 的话。

1
2
3
4
5
// Swift
print(now.description(with: .current))

// Objective-C
NSLog(@"%@", [now descriptionWithLocale:NSLocale.currentLocale]);

输出结果如下(基于当前时区和地区):

1
Thursday, March 1, 2018 at 2:42:00 PM Eastern Standard Time

DateFormatter 一节,我们将学习如何定制 Date 的展示样式。

地区

Locale 代表地区:

1
Locale 是对语言、文化、技术惯例和标准有关信息的封装。例如表示小数点的符号,年月日顺序等。

直接影响日期的显示结果:月份和星期的语言,年月日顺序,以及分隔符。

格式化日期时,默认使用当前用户地区。获取当前地区的代码如下:

1
2
3
4
5
// Swift
let locale = Locale.current

// Objective-C
NSLocale *locale = NSLocale.currentLocale;

当然也可以指定地区。首先,我们要知道目标区域代码,一般由语言和国家代码的组成。例如,在 en_CA 表示加拿大英语区;加拿大法语区则对应 fr_CA ,其他地区请查阅文档。创建指定地区的代码如下:

1
2
3
4
5
// Swift
let locale = Locale(identifier: "en_CA")

// Objective-C
NSLocale *locale = [NSLocale localeWithLocaleIdentifier:@"en_CA"];

有一个非常特别的地区,后面很多地方都会用到: en_US_POSIX

1
2
3
4
5
// Swift
let posixLocale = Locale(identifier: "en_US_POSIX")

// Objective-C
NSLocale *posixLocale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];

时区

TimeZone 代表时区。 Date String 二者互相转化时必须格外注意时区问题。大多数操默认基于用户当前时区,除了 description ,它使用 UTC 时区。

以下代码获取用户当前时区

1
2
3
4
5
// Swift
let tz = TimeZone.current

// Objective-C
NSTimeZone *tz = NSTimeZone.systemTimeZone;

取得时区的方式多种多样:唯一代码,缩写(不唯一),或者基于 UTC 的偏移量(单位秒)。更多细节请查看文档。

另外建议大家阅读 Wiki 上 Coordianted Universal Time 下关于时区的章节,以便更好的理解 UTC,Zulu (Z) 以及 GMT。

DateFormatter

理解了 Date Locale TimeZone ,我们正式进入日期格式化的讲解。其目的在于允许时间和字符串互相转换。

日期格式

使用 DateFormatter 向用户展示日期。通过它输出的字符串已经参考了地区因素,确保结果准确、恰当:月份和星期以当地语言表示,年月日顺序以及分隔符符合习惯。

默认使用当前用户地区和设备时区,可以通过 locale timeZone 属性进行修改。

DateFormatter 提供了两种方式定义字符串结果:style(样式)和 format(格式)。推荐使用 前者,它基础地区输出结果。举例来说,如果使用格式,假设结果为:

1
8/3/18

根据用户所在的地区,可以理解为 2018年8月3日,2018年3月8日,等等。而样式可以完全避免这些歧义。

样式

遇到向用户显示 Date 的需求,恰当的设置 DateFormatter 的样式即可。获得的结果是完全本地化的(默认基于用户当前地区)。如果不想显示时间,将 timeStyle 设置为 .none 即可;这也适用于日期,相应的设置 dateStyle .none 。对于要保留的部分,从 .short .medium .long .full 中选择一个最符合需求的(具体样式见文档)。不过最终结果还是取决于地区。日期和时间的样式无需一致。

下面的代码将日期显示为 long ,时间显示为 medium

1
2
3
4
5
6
7
8
9
10
11
// Swift
let dfs = DateFormatter()
dfs.dateStyle = .long
dfs.timeStyle = .medium
print(dfs.string(from: date))

// Objective-C
NSDateFormatter *dfs = [[NSDateFormatter alloc] init];
dfs.dateStyle = NSDateFormatterLongStyle;
dfs.timeStyle = NSDateFormatterMediumStyle;
NSLog(@"%@", [dfs stringFromDate:now]);

以2018年3月1日下午2点42分为例,结果如下,第二列表示所使用的地区代码。

1
2
3
4
5
6
7
8
9
10
March 1, 2018 at 2:42:00 PM	 en_US (English/USA)
1 March 2018 at 14:42:00 en_GB (English/Great Britain)
1. März 2018 um 14:42:00 de_DE (German/Germany)
1 mars 2018 à 14:42:00 fr_FR (French/France)
١ مارس، ٢٠١٨، ٢:٤٢:٠٠ م ar_EG (Arabic/Egypt)
2018年3月1日 下午2:42:00 zh_CN (Chinese/China)
2018년 3월 1일 오후 2:42:00 ko_KR (Korean/Korea)
1 марта 2018 г., 14:42:00 ru_RU (Russian/Russia)
1 de março de 2018 14:42:00 pt_BR (Portuguese/Brazil)
1 de marzo de 2018, 2:42:00 p.m. es_CO (Spanish/Colombia)

还要考虑到系统设置对于结果的影响。例如,iOS 允许用户选择 12 或 24 小时制,优先级高于地区习惯。macOS 中,用户可以在设置页面具体定义日期和时间格式。这也是推荐使用样式的另一个原因,系统设置将被考虑在内。

此前我们提到过使用 description(with:) 方法根据指定地区和当前时区输出结果。这等同于 设置 DateFormatter 的日期和时间样式为 .full

格式

尽管通过样式定义规则,显示给用户肯定万无一失。但实际上不可避免的仍需要按照特定格式转换 Date String 。例如,将日期发送给服务器或者 API,而对方对格式有要求。此时就只能使用格式。

那么如何定义日期格式呢?查看 dateFormate 的文档解释,我们发现提到了 Data Formatting Guide 。打开链接,在第一页又会指向另一个页面, Date Formatters 。其中 Fixed Formats 小节罗列了各个 OS X 和 iOS 版本所支持的具体技术标准。Unicode Technical Standard 第35号是对日期/时间格式进行具体定义的技术文档。其最新版本在 这里 。注意不是所有系统都支持最新技术标准。

确定格式后,就要使用合适的占位符,分隔符和常量字符着手编写。

请看下面例子:

1
2
3
4
5
6
7
8
9
10
// Swift
let dff = DateFormatter()
dff.dateFormat = "yyyy-MM-dd HH:mm:ss"
dff.locale = Locale(identifier: "en_US_POSIX")
print(dff.string(from: date))

// Objective-C
NSDateFormatter *dff = [[NSDateFormatter alloc] init];
dff.dateFormat = @"yyyy-MM-dd HH:mm:ss";
NSLog(@"%@", [dff stringFromDate:date]);

2018年3月1号下午2点42分的对应结果为:

1
2018-03-01 14:42:00

这种方式默认使用用户当前时区。当然也可以通过设置 timeZone 属性选择其他时区。注意这里使用了特殊地区 en_US_POSIX ,详细解释,请看

下面是例子关于格式和结果,目标是 2018年3月1日下午2:42,时区为 Eastern Daylight Time(UTC-5)

1
2
3
4
5
6
7
8

yyyy-MM-dd 2018-03-01
h:mm a 2:42 pm
yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ 2018-03-01T14:42:00.000-05:00
dd/MM/yy 01/03/18
d.M.y 1.3.2018
d-MMM-yyyy 1-Mar-2018
HH:mm:ss Z 14:42:00 -0500

编写格式字符串时,请记住以下几点:

  • 任何字符,请以单引号饮用。只有不需要转译的字符才需要用括号引用。标点符号,数组,空格等不需要引用。
  • DateFormatter

    现在我们已经理解了 Date Locale TimeZone ,是时候介绍格式化了。通过格式化,我们可以将日期在 Date String 之间互相转换。

    格式化日期

    我们经常需要向用户展示日期。这时就应该使用 DateFormatter 。它允许我们将 Date 转换为 String ,同时考虑到 地区 。这样一来,日期就可以得到恰当的格式化。月份、星期以恰当的语言显示;年月日的顺序符合当地习惯,分隔符也会被本地化。

    日期格式默认使用当前用户地区和设备当前时区。通过设置 locale timeZone 属性,我们可以进行切换。

    Date 转换为 String 时, DateFormatter 允许我们通过两种方式定义规则:样式(style)和 格式(format)。推荐使用前者。因为这样可以确保符合用户预期。避免使用特定格式。例如,我们使用了某种格式,其输出结果为:

    8/3/18

    根据用户所在的地区,这串结果可以理解为 2018年8月3日,2018年3月8日,或者其他意义。选择 style 可以免除这些疑惑,结果会自动根据用户地区来计算,避免歧义。

    日期和时间样式

    对于日期展示来说,我们最好通过样式配置合适的 date formatter。这样一来,结果将自动适配区域(默认为用户的当前区域)。假设我们只想显示日期,将时间样式(timeStyle)设置为 .none 即可。同样,如果只想显示时间,则将日期样式(dateStyle)设置为 .none 。对于想展示的部分,有 .short .medium .long .full 可供选择。 DateFormatter.Style 的文档对细节提供了足量描述。但最终结果取决于地区。日期和时间的样式可以不同,无需一致。

    下面的例子展示了长日期,中时间:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // Swift
    let dfs = DateFormatter()
    dfs.dateStyle = .long
    dfs.timeStyle = .medium
    print(dfs.string(from: date))

    // Objective-C
    NSDateFormatter *dfs = [[NSDateFormatter alloc] init];
    dfs.dateStyle = NSDateFormatterLongStyle;
    dfs.timeStyle = NSDateFormatterMediumStyle;
    NSLog(@"%@", [dfs stringFromDate:now]);

    结果如下,第二列是地区,例子所用的日期为 2018年3月1日下午2点42分

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    March 1, 2018 at 2:42:00 PM	en_US (English/USA)
    1 March 2018 at 14:42:00 en_GB (English/Great Britain)
    1. März 2018 um 14:42:00 de_DE (German/Germany)
    1 mars 2018 à 14:42:00 fr_FR (French/France)
    ١ مارس، ٢٠١٨، ٢:٤٢:٠٠ م ar_EG (Arabic/Egypt)
    2018年3月1日 下午2:42:00 zh_CN (Chinese/China)
    2018년 3월 1일 오후 2:42:00 ko_KR (Korean/Korea)
    1 марта 2018 г., 14:42:00 ru_RU (Russian/Russia)
    1 de março de 2018 14:42:00 pt_BR (Portuguese/Brazil)
    1 de marzo de 2018, 2:42:00 p.m. es_CO (Spanish/Colombia)

    另外需要注意最终结果还会受到 iOS 设置影响。例如,用户可以选择12/24小时制。这项设置将覆盖地区设置。 (HH24小时,hh12小时)。macOS 允许用户在设置页面详细定义日期和时间格式。这是我们尽量使用样式的另一个理由,配合系统。

    早前我们提到过 description(with:) 方法,它使用当前时区和地区打印 Date 。其输出结果与使用完全日期和时间的 date formatter 一致。

    日期格式

    尽管我们强力推荐使用样式,但有时还是不可避免的使用格式。比如我们需要将日期按照特定格式转换为字符串发送给 API 或服务器。此时,就不得不使用 dateformatter 的格式。

    要通过格式定义规则,必须提供格式字符串。通过查看文档,我们发现 dateFormate 指向 Date Formatting Guide ,后者又指向 Date Formatter 。查看 Fixed Formats 小节,我们发现 Unicode 35号技术标准,其是对于所有支持格式的解释。最新版本可以在 这里找到 。注意,最新版本可能包含当前 SDK 不支持的特性。

    一旦确定转换的目标格式,下一步就是使用字段符号、标点、常量字符编写格式字符串。(格式字符串 = 标点+常量+filed)

    下面例子根据格式字符串将 Date 转换为 String

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // Swift
    let dff = DateFormatter()
    dff.dateFormat = "yyyy-MM-dd HH:mm:ss"
    // dff.locale = Locale(identifier: "en_US_POSIX")
    print(dff.string(from: date))

    // Objective-C
    NSDateFormatter *dff = [[NSDateFormatter alloc] init];
    dff.dateFormat = @"yyyy-MM-dd HH:mm:ss";
    NSLog(@"%@", [dff stringFromDate:date]);

    转换2018年3月1日下午2:42分的转换结果如下:

    2018-03-01 14:42:00

    请注意 date formatter 默认使用当前时区。如果想要结果根据特定时区显示,需要设置 timeZone 属性。同样注意 en_US_POSIX 的使用,详见本节末尾列表的最后一点。

    下面是一些格式字符串及其结果,时间为2018年3月1日下午2点42分,Eastern Daylight Time 即 UTC-5。

    1
    2
    3
    4
    5
    6
    7
    yyyy-MM-dd	2018-03-01
    h:mm a 2:42 pm
    yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ 2018-03-01T14:42:00.000-05:00
    dd/MM/yy 01/03/18
    d.M.y 1.3.2018
    d-MMM-yyyy 1-Mar-2018
    HH:mm:ss Z 14:42:00 -0500

    定义格式字符串时的注意事项:

  • 常量字符用单引号区分。除此之外,诸如标点、数字、空格等无需单独区分。
  • 单引号本身作为常量字符使用时,要额外加单引号。
  • 占位符区分大小写。例如: M 表示月份; m 表示分钟,请勿混淆。
  • y 表示年。尽管 Y y 的结果一样,但 Y 实际上代表其他意义。
  • h 对应12小时制; H 对应24小时制。前者最好配合 a 使用,即 AM/PM ;后者一般不需要。
  • 要精确到毫秒,使用 ss.SSS S 的数量代表精度。,通常3位即可。
  • 避免使用 z v 表示时区,因为这样产生的时区代码并不唯一。最好使用基于 UTC 的便宜时间或时区 ID。
  • 重要的事情再说一遍:展示 Date 务必使用样式。如果样式枚举无法满足需求,则使用模版。
  • 除非为月份和星期指定语言,默认一律使用 en_US_POSIX 作为地区。这个地区选项能够确保结果严格遵守格式字符串定义的规则,忽略系统设置,月份和星期用英语表示。否则,我们可能获得12小时或24小时两种不同结果。依照惯例,发送给服务器的内容必须是英文。
  • 模版

    样式只有四种,有可能无法满足需求。假设只显示月日,省略年。 DateFormatter 提供模版属性支持这类需求。定义模版所使用的占位符与 dateFormat 一样,但无需其他字符,也不同担心字段顺序,其他的就交给 DateFormatter 处理。注意,先设置地区,在配置模版。

    下面的例子只显示月份,日期和小时。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // Swift
    let dft = DateFormatter()
    dft.setLocalizedDateFormatFromTemplate("MMMdh")
    print(dft.string(from: date))

    // Objective-C
    NSDateFormatter *dft = [[NSDateFormatter alloc] init];
    [dft setLocalizedDateFormatFromTemplate:@"MMdh"];
    NSLog(@"%@", [dft stringFromDate:date]);

    结果如下,第二列是地区,例子所用的日期为 2018年3月1日下午2点42分

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    Mar 1, 2 PM en_US (English/USA) MMM d, h a
    1 Mar, 2 pm en_GB (English/Great Britian) d MMM, h a
    1. März, 2 PM de_DE (German/Germany) d. MMM, h a
    1 mars à 2 PM fr_FR (French/France) d MMM 'à' h a
    ١ مارس، ٢ م ar_EG (Arabic/Egypt) d MMM، h a
    3月1日 下午2时 zh_CN (Chinese/China) MMM d, h a
    3월 1일 오후 2시 ko_KR (Korean/Korea) MMM d일 a h시
    1 марта, 2 ПП ru_RU (Russian/Russia) d MMM, h a
    1 de mar 2 PM pt_BR (Portuguese/Brazil) d 'de' MMM h a
    1 de mar., 2 p.m. es_CO (Spanish/Colombia) d 'de' MMM, h a

    ISO 8601

    ISO 针对日期格式指定了相关标准, ISO 8601 。此时最好使用 ISO8601DateFormatter 而非 DateFormatter

    DateIntervalFormatter

    通过两个日期表示一个时间段时, DateIntervalFormatter 可以用来给出这个时间段的本地化描述。

    其他选择

    事实上不借助 DateFormatter 依然可以将 Date 转换为 String 。但是这种方式仅适用于 debug,千万不要试图将结果展示给用户,或者发送给 API 服务器。下面代码中, date 是一个 Date 对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // Swift
    let str = "\(date)"
    let str = String(describing: date)
    let str = date.description
    let str = date.description(with: nil)
    let str = date.description(with: .current)
    let str = date.debugDescription

    // Objective-C
    NSString *str = [NSString stringWithFormat:@"%@", date];
    NSString *str = date.description;
    NSString *str = [date descriptionWithLocale:nil];
    NSString *str = [date descriptionWithLocale:NSLocal.currentLocale];

    之所以只能用于 debug,是因为文档并未说明它们的具体结果,此外 Date description 的文档甚至明确表示:

    The representation is useful for debugging only.

    解析字符串

    假设给我们一个表示日期的字符串,如何将其转换为 Date 呢?显然这个字符串可以来自 API 或服务器,常见于 JSON 结构中。

    做法几乎与 Date 转 String 相同。首先根据日期字符串定义格式,必须囊括所有常量字符和标点。此外由于字符串的格式是固定的,地区应使用 en_US_POSIX

    完整规范请查看 Date Formats 。不管怎样,规则一定要符合要解析的字符串的格式。

    下面例子解析从 JSON 中取出的字符串。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // Swift
    let dateString = "2018-03-15T16:37:29Z" // the date string to be parsed
    let df = DateFormatter()
    df.locale = Locale(identifier: "en_US_POSIX")
    df.dateFormat = "yyyy-MM-dd'T'HH:mm:ssX"
    if let date = df.date(from: dateString) {
    // do something with the date
    } else {
    print("Unable to parse date string")
    }

    // Objective-C
    NSString *dateString = "2018-03-15T16:37:29Z"; // the date string to be parsed
    NSDateFormatter *df = [[NSDateFormatter alloc] init];
    df.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
    df.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssX";
    NSDate *date = [df dateFromString:dateString];
    if (date) {
    // do something with the date
    } else {
    NSLog(@"Unable to parse date string");
    }

    需要注意的是:

  • 如果字符串符合 ISO 8601 标准(如上例),最好使用 ISO8601DateFormatter
  • 末尾的 Z 表示时区。 Z 念 “Zulu”,表示 +0000,即 UTC 时区。
  • 如果字符串已经包含时区,则没有必要再次设置 DateFormatter 的时区。
  • 格式字符串务必包含常量字符 T
  • 时区的占位符是 X 。虽然人们常常用 Z 表示时区,但严格来说这是不正确的用法。不过 DateFormatter 在实际解析时会自动纠正这类错误。不过,最好按规则使用。
  • date(from:) 返回 Date 。但如果无法解析,则返回空。所以,对于来自 API 或服务器的数据,必须校验,编写防御代码。不要对数据做任何假设,如果服务器改变格式,就会导致程序崩溃。务必优雅的处理意外状况。
  • 我们通常使用固定格式 + en_US_POSIX 作为地区的方式解析字符串,极少使用样式。这是因为其格式不固定,而且还受地区影响。

    最后,务必注意字符串不适合作为数据使用。从服务器或 API 获取到的字符串,要转换为 Date ,在此基础之上进行操作。要显示给用户,就通过 DateFormatter 转换。

    转换日期字符串

    我们还会经常遇到这种情况:将一种格式的日期字符串,转换为另一种格式的日期字符串。做饭很简单:1) 转换为 Date 2) 借助 DateFormatter 格式化。

    下面代码将 2018-03-15 09:45:30 +02:00 转换为更适合用户阅读的格式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    // Swift
    let dateString = "2018-03-15 09:45:30 +02:00" // the date string to be parsed
    print(dateString)
    let df1 = DateFormatter()
    df1.locale = Locale(identifier: "en_US_POSIX")
    df1.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZZZ"
    if let date = df1.date(from: dateString) {
    print(date)
    let df2 = DateFormatter()
    df2.dateStyle = .short
    df2.timeStyle = .short
    let string = df2.string(from: date)
    print(string)
    } else {
    print("Unable to parse date string")
    }

    // Objective-C
    NSString *dateString = "2018-03-15 09:45:30 +02:00"; // the date string to be parsed
    NSLog(@"%@", dateString);
    NSDateFormatter *df1 = [[NSDateFormatter alloc] init];
    df1.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
    df1.dateFormat = @"yyyy-MM-dd HH:mm:ss ZZZZZ";
    NSDate *date = [df1 dateFromString:dateString];
    if (date) {
    NSLog(@"%@", date);
    NSDateFormatter *df2 = [[NSDateFormatter alloc] init];
    df2.dateStyle = NSDateFormatterShortStyle;
    df2.timeStyle = NSDateFormatterShortStyle;
    NSString *string = [df2 stringFromDate:date];
    NSLog(@"%@", string);
    } else {
    NSLog(@"Unable to parse date string");
    }

    显然结果根据地区和时区有所不同。假设我们在纽约,地区代码 en_US ,时区 UTC-4(夏时令),结果为:

    1
    2
    3
    2018-03-15 09:45:30 +02:00
    2018-03-15 07:45:30 +0000
    3/15/18, 3:45 AM

    结果与原始字符串大相径庭,但它们所表示的时间都是正确的。原字符串所在的时区早于 UTC 两小时。第二行是 UTC 时间,它们之间时差两个小时。最后是当前时区,落后 UTC 四个小时。因此最开始的时间相差六小时。

    备注(译者)