我们有一个具有两个Postgres列,prc_sta_dt和prc_end_dt一个TIMESTAMP WITHOUT TIME ZONE
表。我们检查看是否java.util.Date
的开始和结束日期介于英寸
下面是一些Java代码,这是简化的,但得到跨越点。
// This format expects a String such as 2018-12-03T10:00:00
// With a date and a time, but no time zone
String timestamp = "2018-12-03T10:00:00";
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
Date searchDate = formatter.parse(timestamp);
// Here's the Postgres query
String query = "select promotion_cd from promotions " +
"where prc_sta_dt <= :srch_dt and prc_end_dt >= :srch_dt";
Map<String, Object> map = new HashMap<String, Object>();
map.put("srch_dt", searchDate);
List<Promotion> promotions = jdbcTemplate.query(query, map, promotionMapper);
在我们的Postgres的表,我们有开始在对2018年12月3日上午9点,并在当天下午3点结束的促销活动。在我们的数据库中的prc_sta_dt和prc_end_dt colums这些行是2018-12-03 09:00:00.0
和2018-12-03 15:00:00.0
问:当JDBC / Postgres的接受我们的searchDate
,并将其与这些时间戳,不会接纳上午10点的给定的搜索日期(2018-12-03T10:00:00),还是会处理这个时间的时区下是的服务器正在运行,然后将其转换成UTC?
例如,如果服务器在芝加哥运行,然后将它解释上午10 CST上午10点,然后将其转换成UTC下午4点做数据库比较之前?如果是的话,我们的运气了!
我怀疑这会发生,但我只是想确认,所以没有惊喜。
Date
is not a date一个java.util.Date
对象表示UTC,在时间轴上的特定时间点的时刻。因此,它代表了一个日期,时间的日的组合,以及偏移从-UTC的零(UTC为本身)。在这个可怕的类中的很多设计糟糕的选择是,混淆了无数的Java程序员的误导性名称。
TIMESTAMP WITHOUT TIME ZONE
如果你关心的时刻,那么你的数据库列不应该是类型TIMESTAMP WITHOUT TIME ZONE
的。该数据类型代表的日期和时间的,一天没有时区的任何概念或偏移从-UTC。这样,通过定义,该类型不能表示的时刻,是不是在时间线上的一个点。当你的意思是一个日期与时间任何地点或任何地方这种类型的,才应使用。
例子:
TIMESTAMP WITH TIME ZONE
当跟踪特定某个特定时刻,在时间轴上的一个点,使用类型TIMESTAMP WITH TIME ZONE
的列。 Postgres里,这样的值被存储在UTC。与输入提交的任何时区或偏移信息被用来调整到UTC,然后信息被丢弃的区域/偏移。
请注意:有些工具可能在检索UTC值,从而歪曲了什么实际存储后注入时区的用心良苦,但不幸的防功能。
TIMESTAMP WITHOUT TIME ZONE
至于你的类型TIMESTAMP WITHOUT TIME ZONE
列片刻值进行比较,这样做会一般没有意义。
但如果你是清醒和深入的认识日期时间处理,使得这种比较在你的业务逻辑是明智的,让我们开拓上。
您正在使用糟糕的,可怕的,可怕的日期时间类,被取代年前由java.time类(Date
,SimpleDateFormat
等)。请你帮个忙:停止使用旧日期时间类。仅使用java.time。
如果给一个时刻作为java.util.Date
,使用添加到老班的新方法,将。特别地,java.util.Date
由Instant
取代。
Instant instant = myJavaUtilDate.toInstant() ; // Convert from legacy class to modern class.
指定要调整UTC的Instant
时刻进行比较的时区。例如,如果你的数据库是由人谁不明白正确的日期,时间处理建造,使用TIMESTAMP WITHOUT TIME ZONE
列来存储来自魁北克的挂钟时间被人拿去日期与时间值一直,然后使用时区America/Montreal
。
在proper time zone name的格式指定一个continent/region
,如America/Montreal
,Africa/Casablanca
,或Pacific/Auckland
。切勿使用2-4个字母的缩写,如EST
或IST
,因为它们不是真正的时区,不规范,甚至不是唯一的(!)。
ZoneId z = ZoneId.of( "America/Montreal" ) ;
应用该区域我们Instant
获得ZonedDateTime
对象。
ZonedDateTime zdt = instant.atZone( z ) ;
我们得到的ZonedDateTime
对象表示相同的时刻作为对象Instant
,相同点在时间轴上,但具有不同的挂钟时间观看。
要捶了square-peg into a round-hole,我们认为ZonedDateTime
对象转换为LocalDateTime
对象,从而剥离了时区信息,只留下一个日期与时间的日值。
LocalDateTime ldt = zdt.toLocalDateTime() ;
where prc_sta_dt <= :srch_dt and prc_end_dt >= :srch_dt
该逻辑是容易出现故障。一般来说,在日期时间的最佳实践定义一个跨度的时间使用半开,在刚开始是包容性的,而结局是独占时处理。
因此,使用这样的:
WHERE instant >= start_col AND instant < stop_col ;
对于PreparedStatement
,我们将有占位符。
WHERE ? >= start_col AND ? < stop_col ;
在Java方面,如JDBC 4.2,我们可以直接通过getObject
和setObject
方法交换与数据库java.time对象。
你也许可以通过根据您的JDBC驱动程序的Instant
。对于Instant
支持不被JDBC规范所要求的。因此,尝试它,或阅读您的驱动程序的文档。
myPreparedStatement.setObject( 1 , instant ) ;
myPreparedStatement.setObject( 2 , instant ) ;
如果不支持Instant
,从Instant
转换设置为UTC的OffsetDateTime
。对于OffsetDateTime
支持由规范所要求的。
myPreparedStatement.setObject( 1 , instant.atOffset( ZoneOffset.UTC ) ) ;
myPreparedStatement.setObject( 2 , instant.atOffset( ZoneOffset.UTC ) ) ;
恢复。
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;
例如,如果服务器在芝加哥运行,然后将它解释上午10 CST上午10点,然后将其转换成UTC下午4点做数据库比较之前?
程序员不应该依赖于时区(或区域设置的方式)当前被设置为在主机操作系统或JVM的默认。两者都在你的控制。而且两者可以同时运行过程中随时改变!
总是通过传递可选参数,以各种日期,时间的方法指定时区。使这些可选的是,在我看来java.time一个设计缺陷,程序员往往忽略了时区的问题,在他们自己的危险。但是,这是一个令人惊讶的有用和优美的框架,很少有设计缺陷之一。
在我们的代码注意上面我们指定的期望/预期的时区。我们的主机操作系统,我们Postgres数据库连接,我们的JVM的当前默认时区不会改变我们的代码的行为。
如果你想在当前时刻使用任何这些:
Instant.now()
总是UTC,顾名思义。OffsetDateTime.now( someZoneOffset )
如被看见在一个特定的偏移,从-UTC的挂钟时间当前时刻。ZonedDateTime.now( someZoneId )
如看到由居住在一个特定区域内的人使用的挂钟时间当前时刻。如果您使用的是Java 7,那么你有内置的无java.time类。幸运的是,JSR 310和java.time的发明者,斯蒂芬Colebourne对,也带动了ThreeTen-Backport项目生产提供了大部分的java.time功能到Java 6和7的库。
这是在一个单一的java文件一个完整的示例应用程序显示在Java 7中使用回港与H2 Database Engine。
在Java 7,JDBC 4.2是不可用的,所以我们不能直接使用现代类。我们退回到使用java.sql.Timestamp
这实际上代表了UTC了一下,但H2存储到TIMESTAMP WITHOUT TIME ZONE
服用日期和时间的日作为-IS(使用UTC的挂钟时间),而忽略了UTC列方面。我没有Postgres的试过,但我希望你会看到相同的行为。
package com.basilbourque.example;
import java.sql.*;
import org.threeten.bp.*;
public class App {
static final public String databaseConnectionString = "jdbc:h2:mem:localdatetime_example;DB_CLOSE_DELAY=-1"; // The `DB_CLOSE_DELAY=-1` keeps the in-memory database around for multiple connections.
public static void main ( String[] args ) {
App app = new App();
app.doIt();
}
private void doIt () {
System.out.println( "Bonjour tout le monde!" );
// java.sql.Timestamp ts = DateTimeUtils.toSqlTimestamp( ZonedDateTime.of( 2018 , 1 , 23 , 12 , 30 , 0 , 0 , ZoneId.of( "America/Montreal" ) ).toLocalDateTime() );
// System.out.println( ts );
this.makeDatabase();
java.util.Date d = new java.util.Date(); // Capture the current moment using terrible old date-time class that is now legacy, supplanted years ago by the class `java.time.Instant`.
this.fetchRowsContainingMoment( d );
}
private void makeDatabase () {
try {
Class.forName( "org.h2.Driver" );
} catch ( ClassNotFoundException e ) {
e.printStackTrace();
}
try (
Connection conn = DriverManager.getConnection( databaseConnectionString ) ; // The `mem` means “In-Memory”, as in “Not persisted to disk”, good for a demo.
Statement stmt = conn.createStatement() ;
) {
String sql = "CREATE TABLE event_ ( \n" +
" pkey_ IDENTITY NOT NULL PRIMARY KEY , \n" +
" name_ VARCHAR NOT NULL , \n" +
" start_ TIMESTAMP WITHOUT TIME ZONE NOT NULL , \n" +
" stop_ TIMESTAMP WITHOUT TIME ZONE NOT NULL \n" +
");";
stmt.execute( sql );
// Insert row.
sql = "INSERT INTO event_ ( name_ , start_ , stop_ ) VALUES ( ? , ? , ? ) ;";
try (
PreparedStatement preparedStatement = conn.prepareStatement( sql ) ;
) {
preparedStatement.setObject( 1 , "Alpha" );
// We have to “fake it until we make it”, using a `java.sql.Timestamp` with its value in UTC while pretending it is not in a zone or offset.
// The legacy date-time classes lack a way to represent a date with time-of-day without any time zone or offset-from-UTC.
// The legacy classes have no counterpart to `TIMESTAMP WITHOUT TIME ZONE` in SQL, and have no counterpart to `java.time.LocalDateTime` in Java.
preparedStatement.setObject( 2 , DateTimeUtils.toSqlTimestamp( ZonedDateTime.of( 2018 , 1 , 23 , 12 , 30 , 0 , 0 , ZoneId.of( "America/Montreal" ) ).toLocalDateTime() ) );
preparedStatement.setObject( 3 , DateTimeUtils.toSqlTimestamp( ZonedDateTime.of( 2018 , 2 , 23 , 12 , 30 , 0 , 0 , ZoneId.of( "America/Montreal" ) ).toLocalDateTime() ) );
preparedStatement.executeUpdate();
preparedStatement.setString( 1 , "Beta" );
preparedStatement.setObject( 2 , DateTimeUtils.toSqlTimestamp( ZonedDateTime.of( 2018 , 4 , 23 , 14 , 30 , 0 , 0 , ZoneId.of( "America/Montreal" ) ).toLocalDateTime() ) );
preparedStatement.setObject( 3 , DateTimeUtils.toSqlTimestamp( ZonedDateTime.of( 2018 , 5 , 23 , 14 , 30 , 0 , 0 , ZoneId.of( "America/Montreal" ) ).toLocalDateTime() ) );
preparedStatement.executeUpdate();
preparedStatement.setString( 1 , "Gamma" );
preparedStatement.setObject( 2 , DateTimeUtils.toSqlTimestamp( ZonedDateTime.of( 2018 , 11 , 23 , 16 , 30 , 0 , 0 , ZoneId.of( "America/Montreal" ) ).toLocalDateTime() ) );
preparedStatement.setObject( 3 , DateTimeUtils.toSqlTimestamp( ZonedDateTime.of( 2018 , 12 , 23 , 16 , 30 , 0 , 0 , ZoneId.of( "America/Montreal" ) ).toLocalDateTime() ) );
preparedStatement.executeUpdate();
}
} catch ( SQLException e ) {
e.printStackTrace();
}
}
private void fetchRowsContainingMoment ( java.util.Date moment ) {
// Immediately convert the legacy class `java.util.Date` to a modern `java.time.Instant`.
Instant instant = DateTimeUtils.toInstant( moment );
System.out.println( "instant.toString(): " + instant );
String sql = "SELECT * FROM event_ WHERE ? >= start_ AND ? < stop_ ORDER BY start_ ;";
try (
Connection conn = DriverManager.getConnection( databaseConnectionString ) ;
PreparedStatement pstmt = conn.prepareStatement( sql ) ;
) {
java.sql.Timestamp ts = DateTimeUtils.toSqlTimestamp( instant );
pstmt.setTimestamp( 1 , ts );
pstmt.setTimestamp( 2 , ts );
try ( ResultSet rs = pstmt.executeQuery() ; ) {
while ( rs.next() ) {
//Retrieve by column name
Integer pkey = rs.getInt( "pkey_" );
String name = rs.getString( "name_" );
java.sql.Timestamp start = rs.getTimestamp( "start_" );
java.sql.Timestamp stop = rs.getTimestamp( "stop_" );
// Instantiate a `Course` object for this data.
System.out.println( "Event pkey: " + pkey + " | name: " + name + " | start: " + start + " | stop: " + stop );
}
}
} catch ( SQLException e ) {
e.printStackTrace();
}
}
}
当运行。
instant.toString():2018-12-04T05:06:02.573Z
事件PKEY:3 |名称:伽玛|开始:2018年11月23日16:30:00.0 |停止:2018年12月23日16:30:00.0
这里是相同的例子,在概念上,但在Java 8或更高版本,我们可以使用内置java.time类没有ThreeTen-反向移植库。
package com.basilbourque.example;
import java.sql.*;
import java.time.*;
public class App {
static final public String databaseConnectionString = "jdbc:h2:mem:localdatetime_example;DB_CLOSE_DELAY=-1"; // The `DB_CLOSE_DELAY=-1` keeps the in-memory database around for multiple connections.
public static void main ( String[] args ) {
App app = new App();
app.doIt();
}
private void doIt ( ) {
System.out.println( "Bonjour tout le monde!" );
this.makeDatabase();
java.util.Date d = new java.util.Date(); // Capture the current moment using terrible old date-time class that is now legacy, supplanted years ago by the class `java.time.Instant`.
this.fetchRowsContainingMoment( d );
}
private void makeDatabase ( ) {
try {
Class.forName( "org.h2.Driver" );
} catch ( ClassNotFoundException e ) {
e.printStackTrace();
}
try (
Connection conn = DriverManager.getConnection( databaseConnectionString ) ; // The `mem` means “In-Memory”, as in “Not persisted to disk”, good for a demo.
Statement stmt = conn.createStatement() ;
) {
String sql = "CREATE TABLE event_ ( \n" +
" pkey_ IDENTITY NOT NULL PRIMARY KEY , \n" +
" name_ VARCHAR NOT NULL , \n" +
" start_ TIMESTAMP WITHOUT TIME ZONE NOT NULL , \n" +
" stop_ TIMESTAMP WITHOUT TIME ZONE NOT NULL \n" +
");";
stmt.execute( sql );
// Insert row.
sql = "INSERT INTO event_ ( name_ , start_ , stop_ ) VALUES ( ? , ? , ? ) ;";
try (
PreparedStatement preparedStatement = conn.prepareStatement( sql ) ;
) {
preparedStatement.setObject( 1 , "Alpha" );
// We have to “fake it until we make it”, using a `java.sql.Timestamp` with its value in UTC while pretending it is not in a zone or offset.
// The legacy date-time classes lack a way to represent a date with time-of-day without any time zone or offset-from-UTC.
// The legacy classes have no counterpart to `TIMESTAMP WITHOUT TIME ZONE` in SQL, and have no counterpart to `java.time.LocalDateTime` in Java.
preparedStatement.setObject( 2 , ZonedDateTime.of( 2018 , 1 , 23 , 12 , 30 , 0 , 0 , ZoneId.of( "America/Montreal" ) ).toLocalDateTime() );
;
preparedStatement.setObject( 3 , ZonedDateTime.of( 2018 , 2 , 23 , 12 , 30 , 0 , 0 , ZoneId.of( "America/Montreal" ) ).toLocalDateTime() );
preparedStatement.executeUpdate();
preparedStatement.setString( 1 , "Beta" );
preparedStatement.setObject( 2 , ZonedDateTime.of( 2018 , 4 , 23 , 14 , 30 , 0 , 0 , ZoneId.of( "America/Montreal" ) ).toLocalDateTime() );
preparedStatement.setObject( 3 , ZonedDateTime.of( 2018 , 5 , 23 , 14 , 30 , 0 , 0 , ZoneId.of( "America/Montreal" ) ).toLocalDateTime() );
preparedStatement.executeUpdate();
preparedStatement.setString( 1 , "Gamma" );
preparedStatement.setObject( 2 , ZonedDateTime.of( 2018 , 11 , 23 , 16 , 30 , 0 , 0 , ZoneId.of( "America/Montreal" ) ).toLocalDateTime() );
preparedStatement.setObject( 3 , ZonedDateTime.of( 2018 , 12 , 23 , 16 , 30 , 0 , 0 , ZoneId.of( "America/Montreal" ) ).toLocalDateTime() );
preparedStatement.executeUpdate();
}
} catch ( SQLException e ) {
e.printStackTrace();
}
}
private void fetchRowsContainingMoment ( java.util.Date moment ) {
// Immediately convert the legacy class `java.util.Date` to a modern `java.time.Instant`.
Instant instant = moment.toInstant();
System.out.println( "instant.toString(): " + instant );
String sql = "SELECT * FROM event_ WHERE ? >= start_ AND ? < stop_ ORDER BY start_ ;";
try (
Connection conn = DriverManager.getConnection( databaseConnectionString ) ;
PreparedStatement pstmt = conn.prepareStatement( sql ) ;
) {
pstmt.setObject( 1 , instant );
pstmt.setObject( 2 , instant );
try ( ResultSet rs = pstmt.executeQuery() ; ) {
while ( rs.next() ) {
//Retrieve by column name
Integer pkey = rs.getInt( "pkey_" );
String name = rs.getString( "name_" );
Instant start = rs.getObject( "start_" , OffsetDateTime.class ).toInstant();
Instant stop = rs.getObject( "stop_" , OffsetDateTime.class ).toInstant();
// Instantiate a `Course` object for this data.
System.out.println( "Event pkey: " + pkey + " | name: " + name + " | start: " + start + " | stop: " + stop );
}
}
} catch ( SQLException e ) {
e.printStackTrace();
}
}
}
当运行。
instant.toString():2018-12-04T05:10:54.635Z
事件PKEY:3 |名称:伽玛|开始:2018-11-24T00:30:00Z |停车:2018-12-24T00:30:00Z
该java.time框架是建立在Java 8和更高版本。这些类取代的麻烦老legacy日期时间类,如java.util.Date
,Calendar
,与SimpleDateFormat
。
该项目Joda-Time,现在在maintenance mode,建议迁移到java.time类。
要了解更多信息,请参见Oracle Tutorial。和搜索堆栈溢出了很多例子和解释。规格是JSR 310。
您可以直接与数据库交换java.time对象。使用符合JDBC driver或更高版本的JDBC 4.2。无需串,没有必要java.sql.*
类。
从哪里获取java.time类?
该项目ThreeTen-Extra与java.time其他类延伸。该项目是为将来可能增加的java.time试验场。您可以在这里找到一些有用的类,如Interval
,YearWeek
,YearQuarter
和more。