Here is the problem I am trying to solve: Read a string from database A, convert the string into a Date object, store the Date object into database B.
EX) Database A: Read in date string "2015-03-08 02:00:00" from database A, convert into a Date object, store back into database B.
The problem here occurs because 2:00 AM is the beginning of DST in U.S. Central time, so the Data object converts 2:00 AM straight into 3:00 AM, which means 3:00 AM gets stored into database B.
Is there any way to correct this? I am not opposed to using Joda Time if necessary.
I am trying to focus on the above date, 2015-03-08 02:00:00
This is the code I am using:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
String date = "2015-03-08 02:00:00.0";
try
{
d = sdf.parse(date);
sdf.format(d);
//Insert into database here
// ---
//
}
catch (ParseException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
You have multiple issues intertwined.
You should not be reading strings from a database for date-time values, you should be reading date-time objects. There are many Questions on StackOverflow about reading/writing date-time values from/to databases, so no need to repeat here.
If you do have a string, such as "2015-03-08 02:00:00", notice the lack of any indicator of a time zone or offset. If you want to assume that string represents a time specific the US Central Time, then you must accept the fact that there is no such date-time as that because Daylight Saving Time (DST) defines that as 3 AM. At the stroke of 2 AM, the time labeling jumps to 2 AM. So there is no point in trying to get such a non-existent date-time.
Use Proper Time Zone Names
Big tip for date-time work: Avoid thinking about time zones as "Central Time" and the 3-4 letter codes like "CST". These are not standardized, nor are the unique (many duplicates), and further confuse the mess that is Daylight Saving Time. Use a proper time zone, in pattern of "continent/majorCityOrRegion".
Local Date-Time
Perhaps what you mean is what we call "local time" where the date-time is not specific to any one time zone. For example, "Christmas starts at midnight on December 25th 2015". That means a different moment in each particular time zone. Christmas dawns earlier in Paris, than Montréal, for example.
Joda-Time
Let's interpret that string as a LocalDateTime
in Joda-Time. First, for convenience, we replace the SPACE with a "T" to take advantage of Joda-Time’s built-in parsers for ISO 8601 formats.
String input = "2015-03-08 02:00:00";
String inputStandardized = input.replace( " ", "T" ); // For convenience, convert input text to comply with ISO 8601 standard’s canonical format. Replace SPACE between date & time portions with "T".
Next we parse that standardized string.
LocalDateTime localDateTime = LocalDateTime.parse( inputStandardized );
Dump to console.
System.out.println( "inputStandardized: " + inputStandardized );
System.out.println( "localDateTime: " + localDateTime );
When run.
inputStandardized: 2015-03-08T02:00:00
localDateTime: 2015-03-08T02:00:00.000
This local date-time could be stored in a SQL database using the SQL type TIMESTAMP WITHOUT TIME ZONE
. This type means no adjustments to UTC time zone are to be made in either getting (SELECT) or putting (INSERT / UPDATE) database values. See Postgres doc for more info on these SQL types.
Zoned Date-Time
If you meant to represent the specific moment in a specific time zone such as America/Chicago
, when we need to assign that time zone. For this kind of time-zone-specific values, in your database you would use the data type TIMESTAMP WITH TIME ZONE
. That type name is misleading -- it means with respect for time zone, as it adjusts incoming data to UTC. The data's original time zone is then lost.
Unfortunately, this is one of the few situations where Joda-Time lets us down. Rather than do an adjustment, Joda-Time refuses, throwing an exception. ☹
See for yourself… Let's add the following code to the example code above.
DateTimeZone zone = DateTimeZone.forID( "America/Chicago" );
DateTime dateTimeChicago = localDateTime.toDateTime( zone ); // If the input lacks an offset, then Joda-Time *assigns* the value the specified time zone. If the input has an offset, Joda-Time *adjusts* the value to the specified zone.
Dump to console.
System.out.println( "zone: " + zone );
System.out.println( "dateTime: " + dateTimeChicago );
When run.
Exception in thread "main" org.joda.time.IllegalInstantException: Illegal instant due to time zone offset transition (daylight savings time 'gap'): 2015-03-08T02:00:00.000 (America/Chicago
…
There appears to be no good generalized workaround, just hacks. Basically, if you expect a certain time zone, you make the adjustment yourself. See discussions like this, this, this, and the Joda-Time FAQ.
java.time
In Java 8 and later, we have the new built-in date-time framework in the java.time package (Tutorial). This framework was inspired by Joda-Time, and has some advantages over Joda-Time. One of those advantages is handling of this DST non-existent value problem.
String input = "2015-03-08 02:00:00";
String inputStandardized = input.replace( " ", "T" );
LocalDateTime localDateTime = LocalDateTime.parse( inputStandardized );
Let's adjust that local date-time to assign a specific time zone. The java.time framework detects the non-existent date-time and automatically slides the time-of-day forward to respect the DST transition.
ZoneId zone = ZoneId.of( "America/Chicago" );
ZonedDateTime zdt = ZonedDateTime.of( localDateTime, zone );
Dump to console.
System.out.println("inputStandardized: " + inputStandardized );
System.out.println("localDateTime: " + localDateTime );
System.out.println("zone: " + zone );
System.out.println("zdt: " + zdt );
When run.
inputStandardized: 2015-03-08T02:00:00
localDateTime: 2015-03-08T02:00
zone: America/Chicago
zdt: 2015-03-08T03:00-05:00[America/Chicago]
SQL
As said above, you can search StackOveflow for much info on getting date-times in and out of databases.
Ideally, with java.time, you could directly feed either the LocalDateTime
or ZonedDateTime
to your JDBC driver. But most drivers have not yet be updated to handle the java.time types. Until your driver is updated, fall back on the java.sql.* classes. Convenient conversion methods can be found on both the new and old classes bundled with Java.
java.sql.Timestamp ts = java.sql.Timestamp.valueOf( localDateTime );
…or…
Instant instant = zdt.toInstant();
java.sql.Timestamp ts = java.sql.Timestamp.from( instant );
It's usually better to store the date as milliseconds since epoch. That way, you can use a long to store the number in your database, and when you need to format the date, you can either use Joda Time's DateTime(long)
constructor or just the built-in Date(long)
constructor.