Reputation: 19895
Converting a Java Date
object to a specific ("target") time zone is a question that has been asked numerous times before in StackOverflow
and the suggested solutions apparently are:
Date
object by the timezone offsetSimpleDateFormat
(with the timezone) to produce the desired date as a StringHowever, there seems to be no "plain Java" solution that parses a date string (with or without timezone) into a Date
object and then adjusts a corresponding Calendar
object to the target timezone, so that the "converted" date behaves in all cases in the correct way (e.g., when using the Calendar
object to retrieve the year of the date).
The main issue is that, reasonably, when the SimpleDateFormat
pattern used to parse the date string does not have a timezone, the system assumes that the time is in the parsing system's timezone. If, however, the pattern does have a timezone, the time is counted from UTC
. So, what is needed is essentially an indication as to whether the original date string corresponds to a pattern with or without a timezone, which apparently cannot be done without analyzing the pattern itself.
The attached source code of the TimezoneDate
class demonstrates the discrepancies.
Here is the output from running the main()
method for 2 "original dates", the first without and the second with a timezone, when they are converted to UTC on a machine running on Pacific Standard Time (PST):
Original date: 20/12/2012 08:12:24
Formatted date (TZ): 20/12/2012 16:12:24
Formatted date (no TZ): 20/12/2012 08:12:24
Calendar date: 20/12/2012 16:12:24
Expected date: 20/12/2012 08:12:24
Original date: 20/10/2012 08:12:24 +1200
Formatted date (TZ): 19/10/2012 20:12:24
Formatted date (no TZ): 19/10/2012 13:12:24
Calendar date: 19/10/2012 20:12:24
Expected date: 19/10/2012 20:12:24
The correct operation is to have the "Formatted" and "Calendar" strings identical to the "Expected date" for each of the 2 example date strings ("original dates").
Apparently one needs to make a distinction between the case where the date string contains a timezone symbol (TZ) and the case where it does not (no TZ), but this means knowing the SimpleDateFormat
pattern beforehand, which is not possible when handling a Date
object and not the original string.
So, the question is really about whether a generic "plain Java" (no third party libraries) solution exists that does not require prior knowledge of the pattern and works correctly with the corresponding Calendar
object.
Following is the full source code of the TimezoneDate
class.
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
public class TimezoneDate
{
private final Date time;
private final TimeZone timezone;
private final Calendar calendar;
/**
* Creates a wrapper for a {@code Date} object that is converted to the
* specified time zone.
*
* @param date The date to wrap
* @param timezone The timezone to convert to
*/
public TimezoneDate(Date date, TimeZone timezone)
{
this.calendar = TimezoneDate.getPlainCalendar(date, timezone);
this.time = this.calendar.getTime();
this.timezone = timezone;
}
private static Calendar getPlainCalendar(Date date, TimeZone timezone)
{
Calendar calendar = Calendar.getInstance(timezone);
calendar.setTime(date);
return calendar;
}
private static Calendar getAdjustedCalendar(Date date, TimeZone timezone)
{
long time = date.getTime();
time = time + timezone.getOffset(time) - TimeZone.getDefault().getOffset(time);
date = new Date(time);
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
return calendar;
}
public int getYear()
{
return this.calendar.get(Calendar.YEAR);
}
public int getMonth()
{
return (this.calendar.get(Calendar.MONTH)+1);
}
public int getMonthDay()
{
return this.calendar.get(Calendar.DAY_OF_MONTH);
}
public int getHour()
{
return this.calendar.get(Calendar.HOUR_OF_DAY);
}
public int getMinutes()
{
return this.calendar.get(Calendar.MINUTE);
}
public int getSeconds()
{
return this.calendar.get(Calendar.SECOND);
}
public String toCalendarDate() // The date as reported by the Calendar
{
StringBuilder sb = new StringBuilder();
sb.append(this.getMonthDay()).append("/").append(this.getMonth()).
append("/").append(this.getYear()).append(" ").
append(this.getHour()).append(":").append(this.getMinutes()).
append(":").append(this.getSeconds());
return sb.toString();
}
public String toFormattedDate(boolean addTimezone) // The formatted date string
{
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
if (addTimezone)
{
sdf.setTimeZone(this.timezone);
}
return sdf.format(this.time);
}
public static void main(String[] args) throws Exception
{
// Each data "vector" contains 3 strings, i.e.:
// - Original (example) date
// - SimpleDateFormat pattern
// - Expected date after converting the original date to UTC
String[][] data = new String[][]
{
{"20/12/2012 08:12:24", "dd/MM/yyyy' 'HH:mm:ss", "20/12/2012 08:12:24"},
{"20/10/2012 08:12:24 +1200", "dd/MM/yyyy HH:mm:ss Z", "19/10/2012 20:12:24"}
};
Date originalDate;
TimezoneDate timezoneDate;
SimpleDateFormat format;
TimeZone UTC = TimeZone.getTimeZone("UTC");
for (String[] vector:data)
{
format = new SimpleDateFormat(vector[1]);
originalDate = format.parse(vector[0]);
timezoneDate = new TimezoneDate(originalDate, UTC);
System.out.println();
System.out.println("Original date: " + vector[0]);
System.out.println("Formatted date (TZ): " + timezoneDate.toFormattedDate(true));
System.out.println("Formatted date (no TZ): " + timezoneDate.toFormattedDate(false));
System.out.println("Calendar date: " + timezoneDate.toCalendarDate());
System.out.println("Expected date: " + vector[2]);
}
}
}
Upvotes: 1
Views: 1044
Reputation:
Rather than use Java's Date
you should use the Joda-Time DateTime
class. This allows you to carry out timezone operations in a very simple fashion, using the withTimezone()
and withTimezoneRetainFields()
methods depending on your particular requirements.
Upvotes: 3