如果超过特定时区,如何捕获基于 UTC 的事件?

How to Capture Events Based in UTC if they Surpass a Specific Timezone?

有一个 MySQL 数据库,其默认时区是 Linux 的系统时区,即 UTC。

CREATE TABLE `event_schedule` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `description` varchar(30) DEFAULT NULL,
   `event_id` varchar(30) NOT NULL DEFAULT,
   `event_type_id` varchar(30) NOT NULL DEFAULT,
   `event_date` datetime DEFAULT NULL,
   `event_date_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
)

内容如下:

| 1 | karoake | aab3223  | 2  |  2017-08-15 00:00:00 | 2017-08-15 16:00:00 |
| 2 | comedy  | cce8465  | 3  |  2017-08-25 00:00:00 | 2017-08-25 19:00:00 | 

在我的 Spring JDBC 查询中,我得到这样的当前事件:

private String getEventIdFromEventDay(String eventTypeId) {
    String eventId = "";
    SqlRowSet resultSet = null;

    try {
        resultSet = jdbcTemplate.queryForRowSet(
                "select * from event_schedule where event_date >= CURDATE() and event_type_id=? order by event_date limit 1;",
                new Object[] { eventTypeId });
        if (resultSet != null && resultSet.next()) {
                eventId = resultSet.getString("event_id");
            }
        }
    } 
    catch (Exception e) {
        logger.error("Exception om getEventIdFromEventDay: " + e);
    }
    return eventId;
}

所以,情况是这样的:

我想知道是否有更好的查询来解决我正在尝试做的事情...

试过这个:

select * from event_schedule where CONVERT_TZ(event_date,'+00:00','-07:00') >= CONVERT_TZ(CURDATE(),'+00:00','-07:00') and CONVERT_TZ(TIMESTAMP(event_date, '23:59:59'),'+00:00','-07:00') <= CONVERT_TZ(TIMESTAMP(CURDATE(), '23:59:59'),'+00:00','-07:00') and event_type_id = "2";

产量:

Empty set (0.00 sec)

有谁知道我该如何解决这个问题?


阅读 Basil 的建议后编辑

在我的文件中使用了 ThreeTen-Backport 库并尝试了这个):

// Get current time in UTC
Instant now = Instant.now();

// Get time zone in PST
ZoneId pstTimeZone = ZoneId.of("America/Los_Angeles");

// Adjust into that time zone from UTC, producing a ZonedDateTime object.
ZonedDateTime adjustedZDTimeZoneFromUtc =now.atZone(pstTimeZone);

// Extract Date Only Value
LocalDate localDate = adjustedZDTimeZoneFromUtc.toLocalDate();

// Determine the first moment of the following day for that timezone
ZonedDateTime zdt = localDate.plusDays(1).atStartOfDay(pstTimeZone);

// Adjust back to UTC from that Time Zone
Instant firstMomentOfTomorrow = zdt.toInstant();
System.out.println("\n\n\t\tFirst moment of tomorrow: " + firstMomentOfTomorrow + "\n\n");

这是输出:

First moment of tomorrow: 2017-10-25T07:00:00Z

因此,当尝试在原始 SQL 调用中使用明天日期的第一时刻时(为了在将其放回我的 Java 代码之前对其进行测试):

SELECT * FROM event_schedule WHERE event_date >= now() AND event_date <= "2017-10-25T07:00:00Z";

它产生:

Empty set, 1 warning (0.00 sec)

也像这样尝试了 Basil 的查询:

SELECT * FROM event_schedule WHERE when >= now() AND when < "2017-10-25T07:00:00Z";

产量:

ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'when >= now() AND when < "2017-10-25T07:00:00Z"' at line 1

问题:

  1. 我不是 MySQL 大师 所以 "when" 是可以使用的关键字吗?

  2. 我是否应该为每一行在我的事件 table 中创建一个名为 first_moment_of_tomorrow 的列并进行日期范围检查?

select now()curdate() 的问题在于,现在用实际秒数返回实际日期,而 event_date 的格式如下:

2017-08-15 00:00:00 

所以,当尝试 curdate 时:

select curdate();

产生这个:

+------------+
| curdate()  |
+------------+
| 2017-10-24 |
+------------+
1 row in set (0.01 sec)

现在尝试时():

select now();

产生这个:

+---------------------+
| now()               |
+---------------------+
| 2017-10-24 18:27:37 |
+---------------------+
1 row in set (0.00 sec)

有人知道问题出在哪里吗?我需要对 event_schedule.event_date 进行某种类型的 SQL 查询,但现在有点困惑...... :(

只存储一个日期时间

试图兼顾你的一对日期时间值会让你抓狂。

学习在 UTC 中思考和工作。通常,您所有的数据存储和数据交换都应该使用 UTC,您的大部分业务逻辑也应该使用 UTC。仅在需要时调整到时区,例如通过使用用户的时区来确定 "same day" 的含义。

重要提示:如果您将事件安排在未来足够长的时间以至于它可能与新宣布的政府更改时区的授权相冲突,那么您还有其他问题。一般这个时间范围是几个或几个月,但最近在土耳其等一些地方可能只有几个星期。在这样的未来计划中,您将存储日期和时间 而没有 时区,然后在查询中动态应用时区。我会在这里忽略这个问题。

查询前确定时间点

使用Java确定您的时间点。请勿出于您的目的在 SQL 中调用当前时间函数,因为如您所见,在 执行期间 会发生变化。

仅使用 java.time 类,避免麻烦的遗留问题 类(DateCalendar 等)。

MySQL 中的 TIMESTAMP 类型按照 SQL 标准类型 TIMESTAMP WITH TIME ZONE 的方式工作,其中输入值被调整为 UTC,然后存储。 所以按UTC查询。

以 UTC 格式获取当前时刻。

Instant now = Instant.now();

确定你的目标范围。显然你想要当天剩下的时间,这一天由用户的 preferred/expected 时区定义。

指定 proper time zone name in the format of continent/region, such as America/Montreal, Africa/CasablancaPacific/Auckland。切勿使用 PSTESTIST 等 3-4 个字母的缩写,因为它们 不是 真实时区,未标准化,也不是甚至是独一无二的(!)。

ZoneId z = ZoneId.of( "America/Los_Angeles" ) ;

从 UTC 调整到该时区,生成一个 ZonedDateTime 对象。

ZonedDateTime nowZdt = now.atZone( z ) ;

从那个 ZonedDateTime 中提取仅日期值,一个 LocalDate 对象。

LocalDate ld = nowZdt.toLocalDate() ;

确定该时区的第二天的第一个时刻。

ZonedDateTime zdt = ld.plusDays( 1 ).atStartOfDay( z ) ;

从那个时区调整回 UTC。

Instant firstMomentOfTomorrow = zdt.toInstant() ;

按照这些行进行查询:

String sql = "SELECT * FROM tbl WHERE when >= ? AND when < ? ; " ; // Do not use `BETWEEN`.
…
myPreparedStatement.setObject( 1 , now ) ;
myPreparedStatement.setObject( 2 , firstMomentOfTomorrow ) ;

使用符合 JDBC 4.2 或更高版本的 JDBC 驱动程序进行检索。

Instant instant = myResultSet.getObject( … , Instant.class ) ;

所有这些问题已经在 Stack Overflow 上多次讨论过。搜索更多信息、讨论和示例代码。


关于java.time

java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

Joda-Time project, now in maintenance mode, advises migration to the java.time 类.

要了解更多信息,请参阅 Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310

在哪里获取java.time类?

ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

通过将时区加载到 MySQL 5 并将查询更改为此来修复它:

select * from event_schedule where event_id=? and bye_week=0 and date(convert_tz(event_date_time,'UTC','US/Pacific'))=date(convert_tz(now(), @@system_time_zone, 'US/Pacific'))