简体   繁体   中英

Java Time Zones

I'm using Java FX form and controller. It is connected to a MySQL database as well. I am on a screen to input data, and one of the fields is "start" and "end" times for a specified date. In the database, there is a start timestamp, and end timestamp.

Problem is, this application is set to run in a variety of time zones. It needs validation. So, I was thinking - maybe I could populate combo boxes in 1 hour increments for start and end times based on the end-users time zone.

The business is open from 8a-5p EST

So what I was thinking is I could grab the time zone to figure out what 8a is in the end-users time zone, and figure out what 5p is in the end-users time zone, and write code to populate the combo boxes with this time information in one hour increments. Therefore it would be bypassing the validation.

If I don't do this, I have to populate combo boxes with a very wide time range and validate the time falls within 8a-5p EST. I am also totally stupid when it comes to understanding the ZonedDateTime, LocalDateTime, and how to pass this information around. A brief explanation would be much appreciated or examples of how I can do this without losing my mind. When I push or pull data from the database this happens on the fly, but not so much inside the program. thanks!

Date-time handling is surprisingly tricky.

Booking appointments has been handled many times on Stack Overflow, so I'll be brief.

The Instant, OffsetDateTime, and ZonedDateTime classes all represent a moment, a specific point on the time line. For example, when logging, use Instant .

The LocalDateTime class does not represent a moment. Holding only a date and a time of day, this class lacks the context of a time zone or offset. For example, “noon on the 23rd of January this year” does not tell us if you mean noon in Tokyo Japan or noon in Toledo Ohio US, two very different moments several hours apart.

Booking appointments generally focuses on a particular time-of-day rather than a moment. Politicians around the world have shown a penchant for changing the rules of the time zones under their jurisdiction. They do so with little or no forewarning. So we must record an appointment in a database as two columns, the date with time when appointment begins goes in a column of type TIMESTAMP WITHOUT TIME ZONE , a column of type text keeps the name of the intended time zone, and a third column tracks the duration of the appointment. If the database does not have a type for span-of-time, then use a textual type to store the ISO 8601 format for durations .

You seem to have the additional complication of wanting to provide a user interface that displays the range of possible appointments by the user's time zone rather than the vendor's.

So you need to record the appointment in those two columns using the vendor's zone, but adjust display to the user's zone. The use's time zone will be used only for display, not storage.

You said:

The business is open from 8a-5p EST

LocalTime open = LocalTime.of( 8 , 0 ) ;
LocalTime close = LocalTime.of( 17 , 0 ) ;

There is no such time zone as EST . Those 2-4 letter abbreviations are non-standard vague indicators of an intended time zone along with whether or not Daylight Saving Time (DST) is in effect. Never use these pseudo-zones in your work. User real time zone names in format of Continent/Region .

ZoneId zoneOfBusiness = ZoneId.of( "America/New_York" ) ;

You said:

1 hour increments for start and end times

List< LocalTime > hoursOfBusiness = new ArrayList<>() ;
LocalTime lt = open ; 
while( lt.isBefore( close ) ) 
{
    hoursOfBusiness.add( lt ) ;
    // Set up next loop.
    lt = lt.plusHours( 1 ) ;
}

[08:00, 09:00, 10:00, 11:00, 12:00, 13:00, 14:00, 15:00, 16:00]

To get moments, specify a date. Keep in mind that a date in ambiguous. For any given moment, the date varies around the globe by time zone.

LocalDate ld = LocalDate.of( 2022 , Month.JANUARY , 23 ) ;

Determine moments for each of those hours.

LocalDate ld = LocalDate.of( 2022 , Month.JANUARY , 23 ) ;
ArrayList< ZonedDateTime > hoursOfBusinessOnDate = new ArrayList<>() ;
for( LocalTime localTime : hoursOfBusiness )
{
    ZonedDateTime zdt = ZonedDateTime.of( ld , localTime , zoneOfBusiness ) ;
    hoursOfBusinessOnDate.add( zdt ) ;
}

[2022-01-23T08:00-05:00[America/New_York], 2022-01-23T09:00-05:00[America/New_York], 2022-01-23T10:00-05:00[America/New_York], 2022-01-23T11:00-05:00[America/New_York], 2022-01-23T12:00-05:00[America/New_York], 2022-01-23T13:00-05:00[America/New_York], 2022-01-23T14:00-05:00[America/New_York], 2022-01-23T15:00-05:00[America/New_York], 2022-01-23T16:00-05:00[America/New_York]]

ZoneId zoneOfUser = ZoneId.of( "America/Los_Angeles" ) ;
ArrayList< ZonedDateTime > hoursOfUserOnDate = new ArrayList<>() ;
for( ZonedDateTime zdtBusiness : hoursOfBusinessOnDate )
{
    ZonedDateTime zdtUser = zdtBusiness.withZoneSameInstant( zoneOfUser ) ;
    hoursOfUserOnDate.add( zdtUser ) ;
}

[2022-01-23T05:00-08:00[America/Los_Angeles], 2022-01-23T06:00-08:00[America/Los_Angeles], 2022-01-23T07:00-08:00[America/Los_Angeles], 2022-01-23T08:00-08:00[America/Los_Angeles], 2022-01-23T09:00-08:00[America/Los_Angeles], 2022-01-23T10:00-08:00[America/Los_Angeles], 2022-01-23T11:00-08:00[America/Los_Angeles], 2022-01-23T12:00-08:00[America/Los_Angeles], 2022-01-23T13:00-08:00[America/Los_Angeles]]

Present that list to the user, after automatically localizing. Let's suppose our user is from Québec.

Locale locale = Locale.CANADA_FRENCH ;
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.MEDIUM ).withLocale( locale ) ;
List< String > choices = new ArrayList<>() ;
for( ZonedDateTime zdt : hoursOfUserOnDate ) 
{
    String choice = zdt.format( f ) ;
    choices.add( choice ) ;
}

choices: [23 janv. 2022 05 h 00 min 00 s, 23 janv. 2022 06 h 00 min 00 s, 23 janv. 2022 07 h 00 min 00 s, 23 janv. 2022 08 h 00 min 00 s, 23 janv. 2022 09 h 00 min 00 s, 23 janv. 2022 10 h 00 min 00 s, 23 janv. 2022 11 h 00 min 00 s, 23 janv. 2022 12 h 00 min 00 s, 23 janv. 2022 13 h 00 min 00 s]

When the user chooses one, convert back to the business zone.

ZonedDateTime userChoice = hoursOfUserOnDate.get( 1 ) ; // Get second element, simulating user's choice.
ZonedDateTime userChoiceForBusiness = userChoice.withZoneSameInstant( zoneOfBusiness ) ;

userChoice: 2022-01-23T06:00-08:00[America/Los_Angeles]

userChoiceForBusiness: 2022-01-23T09:00-05:00[America/New_York]

Extract the date and time only, omitting the time zone.

LocalDateTime appointmentStart = userChoiceForBusiness.toLocalDateTime() ;

appointmentStart: 2022-01-23T09:00

In database, record the start, the intended time zone, and the duration.

myPreparedStatement.setObject( … , appointmentStart ) ;
myPreparedStatement.setString( … , zoneOfBusiness.toString() ) ;
myPreparedStatement.setString( … , Duration.ofHours( 1 ).toString() ) ;

Retrieve from database.

LocalDateTime appointmentStart = myResultSet.getObject( … , LocalDateTime.class ) ;
ZoneId zoneOfBusiness = ZoneId.of( myResultSet.getString( … ) ) ;
Duration duration = Duration.parse( myResultSet.getString( … ) ) ;

When building a schedule, you will need to determine a moment. Never store this moment , as it is not really your appointment. It represents the moment of your appointment as it seems now given the current set of time zone rules.

ZonedDateTime begin = appointmentStart.atZone( zoneOfBusiness ) ;
ZonedDateTime end = begin.plus( duration ) ;

begin: 2022-01-23T09:00-05:00[America/New_York]

end: 2022-01-23T10:00-05:00[America/New_York]

To adjust to UTC, extract an Instant .

Instant beginUtc = begin.toInstant() ;

beginUtc: 2022-01-23T14:00:00Z

See most of that code run live at IdeOne.com .


Be sure to keep all the tzdata data files up-to-date. Your server's host operating system has one. Your JVM has one. And if your database is sophisticated, such as Postgres , you'll find a tzdata there too.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM