tl;博士
LocalDateTime.of( // A `LocalDateTime` represents a set of *potential* moments along a range of about 26-27 hours. Not an actual moment, not a point on the timeline.
LocalDate.systemDefault() , // Get the JVM’s current default time zone. Can change at any moment *during* runtime. When crucial, always confirm with the user.
LocalTime.parse( "14:57" ) // Parse a string in standard ISO 8601 format as a time-of-day without regard for time zone or offset-from-UTC.
) // Returns a `LocalDateTime` object.
.atZone( // Determine an actual moment, a point on the timeline.
ZoneId( "Africa/Tunis" ) // Specify a time zone as `Continent/Region`, never as 3-4 letter pseudo-zones such as `PST`, `CST`, or `IST`.
) // Returns a `ZonedDateTime` object.
.toInstant() // Extracts a `Instant` object from the `ZonedDateTime` object, always in UTC by default.
详情
目前有点混乱
日期时间处理是非常令人困惑的工作。
提示:
- 忘记您自己的时区。考虑UTC,而不是您自己的狭隘时区。
- 了解真实时刻(时间轴上的点)与时间轴上不的日期时间近似值之间的差异,通常称为“本地”值。
- 阅读 Stack Overflow 或 Internet 上有关日期时间的其他地方时要小心。您会遇到糟糕的建议和许多错误的解决方案。
有所有的选择;时间戳、日期、当地时间等
永远不要使用与 Java 的最早版本捆绑在一起的麻烦的旧旧日期时间类。切勿使用java.sql.Timestamp、java.util.Date、java.util.Calendar 等。
➡ 仅使用 java.time package 中的类。
java.time 类是行业领先的日期时间框架。非常精心设计和深思熟虑,从成功的 Joda-Time 项目中吸取了教训。
有人知道如何做到这一点,或者可以提供一些提示/最佳做法吗?
你可能会后悔你问了。继续阅读。
目前我得到了一个带有 Date 数据类型的类“Flight”;出发和到达日期时间。
所以定义一个Flight 类。
在现实生活中,航班发生在足够远的未来,以至于我们冒着政客改变时区定义的风险。最常见的这些更改是采用/删除/更改Daylight Saving Time (DST)。但是出于各种原因,会定期进行任意更改。我们可以辩论这种变化的智慧/理智,但事实是它们发生了。它们经常发生,因为政治家似乎奇怪地倾向于在世界各地的许多国家进行这些改变。几乎所有人都在没有预先警告的情况下这样做,有时只是几周。甚至根本没有警告,如North Korea did this week。
我不了解航空公司的实际运作方式,但从查看航空公司时刻表和各种读数来看,他们似乎试图使用出发地的分区时间来维持他们的时刻表。因此,如果航班计划在早上 6 点从洛杉矶国际机场起飞,他们会在 DST 转换的前一天、当天和后一天保留该航班时刻表。如果这确实是一般意图,那意味着在一个 DST 切换上坐下来消磨时间,同时试图在另一个 DST 切换上节省一个小时。显然,Amtrak adopts this practice 它的火车。让我们继续这种方法。
使用这种“虚构的”日程安排方法意味着我们无法确定未来早上 6 点的确切时间。所以我们需要在不应用时区的情况下记录我们对那个日期和那个时间的渴望。但是我们必须记录所需的时区,以便我们知道在什么情况下我们可以在以后确定确切的时刻,当时间足够接近时,我们不必担心时区变化。
所以我们使用LocalDate 和LocalTime 类型,因为它们故意缺少任何time zone(Continent/Region 格式的名称)或offset-from-UTC(小时-分钟-秒数)的概念。
ZoneId 类代表一个时区。
我在名称中使用了Unzoned 这个词来提醒我们这些值不代表时间轴上的实际时刻。 “本地”这个词往往会使初学者感到困惑。
public class Flight {
private String flightNumber;
private LocalDate departureDateUnzoned;
private LocalTime departureTimeUnzoned;
private ZoneId departureZoneId ;
}
至于到达,存储该航班的预期时间跨度,而不是到达日期时间。您可以计算到达,因此无需存储它。 Duration 类跟踪小时数、分钟数、秒数和小数秒数。
为了计算到达,让我们使用 LocalDateTime 类返回一个值,该类简单地将 LocalDate 与 LocalTime 组合在一起。我们可以使用这种类型在我们的类定义中创建一个departureUnzoned 成员变量。我选择了单独的LocalDate 和LocalTime 作为构建块,这样你就可以理解这些部分了。如此多的程序员使用他们的直觉而不是文档来假设LocalDateTime 表示某个地方的特定时刻,而实际上它的意思恰恰相反。 (你会在 Stack Overflow 上发现很多不正确的答案,建议 LocalDateTime 实际上应该使用 Instant 或 ZonedDateTime。)
让我们添加一个方法来计算到达时间。
public class Flight {
private String flightNumber;
private LocalDate departureDateUnzoned;
private LocalTime departureTimeUnzoned;
private ZoneId departureZoneId;
private Duration duration;
public LocalDateTime arrivalDateTimeUnzoned () {
LocalDateTime departureUnzoned = LocalDateTime.of( this.departureDateUnzoned , this.departureTimeUnzoned );
LocalDateTime ldt = departureUnzoned.plus( this.duration );
return ldt;
}
}
但是这个返回的LocalDateTime 没有考虑时区。通常,航空公司和火车会向客户报告调整到该地区时区的预计到达时间。所以我们需要一个到达时区。我们可以在计算到达时使用该区域,从而产生ZonedDateTime。 ZonedDateTime 是一个特定的时刻,它是时间线上的一个点,不像LocalDateTime。但请记住,如果我们将航班安排到未来,如果我们的代码在政客重新定义时区后运行,计算得到的 ZonedDateTime 将会改变。
public class Flight {
private String flightNumber;
private LocalDate departureDateUnzoned;
private LocalTime departureTimeUnzoned;
private ZoneId departureZoneId;
private Duration duration;
private ZoneId arrivalZoneId;
public ZonedDateTime arrivalDateTimeZoned () {
ZonedDateTime departureZoned = ZonedDateTime.of( this.departureDateUnzoned , this.departureTimeUnzoned , this.departureZoneId );
ZonedDateTime zdt = departureZoned.plus( this.duration );
return zdt;
}
}
回到问题中关于自动确定日期的部分。这需要一个时区。对于任何给定的时刻,日期在全球范围内都会有所不同。考虑一下。法国巴黎午夜过后几分钟是新的一天,而魁北克蒙特利尔仍然是“昨天”。
我们可以询问JVM当前的默认时区。
ZoneId userZoneId = ZoneId.systemDefault() ;
但在关键时,您必须与用户确认。
ZoneId userZoneId = ZoneId.of( "America/Montreal" ) ;
所以现在我们可以添加您要求的构造函数,传递时间(LocalTime,并使用 JVM 的当前默认值猜测时区。
但我们仍然需要所有其他部分。所以默认日期并不能为我们节省太多。
public class Flight {
private String flightNumber;
private LocalDate departureDateUnzoned;
private LocalTime departureTimeUnzoned;
private ZoneId departureZoneId;
private Duration duration;
private ZoneId arrivalZoneId;
// Constructor
public Flight ( String flightNumber , LocalTime departureTimeUnzoned , ZoneId departureZoneId , Duration duration , ZoneId arrivalZoneId ) {
this.flightNumber = flightNumber;
this.departureTimeUnzoned = departureTimeUnzoned;
this.departureZoneId = departureZoneId;
this.duration = duration;
this.arrivalZoneId = arrivalZoneId;
// Determine today’s date using JVM’s current default time zone. Not advisable in many business scenarios, but specified by our Question at hand.
ZoneId z = ZoneId.systemDefault();
LocalDate today = LocalDate.now( z );
this.departureDateUnzoned = today;
}
public ZonedDateTime arrivalDateTimeZoned () {
ZonedDateTime departureZoned = ZonedDateTime.of( this.departureDateUnzoned , this.departureTimeUnzoned , this.departureZoneId );
ZonedDateTime zdt = departureZoned.plus( this.duration );
return zdt;
}
}
让我们添加一个toString 报告方法。
我们将日期时间值表示为标准ISO 8601 格式的字符串。 java.time 类在解析/生成字符串时使用这些标准格式。末尾的Z 发音为Zulu,意思是UTC。
虽然航空公司和火车会向他们所在地区时区的客户报告日期时间,但我们可以假设他们在内部仅使用 UTC。 Instant 类专门表示 UTC 中的值。所以我们的toString 从ZonedDateTime 对象中提取Instant 对象。
我们添加了一个main 方法进行演示。这是完整的课程,包括import 等。
package com.basilbourque.example;
import java.time.*;
public class Flight {
private String flightNumber;
private LocalDate departureDateUnzoned;
private LocalTime departureTimeUnzoned;
private ZoneId departureZoneId;
private Duration duration;
private ZoneId arrivalZoneId;
// Constructor
public Flight ( String flightNumber , LocalTime departureTimeUnzoned , ZoneId departureZoneId , Duration duration , ZoneId arrivalZoneId ) {
this.flightNumber = flightNumber;
this.departureTimeUnzoned = departureTimeUnzoned;
this.departureZoneId = departureZoneId;
this.duration = duration;
this.arrivalZoneId = arrivalZoneId;
// Determine today’s date using JVM’s current default time zone. Not advisable in many business scenarios, but specified by our Question at hand.
ZoneId z = ZoneId.systemDefault();
LocalDate today = LocalDate.now( z );
this.departureDateUnzoned = today;
}
public ZonedDateTime arrivalDateTimeZoned () {
ZonedDateTime departureZoned = ZonedDateTime.of( this.departureDateUnzoned , this.departureTimeUnzoned , this.departureZoneId );
ZonedDateTime zdt = departureZoned.plus( this.duration );
return zdt;
}
@Override
public String toString () {
ZonedDateTime departureZoned = ZonedDateTime.of( this.departureDateUnzoned , this.departureTimeUnzoned , this.departureZoneId );
String flightInUtc = departureZoned.toInstant().toString() + "/" + this.arrivalDateTimeZoned().toInstant().toString();
return "Flight{ " +
"flightNumber='" + this.flightNumber + '\'' +
" | departureDateUnzoned=" + this.departureDateUnzoned +
" | departureTimeUnzoned=" + this.departureTimeUnzoned +
" | departureZoneId=" + this.departureZoneId +
" | departureZoned=" + departureZoned +
" | duration=" + this.duration +
" | arrivalZoneId=" + this.arrivalZoneId +
" | calculatedArrival=" + this.arrivalDateTimeZoned() +
" | flightInUtc=" + flightInUtc +
" }";
}
public static void main ( String[] args ) {
LocalTime lt = LocalTime.of( 6 , 0 ); // 6 AM.
Flight f = new Flight( "A472" , lt , ZoneId.of( "America/Los_Angeles" ) , Duration.parse( "PT6H37M" ) , ZoneId.of( "America/Montreal" ) );
String output = f.toString();
System.out.println( output );
}
}
运行时。
航班{ flightNumber='A472' |出发日期未分区=2018-05-06 |出发时间未分区=06:00 |离境区ID=美国/洛杉矶|出发分区=2018-05-06T06:00-07:00[美国/洛杉矶] |持续时间=PT6H37M |到达ZoneId=美国/蒙特利尔|计算到达=2018-05-06T12:37-07:00[美国/洛杉矶] | flightInUtc=2018-05-06T13:00:00Z/2018-05-06T19:37:00Z }
要从控制台使用此功能,请向用户询问 24 小时制的时间。解析输入字符串。
String input = "14:56" ; // 24-hour clock.
LocalTime lt = LocalTime.parse( input ) ;
对于现实世界的工作来说,这还远远不够。但希望它可以成为一个教育示例。
关于java.time
java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.Date、Calendar 和 SimpleDateFormat。
Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。
要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310。
您可以直接与您的数据库交换 java.time 对象。使用符合JDBC 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.* 类。
从哪里获得 java.time 类?
ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如Interval、YearWeek、YearQuarter 和more。