Postgresql似乎转换了PreparedStatement的timestamp参数

时间:2016-06-30 12:48:08

标签: java postgresql jdbc

Postgresql似乎转换了我使用setTimestamp设置的PreparedStatement的timestamp参数。

[我想做什么]

我想查询今天的数据。 (2016-06-30 00:00:00~2016-06-30 23:59:59)

但是,当我从DB获得结果时,它是2016-06-29 15:00:00到2016-06-30 14:59:59的数据。 (9小时差距)

我当前的时区:GMT + 9(KST)

数据库时区:UTC(GMT + 0)(在表中,UTC时间存储为更新时间。我检查过。)

我估计这9小时差距。当我将UTC时间戳参数传递给postgresql时,它从我的时间戳参数中减去了9个小时。我想知道为什么postgresql会这样做,以及我如何防止这种情况。

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd 00:00:00");
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd 23:59:59");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
sdf2.setTimeZone(TimeZone.getTimeZone("UTC"));
Timestamp startTime = Timestamp.valueOf(sdf.format(new Date(System.currentTimeMillis())));
Timestamp endTime = Timestamp.valueOf(sdf2.format(new Date(System.currentTimeMillis())));

PreparedStatement pstmt = conn.prepareStatement( 
 "SELECT * FROM some_table WHERE update_time BETWEEN ? AND ? "
);
pstmt.setTimestamp(1, startTime);
pstmt.setTimestamp(2, endTime);
ResultSet rs = pstmt.executeQuery();

[表结构]

CREATE TABLE some_table
(
mem_no      bigserial
,data   char(2)
,update_time    timestamp with time zone DEFAULT current_timestamp
,CONSTRAINT pk_some_table PRIMARY KEY (mem_no)
);

[有点奇怪]

使用调试工具,我检查了pstmt值。奇怪的是+09:00:00被添加到我的参数中。

pstmt => SELECT * FROM some_table WHERE update_time BETWEEN 2016-06-30 00:00:00 +09:00:00和2016-06-30 23:59:59 +09:00:00

DB:postgresql 9.3

2 个答案:

答案 0 :(得分:0)

我解决了这个^^

[我的回答] (1)WHERE子句(2)字符串而不是时间戳

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd 00:00:00");
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd 23:59:59");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
sdf2.setTimeZone(TimeZone.getTimeZone("UTC"));
String startTime = sdf.format(new Date(System.currentTimeMillis()));
String endTime = sdf2.format(new Date(System.currentTimeMillis()));

PreparedStatement pstmt = conn.prepareStatement( 
 "SELECT * FROM some_table " + 
 "WHERE update_time " + 
  "BETWEEN (CAST(TO_TIMESTAMP(?,'YYYY-MM-DD HH24:MI:SS') AS TIMESTAMP) AT TIME ZONE 'UTC') " + 
    "AND (CAST(TO_TIMESTAMP(?,'YYYY-MM-DD HH24:MI:SS') AS TIMESTAMP) AT TIME ZONE 'UTC')");
pstmt.setString(1, startTime);
pstmt.setString(2, endTime);
ResultSet rs = pstmt.executeQuery();

显然,时区偏移+09会自动附加。所以我决定给String参数而不是时间戳。 (JDBC或Postgresql不会更改字符串值)然后我测试了一些像这样的情况。

<强> [测试]

PreparedStatement pstmt = conn.prepareStatement( 
 "SELECT TO_TIMESTAMP(?,'YYYY-MM-DD HH24:MI:SS') test1 " +
   "CAST(TO_TIMESTAMP(?,'YYYY-MM-DD HH24:MI:SS') AS TIMESTAMP) test2 " +
   "(CAST(TO_TIMESTAMP(?,'YYYY-MM-DD HH24:MI:SS') AS TIMESTAMP) AT TIME ZONE 'UTC') test3 " +
 "FROM some_table " + 
 "WHERE update_time " + 
  "BETWEEN TO_TIMESTAMP(?,'YYYY-MM-DD HH24:MI:SS') " +  => In test, this is not important
    "AND TO_TIMESTAMP(?,'YYYY-MM-DD HH24:MI:SS') ");  => In test, this is not important
pstmt.setString(1, startTime);
pstmt.setString(2, startTime);
pstmt.setString(3, startTime);
pstmt.setString(4, startTime);
pstmt.setString(5, endTime);
ResultSet rs = pstmt.executeQuery();

System.out.println(rs.getString("test1") + "/" +rs.getString("test2") + "/" + rs.getString("test3") );

<强> [结果]

当前时间:2016-07-01 15:17:40 + 00(UTC)

startTime:“2016-07-01 00:00:00”=&gt;串

test1 : 2016-07-01 00:00:00+09  => cause subtraction ( 2017-06-30 15:00:00)
test2 : 2016-07-01 00:00:00
test3 : 2016-07-01 09:00:00+09  => Wow ^^

[test3]是我想要的。当我更改上面的where子句时,我可以获得 2016-07-01 00:00:00~2016-07-01 23:59:59

的数据

答案 1 :(得分:0)

tl; dr

要查找在特定日期发生记录时刻的所有行,请使用以下SQL:
"SELECT * FROM tbl WHERE when !< ? AND when < ? ; " ;,该方法使用“半开”方法来计算时间跨度。

myPreparedStatement
.setObject( 
    1 , 
    LocalDate                   // Represent a date-only value, without a time-of-day and without a time zone.
    .parse( "2016-06-30" )      // Returns a `LocalDate` object.
    .atStartOfDay()             // Returns a `OffsetDateTime` object. JDBC 4.2 and later requires support for this class.
) ;

myPreparedStatement
.setObject( 
    2 , 
    LocalDate
    .parse( "2016-06-30" )
    .plusDays( 1 )              // Add a day, to get first moment of the following day.
    .atStartOfDay()
) ;

详细信息

您的代码有多个问题。有关更多讨论,请参见其他答案,例如我的this one。在这里我会简短介绍,因为已经讨论了很多次了。

  • 仅使用 java.time 类,绝不使用在采用JSR 310时所取代的旧式日期时间类。
  • 使用半开方法定义时间范围,其中开始时间为包含,结束时间为排他
  • 要清楚是要跟踪时刻,时间轴上的特定点还是带有日期的模糊日期,但缺少时区或与UTC偏移的上下文。
  • 如果要跟踪时刻,则列必须的数据类型为TIMESTAMP WITH TIME ZONE,而不是WITHOUT
  • 对于任何给定时刻,日期(以及一天中的时间)在全球各地均按时区变化。
  • 某些时区中的某些日期不是在00:00开始。始终让 java.time 确定一天的开始。

显然,您希望所有具有日期时间的行都发生在2016-06-30日期。

LocalDate startDate = LocalDate.parse( "2016-06-30" ) ;
LocalDate stopDate = startDate.plusDays( 1 ) ;

指定您要用来解释日期并获取一天中第一时刻的时区。

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;

获取开始日期和结束日期的第一时间。

ZonedDateTime zdtStart = startDate.atStartOfDay( z ) ;
ZonedDateTime zdtStop = startDate.atStopOfDay( z ) ;

在JDBC 4.2中不需要支持ZonedDateTime类。该类可能与您的JDBC驱动程序一起起作用,也可能不起作用。如果没有,请使用OffsetDateTime类,JDBC 4.2需要支持该类。

OffsetDateTime start = zdtStart.toOffsetDateTime() ;
OffsetDateTime stop = zdtStop.toOffsetDateTime() ;

使用占位符?编写SQL。我建议始终包括语句终止符;。绝对不要将BETWEEN用于日期时间工作,因为它是全封闭而不是半开放的。

String sql = "SELECT * FROM tbl WHERE when !< ? AND when < ? ; " ;  // Half-Open span-of-time where beginning is inclusive while the ending is exclusive.

传递占位符对象。

myPreparedStatement.setObject( 1 , start ) ;
myPreparedStatement.setObject( 2 , stop ) ;

检索结果。

OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;

该结果将以UTC表示。您可以调整到所需的时区。时间轴上的相同时刻,相同点,但挂钟时间不同。

ZoneId z = ZoneId.of( "Asia/Tokyo" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;

始终指定区域/偏移量

  

我的本​​地时区:GMT + 9(KST)

     

DB时区:UTC(GMT + 0)(在表中,UTC时间存储为更新时间。我检查过。)

编写Java应用程序,以使您永远不必依赖JVM或数据库服务器的当前默认时区。

请注意,上面使用对象的代码对时区没有任何惊讶,并且明确指定了期望/期望的时区或偏移量。令人惊奇的是中间件和实用程序被认为是将区域或偏移量注入到检索到的值中。 Postgres本身总是以UTC的形式存储和检索TIMESTAMP WITH TIME ZONE值(零小时-分钟-秒的偏移量)。

半开放

  

我想查询今天的数据。 (2016-06-30 00:00:00〜2016-06-30 23:59:59)

不,您错过了当天的最后一秒。

相反,请使用“半开”方法定义时间跨度,其中开始是包括在内的,而结尾是排除的。这使时间跨度整齐地彼此邻接,而没有间隙和重叠。

因此,一周从星期一开始,一直持续到(但不包括)下一个星期一。午餐时段从中午12:00开始,一直持续到(但不包括)时钟13:00为止。一天从一天的第一时刻开始(顺便说,不是总是00:00!),直到(但不包括)第二天的第一时刻。研究此答案中显示的SQL和Java,以了解其工作原理。


enter image description here