AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / coding / 问题 / 77251539
Accepted
Paul Marcelin Bejan
Paul Marcelin Bejan
Asked: 2023-10-08 05:16:31 +0800 CST2023-10-08 05:16:31 +0800 CST 2023-10-08 05:16:31 +0800 CST

如何正确使用java.time类?

  • 772

我知道经过 3 年的软件开发后这可能听起来很尴尬,但我正在尝试更好地理解 java.time 类以及如何使用它们来避免错误和不良实践。

在我参与的第一个项目中,使用的 java 版本是 8,但日期值存储在遗留类 java.util.Date 上

两年后,新公司、新项目使用 java 版本 17,但日期值仍然存储在遗留类 java.util.Date 中

当我看到项目上有一个 DateUtils ,其方法如下:

public static String toString_ddMMyyyy_dotted(Date date){
    LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefualt()).toLocalDateTime();
    DateTimeFormatter.ofPattern("dd.MM.yyyy").format(localDateTime);
}

public static Date getBeginOfTheYear(Date date){
    LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefualt()).toLocalDate();
    LocalDate beginOfTheYear = localDate.withMonth(1).withDayOfMonth(1);
    return Date.from(beginOfTheYear.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
}

所以我的问题是,为什么不直接使用具有非常好的 API 的 java.time 类,而不是在需要对日期进行操作时将变量转换为 LocalDate/LocalDateTime ?

我们的想法是尽快重构项目,将 Date/DateTime/Time 值存储到 java.time 类中。我从一个简单的模块开始,它将一些数据保存到 MongoDB 中。我应用了以下字段类型转换:

  • 将日期值转换为 LocalDate
  • 日期和时间值转换为 LocalDateTime

在测试过程中我立即发现一个问题,值为 2023-10-07 12:00:00 的 localDateTime 变量在 MongoDB 上保存为 2023-10-07 10:00:00 只要当数据被取回到java中,该值又回到了2023-10-07 12:00:00,但是这并没有发生,所以这是一个大问题。

首先,这种情况是仅在使用LocalDateTime时发生,还是在使用LocalDate时也会发生?

在实体/文档类上存储日期/日期时间值的最佳实践是什么?我应该使用 ZonedDateTime 而不是 LocalDateTime 吗?

java
  • 1 1 个回答
  • 120 Views

1 个回答

  • Voted
  1. Best Answer
    Basil Bourque
    2023-10-08T10:12:42+08:002023-10-08T10:12:42+08:00

    转换而不是改造

    我们的想法是尽快重构项目,将 Date/DateTime/Time 值存储到 java.time 类中

    根据代码库的大小和复杂性,这可能相当危险。

    更保守的方法是在java.time中进行所有新编程。要与尚未更新到java.time 的旧代码进行互操作,请进行转换。您会在旧类上找到新的转换方法。寻找to…&from…方法。这些转换方法提供了完整的覆盖范围,让您可以来回转换。

    修改代码时要注意的另一件事是:许多/大多数程序员对日期时间处理了解甚少。这个话题出人意料地棘手和复杂,第一次遇到时概念很模糊。在处理编程和数据库的严格性时,我们对日期时间的日常理解实际上对我们不利。所以要小心那些糟糕的代码、有缺陷的代码、错误处理日期时间的代码。每次发现此类错误代码时,您都会遇到麻烦,因为生成的报告可能显示不正确的结果,数据库可能存储无效数据等。

    您可以搜索现有的 Stack Overflow 问题和答案以了解更多信息。以下是一些可以帮助指导您的简短要点。

    瞬间与非瞬间

    将日期值转换为 LocalDate

    日期和时间值转换为 LocalDateTime

    不正确。或者说不完整,我应该说。

    我建议你这样思考……时间追踪有两种:

    • 时刻(确定)
    • 不是一刻(不定)

    片刻

    时刻是时间轴上的特定点。自 UTC 1970 年第一个时刻的纪元参考以来, java.time中的时刻以整秒计数的形式进行跟踪,加上以纳秒计数的小数秒的计数。“in UTC”是“与 UTC 时间子午线的零时-分-秒偏移”的缩写。

    表示时刻的基本类是java.time.Instant。此类的对象表示以 UTC 表示的时刻,且始终以 UTC 表示。此类是jav.time框架的基本构建块。这门课应该是你处理时刻时第一个也是最后一个想到的。用于Instant跟踪时刻,除非您的业务规则特别涉及特定区域或偏移量。并被java.util.Date专门替换为java.time.Instant,两者都代表 UTC 中的时刻,但Instant纳秒分辨率比java.util.Date毫秒分辨率更精细。

    另外两个类跟踪java.time中的时刻:OffsetDateTime& ZonedDateTime。

    OffsetDateTime表示与 UTC 的偏移量不为零时所看到的时刻。偏移量只是比 UTC 早或晚的小时数、分钟数、秒数。因此,巴黎的人们将墙上的时钟设置为比 UTC 早一到两个小时,而东京的人们将时钟设置为比 UTC 早九小时,而加拿大新斯科舍省的人们将时钟设置为比 UTC 晚三到四个小时。

    时区不仅仅是一个偏移量。时区是特定地区人民所使用的偏移量的过去、现在和未来变化的命名历史,由其政治家决定。时区的名称格式为&Continent/Region等。Europe/ParisAsia/Tokyo

    要通过特定区域的挂钟时间查看日期和时间,请使用ZonedDateTime。

    请注意,您可以通过它们的、和方法轻松、干净地在这三个类Instant、 、OffsetDateTime和之间来回移动。它们提供了三种观察同一时刻的方式。通常您在业务逻辑和数据存储和数据交换中使用,同时用于呈现给用户。ZonedDateTimeto…at…with…InstantZonedDateTime

    不是片刻

    另一方面,我们有“没有一刻”的追踪。这些是不定类型。

    造成最混乱的非时刻类是LocalDateTime. 此类表示带有一天中的时间但缺少任何偏移量或时区概念的日期。因此,如果我说明年 2024 年 1 月 23 日中午,LocalDateTime.of( 2024 , Month.JANUARY , 23 , 12 , 0 , 0 , 0 )您无法知道我指的是东京的中午、图卢兹的中午,还是美国俄亥俄州托莱多的中午——三个截然不同的时刻,相隔几个小时。

    如有疑问,请勿使用LocalDateTime。通常,在编写业务应用程序时,我们关心时刻,因此很少使用LocalDateTime.

    当我们在商业应用程序中最常需要时LocalDateTime,最大的例外是预约,例如牙医预约。在那里,我们需要分别LocalDateTime保存 a和 time zone( ZoneId)而不是将它们组合成一个。原因很关键:政治家经常改变时区规则ZonedDateTime,而且他们的行为是不可预测的,而且往往没有任何预警。因此,如果那里的政治家决定采用夏令时 (DST),或决定放弃 DST,或决定全年保持 DST(最迟),那么下午 3 点的牙医预约可能会提前一小时或晚一小时。时尚),或决定不同的偏移量可能会带来商机(例如:冰岛采用零偏移量)或解决政治问题(例如:整个印度的一个单一时区),或决定调整其时钟作为相对于的外交举措邻国,或者必须采用入侵/占领军的抵消措施。所有这些发生的频率比大多数人(包括程序员)意识到的要多得多。此类更改不必要地破坏了简单编写的应用程序。

    其他不定类型包括:

    • LocalDate对于仅日期值,没有日期时间,也没有任何区域/偏移量。
    • LocalTime对于仅时间值,没有任何日期,也没有任何区域/偏移。

    这LocalDate可能很棘手,因为许多人没有意识到,在任何特定时刻,全球各地的日期都会因时区而异。现在,日本东京是“明天”,而加拿大埃德蒙顿则是“昨天”。

    你的代码

    LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefualt()).toLocalDateTime();
    

    该代码有两个问题。

    • 一是使用不当LocalDateTime。java.util.Datevar后面的类代表date偏移量为零的时刻。当转换为LocalDateTime.
    • 另一个问题是使用ZoneId.systemDefualt(). 这意味着结果会根据 JVM 当前的默认时区而有所不同。如上所述,这意味着日期可能会有所不同,而不仅仅是一天中的时间。在两台不同机器上运行的相同代码可能会产生两个不同的日期。这可能是您在应用程序中想要的,或者对于不知情的程序员来说这可能是一个粗鲁的觉醒。

    你的另一段代码也有问题。

    public static Date getBeginOfTheYear(Date date){
        LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefualt()).toLocalDate();
        LocalDate beginOfTheYear = localDate.withMonth(1).withDayOfMonth(1);
        return Date.from(beginOfTheYear.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
    }
    

    首先,请注意,不幸的是,遗留日期时间类中有两个 类:一会儿,假装代表仅日期,但实际上是一个时刻(在一个非常糟糕的类设计决策中)。我会承担你的手段。Datejava.util.Datejava.sql.Datejava.util.Date

    其次,避免使用此类代码的此类调用链。可读性、调试和日志记录都变得更加困难。在进行一系列转换时,请编写简单的短行。在每行上使用注释来证明操作的合理性,并解释您的动机。var并且,出于同样的原因避免使用;进行此类转换时使用显式返回类型。

    是date.toInstant()好的。遇到 a 时java.util.Date,立即转换为 a Instant。

    同样,使用 意味着ZoneId.systemDefualt()代码的结果会因任何系统管理员或更改默认时区的用户的突发奇想而异。这可能是这段代码的作者的意图,但我对此表示怀疑。

    当您不传递任何参数时,该部分beginOfTheYear.atStartOfDay()会生成 a 。LocalDateTime因此,您再次丢弃了有价值的信息(抵消),而没有获得任何回报。

    另一个问题:您的代码甚至无法编译。该java.util.Date.from方法采用 an Instant,而不是LocalDateTime您的调用返回的a beginOfTheYear.atStartOfDay()。

    为了正确获得特定时刻的第一时刻,您几乎肯定会希望决定业务规则的人规定特定的时区。

    ZoneId zTokyo = ZoneId.of( "Asia/Tokyo" ) ;
    

    您确实应该将传入转换java.util.Date为Instant.

    Instant instant = myJavaUtilDate.toInstant() ;
    

    然后应用该区域。

    ZonedDateTime zdt = instant.atZone( zTokyo ) ;
    

    提取年份部分。如上所述,日期可能因时区而异。因此,年份可能会有所不同,具体取决于您在上面的前一行代码中提供的区域!

    Year year = Year.from( zdt ) ;
    

    获取该年的第一天。

    LocalDate firstOfYear = year.atDay( 1 ) ;
    

    让java.time确定该区域当天的第一个时刻。不要假设开始时间是 00:00。某些区域中的某些日期在其他时间开始,例如 01:00。

    ZoneId请注意,在确定一年中的第一个时刻时,我们传递了to的参数atStartOfDay,这与您的代码不同。这里的结果是 aZonedDateTime而不是LocalDateTime代码中的 the 。

    ZonedDateTime zdtFirstMomentOfTheYearInTokyo = firstOfYear.atStartOfDay( zTokyo ) ; 
    

    最后,我们转换为java.util.Date. 在绿地代码中,我们会像瘟疫一样避免此类。但在您现有的代码库中,我们必须进行转换,以便与尚未更新到java.time的旧代码部分进行互操作。

    Instant instantFirstMomentOfTheYearInTokyo = zdtFirstMomentOfTheYearInTokyo.toInstant(); 
    java.util.Date d = java.util.Date.from( instantFirstMomentOfTheYearInTokyo ) ;
    

    MongoDB

    你说:

    在测试过程中我立即发现一个问题,值为 2023-10-07 12:00:00 的 localDateTime 变量在 MongoDB 上保存为 2023-10-07 10:00:00 只要当数据被取回到java中,该值又回到了2023-10-07 12:00:00,但是这并没有发生,所以这是一个大问题。

    那里有太多的东西需要解开,而你提供的细节太少。

    我建议您专门针对该问题发布另一个问题。提供足够的详细信息以及示例代码来进行诊断。

    • 1

相关问题

  • Lock Condition.notify 抛出 java.lang.IllegalMonitorStateException

  • 多对一微服务响应未出现在邮递员中

  • 自定义 SpringBoot Bean 验证

  • Java 套接字是 FIFO 的吗?

  • 为什么不可能/不鼓励在服务器端定义请求超时?

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    使用 <font color="#xxx"> 突出显示 html 中的代码

    • 2 个回答
  • Marko Smith

    为什么在传递 {} 时重载解析更喜欢 std::nullptr_t 而不是类?

    • 1 个回答
  • Marko Smith

    您可以使用花括号初始化列表作为(默认)模板参数吗?

    • 2 个回答
  • Marko Smith

    为什么列表推导式在内部创建一个函数?

    • 1 个回答
  • Marko Smith

    我正在尝试仅使用海龟随机和数学模块来制作吃豆人游戏

    • 1 个回答
  • Marko Smith

    java.lang.NoSuchMethodError: 'void org.openqa.selenium.remote.http.ClientConfig.<init>(java.net.URI, java.time.Duration, java.time.Duratio

    • 3 个回答
  • Marko Smith

    为什么 'char -> int' 是提升,而 'char -> Short' 是转换(但不是提升)?

    • 4 个回答
  • Marko Smith

    为什么库中不调用全局变量的构造函数?

    • 1 个回答
  • Marko Smith

    std::common_reference_with 在元组上的行为不一致。哪个是对的?

    • 1 个回答
  • Marko Smith

    C++17 中 std::byte 只能按位运算?

    • 1 个回答
  • Martin Hope
    fbrereto 为什么在传递 {} 时重载解析更喜欢 std::nullptr_t 而不是类? 2023-12-21 00:31:04 +0800 CST
  • Martin Hope
    比尔盖子 您可以使用花括号初始化列表作为(默认)模板参数吗? 2023-12-17 10:02:06 +0800 CST
  • Martin Hope
    Amir reza Riahi 为什么列表推导式在内部创建一个函数? 2023-11-16 20:53:19 +0800 CST
  • Martin Hope
    Michael A fmt 格式 %H:%M:%S 不带小数 2023-11-11 01:13:05 +0800 CST
  • Martin Hope
    God I Hate Python C++20 的 std::views::filter 未正确过滤视图 2023-08-27 18:40:35 +0800 CST
  • Martin Hope
    LiDa Cute 为什么 'char -> int' 是提升,而 'char -> Short' 是转换(但不是提升)? 2023-08-24 20:46:59 +0800 CST
  • Martin Hope
    jabaa 为什么库中不调用全局变量的构造函数? 2023-08-18 07:15:20 +0800 CST
  • Martin Hope
    Panagiotis Syskakis std::common_reference_with 在元组上的行为不一致。哪个是对的? 2023-08-17 21:24:06 +0800 CST
  • Martin Hope
    Alex Guteniev 为什么编译器在这里错过矢量化? 2023-08-17 18:58:07 +0800 CST
  • Martin Hope
    wimalopaan C++17 中 std::byte 只能按位运算? 2023-08-17 17:13:58 +0800 CST

热门标签

python javascript c++ c# java typescript sql reactjs html

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve