0

Have a MySQL database whose default timezone is the system timezone on Linux which is 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'
)

The content is as follows:

| 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 | 

Inside my Spring JDBC query, I am getting the current event like this:

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;
}

So, the situation is this:

  • If an event is on a particular day, for example, if the comedy event is on 2017-08-24 starting at 7:00 p.m. PST in my database it's in UTC time:

    | 2017-08-25 00:00:00 | 2017-08-25 19:00:00 |

    So, for every event that is starting at 5:00 p.m. PST, or after, this query works.

  • However, let's say that an event starts at 2017-08-15 4:00 p.m. PST (which means it's 11:00 p.m. UTC), this query only works until 4:59 p.m. PST, because at 5:00 p.m PST, the System clock in MySQL becomes midnight (resulting in the next day). The only way to work around this was to manually update that date to the next day 2017-08-16, for this query to work.

Am wondering if there's a better query for what I am trying to do...

Tried this:

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";

Yields in:

Empty set (0.00 sec)

Does anyone know how I can fix this?


Edited after reading Basil's suggestions

Used the ThreeTen-Backport library in my file and tried this):

// 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");

This is the output:

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

So, when trying to use the first moment of tomorrow's date inside a raw SQL call (in order to test it before putting it back inside my Java code):

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

It yields:

Empty set, 1 warning (0.00 sec)

Tried Basil's query like this as well:

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

Yields in:

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

Questions:

  1. Am not a MySQL guru so is "when" a keyword that can be used?

  2. Should I create a column called first_moment_of_tomorrow and inside my event table for each row and do date range check?

The issue with select now() vs. curdate() is that now brings back the actual date with actual seconds and whereas event_date is in a format like this:

2017-08-15 00:00:00 

So, when trying curdate:

select curdate();

Yields this:

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

And when trying now():

select now();

Yields this:

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

Does anyone understand what the issue is? I need to do some type of SQL query against event_schedule.event_date but am somewhat confused now... :(

PacificNW_Lover
  • 4,746
  • 31
  • 90
  • 144

2 Answers2

1

Just store one date-time

Trying to juggle your pair of date-time values will drive you nuts.

Learn to think and work in UTC. Generally, all of your data storage and data exchange should be in UTC, and much of your business logic in UTC as well. Adjust into a time zone only where needed, such as determining what "same day" means by using the user's time zone.

Important: If you schedule events far enough out in the future that it may conflict with a newly-announced government-mandate to change time zones, you have other issues. Generally this range of time is a few or several months, but in some places such as Turkey recently it may only be several weeks. In such future schedules, you would store the date and time without a time zone, then apply a time zone dynamically on queries. I'll ignore this issue here.

Determine points in time before query

Use Java to determine your points in time. Do not call a current time function in SQL for your purposes, as that varies during execution as you have seen.

Use only java.time classes, avoiding the troublesome legacy classes (Date, Calendar, etc.).

The TIMESTAMP type in MySQL works along the lines of the SQL standard type TIMESTAMP WITH TIME ZONE where the incoming values are adjusted into UTC and then stored. So query by UTC.

Get current moment in UTC.

Instant now = Instant.now();

Determine your target range. Apparently you want the rest of the day, with the day defined by the user’s preferred/expected time zone.

Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as PST or EST or IST as they are not true time zones, not standardized, and not even unique(!).

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

Adjust into that time zone from UTC, producing a ZonedDateTime object.

ZonedDateTime nowZdt = now.atZone( z ) ;

From that ZonedDateTime, extract the date-only value, a LocalDate object.

LocalDate ld = nowZdt.toLocalDate() ;

Determine the first moment of the following day for that time zone.

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

Adjust back to UTC from that time zone.

Instant firstMomentOfTomorrow = zdt.toInstant() ;

Make a query along these lines:

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

Retrieve using a JDBC driver compliant with JDBC 4.2 or later.

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

All these issues have been covered many times already on Stack Overflow. Search for much more info, discussion, and example code.


About java.time

The 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.

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

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

Where to obtain the java.time classes?

The 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.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • thanks for the answer, why do I need to return Instant? I am just trying to obtain the event_id... Can you show me using the code I pasted on how I can setup this range for both edge cases? – PacificNW_Lover Oct 24 '17 at 21:50
  • @PacificNW_Lover For your comment’s first question: I am showing how to extract the event's start time via JDBC directly into a java.time type so you can avoid the troublesome legacy `java.sql` types. For your second question: No. You can write that code, it is your job and you'll learn more by doing it yourself. Tip: Practice queries with fake data in an interactive SQL environment rather than in Java. But be sure to set the session's time zone to UTC so you can see the stored values clearly. Tip: Your "edge case" isn’t. Search for "Half-Open" approach to handling date-time ranges. – Basil Bourque Oct 24 '17 at 21:54
  • I am using Java 1.7, eclipse can't see to find the Instant class? – PacificNW_Lover Oct 24 '17 at 21:55
  • See edit, added section at bottom. Look for "ThreeTen-Backport". Well worth the trouble of adding a jar to your project. The legacy date-time classes are a wretched mess. Tip: Don't expect date-time work to be easy, it is tricky, slippery, and counter-intuitive work. Invest some time into study and practice. – Basil Bourque Oct 24 '17 at 21:58
  • Thanks for this ThreeTen-Backport library suggestion, it rocks! However, I am still stuck. Edited my original post with my new findings. – PacificNW_Lover Oct 25 '17 at 01:58
0

Fixed it by loading timezones into MySQL 5 and just changing the query to this:

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'))
PacificNW_Lover
  • 4,746
  • 31
  • 90
  • 144
  • I believe that code will be burdening the database server with time zone conversion on every row’s value, which besides CPU use for conversions also means a sequential scan without benefit of indexes. Furthermore I believe that `now()` function will be called on each row meaning your criteria is changing *during* the query execution. Not good. Again I suggest you determine your time points in Java *before* executing the SQL query, as discussed in [my Answer](https://stackoverflow.com/a/46920487/642706). Much simpler, much faster. – Basil Bourque Oct 29 '17 at 20:48