tl;博士
LocalDate.now( ZoneId.of( "Africa/Tunis" ) )
.atStartOfDay( ZoneId.of( "Africa/Tunis" ) )
.toEpochSecond()
…
LocalDate.now( ZoneId.of( "Africa/Tunis" ) )
.plusDays( 1 )
.atStartOfDay( ZoneId.of( "Africa/Tunis" ) )
.toEpochSecond()
…
"SELECT * FROM orders WHERE placed >= ? AND placed < ? ; "
…
myPreparedStatement.setObject( 1 , start )
myPreparedStatement.setObject( 2 , stop )
java.time
您正在使用麻烦的旧日期时间类,这些类现在是遗留的,被现代 java.time 类所取代。
显然,您将时刻存储在数据库中某个整数类型的列中。那是不幸的。相反,您应该使用诸如 SQL 标准 TIMESTAMP WITH TIME ZONE 之类的类型的列。但是,对于这个答案,我们将使用我们所拥有的。
如果您的记录表示以毫秒为分辨率的时刻,并且您想要一整天的所有记录,那么我们需要一个时间范围。我们必须有一个开始时刻和一个停止时刻。您的查询只有一个日期时间标准,而它应该有一对。
LocalDate 类表示没有时间和时区的仅日期值。
时区对于确定日期至关重要。对于任何给定的时刻,日期在全球范围内因区域而异。例如,Paris France 中午夜后几分钟是新的一天,而 Montréal Québec 中仍然是“昨天”。
如果没有指定时区,JVM 会隐式应用其当前的默认时区。该默认值可能随时更改,因此您的结果可能会有所不同。最好将您想要/预期的时区明确指定为参数。
以continent/region 的格式指定proper time zone name,例如America/Montreal、Africa/Casablanca 或Pacific/Auckland。切勿使用 3-4 个字母的缩写,例如 EST 或 IST,因为它们不是真正的时区,没有标准化,甚至不是唯一的 (!)。
ZoneId z = ZoneId.of( "America/Montreal" ) ;
LocalDate today = LocalDate.now( z ) ;
如果你想使用 JVM 当前的默认时区,请求它并作为参数传递。如果省略,则隐式应用 JVM 的当前默认值。最好是明确的,因为默认值可能会在任何时候在运行时被 JVM 中任何应用程序的任何线程中的任何代码更改。
ZoneId z = ZoneId.systemDefault() ; // Get JVM’s current default time zone.
或者指定一个日期。您可以通过数字设置月份,1 月至 12 月的编号为 1-12。
LocalDate ld = LocalDate.of( 1986 , 2 , 23 ) ; // Years use sane direct numbering (1986 means year 1986). Months use sane numbering, 1-12 for January-December.
或者,更好的是,使用预定义的Month 枚举对象,一年中的每个月一个。提示:在整个代码库中使用这些 Month 对象,而不是仅仅使用整数,以使您的代码更具自记录性、确保有效值并提供 type-safety。
LocalDate ld = LocalDate.of( 1986 , Month.FEBRUARY , 23 ) ;
有了LocalDate,接下来我们需要将其转换为一对时刻,即一天的开始和结束。不要假设一天从一天中的 00:00:00 开始。由于夏令时 (DST) 等异常情况,一天可能会从另一个时间开始,例如 01:00:00。所以让 java.time 确定一天中的第一个时刻。我们将ZoneId 参数传递给LocalDate::atStartOfDay 以查找任何此类异常。结果是ZonedDateTime。
ZonedDateTime zdtStart = ld.atStartOfDay( z ) ;
通常,定义时间跨度的最佳方法是半开放方法,其中开始是包含,而结尾是排除。因此,一天从第一刻开始,一直持续到但不包括第二天的第一刻。
ZonedDateTime zdtStop = ld.plusDays( 1 ).atStartOfDay( z ) ; // Determine the following date, and ask for the first moment of that day.
我们一整天的查询不能使用 SQL 命令BETWEEN。该命令是完全关闭的([])(包括开头和结尾),而我们想要半开([))。我们使用一对标准 >= 和 <。
您的专栏名称不正确。避免各种数据库保留的千字中的任何一个。我们在这个例子中使用placed。
您的代码应该使用 ? 占位符来指定我们的时刻。
String sql = "SELECT * FROM orders WHERE placed >= ? AND placed < ? ; " ;
但是我们手头有ZonedDateTime 对象,而您的数据库显然正在存储如上所述的整数。如果您已经正确定义了列,我们可以简单地使用任何支持 JDBC 4.2 或更高版本的 JDBC 驱动程序传递 ZonedDateTime 对象。
但相反,我们需要在整秒内获得一个从 epoch 开始的计数。我假设您的纪元参考日期是 1970 年 UTC 的第一刻。请注意可能的数据丢失,因为ZonedDateTime 类能够达到纳秒级分辨率。以下行中的任何小数秒都将被截断。
long start = zdtStart().toEpochSecond() ; // Transform to a count of whole seconds since 1970-01-01T00:00:00Z.
long stop = zdtStop().toEpochSecond() ;
现在我们准备将这些整数传递给上面定义的 SQL 代码。
PreparedStatement ps = con.prepareStatement( sql );
ps.setObject( 1 , start ) ;
ps.setObject( 2 , stop ) ;
ResultSet rs = ps.executeQuery();
当您从 ResultSet 检索整数值时,您可以转换为 Instant 对象(始终采用 UTC),或转换为 ZonedDateTime 对象(具有指定的时区)。
Instant instant = rs.getObject( … , Instant.class ) ;
ZonedDateTime zdt = instant.atZone( z ) ;
关于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 类?
ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如Interval、YearWeek、YearQuarter 和more。