java - 如何使用一个月的单个日期作为条件

标签 java sql

我正在编写一个脚本,在其中我需要继续检查一个月的各个日期。到目前为止,我可以直接检查一个月,但是我需要一天,因为它一天返回多个值。

代码如下:

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:002015-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/

相关文章:

java - 如何禁用 "this page is tracking your location"

java - Cukespace Hook 之前/之后的场景?

MySQL 查询给出错误

mysql - 需要查询相隔7天内的所有记录

mysql - 使用自动生成列的值进行后续查询

java - 过滤涉及安全约束的请求

java - 写入问题——丢失原始数据

java - 如何设置优先鼠标监听器

php - Mysql LEFT Join 大数据表

php - 显示基于另一行值的金额总和