Java Best Practice for Date Manipulation/Storage for Geographically Diverse Users

What is the Best Practice for manipulating and storing dates in Java?

Joda is the way to go. Why ?

  1. it has a much more powerful and intuitive interface than the standard Date/Time API
  2. there are no threading issues with date/time formatting. java.text.SimpleDateFormat is not thread-safe (not a lot of people know this!)

At some stage the Java Date/Time API is going to be superseded (by JSR-310). I believe this is going to be based upon the work done by those behind Joda, and as such you'll be learning an API that will influence a new standard Java API.

Incorrect date parsing with SimpleDateFormat

Try HH:mm:ss for the time part. hh is used for "Hour in am/pm (1-12)", see the javadoc.

How to store a date in ET with offset when the java code executes in UTC

First, you need to be aware that a Date object doesn't have a time zone at all. It's just an instant in time. So even when you've parsed the value correctly, it'll represent the right instant in time, but you may need to convert it back to Eastern Time later on.

Second, you need to be aware that storing values like this introduces ambiguity - if you store a value of (say) 11/03/2019 01:30, that local time occurs twice - once before the daylight saving transition and once afterwards. If you're always storing times in the past, you should at least consider storing UTC instead - although that's not always the right answer, particularly not if you're storing future date/time values.

For the parsing part, you just need to set the time zone in the Calendar used by SimpleDateFormat. For example:

SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("America/New_York");
Date beginDate = sdf.parse("03/29/2019 23:23:03");

Finally, I'd strongly advise you to start migrating your code to use java.time if at all possible. It's a much nicer API than the java.util.Date etc API.

What is best practice for date data field type in GAE-GWT?

  1. java.util.Date is a supported type
  2. How you show this to the user is a more generic java problem. All dates are stored in UTC. I suggest you take a look at joda-time for handling timezones and dates.
  3. Yes, sorting of Date objects is supported in queries etc.
  4. Filtering is also supported, eg, date > yesterday && date < today. Don't forget to bear in mind there are limitations when you impose filters. For example, those inequality filters can only apply to one property at a time. See restrictions on queries

Create a datetime object to represent an instance regardless of timezones for string manipulation

It seems to me that you are asking the impossible or the senseless here.

Let’s take the example from the comments. A passenger books a flight with a time of arrival in İstanbul of 25/04/2019 13:10. By convention arrival time is given in the local time of the arrival airport. Since İstanbul is at UTC offset +03:00, the arrival time is equal to 2019-04-25 10:10 UTC.

Commonly acknowledged good practice is to store dates and times in UTC in your database. If you are using an SQL database, you should normally use its timestamp with time zone datatype (the “with time zone” part is a bit of a lie since you cannot store the timestamp with a time zone of your choice; it is always UTC, but I just said that this is good practice, so this is great).

Say that you’ve got this as input from a user: 25/04/2019 13:10. Converting it to a LocalDateTime is easy (when you know how; code example below). Storing it in UTC is impossible if we don’t know either a time zone (such as Europe/Istanbul or Asia/Istanbul) or a UTC offset (such as +03:00). Imagine that passenger John Doe Jr. has never flown before and doesn’t know of the convention to give arrival time in the local time of the arrival airport. So to him 13:10 could be 13:10 in his own time zone (America/Chicago, equal to 18:10 UTC), 13:10 in the departure time zone (America/New_York, equal to 17:10 UTC), 13:10 UTC, 13:10 in the arrival time zone (equal to 10:10 UTC) or something else.

Now let’s says that we do know that the input is in Europe/Istanbul time zone. Then conversion to UTC is straightforward. We store the time as 2019-04-25 10:10 UTC. Now it won't change due to any time zone setting of the computer or JVM. It’s straightforward to compare with the other UTC times in your database, and you can safely ignore time zones while doing that. When you need to present the time to a user (for instance, print it on a ticket), you convert to Istanbul time (13:10) (or to which time zone the user wants).

Don’t bother with epoch times unless you are using an API that requires them. The standard Java epoch is defined as January 1, 1970 at 00:00 UTC. Note that it is defined in UTC, so it is a well-defined point in time. An epoch time is a signed count of seconds or milliseconds since the epoch. Our example arrival time is 1 556 187 000 seconds since the epoch, so this is your epoch time. As you can see, it’s not meant for human readability. You won’t want to decipher a log file or run a debugging session where time is represented in this way. You don’t want to make any query to a database where time is represented in this way either.

Stay away from string manipulations. Do your date and time work in date and time objects only. When you receive a string, parse it into an appropriate date-time object. Only when you need to present it to a user or transmit it as a string to another system, format it into a string for that purpose.

Java date/time types

Java offers the following date and time types for us:

  • A LocalDateTime is a date and a time of day without UTC offset or time zone, for example 2019-04-25T13:10. So it does not define a point in time.
  • An Instant on the other hand is a point in time without UTC offset or time zone. So it does not define a date and time of day. It prints in UTC (e.g., 2019-04-25T10:10Z) and is internally represented as counts of seconds and nanoseconds since the epoch.
  • An OffsetDateTime is date and time of day with UTC offset, for example 2018-04-25T13:10+03:00. So this does define a point in time and does define date and time of day.
  • A ZonedDateTime is date and time of day with time zone, for example 2018-04-25T13:10+03:00[Europe/Istanbul]. So this too does define a point in time and does define date and time of day.

How it may look in code

    DateTimeFormatter userInputFormatter = DateTimeFormatter.ofPattern("dd/MM/uuuu HH:mm");
String userInput = "25/04/2019 13:10";
ZoneId arrivalTimeZone = ZoneId.of("Europe/Istanbul");

// Parse into a LocalDateTime
LocalDateTime arrivalTimeLocal = LocalDateTime.parse(userInput, userInputFormatter);
System.out.println("Arrival time: " + arrivalTimeLocal);

Arrival time: 2019-04-25T13:10

    // Convert to Instant in order to have a well-defined point in time
Instant arrivalTime = arrivalTimeLocal.atZone(arrivalTimeZone).toInstant();
System.out.println("Arrival point in time (printed in UTC): " + arrivalTime);

Arrival point in time (printed in UTC): 2019-04-25T10:10:00Z

    // Convert to UTC for storing in SQL database
OffsetDateTime arrivalTimeUtc = arrivalTime.atOffset(ZoneOffset.UTC);
System.out.println("Arrival time in UTC: " + arrivalTimeUtc);

Arrival time in UTC: 2019-04-25T10:10Z

Edit: As I said, in your SQL database you would normally want to save your date and time in UTC in a column of datatype timestamp with time zone. The details depend on your database and JDBC driver. I believe that in MySQL (and possibly other DBMSs) the type would be just timestamp. This is a typical example:

    // Save into a database column of datatype timestamp with time zone
PreparedStatement insert = yourDatabaseConnection.prepareStatement(
"insert into your_table (your_timestamp_with_time_zone_column) values (?);");
insert.setObject(1, arrivalTimeUtc);
int rowsInserted = insert.executeUpdate();

If SQLite hasn’t got a timezone or datetime datatype, prefer to store ISO 8601 format in a character column since this is more readable than epoch time in a numeric column. Instant.toString() produces the string you need for that:

    PreparedStatement insert = yourDatabaseConnection.prepareStatement(
"insert into your_table (your_varchar_column) values (?);");
insert.setString(1, arrivalTime.toString());
int rowsInserted = insert.executeUpdate();

Instant.parse will parse the same string back after retrieval.

    // Convert to arrival time zone, e.g., for printing on ticket
String arrivalTimeForUser = arrivalTime.atZone(arrivalTimeZone)
.format(userInputFormatter);
System.out.println("Formatted arrival time in local time: " + arrivalTimeForUser);

Formatted arrival time in local time: 25/04/2019 13:10

Links

  • Mandatory read: Related question: Java Best Practice for Date Manipulation/Storage for Geographically Diverse Users
  • Oracle tutorial: Date Time explaining how to use java.time.
  • Documentation of

    • LocalDateTime
    • LocalTime
    • Instant
    • OffsetDateTime
    • ZonedDateTime


Related Topics



Leave a reply



Submit