将 Java Date
对象转换为特定("目标")时区是一个在 StackOverflow
中多次提出的问题,建议的解决方案显然是:
- 按时区偏移量调整
Date
对象的时间(毫秒)值 - 使用
SimpleDateFormat
(带时区)将所需日期生成为字符串
但是,似乎没有"纯Java"解决方案可以将日期字符串(带或不带时区)解析为Date
对象,然后将相应的Calendar
对象调整为目标时区,以便"转换"的日期在所有情况下都以正确的方式运行(例如,当使用Calendar
对象检索日期的年份时)。
主要问题是,当用于解析日期字符串的SimpleDateFormat
模式没有时区时,系统会合理地假定时间在解析系统的时区中。但是,如果模式确实具有时区,则时间将从 UTC
开始计算。因此,需要的本质上是指示原始日期字符串是否对应于带有或不具有时区的模式,如果不分析模式本身,这显然是无法完成的。
附加的 TimezoneDate
类源代码演示了这些差异。
太平洋标准时间 (PST) 运行的计算机上将它们转换为 UTC 时,为 2 个"原始日期"运行 main()
方法的输出,第一个没有时区,第二个有时区:
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
正确的操作是使"格式化"和"日历"字符串与 2 个示例日期字符串("原始日期")中每个字符串的"预期日期"相同。
显然,需要区分日期字符串包含时区符号 (TZ) 的情况和不包含时区符号(无 TZ)的情况,但这意味着事先了解SimpleDateFormat
模式,这在处理Date
对象而不是原始字符串时是不可能的。
因此,问题实际上是关于是否存在通用的"纯Java"(没有第三方库)解决方案,该解决方案不需要事先了解模式并且可以与相应的Calendar
对象一起正常工作。
以下是 TimezoneDate
类的完整源代码。
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]);
}
}
}
Joda-Time
与其使用Java的Date
不如使用Joda-Time DateTime
类。 这允许您以非常简单的方式执行时区操作,根据您的特定要求使用withTimezone()
和withTimezoneRetainFields()
方法。