# 时间与日期

CodeIgniter 提供一个完全本地化、不可变的日期/时间类，该类基于 PHP 的 DateTimeImmutable 类构建，但利用 Intl 扩展的功能实现时区转换，并以不同区域设置正确显示输出。此类为 `Time` 类，位于 `CodeIgniter\I18n` 命名空间中。

备注

自 4.3.0 起，Time 类继承 `DateTimeImmutable`，若需要此类未提供的功能， 可直接在 DateTimeImmutable 类中查找。

备注

4.3.0 之前，Time 类继承 `DateTime`，部分继承方法会更改 当前对象状态。该问题已在 4.3.0 中修复。如需向后兼容， 可暂时使用已弃用的 `TimeLegacy` 类。

## 实例化

创建 Time 实例有多种方式。最简单的方式是直接像普通类一样实例化。

实例化时可传入表示所需时间的字符串。该字符串可为 PHP DateTimeImmutable 构造函数能解析的任意格式。详见 支持的日期和时间格式。

    <?php

    use CodeIgniter\I18n\Time;

    $myTime = new Time('2024-01-01');
    $myTime = new Time('2024-01-01 12:00:00');
    $myTime = new Time('now');
    $myTime = new Time('+3 week');

第二和第三个参数可分别传入时区和区域设置字符串。时区 可为 PHP DateTimeZone 类支持的所有值。区域设置可为 PHP Locale 类支持的所有值。如未提供区域设置或时区， 将使用应用默认值。

    <?php

    use CodeIgniter\I18n\Time;

    $myTime = new Time('now', 'America/Chicago', 'en_US');

### now()

Time 类提供多个辅助方法来实例化。首先是 `now()` 方法， 返回一个当前时间的新实例。第二和第三个参数可分别传入时区和区域设置字符串。如未提供时区或区域设置， 将使用应用默认值。

    <?php

    use CodeIgniter\I18n\Time;

    $myTime = Time::now('America/Chicago', 'en_US');

### parse()

此辅助方法为默认构造方法的静态版本。第一个参数为 DateTimeImmutable 构造函数可接受的字符串，第二个参数为时区，第三个参数为区域设置：

    <?php

    use CodeIgniter\I18n\Time;

    $myTime = Time::parse('next Tuesday', 'America/Chicago', 'en_US');

### today()

返回新实例，日期设为当前日期，时间设为午夜。第一和第二个参数可接受时区和区域设置字符串：

    <?php

    use CodeIgniter\I18n\Time;

    $myTime = Time::today('America/Chicago', 'en_US');

### yesterday()

返回新实例，日期设为昨天的日期，时间设为午夜。第一和第二个参数可接受时区和区域设置字符串：

    <?php

    use CodeIgniter\I18n\Time;

    $myTime = Time::yesterday('America/Chicago', 'en_US');

### tomorrow()

返回新实例，日期设为明天的日期，时间设为午夜。第一和第二个参数可接受时区和区域设置字符串：

    <?php

    use CodeIgniter\I18n\Time;

    $myTime = Time::tomorrow('America/Chicago', 'en_US');

### createFromDate()

分别传入 年、月、日，返回新实例。如省略任一参数， 将使用当前的年、月和日。第四和第五个参数可接受时区和区域设置字符串：

    <?php

    use CodeIgniter\I18n\Time;

    $today       = Time::createFromDate();     // Uses current year, month, and day
    $anniversary = Time::createFromDate(2018); // Uses current month and day
    $date        = Time::createFromDate(2018, 3, 15, 'America/Chicago', 'en_US');

### createFromTime()

与 `createFromDate()` 类似，仅处理 时、分、秒。日期部分使用 当前日期。第四和第五个参数可接受时区和区域设置字符串：

    <?php

    use CodeIgniter\I18n\Time;

    $lunch  = Time::createFromTime(11, 30);     // 11:30 am today
    $dinner = Time::createFromTime(18, 00, 00); // 6:00 pm today
    $time   = Time::createFromTime($hour, $minutes, $seconds, $timezone, $locale);

### create()

结合上述两个方法，分别接受 年、月、日、时、分、秒 作为独立参数。任何未提供的值将使用当前日期和时间。第七和第八个参数可接受时区和区域设置字符串：

    <?php

    use CodeIgniter\I18n\Time;

    $time = Time::create($year, $month, $day, $hour, $minutes, $seconds, $timezone, $locale);

### createFromFormat()

此方法为 DateTimeImmutable 同名方法的替代版本。可同时设置时区， 并返回 `Time` 实例而非 DateTimeImmutable：

    <?php

    use CodeIgniter\I18n\Time;

    $time = Time::createFromFormat('j-M-Y', '15-Feb-2009', 'America/Chicago');

### createFromTimestamp()

此方法接受 UNIX 时间戳，以及可选的时区和区域设置， 创建新 Time 实例：

    <?php

    use CodeIgniter\I18n\Time;

    $time = Time::createFromTimestamp(1501821586, 'America/Chicago', 'en_US');

如未显式传入时区，返回 UTC 时区的 Time 实例。

备注

建议始终传入两个参数调用 `createFromTimestamp()` （即显式传入时区），除非默认使用 UTC 时区。

备注

4.4.6 至 4.6.0 之前的版本中，如未指定时区，此方法返回 默认时区的 Time 实例。

### createFromInstance()

使用其他库提供的 DateTime 实例时，可使用方法将 DateTime 实例 转换为 Time 实例，并可选设置区域设置。时区将从传入的 DateTime 实例自动推断：

    <?php

    use CodeIgniter\I18n\Time;

    $dt   = new \DateTime('now');
    $time = Time::createFromInstance($dt, 'en_US');

### toDateTime()

此方法非实例化方法，而是与 instance 方法相反，将 Time 实例转换为 DateTime 实例。转换保留时区设置，但丢失区域设置，因为 DateTime 不支持区域设置：

    <?php

    use CodeIgniter\I18n\Time;

    $datetime = Time::toDateTime();

## 显示值

Time 类继承自 DateTimeImmutable，因此可直接使用该类提供的所有输出方法，包括 `format()` 方法。 但 DateTimeImmutable 的方法不提供本地化输出。Time 类提供多个辅助方法 用于显示本地化值。

### toLocalizedString()

此方法为 DateTimeImmutable `format()` 方法的本地化版本。不同于熟悉的格式化字符， 必须使用 IntlDateFormatter 类可接受的值。 完整值列表见 此处。

    <?php

    use CodeIgniter\I18n\Time;

    // Locale: en
    $time = Time::parse('March 9, 2016 12:00:00', 'America/Chicago');
    echo $time->toLocalizedString('MMM d, yyyy'); // March 9, 2016

    // Locale: fa
    $time = Time::parse('March 9, 2016 12:00:00', 'America/Chicago');
    echo $time->toLocalizedString('MMM d, yyyy'); // مارس ۹, ۲۰۱۶

### toDateTimeString()

此为三个辅助方法中的第一个，无需记忆 IntlDateFormatter 的格式化值。 返回格式化为 `Y-m-d H:i:s` 的本地化字符串：

    <?php

    use CodeIgniter\I18n\Time;

    // Locale: en
    $time = Time::parse('March 9, 2016 12:00:00', 'America/Chicago');
    echo $time->toDateTimeString(); // 2016-03-09 12:00:00

    // Locale: fa
    $time = Time::parse('March 9, 2016 12:00:00', 'America/Chicago');
    echo $time->toDateTimeString(); // ۲۰۱۶-۰۳-۰۹ ۱۲:۰۰:۰۰

### toDateString()

仅显示 Time 的本地化日期部分：

    <?php

    use CodeIgniter\I18n\Time;

    // Locale: en
    $time = Time::parse('March 9, 2016 12:00:00', 'America/Chicago');
    echo $time->toDateString(); // 2016-03-09

    // Locale: fa
    $time = Time::parse('March 9, 2016 12:00:00', 'America/Chicago');
    echo $time->toDateString(); // ۲۰۱۶-۰۳-۰۹

### toTimeString()

仅显示值的本地化时间部分：

    <?php

    use CodeIgniter\I18n\Time;

    // Locale: en
    $time = Time::parse('March 9, 2016 12:00:00', 'America/Chicago');
    echo $time->toTimeString(); // 12:00:00

    // Locale: fa
    $time = Time::parse('March 9, 2016 12:00:00', 'America/Chicago');
    echo $time->toTimeString(); // ۱۲:۰۰:۰۰

### humanize()

此方法返回一个字符串，以人类可读格式显示当前日期/时间与实例之间的差异， 便于快速理解。可生成类似"3 小时前"、"1 个月后"等字符串：

    <?php

    use CodeIgniter\I18n\Time;

    // Assume current time is: March 10, 2017 (America/Chicago)
    $time = Time::parse('March 9, 2016 12:00:00', 'America/Chicago');

    echo $time->humanize(); // 1 year ago

显示时间的具体规则如下：

时间差 | 结果
--- | ---
1 年 < $time < 2 年 | in 1 year / 1 year ago
1 个月 < $time < 1 年 | in 6 months / 6 months ago
7 天 < $time < 1 个月 | in 3 weeks / 3 weeks ago
今天 < $time < 7 天 | in 4 days / 4 days ago
$time == 昨天 / 明天 | Tomorrow / Yesterday
59 分钟 < $time < 1 天 | in 2 hours / 2 hours ago
现在 < $time < 1 小时 | in 35 minutes / 35 minutes ago
$time == 现在 | Now

结果字符串来自语言文件 system/Language/en/Time.php。 如需覆盖，创建 app/Language/{locale}/Time.php。

## 处理单个值

Time 对象提供多种方法，用于获取和设置现有实例的单个值，如年、月、小时等。 通过以下方法获取的所有值都会完全本地化，并遵守 创建 Time 实例时指定的区域设置。

以下所有 `getX()` 和 `setX()` 方法均可像类属性一样使用。例如，`getYear()` 可通过 `$time->year` 访问，以此类推。

### Getter 方法

以下为基础 getter 方法：

    <?php

    use CodeIgniter\I18n\Time;

    $time = Time::parse('August 12, 2016 4:15:23pm');

    // The output may vary based on locale.
    echo $time->getYear();   // '2016'
    echo $time->getMonth();  // '8'
    echo $time->getDay();    // '12'
    echo $time->getHour();   // '16'
    echo $time->getMinute(); // '15'
    echo $time->getSecond(); // '23'

    echo $time->year;   // '2016'
    echo $time->month;  // '8'
    echo $time->day;    // '12'
    echo $time->hour;   // '16'
    echo $time->minute; // '15'
    echo $time->second; // '23'

此外，还提供多个方法获取关于日期的额外信息：

    <?php

    use CodeIgniter\I18n\Time;

    $time = Time::parse('August 12, 2016 4:15:23pm');

    // The output may vary based on locale.
    echo $time->getDayOfWeek();   // '6'
    echo $time->getDayOfYear();   // '225'
    echo $time->getWeekOfMonth(); // '2'
    echo $time->getWeekOfYear();  // '33'
    echo $time->getTimestamp();   // 1471018523 - UNIX timestamp (locale independent)
    echo $time->getQuarter();     // '3'

    echo $time->dayOfWeek;   // '6'
    echo $time->dayOfYear;   // '225'
    echo $time->weekOfMonth; // '2'
    echo $time->weekOfYear;  // '33'
    echo $time->timestamp;   // 1471018523
    echo $time->quarter;     // '3'

#### getAge()

返回 Time 实例与当前时间之间的年龄（以年为单位）。适用于根据生日 计算年龄的场景：

    <?php

    use CodeIgniter\I18n\Time;

    $time = Time::parse('5 years ago');

    echo $time->getAge(); // 5
    echo $time->age;      // 5

#### getDST()

返回布尔值，表示 Time 实例当前是否处于夏令时：

    <?php

    use CodeIgniter\I18n\Time;

    echo Time::createFromDate(2012, 1, 1)->getDst(); // false
    echo Time::createFromDate(2012, 9, 1)->dst;      // true

#### getLocal()

返回布尔值，表示 Time 实例是否与应用当前运行的时区相同：

    <?php

    use CodeIgniter\I18n\Time;

    echo Time::now()->getLocal();           // true
    echo Time::now('Europe/London')->local; // false

#### getUtc()

返回布尔值，表示 Time 实例是否处于 UTC 时间：

    <?php

    use CodeIgniter\I18n\Time;

    echo Time::now('America/Chicago')->getUtc(); // false
    echo Time::now('UTC')->utc;                  // true

#### getTimezone()

返回新 DateTimeZone 对象，时区设为 Time 实例的时区：

    <?php

    use CodeIgniter\I18n\Time;

    $tz = Time::now()->getTimezone();
    $tz = Time::now()->timezone;

    echo $tz->getName();
    echo $tz->getOffset();

#### getTimezoneName()

返回 Time 实例的完整 时区字符串：

    <?php

    use CodeIgniter\I18n\Time;

    echo Time::now('America/Chicago')->getTimezoneName(); // America/Chicago
    echo Time::now('Europe/London')->timezoneName;        // Europe/London

### Setter 方法

以下为基础 setter 方法。如设置的值超出范围，将抛出 `InvalidArgumentExeption`。

备注

所有 setter 均返回新的 Time 实例，原始实例保持不变。

备注

如值超出范围，所有 setter 均会抛出 InvalidArgumentException。

    <?php

    $time = $time->setYear(2017);
    $time = $time->setMonth(4);       // April
    $time = $time->setMonth('April');
    $time = $time->setMonth('Feb');   // February
    $time = $time->setDay(25);
    $time = $time->setHour(14);       // 2:00 pm
    $time = $time->setMinute(30);
    $time = $time->setSecond(54);

#### setTimezone()

将时间从当前时区转换为新时区：

    <?php

    use CodeIgniter\I18n\Time;

    $time  = Time::parse('13 May 2020 10:00', 'America/Chicago');
    $time2 = $time->setTimezone('Europe/London'); // Returns new instance converted to new timezone

    echo $time->getTimezoneName();  // American/Chicago
    echo $time2->getTimezoneName(); // Europe/London

    echo $time->toDateTimeString();  // 2020-05-13 10:00:00
    echo $time2->toDateTimeString(); // 2020-05-13 18:00:00

#### setTimestamp()

返回新实例，日期设为新时间戳：

    <?php

    use CodeIgniter\I18n\Time;

    // The Application Timezone is "America/Chicago".

    $time  = Time::parse('May 10, 2017');
    $time2 = $time->setTimestamp(strtotime('April 1, 2017'));

    echo $time->toDateTimeString();  // 2017-05-10 00:00:00
    echo $time2->toDateTimeString(); // 2017-04-01 00:00:00

备注

4.6.0 之前的版本中，由于 bug，此方法可能返回错误的 日期/时间。详见 升级指南。

### 修改值

以下方法支持通过增加或减少值来修改当前 Time 的日期。不会 修改现有 Time 实例，而是返回新实例。

    <?php

    $time = $time->addSeconds(23);
    $time = $time->addMinutes(15);
    $time = $time->addHours(12);
    $time = $time->addDays(21);
    $time = $time->addMonths(14);
    $time = $time->addCalendarMonths(2);
    $time = $time->addYears(5);

    $time = $time->subSeconds(23);
    $time = $time->subMinutes(15);
    $time = $time->subHours(12);
    $time = $time->subDays(21);
    $time = $time->subMonths(14);
    $time = $time->subCalendarMonths(2);
    $time = $time->subYears(5);

#### addCalendarMonths() / subCalendarMonths()

通过增加或减少完整的日历月份来修改当前 Time。若需确保重复发生的日期不跳过任何日历月份，这些方法将非常有用。下表对比了初始日期为 `2025-01-31` 时，`addMonths()` 与 `addCalendarMonths()` 的差异。

$months | addMonths() | addCalendarMonths()
--- | --- | ---
1 | 2025-03-03 | 2025-02-28
2 | 2025-03-31 | 2025-03-31
3 | 2025-05-01 | 2025-04-30
4 | 2025-05-31 | 2025-05-31
5 | 2025-07-01 | 2025-06-30
6 | 2025-07-31 | 2025-07-31

### 比较两个时间

以下方法允许比较两个 Time 实例。所有比较均先转换为 UTC 后进行，以确保不同时区能正确响应。

#### equals()

判断传入的日期/时间是否等于当前实例。此处的"等于"表示两者表示同一 时刻，不要求处于相同时区，因为两个时间均转换为 UTC 后 进行比较：

    <?php

    use CodeIgniter\I18n\Time;

    $time1 = Time::parse('January 10, 2017 21:50:00', 'America/Chicago');
    $time2 = Time::parse('January 11, 2017 03:50:00', 'Europe/London');

    $time1->equals($time2); // true

被测试的值可为 Time 实例、DateTime 实例或完整的日期时间 字符串（格式需能被新 DateTime 实例解析）。第一个参数传入字符串时，可传入 时区字符串作为第二个参数。如未提供时区，将使用系统默认值：

    <?php

    $time1->equals('January 11, 2017 03:50:00', 'Europe/London'); // true

#### sameAs()

此方法与 `equals()` 类似，但仅当日期、时间与时区 全部相同时才返回 true：

    <?php

    use CodeIgniter\I18n\Time;

    $time1 = Time::parse('January 10, 2017 21:50:00', 'America/Chicago');
    $time2 = Time::parse('January 11, 2017 03:50:00', 'Europe/London');

    $time1->sameAs($time2); // false
    $time2->sameAs('January 10, 2017 21:50:00', 'America/Chicago'); // true

#### isBefore()

检查传入的时间是否在当前实例之前。比较基于 两个时间的 UTC 版本进行：

    <?php

    use CodeIgniter\I18n\Time;

    $time1 = Time::parse('January 10, 2017 21:50:00', 'America/Chicago');
    $time2 = Time::parse('January 11, 2017 03:50:00', 'America/Chicago');

    $time1->isBefore($time2); // true
    $time2->isBefore($time1); // false

被测试的值可为 Time 实例、DateTime 实例或完整的日期时间 字符串（格式需能被新 DateTime 实例解析）。第一个参数传入字符串时，可传入 时区字符串作为第二个参数。如未提供时区，将使用系统默认值：

    <?php

    $time1->isBefore('March 15, 2013', 'America/Chicago'); // false

#### isAfter()

与 `isBefore()` 工作方式相同，检查时间是否在传入时间之后：

    <?php

    use CodeIgniter\I18n\Time;

    $time1 = Time::parse('January 10, 2017 21:50:00', 'America/Chicago');
    $time2 = Time::parse('January 11, 2017 03:50:00', 'America/Chicago');

    $time1->isAfter($time2); // false
    $time2->isAfter($time1); // true

#### isPast()

Added in version 4.7.0.

用于判断当前实例的时间相对于“现在”是否为过去。 返回布尔值 true/false:

    .. literalinclude:: time/043.php

#### isFuture()

Added in version 4.7.0.

用于判断当前实例的时间相对于“现在”是否为未来。 返回布尔值 true/false:

    .. literalinclude:: time/044.php

### 查看差异

直接比较两个 Time，应使用 `difference()` 方法，返回 `CodeIgniter\I18n\TimeDifference` 实例。

第一个参数可为 Time 实例、DateTime 实例或日期/时间字符串。如 第一个参数传入字符串，第二个参数可为时区字符串：

    <?php

    use CodeIgniter\I18n\Time;

    $time = Time::parse('March 10, 2017', 'America/Chicago');

    $diff = $time->difference(Time::now());
    $diff = $time->difference(new \DateTime('July 4, 1975', 'America/Chicago'));
    $diff = $time->difference('July 4, 1975 13:32:05', 'America/Chicago');

获取 TimeDifference 实例后，可使用多种方法获取两个时间 之间的差异信息。如果相对于原始时间为过去则返回负值，为未来则返回正值：

    <?php

    use CodeIgniter\I18n\Time;

    $current = Time::parse('March 10, 2017', 'America/Chicago');
    $test    = Time::parse('March 10, 2010', 'America/Chicago');

    $diff = $current->difference($test);

    echo $diff->getYears();   // -7
    echo $diff->getMonths();  // -84
    echo $diff->getWeeks();   // -365
    echo $diff->getDays();    // -2557
    echo $diff->getHours();   // -61368
    echo $diff->getMinutes(); // -3682080
    echo $diff->getSeconds(); // -220924800

备注

4.4.7 之前的版本中，Time 类在比较前总是先将时区转换为 UTC。 如果日期因夏令时（DST）导致时长并非 24 小时， 此类转换可能会导致非预期结果。

4.4.7 起，比较相同时区的日期/时间时， 直接进行比较，不转换为 UTC。

>     <?php

>     use CodeIgniter\I18n\Time;

>     // 31 Mar 2024 - Daylight Saving Time Starts
>     $current = Time::parse('2024-03-31', 'Europe/Madrid');
>     $test    = Time::parse('2024-04-01', 'Europe/Madrid');

>     $diff = $current->difference($test);

>     echo $diff->getDays();
>     // 0 in v4.4.6 or before
>     // 1 in v4.4.7 or later

可使用 `getX()` 方法，或像访问属性一样访问计算值：

    <?php

    echo $diff->years;   // -7
    echo $diff->months;  // -84
    echo $diff->weeks;   // -365
    echo $diff->days;    // -2557
    echo $diff->hours;   // -61368
    echo $diff->minutes; // -3682080
    echo $diff->seconds; // -220924800

#### humanize()

与 Time 的 `humanize()` 方法类似，返回人类可读格式的字符串， 显示两个时间之间的差异，便于快速理解。可生成类似"3 小时前"、 "1 个月后"等字符串。最大区别在于处理非常近的日期时：

    <?php

    use CodeIgniter\I18n\Time;

    $current = Time::parse('March 10, 2017', 'America/Chicago');
    $test    = Time::parse('March 9, 2016 12:00:00', 'America/Chicago');

    $diff = $current->difference($test);

    echo $diff->humanize(); // 1 year ago

显示时间的具体规则如下：

时间差 | 结果
--- | ---
1 年 < $time < 2 年 | in 1 year / 1 year ago
1 个月 < $time < 1 年 | in 6 months / 6 months ago
7 天 < $time < 1 个月 | in 3 weeks / 3 weeks ago
今天 < $time < 7 天 | in 4 days / 4 days ago
1 小时 < $time < 1 天 | in 8 hours / 8 hours ago
1 分钟 < $time < 1 小时 | in 35 minutes / 35 minutes ago
$time < 1 分钟 | Now

结果字符串来自语言文件 system/Language/en/Time.php。 如需覆盖，创建 app/Language/{locale}/Time.php。