Java11开发秘籍-十三、使用新的日期和时间 API

作者: Java开发者

在本章中,我们将介绍以下配方:

  • 如何构造与时区无关的日期和时间实例
  • 如何构造时区相关的时间实例
  • 如何在日期实例之间创建基于日期的期间
  • 如何在时间实例之间创建基于时间的时段
  • 如何表示大纪元时间
  • 如何操作日期和时间实例
  • 如何比较日期和时间
  • 如何使用不同的日历系统
  • 如何使用DateTimeFormatter设置日期格式

介绍

Stephen Colebourne 之前,与java.util.Datejava.util.Calendar一起工作对 Java 开发人员来说是一种痛苦。 Java8 引入了 JodaTime,一个用 Java 处理日期和时间的库。与 JDK API 相比,Joda Time 提供了以下优势:

  • 更丰富的 API,用于获取日期组件(如一个月的一天、一周的一天、一个月和一年),以及时间组件(如小时、分钟和秒)。
  • 易于操作和比较日期和时间。
  • 时区独立 API 和时区相关 API 都可用。大多数情况下,我们将使用与时区无关的 API,这使得 API 的使用更加容易。
  • 惊人的 API 计算日期和时间之间的持续时间。
  • 默认情况下,日期格式和持续时间计算遵循 ISO 标准。
  • 支持多种日历,如公历、佛历和伊斯兰教日历。 JodaTime 启发了 JSR-310,它在java.time包下将 API 移植到 JDK,并作为 Java8 的一部分发布。由于新的日期/时间 API 基于 ISO 标准,因此跨应用程序的不同层集成日期/时间库非常简单。例如,在 JavaScript 层,我们可以使用 moment.js 处理日期和时间,并使用其默认格式样式(符合 ISO 标准)向服务器发送数据。在服务器层,我们可以根据需要使用新的日期/时间 API 来获取日期和时间实例。因此,我们使用标准的日期表示在客户机和服务器之间进行交互。

在本章中,我们将研究利用新的日期/时间 API 的不同方法。

如何使用与时区无关的日期和时间实例

在 JSR-310 之前,为日历中的任何时间点或任何一天创建日期和时间实例并不简单。唯一的方法是使用java.util.Calendar对象设置所需的日期和时间,然后调用getTime()方法获取java.util.Date的实例。这些日期和时间实例也包含时区信息,这有时会导致应用程序出现错误。

在新的 API 中,获取日期和时间实例要简单得多,而且这些日期和时间实例没有任何与之关联的时区信息。在此配方中,我们将向您展示如何使用由java.time.LocalDate表示的仅日期实例、由java.time.LocalTime表示的仅时间实例以及由java.time.LocalDateTime表示的日期/时间实例。这些日期和时间实例与时区无关,表示机器当前时区中的信息。

准备

您至少需要安装 JDK 8 才能使用这些较新的库,本章中的示例使用 Java 10 及以后版本支持的语法。如果需要,可以直接在 JShell 中运行这些代码段。您可以使用 JShell 访问第十二章“读取评估打印循环(REPL)”,了解更多关于 JShell 的信息。

怎么做…

  1. java.time.LocalDate中包装的当前日期可以通过now()方法获得,如下所示:
    var date = LocalDate.now();
    
  2. 我们可以使用通用的get(fieldName)方法或具体的getDayOfMonth()getDayOfYear()getDayOfWeek()getMonth()getYear()等方法获取java.time.LocalDate实例的各个字段,如下所示:
    var dayOfWeek = date.getDayOfWeek();
    var dayOfMonth = date.getDayOfMonth();
    var month = date.getMonth();
    var year = date.getYear();
    
  3. 我们可以使用of()方法获取日历中任意日期的java.time.LocalDate实例,如下所示:
    var date1 = LocalDate.of(2018, 4, 12);
    var date2 = LocalDate.of(2018, Month.APRIL, 12);
    date2 = LocalDate.ofYearDay(2018, 102);
    date2 = LocalDate.parse("2018-04-12");
    
  4. java.time.LocalTime类,用于表示任何时间实例,而不考虑日期。可使用以下方法获得当前时间:
    var time = LocalTime.now();
    
  5. java.time.LocalTime类还附带了of()工厂方法,可用于创建表示任何时间的实例。同样,也有一些方法可以获得时间的不同部分,如下所示:
    time = LocalTime.of(23, 11, 11, 11);
    time = LocalTime.ofSecondOfDay(3600);
    var hour = time.getHour();
    var minutes = time.getMinute();
    var seconds = time.get(ChronoField.SECOND_OF_MINUTE);
    
  6. java.time.LocalDateTime用于表示同时包含时间和日期的实体。由java.time.LocalDatejava.time.LocalTime组成,分别表示日期和时间。可以使用now()of()工厂方法的不同风格创建其实例,如下所示:
    var dateTime1 = LocalDateTime.of(2018, 04, 12, 13, 30, 22);
    var dateTime2 = LocalDateTime.of(2018, Month.APRIL, 12, 13, 30, 22);
    dateTime2 = LocalDateTime.of(date2, LocalTime.of(13, 30, 22));
    

它是如何工作的…

java.time包中的以下三个类代表默认时区(系统时区)中的日期和时间值:

  • java.time.LocalDate:仅包含日期信息
  • java.time.LocalTime:仅包含时间信息
  • java.time.LocalDateTime:包含日期和时间信息

每个类都由字段组成,即以下字段:

  • 一天
  • 时辰
  • 会议记录
  • 毫秒

所有类都包含返回当前日期和时间值的now()方法。提供了of()工厂方法,从其字段(如日、月、年、小时和分钟)构建日期和时间实例。java.time.LocalDateTimejava.time.LocalDatejava.time.LocalTime组成,因此可以从java.time.LocalDatejava.time.LocalTime构建java.time.LocalDateTime

从这个配方中学到的重要 API 如下:

  • now():给出当前日期和时间
  • of():此工厂方法用于构造所需的日期、时间和日期/时间实例

还有更多…

在 Java 9 中,有一个新的 API,datesUntil,它获取结束日期并返回从当前对象的日期到结束日期(但不包括结束日期)的连续日期流(换句话说,java.time.LocalDate。使用此 API 可将给定月份和年份的所有日期分组为一周中各自的几天,即周一、周二、周三等。

让我们接受月份和年份,并将其分别存储在monthyear变量中。该范围的起始日期为该月和该年的第一天,如下所示:

var startDate = LocalDate.of(year, month, 1);

范围的结束日期将是当月的天数,如以下代码段所示:

var endDate = startDate.plusDays(startDate.lengthOfMonth());

我们正在使用lengthOfMonth方法获取月份的天数。然后我们使用datesUntil方法得到java.time.LocalDate的流,然后我们执行一些流操作:

  • 按一周中的某一天对java.time.LocalDate实例进行分组。
  • 正在将分组的实例收集到java.util.ArrayList。但在此之前,我们正在应用一个转换,将java.time.LocalDate实例转换为一个简单的月份,这将为我们提供一个表示月份日期的整数列表。

代码中的前两个操作显示在以下代码段中:

var dayBuckets = startDate.datesUntil(endDate).collect(
Collectors.groupingBy(date -> date.getDayOfWeek(), 
    Collectors.mapping(LocalDate::getDayOfMonth, 
        Collectors.toList())
));

此代码可在下载代码的Chapter13/1_2_print_calendar处找到。

如何构造时区相关的时间实例

在前面的配方“如何构造时区独立的日期和时间实例”中,我们构造了不包含任何时区信息的日期和时间对象。它们隐式地表示系统时区中的值;这些类别分别为java.time.LocalDatejava.time.LocalTimejava.time.LocalDateTime

通常,我们需要表示某个时区的时间;在这种情况下,我们将使用java.time.ZonedDateTime,它包含时区信息和java.time.LocalDateTime。使用java.time.ZoneIdjava.time.ZoneOffset实例嵌入时区信息。还有另外两个类,java.time.OffsetTimejava.time.OffsetDateTime,它们也是java.time.LocalTimejava.time.LocalDateTime的时区特定变体。

在本食谱中,我们将向您展示如何使用java.time.ZonedDateTimejava.time.ZoneIdjava.time.ZoneOffsetjava.time.OffsetTimejava.time.OffsetDateTime

准备

我们将使用 Java10 语法,该语法将var用于局部变量声明和模块。除了 Java10 和更高版本之外,没有其他先决条件。

怎么做…

  1. 我们将使用now()工厂方法,根据系统时区获取当前日期、时间和时区信息,如下所示:
    var dateTime = ZonedDateTime.now();
    
  2. 我们将利用java.time.ZoneId获取基于任何给定时区的当前日期和时间信息:
    var indianTz = ZoneId.of("Asia/Kolkata");
    var istDateTime = ZonedDateTime.now(indianTz);
    
  3. java.time.ZoneOffset还可用于提供日期和时间的时区信息,如下所示:
    var indianTzOffset = ZoneOffset.ofHoursMinutes(5, 30);
    istDateTime = ZonedDateTime.now(indianTzOffset);
    
  4. 我们利用of()工厂方法构建java.time.ZonedDateTime实例:
    ZonedDateTime dateTimeOf = ZonedDateTime.of(2018, 4, 22, 14, 30, 11, 33, indianTz);
    
  5. 我们甚至可以从java.time.ZonedDateTime中提取java.time.LocalDateTime
    var localDateTime = dateTimeOf.toLocalDateTime();
    

它是如何工作的…

首先,让我们看看如何捕获时区信息。它是根据格林威治标准时间(GMT)(也称为协调世界时(UTC))的小时数和分钟数捕获的。例如,印度标准时间(IST),也称为亚洲/加尔各答,比格林尼治标准时间早 5:30 小时。 Java 提供了java.time.ZoneIdjava.time.ZoneOffset来表示时区信息。java.time.ZoneId根据时区名称捕获时区信息,如亚洲/加尔各答、美国/太平洋和美国/山区。大约有 599 个区域 ID。这是使用以下代码行计算的:

jshell> ZoneId.getAvailableZoneIds().stream().count()
$16 ==> 599

我们将打印 10 个区域 ID:

jshell> ZoneId.getAvailableZoneIds().stream().limit(10).forEach(System.out::println)
Asia/Aden
America/Cuiaba
Etc/GMT+9
Etc/GMT+8
Africa/Nairobi
America/Marigot
Asia/Aqtau
Pacific/Kwajalein
America/El_Salvador
Asia/Pontianak

Time zone names, such as Asia/Kolkata, Africa/Nairobi, and America/Cuiaba, are based on the time zone database released by International Assigned Numbers Authority (IANA). The time zone region names provided by IANA are the default for Java. Sometimes time zone region names are also represented as GMT+02:30 or simply +02:30, which indicates the offset (ahead or behind) of the current time zone from the GMT zone.

java.time.ZoneId捕获java.time.zone.ZoneRules,其中包含获取时区偏移过渡和其他信息(如夏令时)的规则。让我们调查一下美国/太平洋地区的区域规则:

jshell> ZoneId.of("US/Pacific").getRules().getDaylightSavings(Instant.now())
$31 ==> PT1H
jshell> ZoneId.of("US/Pacific").getRules().getOffset(LocalDateTime.now())
$32 ==> -07:00
jshell> ZoneId.of("US/Pacific").getRules().getStandardOffset(Instant.now())
$33 ==> -08:00

getDaylightSavings()方法返回一个java.time.Duration对象,该对象以小时、分钟和秒表示一些持续时间。默认的toString()实现返回使用基于 ISO 8601 秒的表示法表示的持续时间,其中 1 小时、20 分钟和 20 秒的持续时间表示为PT1H20M20S。关于这方面的更多信息将在本章的“如何在时间实例之间创建基于时间的时段”配方中介绍。

我们不会详细讨论它是如何计算的。欲了解更多关于java.time.zone.ZoneRulesjava.time.ZoneId的信息,请分别访问这里这里java.time.ZoneOffset类根据时区在 GMT 之前或之后的小时数和分钟数捕获时区信息。让我们使用of*()工厂方法创建java.time.ZoneOffset类的实例:

jshell> ZoneOffset.ofHoursMinutes(5,30)
$27 ==> +05:30

java.time.ZoneOffset类从java.time.ZoneId扩展并添加了一些新方法。需要记住的重要一点是根据应用程序中使用的所需时区构造正确的java.time.ZoneOffsetjava.time.ZoneId实例。

现在我们已经了解了时区表示法,java.time.ZonedDateTime只不过是java.time.LocalDateTime以及java.time.ZoneIdjava.time.ZoneOffset。还有另外两个类,java.time.OffsetTimejava.time.OffsetDateTime,分别与java.time.ZoneOffset一起包装java.time.LocalTimejava.time.LocalDateTime

让我们来看一些构造java.time.ZonedDateTime实例的方法。

第一种方式是使用now()

Signatures:
ZonedDateTime ZonedDateTime.now()
ZonedDateTime ZonedDateTime.now(ZoneId zone)
ZonedDateTime ZonedDateTime.now(Clock clock)
jshell> ZonedDateTime.now()
jshell> ZonedDateTime.now(ZoneId.of("Asia/Kolkata"))
$36 ==> 2018-05-04T21:58:24.453113900+05:30[Asia/Kolkata]
jshell> ZonedDateTime.now(Clock.fixed(Instant.ofEpochSecond(1525452037), ZoneId.of("Asia/Kolkata")))
$54 ==> 2018-05-04T22:10:37+05:30[Asia/Kolkata]

第一次使用now()使用系统时钟以及系统时区打印当前日期和时间。第二次使用now()使用系统时钟,但时区由java.time.ZoneId提供,在本例中为亚洲/加尔各答。now()的第三种用法使用java.time.ZoneId提供的固定时钟和时区。

固定时钟是使用java.time.Clock类及其静态方法fixed()创建的,该方法以java.time.Instantjava.time.ZoneId为例。java.time.Instant的实例是在纪元之后使用一些静态秒数构建的。java.time.Clock用于表示一个时钟,新的日期/时间 API 可以使用该时钟来确定当前时间。如前所述,时钟可以固定,然后我们可以在亚洲/加尔各答时区创建一个比当前系统时间提前一小时的时钟,如下所示:

var hourAheadClock = Clock.offset(Clock.system(ZoneId.of("Asia/Kolkata")), Duration.ofHours(1));

我们可以使用这个新时钟来构建java.time.LocalDateTimejava.time.ZonedDateTime的实例,如下所示:

jshell> LocalDateTime.now(hourAheadClock)
$64 ==> 2018-05-04T23:29:58.759973700
jshell> ZonedDateTime.now(hourAheadClock)
$65 ==> 2018-05-04T23:30:11.421913800+05:30[Asia/Kolkata]

日期和时间值都基于相同的时区,即Asia/Kolkata,但正如我们已经了解到的,java.time.LocalDateTime没有任何时区信息,它基于系统的时区或本例中提供的java.time.Clock来计算。另一方面,java.time.ZonedDateTime包含时区信息并显示为[Asia/Kolkata]

创建java.time.ZonedDateTime实例的另一种方法是使用其of() 工厂方法:

Signatures:
ZonedDateTime ZonedDateTime.of(LocalDate date, LocalTime time, ZoneId zone)
ZonedDateTime ZonedDateTime.of(LocalDateTime localDateTime, ZoneId zone)
ZonedDateTime ZonedDateTime.of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond, ZoneId zone)
jshell> ZonedDateTime.of(LocalDateTime.of(2018, 1, 1, 13, 44, 44), ZoneId.of("Asia/Kolkata"))
$70 ==> 2018-01-01T13:44:44+05:30[Asia/Kolkata]
jshell> ZonedDateTime.of(LocalDate.of(2018,1,1), LocalTime.of(13, 44, 44), ZoneId.of("Asia/Kolkata"))
$71 ==> 2018-01-01T13:44:44+05:30[Asia/Kolkata]
jshell> ZonedDateTime.of(LocalDate.of(2018,1,1), LocalTime.of(13, 44, 44), ZoneId.of("Asia/Kolkata"))
$72 ==> 2018-01-01T13:44:44+05:30[Asia/Kolkata]
jshell> ZonedDateTime.of(2018, 1, 1, 13, 44, 44, 0, ZoneId.of("Asia/Kolkata"))
$73 ==> 2018-01-01T13:44:44+05:30[Asia/Kolkata] 

还有更多…

我们提到了java.time.OffsetTimejava.time.OffsetDateTime类。两者都包含特定于时区的时间值。在总结本食谱之前,让我们先来看看这些课程:

  • 使用of()工厂方法:
    jshell> OffsetTime.of(LocalTime.of(14,12,34), ZoneOffset.ofHoursMinutes(5, 30))
    $74 ==> 14:12:34+05:30
    jshell> OffsetTime.of(14, 34, 12, 11, ZoneOffset.ofHoursMinutes(5, 30))
    $75 ==> 14:34:12.000000011+05:30
    
  • 使用now()工厂方法:
    Signatures:
    OffsetTime OffsetTime.now()
    OffsetTime OffsetTime.now(ZoneId zone)
    OffsetTime OffsetTime.now(Clock clock)
    jshell> OffsetTime.now()
    $76 ==> 21:49:16.895192800+03:00
    jshell> OffsetTime.now(ZoneId.of("Asia/Kolkata"))
    jshell> OffsetTime.now(ZoneId.of("Asia/Kolkata"))
    $77 ==> 00:21:04.685836900+05:30
    jshell> OffsetTime.now(Clock.offset(Clock.systemUTC(), Duration.ofMinutes(330)))
    $78 ==> 00:22:00.395463800Z
    

值得注意的是,我们构建java.time.Clock实例的方式比 UTC 时钟早 330 分钟(5 小时 30 分钟)。另一个类java.time.OffsetDateTimejava.time.OffsetTime相同,只是它使用了java.time.LocalDateTime。因此,您将把日期信息,即年、月和日,连同时间信息一起传递到其工厂方法of()

如何在日期实例之间创建基于日期的期间

过去,我们曾尝试测量两个日期实例之间的时间间隔,但由于 Java 8 之前缺乏 API,也缺乏捕获此信息的适当支持,因此我们采用了不同的方法。我们记得使用基于 SQL 的方法来处理此类信息。但是从 Java8 开始,我们有了一个新的类,java.time.Period,它可以用来捕获两个日期实例之间的时间段,以年、月和日为单位。

此外,此类还支持解析用于表示句点的基于 ISO 8601 标准的字符串。本标准规定,任何时期都可以用PnYnMnD表示,其中P为固定字符表示时期,nY表示年数,nM表示月数,nD表示天数。例如,2 年、4 个月和 10 天的周期表示为P2Y4M10D

准备

您至少需要 JDK8 来处理java.time.Period,jdk9 来使用 JShell,至少需要 JDK10 来使用本配方中使用的示例。

怎么做…

  1. 让我们使用java.time.Periodof()工厂方法创建一个java.time.Period实例,其签名为Period.of(int years, int months, int days)
    jshell> Period.of(2,4,30)
    $2 ==> P2Y4M30D
    
  2. of*()方法有具体变体,即ofDays()ofMonths()ofYears(),也可以使用:
    jshell> Period.ofDays(10)
    $3 ==> P10D
    jshell> Period.ofMonths(4)
    $4 ==> P4M
    jshell> Period.ofWeeks(3)
    $5 ==> P21D
    jshell> Period.ofYears(3)
    $6 ==> P3Y
    

请注意,ofWeeks()方法是一种助手方法,通过接受周数,基于天构建java.time.Period

  1. 周期也可以使用周期字符串构造,周期字符串的形式通常为P<x>Y<y>M<z>D,其中xyz分别表示年数、月数和天数:
    jshell> Period.parse("P2Y4M23D").getDays()
    $8 ==> 23
    
  2. 我们还可以计算java.time.ChronoLocalDate的两个实例之间的周期(它的一个实现是java.time.LocalDate
    jshell> Period.between(LocalDate.now(), LocalDate.of(2018, 8, 23))
    $9 ==> P2M2D
    jshell> Period.between(LocalDate.now(), LocalDate.of(2018, 2, 23))
    $10 ==> P-3M-26D
    

这些是创建java.time.Period实例最有用的方法。开始日期为包含日期,结束日期为独占日期。

它是如何工作的…

我们使用java.time.Period中的工厂方法来创建它的实例。java.time.Period有三个字段分别保存年、月和日的值,如下所示:

/**
* The number of years.
*/
private final int years;
/**
* The number of months.
*/
private final int months;
/**
* The number of days.
*/
private final int days;

有一组有趣的方法,即withDays()withMonths()withYears()。如果要更新的字段具有相同的值,则这些方法返回相同的实例;否则,它将返回一个具有更新值的新实例,如下所示:

jshell> Period period1 = Period.ofWeeks(2)
period1 ==> P14D
jshell> Period period2 = period1.withDays(15)
period2 ==> P15D
jshell> period1 == period2
$19 ==> false
jshell> Period period3 = period1.withDays(14)
period3 ==> P14D
jshell> period1 == period3
$21 ==> true

还有更多…

我们甚至可以使用java.time.ChronoLocalDate中的until()方法计算两个日期实例之间的java.time.Period

jshell> LocalDate.now().until(LocalDate.of(2018, 2, 23))
$11 ==> P-3M-26D
jshell> LocalDate.now().until(LocalDate.of(2018, 8, 23))
$12 ==> P2M2D

给定一个java.time.Period实例,我们可以使用它来操作一个给定的日期实例。有两种可能的方法:

  • 使用周期对象的addTosubtractFrom方法
  • 使用日期对象的plusminus方法

以下代码段中显示了这两种方法:

jshell> Period period1 = Period.ofWeeks(2)
period1 ==> P14D
jshell> LocalDate date = LocalDate.now()
date ==> 2018-06-21
jshell> period1.addTo(date)
$24 ==> 2018-07-05
jshell> date.plus(period1)
$25 ==> 2018-07-05

在类似的线路上,您可以尝试subtractFromminus方法。还有一组方法用于操作java.time.Period实例,即:

  • minusminusDaysminusMonthsminusYears:从周期中减去给定值。
  • plusplusDaysplusMonthsplusYears:将给定值加到期间。
  • negated:返回新期间,其每个值均为负数。
  • normalized:通过规范化其高阶字段(如月和日),返回新的期间。例如,将 15 个月标准化为 1 年零 3 个月。

我们将从minus方法开始,向您展示这些方法的作用,如下所示:

jshell> period1.minus(Period.of(1,3,4))
$28 ==> P2Y12M25D
jshell> period1.minusDays(4)
$29 ==> P3Y15M25D
jshell> period1.minusMonths(3)
$30 ==> P3Y12M29D
jshell> period1.minusYears(1)
$31 ==> P2Y15M29D

然后我们将看到plus方法:

jshell> Period period1 = Period.of(3, 15, 29)
period1 ==> P3Y15M29D
jshell> period1.plus(Period.of(1, 3, 4))
$33 ==> P4Y18M33D
jshell> period1.plusDays(4)
$34 ==> P3Y15M33D
jshell> period1.plusMonths(3)
$35 ==> P3Y18M29D
jshell> period1.plusYears(1)
$36 ==> P4Y15M29D

最后,这里是negated()normalized()方法:

jshell> Period period1 = Period.of(3, 15, 29)
period1 ==> P3Y15M29D
jshell> period1.negated()
$38 ==> P-3Y-15M-29D
jshell> period1
period1 ==> P3Y15M29D
jshell> period1.normalized()
$40 ==> P4Y3M29D
jshell> period1
period1 ==> P3Y15M29D

请注意,在前面的两种情况下,它都没有改变现有的周期,而是返回一个新实例。

如何在时间实例之间创建基于时间的时段

在我们之前的配方中,我们创建了一个基于日期的时段,由java.time.Period表示。在本食谱中,我们将使用java.time.Duration类在时间实例之间创建以秒和纳秒为单位的基于时间的差异。

我们将研究不同的方法来创建java.time.Duration实例,操作持续时间实例,并以不同的单位获取持续时间,例如小时和分钟。ISO 8601 标准规定了表示持续时间的一种可能模式为PnYnMnDTnHnMnS,适用于以下情况:

  • YMD表示日期组件字段,即年、月、日
  • T将日期与时间信息分开
  • HMS表示时间分量字段,即小时、分钟、秒 java.time.Duration的字符串表示实现松散地基于 ISO 8601。在“工作原理”一节中有更多关于这方面的内容。

准备

您至少需要 JDK 8 才能使用java.time.Duration和 JDK 9 才能使用 JShell。

怎么做…

  1. java.time.Duration实例可以使用of*()工厂方法创建。我们将展示如何使用其中的一些,如下所示:
    jshell> Duration.of(56, ChronoUnit.MINUTES)
    $66 ==> PT56M
    jshell> Duration.of(56, ChronoUnit.DAYS)
    $67 ==> PT1344H
    jshell> Duration.ofSeconds(87)
    $68 ==> PT1M27S
    jshell> Duration.ofHours(7)
    $69 ==> PT7H
    
  2. 也可以通过解析持续时间字符串来创建它们,如下所示:
    jshell> Duration.parse("P12D")
    $70 ==> PT288H
    jshell> Duration.parse("P12DT7H5M8.009S")
    $71 ==> PT295H5M8.009S
    jshell> Duration.parse("PT7H5M8.009S")
    $72 ==> PT7H5M8.009S
    
  3. 它们可以通过查找两个支持时间信息的java.time.Temporal实例(即java.time.LocalDateTime等实例)之间的跨度来构造,如下所示:
    jshell> LocalDateTime time1 = LocalDateTime.now()
    time1 ==> 2018-06-23T10:51:21.038073800
    jshell> LocalDateTime time2 = LocalDateTime.of(2018, 6, 22, 11, 00)
    time2 ==> 2018-06-22T11:00
    jshell> Duration.between(time1, time2)
    $77 ==> PT-23H-51M-21.0380738S
    jshell> ZonedDateTime time1 = ZonedDateTime.now()
    time1 ==> 2018-06-23T10:56:57.965606200+03:00[Asia/Riyadh]
    jshell> ZonedDateTime time2 = ZonedDateTime.of(LocalDateTime.now(), ZoneOffset.ofHoursMinutes(5, 30))
    time2 ==> 2018-06-23T10:56:59.878712600+05:30
    jshell> Duration.between(time1, time2)
    $82 ==> PT-2H-29M-58.0868936S
    

它是如何工作的…

java.time.Duration所需的数据存储在两个字段中,分别表示秒和纳秒。提供了以分钟、小时和天为单位获取持续时间的方便方法,即toMinutes()toHours()toDays()

让我们讨论一下字符串表示的实现。java.time.Duration支持解析 ISO 字符串表示形式,该表示形式只包含日期部分中的日期组件,以及时间部分中的小时、分钟、秒和纳秒。例如,P2DT3M是可以接受的,而解析P3M2DT3M会导致java.time.format.DateTimeParseException,因为字符串包含日期部分中的月份组件。 java.time.DurationtoString()方法总是返回一个PTxHyMz.nS形式的字符串,其中x表示小时数,y表示分钟数,z.n表示秒到纳秒的精度。让我们看一些例子:

jshell> Duration.parse("P2DT3M")
$2 ==> PT48H3M
jshell> Duration.parse("P3M2DT3M")
| Exception java.time.format.DateTimeParseException: Text cannot be parsed to a Duration
| at Duration.parse (Duration.java:417)
| at (3:1)
jshell> Duration.ofHours(4)
$4 ==> PT4H
jshell> Duration.parse("PT3H4M5.6S")
$5 ==> PT3H4M5.6S
jshell> Duration d = Duration.parse("PT3H4M5.6S")
d ==> PT3H4M5.6S
jshell> d.toDays()
$7 ==> 0
jshell> d.toHours()
$9 ==> 3

还有更多…

让我们看一下提供的操作方法,这些方法允许从特定的时间单位(如天、小时、分钟、秒或纳秒)中添加/减去值。这些方法都是不可变的,因此每次都会返回一个新实例,如下所示:

jshell> Duration d = Duration.parse("PT1H5M4S")
d ==> PT1H5M4S
jshell> d.plusDays(3)
$14 ==> PT73H5M4S
jshell> d
d ==> PT1H5M4S
jshell> d.plusDays(3)
$16 ==> PT73H5M4S
jshell> d.plusHours(3)
$17 ==> PT4H5M4S
jshell> d.plusMillis(4)
$18 ==> PT1H5M4.004S
jshell> d.plusMinutes(40)
$19 ==> PT1H45M4S

同样,你也可以尝试minus*()方法,进行减法运算。还有一些方法可以处理java.time.LocalDateTimejava.time.ZonedDateTime等实例。这些方法在日期/时间信息中添加/减去持续时间。让我们看一些例子:

jshell> Duration d = Duration.parse("PT1H5M4S")
d ==> PT1H5M4S
jshell> d.addTo(LocalDateTime.now())
$21 ==> 2018-06-25T21:15:53.725373600
jshell> d.addTo(ZonedDateTime.now())
$22 ==> 2018-06-25T21:16:03.396595600+03:00[Asia/Riyadh]
jshell> d.addTo(LocalDate.now())
| Exception java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Seconds
| at LocalDate.plus (LocalDate.java:1272)
| at LocalDate.plus (LocalDate.java:139)
| at Duration.addTo (Duration.java:1102)
| at (23:1)

您可以在前面的示例中观察到,当我们试图将持续时间添加到仅包含日期信息的实体时,出现了一个异常。

如何表示大纪元时间

在本配方中,我们将使用java.time.Instant表示一个时间点,并将该时间点转换为历元秒/毫秒。Java 历元用于表示从1970-01-01 00:00:00Z开始的时间瞬间,java.time.Instant存储 Java 历元的秒数。正值表示时间在历元之前,负值表示时间在历元之后。它使用 UTC 的系统时钟来计算当前时间的瞬时值。

准备

您需要安装支持新日期/时间 API 和 JShell 的 JDK,以便能够尝试提供的解决方案。

怎么做…

  1. 我们只需创建一个java.time.Instant实例并打印出历元秒数,它将给出 Java 历元后的 UTC 时间:
    jshell> Instant.now()
    $40 ==> 2018-07-06T07:56:40.651529300Z
    jshell> Instant.now().getEpochSecond()
    $41 ==> 1530863807
    
  2. 我们还可以打印出历元毫秒数,它显示历元后的毫秒数。这比几秒钟更精确:
    jshell> Instant.now().toEpochMilli()
    $42 ==> 1530863845158
    

它是如何工作的…

java.time.Instant类在其两个字段中存储时间信息:

  • 秒,属于long类型:存储从1970-01-01T00:00:00Z开始的秒数。
  • 纳秒,属于int 类型:它存储纳秒数

当您调用now()方法时,java.time.Instant使用 UTC 的系统时钟来表示该时刻。然后我们可以使用atZone()atOffset()将其转换为所需的时区,我们将在下一节中看到。

如果您只想在 UTC 中表示操作的时间线,请使用此类;这样,为不同事件存储的时间戳将基于 UTC,然后您可以根据需要将其转换为所需的时区。

还有更多…

我们可以通过添加/减去纳秒、毫秒和秒来操纵java.time.Instant,如下所示:

jshell> Instant.now().plusMillis(1000)
$43 ==> 2018-07-06T07:57:57.092259400Z
jshell> Instant.now().plusNanos(1991999)
$44 ==> 2018-07-06T07:58:06.097966099Z
jshell> Instant.now().plusSeconds(180)
$45 ==> 2018-07-06T08:01:15.824141500Z

同样,你也可以尝试minus*()方法。我们还可以使用java.time.Instant方法、atOffset()atZone()方法获得时区相关日期时间,如下所示:

jshell> Instant.now().atZone(ZoneId.of("Asia/Kolkata"))
$36 ==> 2018-07-06T13:15:13.820694500+05:30[Asia/Kolkata]
jshell> Instant.now().atOffset(ZoneOffset.ofHoursMinutes(2,30))
$37 ==> 2018-07-06T10:15:19.712039+02:30

如何操作日期和时间实例

日期和时间类java.time.LocalDatejava.time.LocalTimejava.time.LocalDateTimejava.time.ZonedDateTime提供了从它们的组件(即天、小时、分钟、秒、周、月、年等)中加减值的方法。

在本食谱中,我们将介绍一些这样的方法,它们可以通过加减不同的值来操作日期和时间实例。

准备

您将需要一个支持新的日期/时间 API 和 JShell 控制台的 JDK 安装。

怎么做…

  1. 让我们操纵java.time.LocalDate
    jshell> LocalDate d = LocalDate.now()
    d ==> 2018-07-27
    jshell> d.plusDays(3)
    $5 ==> 2018-07-30
    jshell> d.minusYears(4)
    $6 ==> 2014-07-27
    
  2. 让我们操纵日期和时间实例,java.time.LocalDateTime
    jshell> LocalDateTime dt = LocalDateTime.now()
    dt ==> 2018-07-27T15:27:40.733389700
    jshell> dt.plusMinutes(45)
    $8 ==> 2018-07-27T16:12:40.733389700
    jshell> dt.minusHours(4)
    $9 ==> 2018-07-27T11:27:40.733389700
    
  3. 让我们操纵时区相关的日期和时间,java.time.ZonedDateTime
    jshell> ZonedDateTime zdt = ZonedDateTime.now()
    zdt ==> 2018-07-27T15:28:28.309915200+03:00[Asia/Riyadh]
    jshell> zdt.plusDays(4)
    $11 ==> 2018-07-31T15:28:28.309915200+03:00[Asia/Riyadh]
    jshell> zdt.minusHours(3)
    $12 ==> 2018-07-27T12:28:28.309915200+03:00[Asia/Riyadh]
    

还有更多…

我们只看了一些由plus*()minus*()表示的加法和减法 API。提供了不同的方法来处理日期和时间的不同组件,例如年、日、月、小时、分、秒和纳秒。您可以尝试使用这些 API 作为练习。

如何比较日期和时间

通常,我们希望将日期和时间实例与其他实例进行比较,以检查它们是否在其他实例之前、之后或与其他实例相同。为了实现这一点,JDK 在java.time.LocalDatejava.time.LocalDateTimejava.time.ZonedDateTime类中提供了isBefore()isAfter()isEqual()方法。在本食谱中,我们将介绍如何使用这些方法来比较日期和时间实例。

准备

您将需要一个具有新的日期/时间 API 并支持 JShell 的 JDK 安装。

怎么做…

  1. 让我们尝试比较两个java.time.LocalDate实例:
    jshell> LocalDate d = LocalDate.now()
    d ==> 2018-07-28
    jshell> LocalDate d2 = LocalDate.of(2018, 7, 27)
    d2 ==> 2018-07-27
    jshell> d.isBefore(d2)
    $4 ==> false
    jshell> d.isAfter(d2)
    $5 ==> true
    jshell> LocalDate d3 = LocalDate.of(2018, 7, 28)
    d3 ==> 2018-07-28
    jshell> d.isEqual(d3)
    $7 ==> true
    jshell> d.isEqual(d2)
    $8 ==> false
    
  2. 我们还可以比较时区相关的日期和时间实例:
    jshell> ZonedDateTime zdt1 = ZonedDateTime.now();
    zdt1 ==> 2018-07-28T14:49:34.778006400+03:00[Asia/Riyadh]
    jshell> ZonedDateTime zdt2 = zdt1.plusHours(4)
    zdt2 ==> 2018-07-28T18:49:34.778006400+03:00[Asia/Riyadh]
    jshell> zdt1.isBefore(zdt2)
    $11 ==> true
    jshell> zdt1.isAfter(zdt2)
    $12 ==> false
    jshell> zdt1.isEqual(zdt2)
    $13 ==> false
    

还有更多…

可在java.time.LocalTimejava.time.LocalDateTime上进行比较。这是留给读者去探索的。

如何使用不同的日历系统

到目前为止,在我们的食谱中,我们使用了 ISO 日历系统,这是世界上沿用的事实上的日历系统。世界上还有其他地区性的日历系统,如 Hijrah、日语和泰语。JDK 还提供对此类日历系统的支持。

在本食谱中,我们将介绍如何使用两种日历系统:日语和 Hijri。

准备

您应该安装一个支持新的日期/时间 API 和 JShell 工具的 JDK。

怎么做…

  1. 让我们在 JDK 支持的不同日历系统中打印当前日期:
    jshell> Chronology.getAvailableChronologies().forEach(chrono -> 
    System.out.println(chrono.dateNow()))
    2018-07-30
    Minguo ROC 107-07-30
    Japanese Heisei 30-07-30
    ThaiBuddhist BE 2561-07-30
    Hijrah-umalqura AH 1439-11-17
    
  2. 让我们来看看日本日历系统中表示的日期:
    jshell> JapaneseDate jd = JapaneseDate.now()
    jd ==> Japanese Heisei 30-07-30
    jshell> jd.getChronology()
    $7 ==> Japanese
    jshell> jd.getEra()
    $8 ==> Heisei
    jshell> jd.lengthOfYear()
    $9 ==> 365
    jshell> jd.lengthOfMonth()
    $10 ==> 31
    
  3. 可以使用java.time.chrono.JapeneseEra枚举日文日历中支持的不同纪元:
    jshell> JapaneseEra.values()
    $42 ==> JapaneseEra[5] 
    
  4. 让我们在 Hijrah 日历系统中创建一个日期:
    jshell> HijrahDate hd = HijrahDate.of(1438, 12, 1)
    hd ==> Hijrah-umalqura AH 1438-12-01
    
  5. 我们甚至可以在 Hijrah 日历系统中将 ISO 日期/时间转换为日期/时间,如下所示:
    jshell> HijrahChronology.INSTANCE.localDateTime(LocalDateTime.now())
    $23 ==> Hijrah-umalqura AH 1439-11-17T19:56:52.056465900
    jshell> HijrahChronology.INSTANCE.localDateTime(LocalDateTime.now()).toLocalDate()
    $24 ==> Hijrah-umalqura AH 1439-11-17
    jshell> HijrahChronology.INSTANCE.localDateTime(LocalDateTime.now()).toLocalTime()
    $25 ==> 19:57:07.705740500
    

它是如何工作的…

日历系统由java.time.chrono.Chronology及其实现表示,其中有几个是java.time.chrono.IsoChronologyjava.time.chrono.HijrahChronologyjava.time.chrono.JapaneseChronologyjava.time.chrono.IsoChronology是世界上使用的基于 ISO 的事实日历系统。每个日历系统中的日期由java.time.chrono.ChronoLocalDate及其实现表示,其中一些是java.time.chrono.HijrahDatejava.time.chrono.JapaneseDate和众所周知的java.time.LocalDate

为了能够在 JShell 中使用这些 API,您需要导入相关包,如下所示:

jshell> import java.time.*
jshell> import java.time.chrono.*

这适用于所有使用 JShell 的配方。

我们可以直接使用java.time.chrono.ChronoLocalDate的实现,例如java.time.chrono.JapaneseDate,或者使用java.time.chrono.Chronology的实现来获取相关的日期表示,如下所示:

jshell> JapaneseDate jd = JapaneseDate.of(JapaneseEra.SHOWA, 26, 12, 25)
jd ==> Japanese Showa 26-12-25
jshell> JapaneseDate jd = JapaneseDate.now()
jd ==> Japanese Heisei 30-07-30
jshell> JapaneseDate jd = JapaneseChronology.INSTANCE.dateNow()
jd ==> Japanese Heisei 30-07-30
jshell> JapaneseDate jd = JapaneseChronology.INSTANCE.date(LocalDateTime.now())
jd ==> Japanese Heisei 30-07-30
jshell> ThaiBuddhistChronology.INSTANCE.date(LocalDate.now())
$41 ==> ThaiBuddhist BE 2561-07-30

从前面的代码片段中,我们可以看到,可以使用日历系统的date(TemporalAccessor temporal)方法将 ISO 系统日期转换为所需日历系统中的日期。

还有更多…

您可以使用 JDK 支持的其他日历系统,即泰国、佛教和民国(中文)日历系统。通过编写java.time.chrono.Chronologyjava.time.chrono.ChronoLocalDatejava.time.chrono.Era的实现来创建我们的定制日历系统也是值得探索的。

如何使用DateTimeFormatter格式化日期

在使用java.util.Date时,我们使用java.text.SimpleDateFormat将日期格式化为不同的文本表示形式,反之亦然。格式化日期是指给定一个日期或一个时间对象,以不同的格式表示该日期,例如:

  • 2018 年 6 月 23 日
  • 23-08-2018
  • 2018-08-23
  • 2018 年 6 月 23 日上午 11:03:33

这些格式由格式字符串控制,例如:

  • dd MMM yyyy
  • dd-MM-yyyy
  • yyyy-MM-DD
  • dd MMM yyyy hh:mm:ss

在这个配方中,我们将查看java.time.format.DateTimeFormatter在新的日期和时间 API 中格式化日期和时间实例,并查看最常用的模式字母。

准备

您将需要一个具有新的日期/时间 API 以及jshell工具的 JDK。

怎么做…

  1. 让我们使用内置格式设置日期和时间的格式:
    jshell> LocalDate ld = LocalDate.now()
    ld ==> 2018-08-01
    jshell> ld.format(DateTimeFormatter.ISO_DATE)
    $47 ==> "2018-08-01"
    jshell> LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)
    $49 ==> "2018-08-01T17:24:49.1985601"
    
  2. 让我们创建一个自定义日期/时间格式:
    jshell> DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd MMM yyyy hh:mm:ss a")
    dtf ==> Value(DayOfMonth,2)' 'Text(MonthOfYear,SHORT)' 'V ... 2)' 'Text(AmPmOfDay,SHORT)
    
  3. 让我们使用自定义java.time.format.DateTimeFormatter设置当前日期/时间的格式:
    jshell> LocalDateTime ldt = LocalDateTime.now()
    ldt ==> 2018-08-01T17:36:22.442159
    jshell> ldt.format(dtf)
    $56 ==> "01 Aug 2018 05:36:22 PM"
    

它是如何工作的…

让我们了解最常用的格式字母:

| 符号 | 含义 | 示例 | | | — | — | | d | 月中的日期 | 1, 2, 3, 5 | | MMMMMMMM | 年中的月份 | M1, 2, 3MMMJun, Jul, AugMMMMJuly, August | | yyy | 年 | yyyyy2017, 2018yy18, 19 | | h | 一天中的小时数(1-12) | 1, 2, 3 | | k | 一天中的小时数(0-23) | 0, 1, 2, 3 | | m | 分 | 1, 2, 3 | | s | 秒 | 1, 2, 3 | | a | 每日上午/下午 | AM, PM | | VV | 时区 ID | Asia/Kolkata | | ZZ | 时区名称 | IST, PST, AST | | O | 时区偏移 | GMT+5:30, GMT+3 |

根据前面的格式字母,我们来格式化java.time.ZonedDateTime

jshell> DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd MMMM yy h:mm:ss a VV")
dtf ==> Value(DayOfMonth,2)' 'Text(MonthOfYear)' 'Reduced ... mPmOfDay,SHORT)' 'ZoneId()
jshell> ZonedDateTime.now().format(dtf)
$67 ==> "01 August 18 6:26:04 PM Asia/Kolkata"
jshell> DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd MMMM yy h:mm:ss a zz")
dtf ==> Value(DayOfMonth,2)' 'Text(MonthOfYear)' 'Reduced ... y,SHORT)' 'ZoneText(SHORT)
jshell> ZonedDateTime.now().format(dtf)
$69 ==> "01 August 18 6:26:13 PM IST"
jshell> DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd MMMM yy h:mm:ss a O")
dtf ==> Value(DayOfMonth,2)' 'Text(MonthOfYear)' 'Reduced ... )' 'LocalizedOffset(SHORT)
jshell> ZonedDateTime.now().format(dtf)
$72 ==> "01 August 18 6:26:27 PM GMT+5:30"

java.time.format.DateTimeFormatter附带了大量基于 ISO 标准的默认格式。在处理日期操作时,如果不涉及任何用户,即在应用程序的不同层之间交换日期和时间,这些格式应该足够了。

但是,为了向最终用户显示日期和时间信息,我们需要将其格式化为可读的格式,为此,我们需要一个自定义的DateTimeFormatter。如果您需要自定义java.time.format.DateTimeFormatter,这里有两种创建方式:

  • 使用模式,例如dd-MMMM-yyyyjava.time.format.DateTimeFormatter中的ofPattern()方法
  • 使用java.time.DateTimeFormatterBuilder 使用模式

我们创建了一个java.time.format.DateTimeFormatter实例,如下所示:

jshell> DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd MMMM yy h:mm:ss a VV")
dtf ==> Value(DayOfMonth,2)' 'Text(MonthOfYear)' 'Reduced ... mPmOfDay,SHORT)' 'ZoneId()

然后我们将该格式应用于日期和时间实例:

jshell> ZonedDateTime.now().format(dtf)
$92 ==> "01 August 18 7:25:00 PM Asia/Kolkata"

模式方法还使用DateTimeFormatterBuilder,其中生成器解析给定的格式字符串以构建DateTimeFormatter对象。 使用java.time.format.DateTimeFormatterBuilder

让我们使用DateTimeFormatterBuilder构建DateTimeFormatter,如下所示:

jshell> DateTimeFormatter dtf = new DateTimeFormatterBuilder().
 ...> appendValue(DAY_OF_MONTH, 2).
 ...> appendLiteral(" ").
 ...> appendText(MONTH_OF_YEAR).
 ...> appendLiteral(" ").
 ...> appendValue(YEAR, 4).
 ...> toFormatter()
dtf ==> Value(DayOfMonth,2)' 'Text(MonthOfYear)' 'Value(Year,4)
jshell> LocalDate.now().format(dtf) E$106 ==> "01 August 2018"

您可以观察到DateTimeFormatter对象由一组关于如何表示日期和时间的指令组成。这些指令以Value()Text()和分隔符的形式呈现。

文章列表

更多推荐

更多
  • IOS开发者的AWS和DevOps指南-十、iOS 应用开发的持续交付渠道 Jenkins 管道公司,AWS 代码管道,摘要,Fastlane 测试阶段,AWS 设备场测试阶段,Fastlane 构建阶段,Fastlane 交付阶段,为 AWS 代码管道设置 Jenkins 环境,在 AWS 控制台上设置代码管
    Apache CN

  • IOS开发者的AWS和DevOps指南-九、将 AWS 设备群用于测试 AWS 设备群简介,为应用测试生成 ipa 包,设置设备场项目并安排测试运行,AWS 设备场 Jenkins 插件,使用 Jenkins 自动化 AWS 设备群测试,摘要,使用 AWS 控制台安排测试运行,使用 AWS CLI 计划测试
    Apache CN

  • IOS开发者的AWS和DevOps指南-八、使用 Fastlane 自动构建、测试和发布 使用 Fastlane 匹配和亚马逊 S3 设置代码签名,设置 Jenkins 环境,用 Fastlane 自动化测试和构建,自动发布到 App Store Connect,摘要,正在初始化 Fastlane 匹配,在亚马逊 S3 存储
    Apache CN

  • IOS开发者的AWS和DevOps指南-六、使用 AWS CodeCommit 的源代码管理 Git 基础,创建 AWS 代码提交存储库,在 AWS 代码提交中添加源代码,AWS 代码提交中分支,AWS 代码提交中的拉请求,摘要,Git 安装,初始化 Git 存储库,记录对 Git 存储库的更改,克隆和使用远程 Git 存储库,
    Apache CN

  • IOS开发者的AWS和DevOps指南-七、将 AWS CodeCommit 与 Jenkins 集成 Jenkins 代码提交插件,设置集成组件,配置插件,使用 AWS 代码提交源创建 Jenkins 作业,摘要,通过 AWS 控制台设置组件,通过 Terraform 设置组件,测试 AWS 代码提交插件, 当应用源代码存储在 AWS
    Apache CN

  • IOS开发者的AWS和DevOps指南-四、AWS 上的 macOS 服务器 Amazon EC2 Mac 服务器,部署 Amazon EC2 Mac 服务器,连接到 Amazon EC2 Mac 服务器,使用 Amazon CloudWatch 监控 EC2 Mac 服务器,清理 Amazon EC2 Mac
    Apache CN

  • IOS开发者的AWS和DevOps指南-五、在 macOS 实例上设置开发工具 增加 macOS 实例宗卷大小,设置 Xcode,陷害 Jenkins,建立 Fastlane,设置 GitLab,摘要,Xcode 命令行工具,供应 Jenkins 控制器,EC2 Mac 实例作为 Jenkins 构建代理,创建 G
    Apache CN

  • IOS开发者的AWS和DevOps指南-三、亚马逊网络服务(AWS)上的 DevOps 三、亚马逊网络服务AWS上的 DevOpsAWS 上的持续集成,AWS 上的连续交付,基础设施作为代码,监控和记录,摘要,AWS 代码提交,AWS 代码构建,AWS 程式码人工因素,AWS 代码部署,AWS 代码管道,AWS 云阵,AW
    Apache CN

  • IOS开发者的AWS和DevOps指南-二、从 Xcode 到 App Store Connect 标识符,应用商店连接,从 Xcode 上传构件,测试和发布,摘要,截图和应用详细信息,TestFlight 软件,应用提交, 在前一章中,我们看到了如何使用 Xcode 在物理 iPhone 设备和模拟器上构建和运行应用。我们将进一步探
    Apache CN

  • IOS开发者的AWS和DevOps指南-一、iOS 应用开发基础 开发要求,迅速发生的,摘要,苹果个人计算机,苹果开发者账户,Xcode 简介,创建应用,构建应用,Xcode 命令行工具, 为了开发 iOS 应用,苹果提供了几种工具和资源。iOS 应用可以用原生编程语言开发,如 Swift 或 Obj
    Apache CN

  • 近期文章

    更多
    文章目录

      推荐作者

      更多