Java日期与日历

有人能建议当前关于DateCalendar类型的“最佳实践”吗?

在编写新代码时,最好总是使用Calendar而不是Date,还是在某些情况下Date是更合适的数据类型?

193144 次浏览

Date是一个更简单的类,主要是为了向后兼容。如果需要设置特定的日期或进行日期计算,请使用Calendar。日历也处理本地化。date之前的日期操作函数已弃用。

就我个人而言,如果有选择的话,我倾向于使用以毫秒为单位的时间作为long(或long,视情况而定)或Calendar。

Date和Calendar都是可变的,这在API中使用时往往会出现问题。

Date最适合存储日期对象。它是持久化的,序列化的…

日历是操作日期的最佳工具。

注意:我们有时也更喜欢java.lang.Long而不是Date,因为Date是可变的,因此不是线程安全的。在Date对象上,使用setTime()和getTime()在两者之间切换。例如,应用程序中的一个常量Date(例如:0 1970/01/01,或应用程序的END_OF_TIME,您设置为2099/12/31;这些对于替换null值作为开始时间和结束时间非常有用,特别是当您在数据库中持久化它们时,因为SQL对null非常特殊)。

对于新代码(如果你的策略允许第三方代码)最好的方法是使用Joda时间库

日期日历都有很多设计问题,它们都不是新代码的好解决方案。

  • DateCalendar实际上是相同的基本概念(它们都表示时刻,并且是底层long值的包装器)。

  • 有人可能会说,Calendar实际上比Date更坏,因为它似乎提供了关于一周中的一天和一天中的时间等具体的事实,而如果你改变它的timeZone属性,具体的东西就会变成blancmange!由于这个原因,这两个对象都不是真正有用的年-月-日日时间的存储。

  • 只使用Calendar作为计算器,当给定DateTimeZone对象时,它将为你进行计算。避免将其用于应用程序中的属性类型。

  • 使用SimpleDateFormatTimeZoneDate一起生成显示字符串。

  • 如果你有冒险的想法,可以使用Joda-Time,尽管在我看来,它是不必要的复杂,而且无论如何,它很快就会被JSR-310日期API取代。

  • 我之前回答过,编写自己的YearMonthDay类并不困难,它在底层使用Calendar来进行日期计算。我对这个建议投了反对票,但我仍然相信这是一个有效的建议,因为Joda-Time(和JSR-310)对于大多数用例来说真的太复杂了。

我一直主张Joda-time。这是为什么。

  1. API是一致的和直观的。不像java.util。日期/日历api
  2. 它没有线程问题,不像java.text.SimpleDateFormat等(我已经看到许多客户端问题,没有意识到标准日期/时间格式不是线程安全的)
  3. 它是新的Java日期/时间api (依照JSR310,计划用于Java 8)的基础。因此,您将使用的api将成为核心Java api。

编辑:如果可以迁移到Java 8, Java 8引入的Java日期/时间类现在是首选的解决方案

Dates应该用作不可变时间点;__abc1是可变的,如果你需要与其他类合作来提出最终日期,可以传递和修改。考虑它们类似于StringStringBuilder,你就会明白我认为它们应该如何使用。

(是的,我知道Date实际上在技术上不是不可变的,但目的是它不应该是不可变的,如果没有调用已弃用的方法,那么它就是不可变的。)

如果可能的话,我通常使用Date。虽然它是可变的,但实际上不推荐使用突变器。最后,它基本上包装了一个表示日期/时间的long。相反,如果我必须操作这些值,我会使用calendar。

您可以这样想:只在需要可以轻松操作的字符串时使用StringBuffer,然后使用toString()方法将它们转换为字符串。同样,我只在需要操作时态数据时使用Calendar。

为了最佳实践,我倾向于尽可能多地在域模型之外使用不可变对象。它极大地降低了任何副作用的可能性,并且它是由编译器代替JUnit测试完成的。你可以通过在类中创建私人的最后字段来使用这种技术。

回到StringBuffer的类比。下面是一些代码,向您展示如何在日历和日期之间进行转换

String s = "someString"; // immutable string
StringBuffer buf = new StringBuffer(s); // mutable "string" via StringBuffer
buf.append("x");
assertEquals("someStringx", buf.toString()); // convert to immutable String


// immutable date with hard coded format.  If you are hard
// coding the format, best practice is to hard code the locale
// of the format string, otherwise people in some parts of Europe
// are going to be mad at you.
Date date =     new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH).parse("2001-01-02");


// Convert Date to a Calendar
Calendar cal = Calendar.getInstance();
cal.setTime(date);


// mutate the value
cal.add(Calendar.YEAR, 1);


// convert back to Date
Date newDate = cal.getTime();


//
assertEquals(new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH).parse("2002-01-02"), newDate);

日期应该重新开发。它不应该是一个长整数,而应该包含年、月、日、小时、分钟、秒作为单独的字段。甚至还可以存储这个日期所关联的日历和时区。

在我们平常的谈话中,如果你安排的约会时间是纽约时间2013年11月1日下午1点,这就是“约会时间”。它不是日历。所以我们在Java中也应该能够像这样交谈。

当Date存储为一个长整数(自1970年1月1日以来的毫秒数)时,计算其当前日期取决于日历。不同的日历会给出不同的日期。这是从给出绝对时间(如大爆炸后1万亿秒)的角度出发的。但通常我们也需要一种方便的对话方式,比如一个对象可以封装年、月等。

我想知道Java中是否有新的进展来协调这两个目标。也许我的java知识太老了。

有点晚了,但是Java在JDK 8中有一个新的日期时间API。您可能希望升级JDK版本并采用该标准。没有更多凌乱的日期/日历,没有更多第三方罐子。

顺便说一句,“date”通常被标记为“obsolete / deprecated”(我不知道确切的原因)-关于它的一些东西写在那里 Java:为什么日期构造函数弃用,我用什么代替? < / p >

看起来这只是构造函数的问题——通过新的日期(int年,int月,int日)的方法,推荐的方法是通过Calendar和set params分开。(< >强Calendar cal = Calendar. getinstance (); ) < / p >

在Java 8中,应该使用新的java。时间包

对象是不可变的,时区和日光节约被考虑在内。

你可以像这样从一个旧的java.util.Date对象创建一个ZonedDateTime对象:

    Date date = new Date();
ZonedDateTime zonedDateTime = date.toInstant().atZone(ZoneId.systemDefault());

博士tl;

建议当前的“最佳实践”围绕DateCalendar

是不是总是偏爱Calendar胜过Date

避免这些遗留类完全。应该使用java.time类。

 Java中所有日期-时间类型的表,包括现代和传统

细节

Ortomala的回答 Lokni建议使用现代的java.time类而不是麻烦的旧的遗留日期-时间类(DateCalendar等)是正确的。但这个答案暗示了一个错误的等价类(见我对那个答案的评论)。

使用java.time

java。time类是对遗留的date-time类的巨大的改进,昼夜差异。旧的类设计很差,令人困惑,而且很麻烦。您应该尽可能避免使用旧的类。但是当你需要从旧的/新的转换时,你可以通过调用添加到类中的新方法来实现。

有关转换的更多信息,请参见我的答案和漂亮的图表到另一个问题将java.util. date转换为java.util. date时间”类型?

搜索Stack Overflow提供了数百个关于使用java.time的示例问题和答案。但这里有一个快速的概要。

Instant

使用Instant获取当前时刻。Instant类表示UTC时间轴上的一个时刻,其分辨率为纳秒(最多九(9)位小数分数)。

Instant instant = Instant.now();

ZonedDateTime

要通过某些特定地区的来观察同一同时力矩,应用一个时区(ZoneId)来得到ZonedDateTime

时区

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

ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = instant.atZone();

抵消

时区是一个地区在其offset-from-UTC中的变化历史。但有时你只得到一个偏移量而没有完整的区域。在这种情况下,使用OffsetDateTime类。

ZoneOffset offset = ZoneOffset.parse( "+05:30" );
OffsetDateTime odt = instant.atOffset( offset );

使用时区比仅仅使用偏移量更可取。

LocalDateTime

Local…类中的“Local”表示任何位置,而不是特定的位置。所以这个名字可能是反直觉的。

LocalDateTimeLocalDateLocalTime故意缺乏任何关于偏移量或时区的信息。所以他们代表实际时刻,他们是时间轴上的点。当有疑问或困惑时,使用ZonedDateTime而不是LocalDateTime。更多讨论请搜索Stack Overflow。

字符串

不要将日期-时间对象与表示其值的字符串合并。可以解析字符串以获得日期-时间对象,也可以从日期-时间对象生成字符串。但是字符串从来不是日期-时间本身。

了解标准的ISO 8601格式,在java中默认使用。时间类。


关于java.time

< em > java.time < / em >框架内置于Java 8及更高版本中。这些类取代了麻烦的旧遗产日期-时间类,如java.util.DateCalendar, &SimpleDateFormat

现在在维护模式中的< em > Joda-Time < / em >项目建议迁移到java.time类。

要了解更多信息,请参见< < em >甲骨文教程/ em >。搜索Stack Overflow可以找到很多例子和解释。规格是JSR 310

使用与JDBC 4.2或更高版本兼容的JDBC驱动程序,可以直接与数据库交换java.time对象。不需要字符串或java.sql。*类。

从哪里获取java。时间类?

当我需要对日期进行一些特定的操作时,我使用日历,但当你需要格式化日期以适应你的需求时,我发现它很有帮助,最近我发现Locale有很多有用的操作和方法。我现在使用Locale !