在 Java 中操作和存储日期的最佳实践是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2168374/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 19:48:54  来源:igfitidea点击:

What is the Best Practice for manipulating and storing dates in Java?

javadate

提问by BestPractices

What is the Best Practice for manipulating and storing Dates e.g. using GregorianCalendar in an enterprise java application?

操作和存储日期的最佳实践是什么,例如在企业 Java 应用程序中使用 GregorianCalendar?

Looking for feedback and I will consolidate any great answers into a Best Practice that others can use. Thanks!

寻求反馈,我会将任何出色的答案整合到其他人可以使用的最佳实践中。谢谢!

采纳答案by Brian Agnew

Jodais the way to go. Why ?

乔达是要走的路。为什么 ?

  1. it has a much more powerful and intuitive interface than the standard Date/Time API
  2. there are no threading issues with date/time formatting. java.text.SimpleDateFormat is not thread-safe (not a lot of people know this!)
  1. 它具有比标准日期/时间 API 更强大、更直观的界面
  2. 日期/时间格式没有线程问题。java.text.SimpleDateFormat 不是线程安全的(不是很多人知道这一点!)

At some stage the Java Date/Time API is going to be superseded (by JSR-310). I believe this is going to be based upon the work done by those behind Joda, and as such you'll be learning an API that will influence a new standard Java API.

在某个阶段,Java 日期/时间 API 将被取代(由 JSR-310)。我相信这将基于 Joda 背后的人所做的工作,因此您将学习影响新标准 Java API 的 API。

回答by SyntaxT3rr0r

The best practice is usually precisely NOTto think in term of heavy date objects but to store a point in time. This is typically done by storing a value that doesn't suffer from corner cases nor from potential parsing problems. To do this, people usually store the number of milliseconds (or seconds) elapsed since a fixed point that we call the epoch (1970-01-01). This is very common and any Java API will always allow you to convert any kind of date to/from the time expressed in ms since the epoch.

最佳实践通常是考虑重日期对象,而是存储一个时间点。这通常是通过存储一个不受极端情况或潜在解析问题影响的值来完成的。为此,人们通常会存储自我们称为纪元 (1970-01-01) 的固定点以来经过的毫秒数(或秒数)。这是非常常见的,任何 Java API 都将始终允许您将任何类型的日期转换为自纪元以来以毫秒表示的时间。

That's for storage. You can also store, for example, the user's preferred timezone, if there's such a need.

那是为了存储。如果有这样的需要,您还可以存储,例如,用户的首选时区。

Now such a date in milliseconds, like:

现在这样的日期以毫秒为单位,例如:

System.out.println( System.currentTimeMillis() );
1264875453

ain't very useful when it's displayed to the end user, that's for granted.

当它显示给最终用户时不是很有用,这是理所当然的。

Which is why you use, for example, the example Joda time to convert it to some user-friendly format before displaying it to the end-user.

这就是为什么您使用示例 Joda time 在将其显示给最终用户之前将其转换为某种用户友好格式的原因。

You asked for best practice, here's my take on it: storing "date" objects in a DB instead of the time in milliseconds is right there with using floating point numbers to represent monetary amounts.

您要求最佳实践,这是我的看法:将“日期”对象存储在数据库中而不是以毫秒为单位的时间就在那里,使用浮点数来表示货币金额。

It's usually a huge code smell.

这通常是一种巨大的代码气味。

So Joda time in Java is the way to manipulate date, yes. But is Joda the way to go to storedates? CERTAINLY NOT.

所以Java中的Joda时间是操纵日期的方式,是的。但是 Joda 是存储日期的方式吗?当然不是

回答by Bozho

Joda time(100% interoperable with the JDK)

Joda 时间(与 JDK 100% 可互操作)

Joda-Time provides a quality replacement for the Java date and time classes. The design allows for multiple calendar systems, while still providing a simple API

Joda-Time 为 Java 日期和时间类提供了高质量的替代品。该设计允许多个日历系统,同时仍提供简单的 API

回答by Basil Bourque

UTC

世界标准时间

Think, work, and store data in UTCrather than any time zone. Think of UTC as the One True Time, and all other time zones are mere variations. So while coding, forget all about your own time zone. Do your business logic, logging, data storage, and data exchange in UTC. I suggest every programmer keep a second clock on their desk set to UTC.

UTC而不是任何时区来思考、工作和存储数据。将 UTC 视为One True Time,所有其他时区都只是变体。因此,在编码时,请忘记您自己的时区。在 UTC 中执行您的业务逻辑、日志记录、数据存储和数据交换。我建议每个程序员在他们的办公桌上保留一个设置为 UTC 的第二个时钟。

java.time

时间

The modern way is the java.timeclasses.

现代方式是java.time类。

The mentioned Joda-Timeproject provided the inspiration for the java.time classes, and the project is now in maintenance mode with the team advising migration to java.time classes.

提到的Joda-Time项目为 java.time 类提供了灵感,该项目现在处于维护模式,团队建议迁移到 java.time 类。

The java.timeframework is built into Java 8 and later. These classes supplant the troublesome old legacydate-time classes such as java.util.Date, .Calendar, & java.text.SimpleDateFormat.

java.time框架是建立在Java 8和更高版本。这些类取代了麻烦的旧的遗留日期时间类,例如java.util.Date, .Calendar, & java.text.SimpleDateFormat

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

要了解更多信息,请参阅Oracle 教程。并在 Stack Overflow 上搜索许多示例和解释。规范是JSR 310

Where to obtain the java.time classes?

从哪里获得 java.time 类?

The ThreeTen-Extraproject extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

ThreeTen-额外项目与其他类扩展java.time。该项目是未来可能添加到 java.time 的试验场。你可能在这里找到一些有用的类,比如IntervalYearWeekYearQuarter,和更多

ISO 8601

ISO 8601

When serializing a date-time value to text, use the ISO 8601standard.

将日期时间值序列化为文本时,请使用ISO 8601标准。

For example, a date-time in UTC is 2016-10-17T01:24:35Zwhere the Zis short for Zuluand means UTC. For other offset-from-UTCthe offset of hours and minutes appears at the end such as 2016-01-23T12:34:56+05:30. The java.time classes extend this standard format to append the name of the time zone (if known) in square brackets, such as 2016-01-23T12:34:56+05:30[Asia/Kolkata].

例如,UTC 中的日期时间是 UTC2016-10-17T01:24:35ZZ缩写Zulu并表示 UTC。对于其他从 UTC 开始的偏移量,小时和分钟的偏移量出现在末尾,例如2016-01-23T12:34:56+05:30. java.time 类扩展了此标准格式,将时区名称(如果已知)附加在方括号中,例如2016-01-23T12:34:56+05:30[Asia/Kolkata].

The standard has many other handy formats as well including for durations, intervals, ordinals, and year-week.

该标准还有许多其他方便的格式,包括持续时间间隔序数年-周

Database

数据库

For database storage, use date-time types for date-time values, such as the SQL standard data typeswhich are primarily DATE, TIME, and TIMESTAMP WITH TIME ZONE.

用于数据库存储,使用日期时间类型日期时间值,如SQL标准的数据类型,其主要是DATETIME,和TIMESTAMP WITH TIME ZONE

Let your JDBC driverdo the heavy lifting. The driver handles the nitty-gritty details about mediating and adapting between the internals of how Java handles the data and how your database handles the data on its side. But be sure to practice with example data to learn the behaviors of your driver and your database. The SQL standard defines very little about date-time handling and so behaviors vary widely, surprisingly so.

让您的JDBC 驱动程序完成繁重的工作。驱动程序处理有关在 Java 如何处理数据的内部结构和您的数据库如何在其侧处理数据之间进行调解和调整的细节。但一定要使用示例数据进行练习,以了解驱动程序和数据库的行为。SQL 标准对日期时间处理的定义很少,因此行为差异很大,令人惊讶。

If using a JDBC driver compliant with JDBC 4.2 and later, you can fetch and store java.time types directly via the ResultSet::getObjectand PreparedStatement::setObjectmethods.

如果使用符合 JDBC 4.2 及更高版本的 JDBC 驱动程序,则可以直接通过ResultSet::getObjectPreparedStatement::setObject方法获取和存储 java.time 类型。

Instant instant = myResultSet.getObject( … );
myPreparedStatement.setObject( … , instant );

For older drivers, you will need to fall back to converting through the java.sql types. Look for new conversion methods added to the old classes. For example, java.sql.Timestamp.toInstant().

对于较旧的驱动程序,您需要回退到通过 java.sql 类型进行转换。寻找添加到旧类的新转换方法。例如,java.sql.Timestamp.toInstant()

Instant instant = myResultSet.getTimestamp( … ).toInstant();
myPreparedStatement.setObject( … , java.sql.Timestamp.from( instant ) );

Use the java.sql types as briefly as possible. They are a badly designed hack, such as java.sql.Datemasquerading as a date-only value but actually as a subclass of java.util.Dateit does indeed have a time-of-day set to the 00:00:00in UTC. And, oh, you are supposed to ignore the fact of that inheritance says the class doc. An ugly mess.

尽可能简短地使用 java.sql 类型。它们是一个设计糟糕的黑客,例如java.sql.Date伪装成仅日期值,但实际上作为java.util.Date它的子类确实将时间设置为00:00:00UTC。而且,哦,你应该忽略继承的事实,类文档说。一个丑陋的混乱。

Example code

示例代码

Get the current moment in UTC.

获取 UTC 中的当前时刻。

Instant instant = Instant.now();

Storing and fetching that Instantobject to/from a database is shown above.

Instant上面显示了在数据库中存储和获取该对象。

To generate an ISO 8601 string, merely call toString. The java.time classes all use ISO 8601 formats by default for parsing and generating strings of their various date-time values.

要生成 ISO 8601 字符串,只需调用toString. 默认情况下,java.time 类都使用 ISO 8601 格式来解析和生成各种日期时间值的字符串。

String output = instant.toString();

Adjust into any offset-from-UTC by applying a ZoneOffsetto get an OffsetDateTime. Call toStringto generate a String in ISO 8601 format.

通过应用 aZoneOffset以获得OffsetDateTime. 调用toString以生成 ISO 8601 格式的字符串。

ZoneOffset offset = ZoneOffset.ofHoursMinutes( 5 , 30 );
OffsetDateTime odt = instant.atOffset( offset );

A time zone is an offset plusa set of rules for handling anomalies such as Daylight Saving Time (DST). When you need to see that same moment through the lens of some region's own wall-clock time, apply a time zone (ZoneId) to get a ZonedDateTimeobject.

时区是一个偏移量加上一组用于处理夏令时 (DST)等异常的规则。当您需要通过某个地区自己的挂钟时间的镜头看到同一时刻,请应用时区 ( ZoneId) 来获取ZonedDateTime对象。

Specify a proper time zone namein the format of continent/region. Never use the 3-4 letter abbreviation such as ESTor ISTas they are not true time zones, not standardized, and not even unique(!).

以 格式指定正确的时区名称continent/region。永远不要使用 3-4 个字母的缩写,例如ESTIST因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。

ZoneId z = ZoneId.of( "Asia/Kolkata" );
ZonedDateTime zdt = instant.atZone( z );

Going the other direction, you can extract an Instantfrom an OffsetDateTimeor ZonedDateTimeby calling toInstant.

反过来,您可以Instant从 anOffsetDateTimeZonedDateTime通过调用toInstant.

Instant instant = zdt.toInstant();

Formatting

格式化

For presentation to the user as strings in formats other than ISO 8601, search Stack Overflowfor use of the DateTimeFormatterclass.

要以 ISO 8601 以外的格式向用户展示字符串,请搜索 Stack Overflow以使用DateTimeFormatter该类。

While you can specify an custom format, usually best to let java.time automatically localize. To localize, specify:

虽然您可以指定自定义格式,但通常最好让 java.time 自动本地化。要本地化,请指定:

  • FormatStyleto determine how long or abbreviated should the string be.
  • Localeto determine (a) the human language for translation of name of day, name of month, and such, and (b) the cultural norms deciding issues of abbreviation, capitalization, punctuation, and such.
  • FormatStyle确定字符串的长度或缩写。
  • Locale确定 (a) 用于翻译日名、月名等的人类语言,以及 (b) 决定缩写、大写、标点等问题的文化规范。

Example:

例子:

Locale l = Locale.CANADA_FRENCH ; 
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL ).withLocale( l );
String output = zdt.format( f );

Conversion

转换

Best to avoid the legacy date-time types whenever possible. But if working with old code not yet updated for the java.time types, you can convert to/from the java.time types. For details, see the Question, Convert java.util.Date to what “java.time” type?.

最好尽可能避免使用旧的日期时间类型。但是,如果使用尚未针对 java.time 类型更新的旧代码,则可以在 java.time 类型之间进行转换。有关详细信息,请参阅问题,将 java.util.Date 转换为什么“java.time”类型?.

Use objects

使用对象

Use objects rather than mere coded primitives and simple strings. For example:

使用对象而不仅仅是编码的原语和简单的字符串。例如:

  • Do not use 1-7 to represent a day-of-week, use the DayOfWeekenum such as DayOfWeek.TUESDAY.
  • Rather than passing around a string as a date, pass around LocalDateobjects.
  • Rather than pass around a pair of integers for a year-and-month, pass around YearMonthobjects.
  • Instead of 1-12 for a month, use the much more readable Monthenum such as Month.JANUARY.
  • 不要使用 1-7 来表示星期几,请使用DayOfWeek枚举,例如DayOfWeek.TUESDAY.
  • 不是将字符串作为日期传递,而是传递LocalDate对象。
  • 不是传递一对整数来表示年和月,而是传递YearMonth对象。
  • 而不是一个月的 1-12,使用更具可读性的Month枚举,例如Month.JANUARY.

Using such objects makes your code more self-documenting, ensures valid values, and provides type-safety.

使用这样的对象可以让你的代码更加自我记录,确保有效值,并提供类型安全

回答by BestPractices

To get the discussion started, here's been my experience:

为了开始讨论,这是我的经验:

When creating standards for a typical 3-tier Java Enterprise project, I would generally recommend that the project use GregorianCalendar for manipulating dates. Reason is GregorianCalendar is the de facto standard over any other Calendar instance e.g. Julian calendar etc. It's the recognized calendar in most countries and properly handles leap years, etc. On top of that, I would recommend that the application store its dates as UTC so that you can easily perform date calculations such as finding the difference between two dates (if it were stored as EST for example, you'd have to take day light savings time into account). The date can be then be localized to whatever timezone you need it to be displayed to the user as -- such as localizing it to EST if you are an east-coast US company and you want your time information shown in EST.

在为典型的 3 层 Java Enterprise 项目创建标准时,我通常建议该项目使用 GregorianCalendar 来处理日期。原因是 GregorianCalendar 是任何其他日历实例的事实上的标准,例如儒略历等。它是大多数国家/地区公认的日历并正确处理闰年等。最重要的是,我建议应用程序将其日期存储为 UTC,因此您可以轻松执行日期计算,例如查找两个日期之间的差异(例如,如果将其存储为 EST,则必须考虑夏令时)。然后可以将日期本地化到您需要将其显示给用户的任何时区 - 例如,如果您是美国东海岸的公司并且希望在 EST 中显示时间信息,则将其本地化为 EST。