我正在编写一个脚本,在其中我需要继续检查一个月的各个日期。到目前为止,我可以直接检查一个月,但是我需要一天,因为它一天返回多个值。
代码如下:
SELECT * FROM table
WHERE end_datetime BETWEEN '2015-03-01 00:00:00' and '2015-04-01 00:00:00'
我需要使用Java代码,可以在其中检查各个日期(例如-在“ 2015-03-01 00:00:00”和“ 2015-03-02 00:00:00”之间;在“ 2015-03- 02 00:00:00”和“ 2015-03-03 00:00:00”,依此类推,直到到达最后一个日期,即“ 2015-04-01 00:00:00”。
最佳答案
半开
日期时间工作中的一种常见方法是“半开式”(或半闭式)。这个想法是使开始时刻具有包容性,而使结束时刻具有排他性。因此,一周从星期一到星期一,一个小时从02:00到03:00,一天从2015-03-01 00:00:00
到2015-03-02 00:00:00
。换句话说,一天从一天的第一时刻开始,一直到但不包括第二天的第一时刻。
这种方法避免了各种问题。主要问题是试图确定一天的最后时刻,将无穷小数秒解析为整秒或毫秒或微秒或纳秒。最好说“三月一日是任何日期时间值> = 2015-03-01 00:00:00 AND <2015-03-02 00:00:00”,第一个条件中带有=
,但不是在第二。
这意味着在SQL中不使用BETWEEN
。使用一对比较>=
和<
。
用于获取今年3月1日发生的事件的示例SQL。
SELECT *
FROM event_
WHERE ( when_ >= '2015-03-01 00:00:00' )
AND ( when_ < '2015-03-02 00:00:00' )
;
时区
上面的例子有一个严重的问题。假设我们在UTC时区中表示“今天”。或者,可能会应用SQL会话的当前默认时区并调整值。无论哪种方式,都是混乱的。
最好指定您希望的/期望的时区,而不是隐式地依赖某些默认值。
如果这些日期时间值应该是魁北克蒙特利尔的午夜,那么我们应该这样说。在计算机编程的上下文中,“午夜”一词似乎引起混乱。我更喜欢短语“一天的第一时刻”。
SELECT *
FROM event_
WHERE ( when_ >= '2015-03-01 00:00:00' AT TIME ZONE 'America/Montreal' )
AND ( when_ < '2015-03-02 00:00:00' AT TIME ZONE 'America/Montreal' )
;
月
同样的逻辑适用于一个月。从三月的第一时刻到四月的第一时刻。
SELECT *
FROM event_
WHERE ( when_ >= '2015-03-01 00:00:00' AT TIME ZONE 'America/Montreal' )
AND ( when_ < '2015-04-01 00:00:00' AT TIME ZONE 'America/Montreal' )
;
爪哇
使用Java时,我们可以使用Java 8及更高版本中内置的新java.time package。
ISO 8601
首先,为方便起见,我们将您的输入字符串转换为符合ISO 8601定义的标准格式。解析和生成日期时间值的字符串表示形式时,java.time框架默认使用ISO 8601。因此,我们无需定义显式格式化程序。
String inputStart = "2015-03-01 00:00:00"; // Month of March.
String inputStop = "2015-04-01 00:00:00";
String inputStartStandard = inputStart.replace( " " , "T" );
String inputStopStandard = inputStop.replace( " " , "T" );
LocalDateTime
我假设您的输入字符串代表某个时区中的第一天。我将以魁北克省为例。实际的输入字符串缺少任何此类信息,因此首先我们将其解析为没有任何时区的“无家可归” LocalDateTime值。
java.time.LocalDateTime localStart = LocalDateTime.parse( inputStartStandard );
java.time.LocalDateTime localStop = LocalDateTime.parse( inputStopStandard );
接下来,我们将其调整为魁北克时区。
java.time.ZonedId zoneId = ZoneId.of( "America/Montreal" );
java.time.ZonedDateTime zdtStart = ZonedDateTime.of( localStart , zoneId );
java.time.ZonedDateTime zdtStop = ZonedDateTime.of( localStop , zoneId );
JDBC 4.2
将来的某一天,我们将能够在这一点上继续执行SQL。但是今天,大多数JDBC驱动程序尚未更新为JDBC 4.2,因此它们无法直接处理新的java.time类型。
转换为旧的java.sql.Timestamp
在等待将来的JDBC驱动程序时,我们可以使用添加到旧java.sql.Timestamp类中的新方法来方便地进行转换。转换需要
Instant
对象,该对象是时间轴上没有时区信息的点(基本上是UTC)。我们的ZonedDateTime
提供了我们所需的toInstant
方法。java.sql.Timestamp tsStart = new java.sql.Timestamp( zdtStart.toInstant() );
java.sql.Timestamp tsStop = new java.sql.Timestamp( zdtStop.toInstant() );
没有数据丢失
请注意,所有这些提及的类(java.sql.Timestamp,java.time。ZonedDateTime和java.time.Instant)的秒分数均为nanosecond分辨率。因此,它们之间没有数据丢失。转换为旧的java.util.Date/.Calendar或第三方Joda-Time库仅支持millisecond分辨率,因此可能涉及数据丢失。
请注意您数据库的日期时间分辨率。许多数据库,例如Postgres使用microsecond。当使用微秒保存到数据库时,在java.time中以纳秒为单位生成的任何值都将被截断。例如,
2015-01-02 03:04:05.123456789
更改为2015-01-02 03:04:05.123456
,仅是小数点后六位数字。PreparedStatement
构建SQL语句的文本。尽管在这种情况下不是必需的,但最好养成using
PreparedStatement
习惯,以避免SQL Injection安全风险。StringBuilder sql = new StringBuilder();
sql.append( "SELECT * " ).append( "\n" );
sql.append( "FROM event_ " ).append( "\n" );
sql.append( "WHERE ( when_ >= ? ) " ).append( "\n" );
sql.append( "AND ( when_rc_ < ? ) " ).append( "\n" );
sql.append( "ORDER BY when_ ASC " ).append( "\n" );
sql.append( ";" )append( "\n" );
执行SQL
最后,执行SQL。将开始和停止时间戳记对象传递给PreparedStatement。
try ( Connection conn = DatabaseHelper.instance().connectionInAutoCommitMode() ;
PreparedStatement pstmt = conn.prepareStatement( sql.toString() ); ) {
pstmt.setTimestamp( 1 , tsStart );
pstmt.setTimestamp( 2 , tsStop );
try ( ResultSet rs = pstmt.executeQuery(); ) {
while ( rs.next() ) {
// …
}
}
} catch ( SQLException ex ) {
logger.error( "SQLException during: " + message + "\n" + ex );
} catch ( Exception ex ) {
logger.error( "Exception during: " + message + "\n" + ex );
}
所有这些示例代码都是未经运行的手写未经测试。
一次一天
问题尚不清楚。如果您希望一次有一天,请按adding a day循环到起点。
ZonedDateTime zdtNextDay = zdtStart.plusDays( 1 );
您也可以通过调用
plusMonths( 1 )
类似地获得下个月的开始。ZonedDateTime zdtNextMonth = zdtStart.plusMonths( 1 );
您可以通过调用
isBefore
在循环中进行测试,以查看“第二天”是否仍在“下一个月”之前。if( zdtNextDay.isBefore( zdtNextMonth ) ) {
…
}
完整月份示例
这是当月逐日循环的完整示例。
我们必须得到这一天的第一刻。要在java.time中做到这一点,我们必须从仅本地日期(
LocalDate
)开始,其中“本地”表示“任何地区”,没有任何特定的时区。然后,我们通过调用atStartOfDay
产生ZonedDateTime
对象来分配时区和时间。您可能会认为一天从00:00:00
开始,但并非总是如此。某些时区,例如Palestine在午夜开始夏令时,因此一天从01:00
开始。java.time之前的旧日期时间类(例如java.sql.Timestamp)实现了
toString
方法,这些方法隐式地应用了JVM的current default time zone。尽管这样做是出于善意,但事实证明这种方法令人困惑。我们在下面的代码中看到了这一点。此代码显示,在偏移量为-07:00
的美国西海岸时区运行时,魁北克的偏移量为-04:00
的午夜时间相差三个小时。因此,据报道魁北克的午夜是前一天晚上9点(21:00)在西海岸。java.time.ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdtThisMonthStart = LocalDate.now( zoneId ).withDayOfMonth( 1 ).atStartOfDay( zoneId );
java.time.ZonedDateTime zdtNextMonthStart = zdtThisMonthStart.plusMonths( 1 );
java.time.ZonedDateTime zdtDayStart = zdtThisMonthStart; // Initialize variable to be modified in loop.
System.out.println( "JVM’s current default time zone applied implicitly by java.sql.Timestamp’s 'toString' method: " + java.util.TimeZone.getDefault( ) );
while ( zdtDayStart.isBefore( zdtNextMonthStart ) ) {
java.time.ZonedDateTime zdtNextDayStart = zdtDayStart.plusDays( 1 );
java.sql.Timestamp tsStart = java.sql.Timestamp.from( zdtDayStart.toInstant( ) );
java.sql.Timestamp tsStop = java.sql.Timestamp.from( zdtNextDayStart.toInstant( ) );
System.out.print( "In java.time, Day is: [" + zdtDayStart + "/" + zdtNextDayStart + "]. " );
System.out.println( "In java.sql.Timestamp, Day is: [" + tsStart + "/" + tsStop + "]" );
//
// … Do SQL work, such as the try-catch-catch seen above in this Answer.
//
// Prep for next loop. Increment to next day.
zdtDayStart = zdtDayStart.plusDays( 1 );
}
运行时。
JVM’s current default time zone applied implicitly by java.sql.Timestamp’s 'toString' method: sun.util.calendar.ZoneInfo[id="US/Pacific",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=US/Pacific,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]]
In java.time, Day is: [2015-08-01T00:00-04:00[America/Montreal]/2015-08-02T00:00-04:00[America/Montreal]]. In java.sql.Timestamp, Day is: [2015-07-31 21:00:00.0/2015-08-01 21:00:00.0]
…
In java.time, Day is: [2015-08-31T00:00-04:00[America/Montreal]/2015-09-01T00:00-04:00[America/Montreal]]. In java.sql.Timestamp, Day is: [2015-08-30 21:00:00.0/2015-08-31 21:00:00.0]
关于java - 如何使用一个月的单个日期作为条件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32143720/