Search code examples
androiddatetimetimezonejodatimeandroid-jodatime

Wrong timezone offset on Android after JodaTime parsing


I try to parse date in ISO format by JodaTime library for android, and get wrong timezone offset.

String dateString = "1990-05-03T20:00:00.000Z";
DateTimeFormatter inputFormatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").withZone(DateTimeZone.UTC);
DateTimeFormatter outputFormatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").withZone(DateTimeZone.getDefault());
DateTime dt = inputFormatter.parseDateTime(dateString);
String result = outputFormatter.print(dt); 

My default timeZone is "Europe/Moscow" But result is "1990-05-04T00:00:00.000+0400", when it should be "1990-05-03T23:00:00.000+0300". Android version is KitKat.

What I did wrong?


Solution

  • If you only want to use actual Moscow zone offset for displaying a historic timestamp then you can use this code:

    String dateString = "1990-05-03T20:00:00.000Z";
    DateTimeFormatter inputFormatter =
        DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").withZone(DateTimeZone.UTC);
    DateTime dt = inputFormatter.parseDateTime(dateString);
    System.out.println(dt); // 1990-05-03T20:00:00.000Z
    
    // only use actual offset for Moscow
    DateTimeZone zoneOffset =
        DateTimeZone.forOffsetMillis(
            DateTimeZone.forID("Europe/Moscow").getOffset(DateTime.now())
        );
    DateTimeFormatter outputFormatter =
        DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").withZone(zoneOffset);
    String result = outputFormatter.print(dt);
    System.out.println(result); // 1990-05-03T23:00:00.000+0300
    

    Still the same instant, just with different offset and local time part of the form you look for.

    About your comment how to parse UTC-time from server:

    String input = "2016-12-10T13:40:38.595";
    DateTime utc =
        DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSS").parseLocalDateTime(input)
        .toDateTime(DateTimeZone.UTC);
    System.out.println(utc); // 2016-12-10T13:40:38.595Z
    

    How to send current device time as UTC to server if device data are wrong?

    First to say: I still think it is better design if there is no need to send current client timestamp to server because the server should use its own clock to register the timestamp when receiving client messages. This effectively prevents fake clients from sending any nonsense time.

    Otherwise, if both the tz-data and the device clock (via System.currentTimeMillis()) are wrong but the local device timestamp is still correct as indicated in your comments (valid field values for year-month-day-hour-minute) then you could apply following workaround:

    // incorrect platform data from device clock or outdated tz-data
    long platformMillis = System.currentTimeMillis();
    int offsetMillis = TimeZone.getDefault().getOffset(platformMillis);
    DateTimeZone tzPlatform = DateTimeZone.forOffsetMillis(offsetMillis); // +04 on your device
    
    // we assume correct local time if the errors of platform data compensate each other
    LocalDateTime ldt = new LocalDateTime(new Date(platformMillis), tzPlatform);
    
    // now we combine local timestamp with the tz-data of Joda-Time (hopefully up-to-date)
    long utcMillis = ldt.toDateTime(DateTimeZone.getDefault()).getMillis();
    

    Side note: My library Time4A enables a shorter solution.

    Moment now = SystemClock.inPlatformView().now().inStdTimezone();
    long utcMillis = TemporalType.MILLIS_SINCE_UNIX.from(now);