首页  >  文章  >  Java  >  让我们正确地进行日期

让我们正确地进行日期

王林
王林原创
2024-08-05 20:17:301104浏览

Lets do dates properly

作为初学者,您将学到的最有趣的主题之一是日期。尽管这听起来有点无聊,但这是您可能知道的关键事情之一;从数据库、API、GUI 等中学习如何正确处理日期是编写优秀应用程序的关键因素。

在我们开始之前

您必须记住的一个重要细节是 Java 被急于应用于网络,因此您有很多奇怪且有些愚蠢的方法来做事情。本指南的目标是教您如何使用最新的 Java API 编写高效的优质代码。我的目标是教您如何不编写任何困难的代码,而是使用标准化 ISO 来实现可扩展的应用程序。

如何使用日期

日期 API

从 Java 8 SE 开始,Date 类的发布定义为:

Date 类表示特定的时刻,精度为毫秒。

此外,我们还添加了抽象日历类(JDK 1.1 进行了重大升级),定义为:

Calendar 类是一个抽象类,它提供了在特定时刻和一组日历字段(如 YEAR、MONTH、DAY_OF_MONTH、HOUR 等)之间进行转换的方法,以及操作日历字段的方法,如获取下周的日期。时间上的瞬间可以用毫秒值来表示,该值是距 纪元,1970 年 1 月 1 日 00:00:00.000 GMT(公历)的偏移量。

正如 Oracle 文档所述,原因是缺乏国际化能力。

值得注意的是,如果您感兴趣的话,上面链接的相同文档会进一步详细介绍其时间安排。

本地日期 API

Java 8 SE 还引入了 LocalDate 类,称为:

ISO-8601 日历系统中没有时区的日期,例如 2007-12-03。
LocalDate 是一个不可变的日期时间对象,表示日期,通常被视为年月日。还可以访问其他日期字段,例如一年中的某一天、一周中的某一天和一年中的星期。例如,值“2nd October 2007”可以存储在 LocalDate 中。

需要注意的一个重要规范是:

此类不存储或表示时间或时区。相反,它是对日期的描述,如生日。如果没有偏移量或时区等附加信息,它无法表示时间线上的瞬间。

区别以及使用哪一个

日期/时间 API 的问题

进一步阅读 LocalDate 类将引导您了解“这个类是不可变的且线程安全的。”这给我们带来了第一个问题。

  • 日期和日历都不是线程安全的。如果您对它的含义有点困惑 - 线程是一种与计算机应用程序实现并发的方法,它允许多个任务同时运行而不是顺序运行(如果您有编程背景,您可以将其视为异步) 。
  • 日期/日历 API 的另一个问题是其糟糕的设计。后来,它被修改为以 ISO 为中心(如果您对 ISO 的整个内容有点迷惑;简而言之,ISO 代表“国际标准化组织”),稍后我们将看到这样做的额外好处。
  • 最后回到缺乏国际化的问题,开发人员需要编写处理不同时区的逻辑,这可能会导致许多错误匹配。

实际差异

最大的区别是,尽管名称 Date 会同时存储时间和日期(正如文档中提到的,自纪元以来有毫秒偏移量 - 我们稍后会讨论纪元)。借助更新的 API,我们可以更轻松地格式化/解析/操作日期。

如果我也想存储时间怎么办?

Java 也通过其 LocalDateTime 类为您提供了帮助

ISO-8601 日历系统中没有时区的日期时间,例如 2007-12-03T10:15:30。
LocalDateTime 是一个不可变的日期时间对象,表示日期时间,通常被视为年-月-日-时-分-秒。还可以访问其他日期和时间字段,例如一年中的某一天、一周中的某一天和一年中的星期。时间以纳秒精度表示。例如,值“2nd October 2007 at 13:45.30.123456789”可以存储在 LocalDateTime 中。

如果您只想存储时间那么您可以使用LocalTime类

快速回顾和用例

Name Should Use Use Case Example
LocalDate Interprating Dates 2021-02-28
LocalTime Interprating Time 19:32:25.457826
LocalDateTime Interprating Time/Date 2021-02-28T19:32:25.457826.
Date prefer not Represents Time Tue Jul 12 18:35:37 IST 2016

本地日期

现在我们已经很好地了解了它的背景以及我们可以用它做什么,让我们来探索 LocalDate 类的方法。

ISO 8601

ISO-8601 日历系统是当今世界大部分地区使用的现代民用日历系统。它相当于提前公历系统,其中今天的闰年规则始终适用。对于当今编写的大多数应用程序来说,ISO-8601 规则完全适合。然而,任何使用历史日期并要求它们准确的应用程序都会发现 ISO-8601 方法不适合。

这是标准 Java 将用来正确格式化我们的日期。

在我们深入研究之前,我想向您介绍 Temporal 对象,所有新的 Date API 都实现了该对象(LocalDate、LocalTime、LocalDateTime),它的定义为:

这是日期、时间和偏移量对象的基本接口类型,它们足够完整,可以使用加号和减号进行操作。它由那些可以提供和操作信息作为字段或查询的类来实现。请参阅 TemporalAccessor 了解此接口的只读版本。

不深入讨论这个只是想让大家在遇到它时明白这是什么。

方法

其格式将按顺序排列 - 方法名称、指定的返回描述以及其功能的快速摘要/或深入研究。如果你想跟着我做,我将使用 jshell 只需导入 java.time.LocalDate 并复制相同的方法。

LocalDate.now(): > >使用系统时钟和默认时区的当前日期,不为空

正如描述中提到的,它将根据时区返回当前日期格式,例如我给出的日期格式为 2024-07-10,但是格式可能会根据您的区域设置时间而变化,具体取决于为您所在国家/地区分配的 ISO 8601 格式(例如,可能是 2024.07.10 或 24/7/10,这是您所在国家/地区使用的标准)

LocalDate.now() 方法可以接受一个参数。要使用它,我们将导入 java.time.ZoneId.

什么是ZoneId?

ZoneId 随 Java 8SE 一起发布,定义为:

ZoneId 用于标识 Instant 和 LocalDateTime 之间转换的规则。 ID 有两种不同类型:

  • 固定偏移量 - 与 UTC/格林威治的完全解析偏移量,对所有本地日期时间使用相同的偏移量
  • 地理区域 - 应用一组特定规则来查找与 UTC/格林威治的偏移量的区域

简单来说,这解决了我们使用旧 Date API 时遇到的国际化问题。导入 java.time.ZoneId 后,我们可以看看它提供的一些方法。

  • ZoneId.getAvailableZoneIds() 向我们显示所有可用区域,例如:欧洲/伊斯坦布尔、美洲/埃鲁内佩、Etc/GMT-4、美洲/密克隆群岛(请记住,其中每一个都是唯一的,我们有两种类型的唯一 ID上面列出了)。
  • ZoneId.systemDefault() 方法定义为“如果系统默认时区更改,则该方法的结果也会更改。”这是不言自明的。
  • ZoneId.of("") 我们使用它从 .getAvailableZoneIds() 列表中定义一个自定义的 ZoneId 集。请记住,此处作为示例的 UTC 和欧洲/伊斯坦布尔都可以使用。
  • ZoneId.from() 还记得我们之前讨论过的时间吗?在这里我们将它作为参数传递。如果您不完全确定它的作用,给定一个代表任意日期和时间信息集的 ==TemporalAccessor,我们将获得一个 ZoneId。如果您仍然有点无能为力,您可以导入 java.time.ZoneId 并使用 ZonedDateTime.now() ,这将为您提供精确的时间/日期,然后您可以将其解析为 ZoneId 。基本上 ZoneId.from(ZonedDateTime.now())

您还可以传递一个 Clock 参数,但当您可以只使用 ZoneID 但可以随意探索时,我认为它没有任何用处

现在我们知道了 - 让我们获取巴黎的当前日期!

LocalDate.now(ZoneId.of("Europe/Paris")) // 2024-07-10
LocalDate.of(年、月、日): >从年、月、日获取 LocalDate 的实例。

记住 LocalDate 代表一个日期。在这种情况下,我们可以将该日期设置为我们想要的任何年、月、日!

LocalDate.ofYearDay(year, dayOfYear): >

在很多情况下,你可能只有一年零一天。此函数将其转换为年-月-日格式。下面是一个示例 LocalDate.ofYearDay(2023, 234) 将是 2023-08-22。闰年也将被考虑在内。

LocalDate.parse(text) : > Obtains an instance of LocalDate from a text string using a specific format.

Basically, if we generate a String Date format like 2023-08-22 this will convert it into a LocalDate format

Epoch

There’s one last method I would like to talk about and even though it might not be that useful, it's a good thing to know.
Epoch in simple terms is a date set to the start. Virtually means it's “The start of time”. For example UNIX epoch is set to 1970/1/1 This is when all Unix time is calculated. Now this is not standardized between all computers. Most programming languages will use the UNIX epoch but not between all devices. For example NTP epoch is 1900/1/1. Now you might be asking why. And the simple answer is I don’t know. Unix was developed in 1969 and released in 71 but I guess the developers found 70 easier to work with and now that’s what we use!

LocalDate.ofEpochDay(epochDay) : >

Just going based on the description I gave about the output of this would be X days from 1970/1/1. Given epochDay is 1 => 1970-01-02 and so on.

~
~
~

By now I think you guys are all bored of all this nerdy talk and most likely lost interest completely but we are going to make it way more interesting in just a bit.
LocalDate methods have a ton of subsets to modify dates as we talked about above. Explaining all of these is just a waste of time. The best thing is for you guys to check ’em out and play around a bit, they are all very simple to understand.

Locale

The main idea of this whole article was to teach you internationalization and we are going to start that now.
If you recall above I talked about how LocalDate.now() might show different numbers/punctuations and this is standardized by Locale Class

A Locale object represents a specific geographical, political, or cultural region. An operation that requires a Locale to perform its task is called locale-sensitive and uses the Locale to tailor information for the user. For example, displaying a number is a locale-sensitive operation— the number should be formatted according to the customs and conventions of the user's native country, region, or culture.

Again the Locale class uses pre-defined ISO standards that Oracle has included.

As I said - In the early days of the web Java was rushed and we can really see that here.

Locale Constant & Why not to use

If you import java.util.Locale you can see that we have a few constants. Locale.CANADA as an example. If you're wondering what these are and why they were picked, it's quite simple. Due to the reason mentioned above, the Java devs just picked some Locale constants to support and you should not use them. Now if you look closely we have a Locale.French
and a Locale.France and you might be confused, what is this?? I’ll talk about it in a second

Now how do we go about using these Locales? By using Locale.availableLocales() you will see a large subset of available locales that you can use. you might see it in the format of en_US and you might get confused. Following up on the question listed above Java has accounted for different language subsets used in different countries. In this France/French example, We can refer to Canadian French versus French used in France. They are not entirely the same. Now a country like Iran will use Persian no matter the place, so you could just use Locale.of(language) to specify the Locale, But for French you could rather use Locale.of(language, country). Remember if you split what you got from Locale.availableLocales() by the _ you can just format it like that. An example would be Locale.of("en", "US") for en_US

Now let's implement this into our time for our different users around the globe.

System.out.println(
LocalDate.now().format(

DateTimeFormatter

.ofLocalizedDate(FormatStyle.SHORT)

.localizedBy(Locale.GERMAN)));

Now don’t mind the extra code, and even me using Locale.GERMAN cause honestly I was looking for a different output and the easiest way was to use Javas constants. I landed on Locale.GERMAN which outputs 10.07.24 as opposed to what Japan uses (2024/07/10).

Why not use Locale.of()

Now it might sound weird, but first of all i tell you not to use constants and prefer Locale.of(), then I tell you not to use Locale.of() an important factor to consider is Locale.of() was introduced with Java 19 which goes against supporting Java 8 SE to combat that from now on we will use

new Locale.Builder()
            .setLanguage("language")
            .setRegion("country").build();

Now another thing you could use is Locale.setDefault(language, country) which will work as well.

DateTimeFormatter

Now looking at the code above you might recall seeing DateTimeFormatter defined by docs as:

Formatter for printing and parsing date-time objects.

LocalDate.format()

As I mentioned above, one of the key features of Date/Time Local* APIs was formatting. And this is how we are going to format our dates. Looking at the code above - everything should make sense apart from the .ofLocalizedDate() method. The easiest way to show what it does is with an example.

LocalDate anotherSummerDay = LocalDate.of(2016, 8, 23);
System.out.println(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).format(anotherSummerDay));
System.out.println(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).format(anotherSummerDay));
System.out.println(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).format(anotherSummerDay));
System.out.println(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).format(anotherSummerDay));

will output the following in order:

Tuesday, August 23, 2016
August 23, 2016
Aug 23, 2016
8/23/16

Now sometimes you might want to have your own pattern which you can easily do with

System.out.println(

LocalDate.now().format(

DateTimeFormatter

.ofPattern("dd-uuuu-MMM")));

Now I’ll quickly go over what these symbols mean:
dd => It's a representation of the day field, now you could also use d and the difference would be that d would not have a fixed character limit since it represents 1 digit from 0-9 and 2 from 10-31. if you're lost it basically means dd would show 01; however d would show 1. However, going into two digits it's the same
uuuu/yyyy => Is the field for years. And you could also do yy which would show only the last two digits of the year. ex. 24 rather than 2024
MMM => The last field as you might have guessed is for the month - MMM tells Java that we want a String representation rather than a number. If you want a number use MM

So by now, you should’ve guessed that the output would be:

10-2024-Jul

Instants

Now that you have a firm grip on Dates. I want to introduce you to Instants defined as:

An instantaneous point on the timeline.

Basically, this refers to Instants showing a specific moment.
By now you might have assumed that LocalDateTime has a timezone, but it doesn’t as the docs mentioned they are just a representation of date/time. However a ZoneId is in fact a representation of a time zone. Also, note that Instants don’t provide methods for altering our dates and times.

Instants capture the current moment in UTC with the Instant.now() . Now based on this, you realize that Local* really doesn’t have a meaning unless it's applied, that’s the reason why you would actually refrain from using it in a business-level application. So prefer using ZonedDateTime and Insant. As a general rule of thumb use Locale When it's a specific point on a timeline rather than a moment in time. A moment would be like the moment I publish this, or the moment you read this, or the moment the world explodes, however a point in a timeline would be like an appointment there’s no moment for an appointment. If you're going on a vacation it's a specific time on a timeline, not a moment.

Whats ZonedDateTime?

Quite simple. It's just an Instant with a ZoneId and this is why we say Insant has a timezone. No matter what you do it's not going to represent a time but rather be a specific moment.

Aigh’t well that was quite the thing, wasn’t it? phew
have a nice one >#

以上是让我们正确地进行日期的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn