团队和做的直观图_直观,可靠的日期和时间处理,终于出现在Java中

团队和做的直观图

日期和时间的概念是许多应用程序的基础。 诸如生日,租期,活动时间戳记和商店营业时间之类的东西都是基于日期和时间的,但是Java SE没有很好的API来处理它们。 使用Java SE 8,有一组新的程序包-java.time-提供了结构良好的API以涵盖日期和时间。

历史

当Java在1.0版中首次启动时,对日期和时间的唯一支持是java.util.Date类。 大多数开发人员注意到该类的第一件事是它不代表“日期”。 它所做的表示其实原因很简单-在时间点瞬时基于精确到毫秒,从1970-01-01Z的时代测量。 但是,由于标准的toString()表单会在JVM的默认时区中显示日期和时间,因此某些开发人员错误地认为它是可识别时区的类。

当需要改进1.1版时,Date类被认为无法修复。 结果,添加了新的API- java.util.Calendar 。 可悲的是,Calendar类并没有比java.util.Date更好。 这些类面临的一些问题是:

  • 可变的。 诸如日期和时间之类的类应该是不变的。
  • 抵消。 日期中的年份从1900开始,两个类别中的月份都从零开始。
  • 命名。 日期不是“日期”。 日历不是“日历”。
  • 格式化。 格式化程序仅适用于Date ,不适用于Calendar ,并且不是线程安全的

在2001年左右, Joda-Time项目开始了。 Joda-Time的目的很简单-为Java提供高质量的日期和时间库。 花费了一段时间,但最终推出了Joda-Time 1.0版,它成为一个非常广泛使用和流行的库。 随着时间的流逝,提供JDK之类的Joda-Time这样的库的需求不断增长。 在来自巴西的Michael Nascimento Santos的帮助下,JSR-310开始了,正式的过程是为JDK创建和集成新的日期和时间API。

总览

新的java.time API包含5个软件包:

大多数开发人员将主要使用基本和格式包,以及临时包。 因此,尽管有68种新的公共类型,但是大多数开发人员只会积极使用其中的三分之一。

日期

LocalDate类是新API中最重要的类之一。 它是表示日期的不可变值类型。 没有时间或时区的表示。

Joda-Time熟悉“本地”术语,该术语最初来自ISO-8601日期和时间标准 。 它特别涉及到缺少时区。 实际上,本地日期是对日期的描述,例如“ 2014年4月5日”。 该特定的本地日期将根据您在地球上的位置在时间轴上的不同点开始。 因此,本地日期将从澳大利亚在伦敦开始的10个小时开始,在旧金山开始的18小时开始。

LocalDate类旨在具有所有通常需要的方法:

LocalDate date = LocalDate.of(2014, Month.JUNE, 10); 
int year = date.getYear(); // 2014 
Month month = date.getMonth(); // JUNE 
int dom = date.getDayOfMonth(); // 10 
DayOfWeek dow = date.getDayOfWeek(); // Tuesday 
int len = date.lengthOfMonth(); // 30 (days in June) 
boolean leap = date.isLeapYear(); // false (not a leap year)

在示例中,我们看到一个使用工厂方法创建的日期(所有构造函数都是私有的)。 然后查询一些基本信息。 请注意, MonthDayOfWeek枚举旨在使代码更具可读性和可靠性。

在下一个示例中,我们将了解如何操作实例。 由于该类是不可变的,因此每次操作都会导致一个新实例,而原始实例不受影响。

LocalDate date = LocalDate.of(2014, Month.JUNE, 10); 
date = date.withYear(2015); // 2015-06-10 
date = date.plusMonths(2); // 2015-08-10 
date = date.minusDays(1); // 2015-08-09

这些更改相对简单,但是通常需要对日期进行更复杂的更改。 该java.time API包括一个机制来处理这一点- TemporalAdjuster。 时间调整器背后的想法是提供一种预包装的实用程序,该实用程序能够处理日期,例如获取与该月的最后一天相对应的实例。 API中提供了常用的,但您可以添加自己的。 使用调节器非常简单,但是可以从静态导入中受益:

import static java.time.DayOfWeek.* 

import static java.time.temporal.TemporalAdjusters.* 

LocalDate date = LocalDate.of(2014, Month.JUNE, 10); 
date = date.with(lastDayOfMonth()); 
date = date.with(nextOrSame(WEDNESDAY));

看到正在使用调节器的立即React通常是对代码与预期业务逻辑的接近程度的评价。 实现日期和时间业务逻辑非常重要。 我们要看的最后一件事是大量的日期手动操作。 如果您要在代码库中执行多次常规操作,请考虑编写一次自己的调节器,并让您的团队将其作为预先编写的,经过预先测试的组件来使用。

日期和时间为值

值得花点时间考虑一下是什么使LocalDate类成为一个值。 值是简单的数据类型,其中两个相等的实例完全可以替换-对象标识没有实际含义。 String类是值的典范示例-我们通过equals()关心两个字符串是否为真,而在==时我们不在乎它们是否相同

大多数日期和时间类也应该是值,并且java.time API满足了这一期望。 因此,从来没有充分的理由使用==比较两个LocalDate实例,实际上Javadoc建议不要这样做。

对于那些想了解更多信息的人,请参阅我最近对VALJO的定义,该定义为Java中的值对象定义了一组严格的规则,包括不可变性,工厂方法以及equals,hashCode,toStringcompareTo的良好定义。

备用日历系统

java.time中的所有主要日期和时间类一样, LocalDate类固定于单个日历系统-ISO-8601标准中定义的日历系统。

在ISO-8601日历系统是事实上的世界历法系统,也描述为proleptic公历。 标准年为365天,leap年为366天。 年每4年发生一次,但不是每100年发生一次,除非可以被400整除。为进行一致的计算,第一年的前一年被视为零年。

使用此日历系统作为默认日历的第一个影响是日期不一定与GregorianCalendar的结果兼容。 在GregorianCalendar中 ,有来自割接儒略历系统的阳历其中15日发生在默认情况下1582年十月在该日期之前一个,儒略历被使用,其中有一个闰年每4年没有失败。 在该日期之后,将使用公历,它具有我们今天使用的更复杂的leap年系统。

鉴于日历系统的这种变化是历史事实,为什么新的java.time API不能对其建模? 原因是,今天大多数使用此类历史日期的Java应用程序都是不正确的,因此继续下去将是一个错误。 原因是,尽管罗马的梵蒂冈城于1582年10月15日更改了日历系统,但世界上大多数其他国家并没有这样做 。 特别是大英帝国,包括早期的美国,直到近200年后才于1752年9月14日改变。 俄罗斯直到1918年2月14日才改变,而瑞典的改变尤其混乱。 因此,事实上,1918年之前的日期的含义很容易被解释,并且对格里高利日历的一次转换的信念是错误的。 因此,选择LocalDate不具有切换功能是一个非常合理的选择。 应用程序需要其他上下文信息,才能准确地解释儒略历和公历之间的特定历史日期。

在所有主要类别中都集中使用ISO-8601事实上的世界日历系统的第二个影响是需要一组额外的类来处理其他日历系统。 Chronology界面是备用日历系统的主要入口点,允许您按语言环境名称查找它们。 Java SE 8还提供了其他四个日历系统-泰国佛教徒,Minguo,日语和Hirah。 其他日历系统可以由应用程序提供。

每个日历系统都有一个专用的日期类,因此有ThaiBuddhistDateMinguoDate,JapaneseDateHijrahDate 。 如果构建高度本地化的应用程序(例如日本政府的应用程序),则使用这些文件。 额外的接口ChronoLocalDate用作这四个接口的基本抽象,加上LocalDate,可在不知道其运行的日历系统的情况下编写代码。 尽管存在这种抽象,但意图是很少使用它。

理解为什么很少使用抽象对于正确使用整个java.time API 至关重要 。 事实是,当检查当今的应用程序时,大多数尝试以日历系统中立方式运行的代码都被破坏了。 例如,您不能假设一年中有12个月,而开发人员却假设他们已经添加了整整一年就添加12个月。 您不能假设所有月份的长度大致相同-科普特日历系统有12个月的30天和1个月的5或6天。 您也不能假设下一年的数字比当前年份大,因为日本皇帝改变时,日历会像日本重新启动年份一样编号,通常是年中(您甚至不能假设同一天有两天月份是同一年!)。

在日历系统中立的方式下跨大型应用程序编写代码的唯一方法是进行繁琐的代码审查,其中要仔细检查每一行日期和时间代码,以免对ISO日历系统产生偏见。 因此, java.time的推荐用法是在整个应用程序中使用LocalDate ,包括所有存储,操纵和解释业务规则。 唯一应使用ChronoLocalDate的时间是在本地化输入或输出时,通常是通过将用户首选的日历系统存储在用户配置文件中来实现的,即使那样,大多数应用程序实际上并不需要该本地化级别。

有关此领域的完整原理,请参阅ChronoLocalDateJavadoc

一天中的时间

超越日期,下一个要考虑的概念是Local-of-day,以LocalTime表示。 典型的例子可能是代表便利店的营业时间,例如从07:00到23:00(从早上7点到晚上11点)。 这样的商店可能会在美国全境的那几个小时营业,但当地时间忽略了时区。

LocalTime是一种没有关联的日期或时区的值类型。 当增加或减少时间量时,它将在午夜左右结束。 因此,20:00加6小时得出02:00。

使用LocalTime类似于使用LocalDate

LocalTime time = LocalTime.of(20, 30); 
int hour = date.getHour(); // 20 
int minute = date.getMinute(); // 30 
time = time.withSecond(6); // 20:30:06 
time = time.plusMinutes(3); // 20:33:06

调整器机制也可以与LocalTime一起使用,但是调用它的时间的复杂处理较少。

合并日期和时间

下一个要考虑的类是LocalDateTime 。 此值类型是LocalDateLocalTime的简单组合。 它代表没有时区的日期和时间。

LocalDateTime可以直接创建,也可以通过组合日期和时间来创建:

LocalDateTime dt1 = LocalDateTime.of(2014, Month.JUNE, 10, 20, 30); 
LocalDateTime dt2 = LocalDateTime.of(date, time); 
LocalDateTime dt3 = date.atTime(20, 30); 
LocalDateTime dt4 = date.atTime(time);

第三个和第四个选项使用atTime() ,它提供了一种建立日期时间的流畅方法。 提供的大多数日期和时间类都具有“ at”方法,可以用这种方法将您拥有的对象与另一个对象结合起来以形成一个更复杂的对象。

LocalDateTime上的其他方法类似于LocalDateLocalTime的方法 。 这种熟悉的方法模式对于帮助学习API很有用。 下表总结了所使用的方法前缀:

字首

描述

从组成部分创建实例的静态工厂方法。

尝试从相似对象中提取实例的静态工厂方法。 from()方法的类型安全性不如of()方法。

现在

在当前时间获取实例的静态工厂方法。

解析

静态工厂方法,允许将字符串解析为对象的实例。

得到

获取日期时间对象的部分状态。

检查日期时间对象是否为真。

返回日期时间对象的副本,其中部分状态已更改。

返回添加了一定时间的日期时间对象的副本。

减去

返回减去时间量的日期时间对象的副本。

将此日期时间对象转换为另一个,可以表示原始对象的部分或全部状态。

将此日期时间对象与其他数据组合以创建更大或更复杂的日期时间对象。

格式

提供格式化该日期时间对象的功能。

瞬间

在处理日期和时间时,我们通常以年,月,日,小时,分钟和秒为单位进行考虑。 但是,这只是时间的一种模式,我称之为“人类”。 第二个通用模型是“机器”或“连续”时间。 在此模型中,时间轴上的一个点由单个大数字表示。 这种方法很容易让计算机处理,并且从1970年的UNIX秒计数中可以看出,与Java中的1970年的毫秒计数相匹配。

java.time API通过Instant值类型提供时间的机器视图。 它提供了在没有任何其他上下文信息(例如时区)的情况下表示时间线上的点的功能。 从概念上讲,它仅表示自1970年(UTC 1970年1月1日开始的午夜)以来的秒数。 由于API基于纳秒,因此Instant类提供了将精度存储到纳秒的能力。

Instant start = Instant.now(); 
// perform some calculation 
Instant end = Instant.now(); 
assert end.isAfter(start);

Instant类通常用于存储和比较时间戳,您需要在其中记录事件发生的时间,而无需记录有关事件发生的时区的任何信息。

在许多方面,Instant有趣的方面是您无法使用它,而不是您可以做什么。 例如,这些代码行将引发异常:

instant.get(ChronoField.MONTH_OF_YEAR); 
instant.plus(6, ChronoUnit.YEARS);

它们会引发异常,因为Instant仅包含数秒和十亿分之一秒的时间,并且无法处理对人类有意义的单位。 如果需要此功能,则需要提供时区信息。

时区

英国引入了时区的概念, 当时铁路的发明以及通信方面的其他改进使人们突然可以覆盖远处,因为太阳时间的变化很重要。 直到那时,每个村庄和城镇都有自己的基于太阳的时间定义,通常以日d为基准。

最初在英国布里斯托(Bristol)交易所大楼上的时钟照片显示了这种混淆的一个例子。 红手显示格林威治标准时间,黑手显示布里斯托尔时间,相差10分钟:

在技​​术的推动下,标准时区系统得到了发展,取代了较早的当地太阳时。 但是关键事实是时区是政治创造。 它们通常用于展示对该地区的政治控制,例如克里米亚到莫斯科时期的最新变化。 与任何政治事物一样,相关规则经常违反逻辑。 规则可以而且确实会在很少通知的情况下进行更改。

时区规则由发布IANA时区数据库的国际组织收集和收集。 这组数据包含地球上每个区域的标识符以及该区域时区变化的历史。 标识符的格式为“欧洲/伦敦”或“美国/纽约”。

java.time API之前,您使用TimeZone类来表示时区。 现在,您使用ZoneId类。 有两个主要区别。 首先, ZoneId是不可变的,它提供了将实例存储在静态变量中的功能。 其次,实际规则本身在ZoneRules举行,而不是在本身了zoneid,只需拨打了zoneid getRules()获得的规则。

时区的一种常见情况是与UTC /格林威治时间的固定偏移量。 在谈论时差时,通常会遇到这种情况,例如纽约比伦敦落后5小时。 ZoneOffset类是ZoneId的子类, 代表伦敦格林威治的零子午线的时间偏移。

作为开发人员,最好不必处理时区及其复杂性。 java.time API允许您尽可能地做到这一点。 尽可能使用LocalDateLocalTimeLocalDateTimeInstant类。 当您无法避免时区时, ZonedDateTime类将处理该要求。

ZonedDateTime类管理从人类时间线(在桌面日历和挂钟上看到)到机器时间线(以秒为单位)的转换。 这样,您可以从本地类或即时实例创建ZonedDateTime:

ZoneId zone = ZoneId.of("Europe/Paris"); 

LocalDate date = LocalDate.of(2014, Month.JUNE, 10); 
ZonedDateTime zdt1 = date.atStartOfDay(zone); 

Instant instant = Instant.now(); 
ZonedDateTime zdt2 = instant.atZone(zone);

时区最烦人的部分之一是夏令时(DST)。 使用DST,与格林威治的偏移量每年更改两次(或多次),通常在Spring前进,在秋季/秋季前进。 进行这些调整后,我们所有人都必须更改房屋周围点缀的挂钟。 这些更改由java.time称为偏移量转换 。 在Spring,本地时间轴上存在一个“间隙”,其中一些本地时间不会发生。 相比之下,在秋天/秋天,当某些本地时间出现两次时会出现“重叠”。

ZonedDateTime类使用其工厂方法和操作方法进行处理。 例如,添加一天将添加一个逻辑日,如果超过了DST边界,则可能会大于或小于24小时。 同样,方法atStartOfDay()之所以如此命名,是因为您不能假设结果时间将是午夜-从午夜到凌晨1点可能存在DST间隙。

关于DST的最后一个技巧。 如果您想证明您已经考虑过DST重叠(同一本地时间发生两次)应该发生什么,则可以使用专用于处理重叠的两种特殊方法之一:

zdt = zdt.withEarlierOffsetAtOverlap(); 
zdt = zdt.withLaterOffsetAtOverlap();

如果对象与DST重叠,则使用这两种方法之一将选择较早或较晚的时间。 在所有其他情况下,这些方法将无效。

时间量

到目前为止讨论的日期和时间类别以各种方式代表了时间轴上的点。 为时间量提供了两个附加值类型。

Duration类表示以秒和纳秒为单位的时间量。 例如,“ 23.6秒”。

Period类代表以年,月和日为单位的时间量。 例如,“ 3年2个月零6天”。

这些可以添加到主要日期和时间类别中,也可以从其中删除:

Period sixMonths = Period.ofMonths(6); 
LocalDate date = LocalDate.now(); 
LocalDate future = date.plus(sixMonths);

格式化和解析

整个软件包专用于格式化和打印日期和时间-java.time.format 。 该软件包围绕DateTimeFormatter及其关联的生成器DateTimeFormatterBuilder展开

创建格式化程序的最常见方法是DateTimeFormatter上的静态方法和常量。 这些包括:

  • 通用 ISO格式的常量,例如ISO_LOCAL_DATE
  • 模式字母,例如ofPattern(“ dd / MM / uuuu” )。
  • 本地化的样式,例如ofLocalizedDate(FormatStyle.MEDIUM )。

拥有格式化程序后,通常可以通过将其传递给主要日期和时间类的相关方法来使用它:

DateTimeFormatter f = DateTimeFormatter.ofPattern("dd/MM/uuuu"); 
LocalDate date = LocalDate.parse("24/06/2014", f); 
String str = date.format(f);

这样,您就可以与格式化程序本身上的format和parse方法隔离。

如果需要控制格式化的语言环境,请在格式化程序上使用withLocale(Locale)方法。 类似的方法可以控制日历系统,时区,十进制编号系统和解析的分辨率。

如果需要更多控制,请参见DateTimeFormatterBuilder类,该类允许逐步建立复杂的格式。 它还具有格式化程序不区分大小写的解析,宽松的解析,填充和可选部分的功能。

摘要

java.time API是Java SE 8中用于日期和时间的新的全面模型。它将在Joda-Time中开始的想法和实现提高到一个新的水平,并最终允许开发人员将java.util.DateCalendar留在后面。 绝对可以再次享受日期和时间编程的时间!

翻译自: https://www.infoq.com/articles/java.time?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

团队和做的直观图

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值