PNS
PNS

Reputation: 19895

Converting a Date to a new TimeZone in Java

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:

However, 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

Answers (1)

user1596371
user1596371

Reputation:

Joda-Time

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

Related Questions