Changing timezone without changing time in Java
Asked Answered
A

8

16

I'm receiving a datetime from a SOAP webservice without timzone information. Hence, the Axis deserializer assumes UTC. However, the datetime really is in Sydney time. I've solved the problem by substracting the timezone offset:

Calendar trade_date = trade.getTradeDateTime();
TimeZone est_tz = TimeZone.getTimeZone("Australia/Sydney");
long millis = trade_date.getTimeInMillis() - est_tz.getRawOffset();
trade_date.setTimeZone( est_tz );
trade_date.setTimeInMillis( millis );

However, I'm not sure if this solution also takes daylight saving into account. I think it should, because all operations are on UTC time. Any experiences with manipulating time in Java? Better ideas on how to solve this problem?

Abscess answered 20/5, 2010 at 11:14 Comment(3)
I always, ever, and only manipulate date (in any language) by using the only unit that is fixed. That unit is the second (or millisecond). There are minutes with 59 or 61 seconds and there are days with 23 or 25 hours. There are hours with 60*60 +/- 1 seconds, etc. All my dates are stored as (milli)seconds since the epoch. That's how they're in the DB, that's how they're sorted. The only time at which I do a timezone/yyyy-mm-dd whatever conversion is when it needs to be displayed to the user (or when using broken APIs that don't understand that a date is expressable in seconds).Mayle
@WizardOfOdds. That's the sound advice, but OP's clearly posted that he receives the timestamp from a web service ( possibly 3rd party ) and used a 3rd-party library to process that request. Clearly there are some things out of his direct control, and converting from timezone to timezone is definitely not straight forward with java.util.Calendar.Knoll
Thanks for your comments. I'm generating the Web Service stubs from WSDL file using Axis. I'm using the generated classes to connect to a third party web service. I actually thing they should transmit datetime fields either with time zone information or in UTC, which is what Axis is expecting. But that's not my choice.Abscess
A
-1

I've decided to reparse the datetime string received with the correct time zone set. This should also consider daylight saving:

public class DateTest {

    private static SimpleDateFormat soapdatetime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");

    /**
     * @param args
     */
    public static void main(String[] args) {
        TimeZone oztz = TimeZone.getTimeZone("Australia/Sydney");
        TimeZone gmtz = TimeZone.getTimeZone("GMT");
        Calendar datetime = Calendar.getInstance( gmtz );

        soapdatetime.setTimeZone( gmtz );
        String soap_datetime = soapdatetime.format( datetime.getTime() );
        System.out.println( soap_datetime );

        soapdatetime.setTimeZone( oztz );
        datetime.setTimeZone( oztz );
        try {
            datetime.setTime(
                    soapdatetime.parse( soap_datetime )
            );
        } catch (ParseException e) {
            e.printStackTrace();
        }

        soapdatetime.setTimeZone( gmtz );
        soap_datetime = soapdatetime.format( datetime.getTime() );
        System.out.println( soap_datetime );
    }
}
Abscess answered 20/5, 2010 at 15:52 Comment(0)
O
22

I pity the fool who has to do dates in Java.

What you have done will almost certainly go wrong around the daylight savings transitions. The best way to to it is probably to create a new Calendar object, set the Timezone on it, and then set all of the fields individually, so year, month, day, hour, minute, second, getting the values from the Date object.

Edit:
To keep the everyone happy, you should probably do this:

Calendar utcTime = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
Calendar sydneyTime = Calendar.getInstance(TimeZone.getTimeZone("Australia/Sydney");
utcTime.setTime(trade_date);
for (int i = 0; i < Calendar.FIELD_COUNT; i++) {
  sydneyTime.set(i, utcTime.get(i));
}

Then you won't be using any deprecated methods.

Oglesby answered 20/5, 2010 at 11:19 Comment(3)
-1: For suggesting using deprecated methods of java.util.Date classKnoll
probably: newCalendar.set(oldCalendar.get(Calender.YEAR), oldCalendar.get(Calendar.MONTH),..., oldCalendar.get(Calendar.SECOND));Rigney
@Alexander Pogrebnyak : I have updated the answer so that users are not lead to the dark side ;-)Oglesby
G
5

I want to thank the person for responce 6. This was a great start for me and an approach I did not consider. There are some addtional steps required to bring it to production code level. In particular observe the steps required for DST_OFFSET and ZONE_OFFSET. I want to share the solution I came up with.

This takes the time from the input Calendar object, copies it to the output time, sets the new time zone to the output. This is used when taking time literally from the database and setting the Time Zone without changing the time.

public static Calendar setNewTimeZoneCopyOldTime( Calendar inputTime, 
        TimeZone timeZone ) {
    if( (inputTime == null) || (timeZone == null) ) { return( null ); }

    Calendar outputTime = Calendar.getInstance( timeZone );
    for( int i = 0; i < Calendar.FIELD_COUNT; i++ ) {
        if( (i != Calendar.ZONE_OFFSET) && (i != Calendar.DST_OFFSET) ) { 
            outputTime.set(i, inputTime.get(i));
        }
    }

    return( (Calendar) outputTime.clone() );
}
Gaal answered 23/5, 2013 at 22:48 Comment(0)
E
3

However, I'm not sure if this solution also takes daylight saving into account. I think it should, because all operations are on UTC time.

Yes, you should take the daylight saving into account, since it affects the offset to UTC.

Any experiences with manipulating time in Java? Better ideas on how to solve this problem?

Joda-Time is a better time API. Maybe the following snippet could be of help :

DateTimeZone zone; // TODO : get zone
DateTime fixedTimestamp = new DateTime(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond, zone);

JodaTime types are immutable which is also a benefit.

Effort answered 20/5, 2010 at 11:20 Comment(2)
Thanks for the link to JodaTime. However, I won't introduce a new third-party library for that project. But I will consider JodaTime the nex time.Abscess
Joda-Time is well worth the trouble of adding a third-party library. Well-worn and proven. The java.util.Date & Calendar classes are notoriously troublesome.Peach
M
2

I normally do it this way

Calendar trade_date_utc = trade.getTradeDateTime();
TimeZone est_tz = TimeZone.getTimeZone("Australia/Sydney");
Calendar trade_date = Calendar.GetInstance(est_tz);
trade_date.setTimeInMillis( millis );
Marchall answered 11/2, 2012 at 17:26 Comment(0)
P
1

Are you getting an ISO 8601 style string from that messed-up Web Service? If so, the Joda-Time 2.3 library makes this very easy.

If you are getting an ISO 8601 string without any time zone offset, you pass a time zone object to the DateTime constructor.

DateTimeZone timeZone = DateTimeZone.forID( "Australia/Sydney" );
String input = "2014-01-02T03:00:00"; // Note the lack of time zone offset at end.
DateTime dateTime = new DateTime( input, timeZone );

Dump to console…

System.out.println( "dateTime: " + dateTime );

When run…

dateTime: 2014-01-02T03:00:00.000+11:00
Peach answered 25/1, 2014 at 0:59 Comment(0)
C
1
 @Test
 public void tzTest() {
     SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS Z");
     TimeZone tz1 = TimeZone.getTimeZone("Europe/Moscow");
     Calendar cal1 = Calendar.getInstance(tz1);
     long l1 = cal1.getTimeInMillis();
     df.setTimeZone(tz1);
     System.out.println(df.format(cal1.getTime()));
     System.out.println(l1);
     TimeZone tz2 = TimeZone.getTimeZone("Africa/Douala");
     Calendar cal2 = Calendar.getInstance(tz2);
     long l2 = l1 + tz1.getRawOffset() - tz2.getRawOffset();
     cal2.setTimeInMillis(l2);
     df.setTimeZone(tz2);
     System.out.println(df.format(cal2.getTime()));
     System.out.println(l2);
     assertNotEquals(l2, l1);
 }


Running CalendarTest
2016-06-30 19:09:16.522 +0300
1467302956522
2016-06-30 19:09:16.522 +0100
1467310156522
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.137 sec

Compensable answered 30/6, 2016 at 16:14 Comment(0)
R
0
public Calendar replaceTimezone(String targetTimezoneID, String sourceTimezoneID, Date sourceDate) {
        TimeZone sourceTZ = TimeZone.getTimeZone(sourceTimezoneID);
        int sourceTZOffsetToGMT = sourceTZ.getOffset(sourceDate.getTime());
        
        TimeZone targetTZ = TimeZone.getTimeZone(targetTimezoneID);
        Calendar targetCal = Calendar.getInstance();
        targetCal.setTime(sourceDate);
        targetCal.setTimeZone(targetTZ);
        Date preTargetDate = targetCal.getTime();
        int targetTZOffsetToGMT = targetTZ.getOffset(preTargetDate.getTime());
        
        int adjustOffset = sourceTZOffsetToGMT - targetTZOffsetToGMT;
        targetCal.add(Calendar.MILLISECOND, adjustOffset);
        
        return targetCal;
    }
Rotary answered 1/2 at 8:50 Comment(0)
A
-1

I've decided to reparse the datetime string received with the correct time zone set. This should also consider daylight saving:

public class DateTest {

    private static SimpleDateFormat soapdatetime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");

    /**
     * @param args
     */
    public static void main(String[] args) {
        TimeZone oztz = TimeZone.getTimeZone("Australia/Sydney");
        TimeZone gmtz = TimeZone.getTimeZone("GMT");
        Calendar datetime = Calendar.getInstance( gmtz );

        soapdatetime.setTimeZone( gmtz );
        String soap_datetime = soapdatetime.format( datetime.getTime() );
        System.out.println( soap_datetime );

        soapdatetime.setTimeZone( oztz );
        datetime.setTimeZone( oztz );
        try {
            datetime.setTime(
                    soapdatetime.parse( soap_datetime )
            );
        } catch (ParseException e) {
            e.printStackTrace();
        }

        soapdatetime.setTimeZone( gmtz );
        soap_datetime = soapdatetime.format( datetime.getTime() );
        System.out.println( soap_datetime );
    }
}
Abscess answered 20/5, 2010 at 15:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.