2

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?

preceptron
  • 193
  • 4
  • 11
  • I think, nothing is wrong. 1990-05-03 is in summer time (May), so Joda-Time uses the summer time offset +04:00 for Europe/Moscow. Maybe you are just confused because actually, Moscow in in winter time. – Meno Hochschild Dec 10 '16 at 10:21
  • @MenoHochschild Since 2014 Russia has only winter time and we not change time every year. How I can disable summer/winter time in Joda-Time? – preceptron Dec 10 '16 at 12:04

1 Answers1

4

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);
Meno Hochschild
  • 42,708
  • 7
  • 104
  • 126
  • It's work. But what if I will use such code for all timezones? `DateTimeZone zoneOffset = DateTimeZone.forOffsetMillis(DateTimeZone.getDefault().getOffset(DateTime.now())); DateTimeFormatter outputFormatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").withZone(zoneOffset);` – preceptron Dec 10 '16 at 12:52
  • @preceptron Why not working for any other timezone? You can display any instant with an arbitrary offset keeping the instant but changing the local timestamp. However, the historical form with historical offset seems to be more natural for me and not your request to use only the actual offset which is not related to the timestamp you want to display. – Meno Hochschild Dec 10 '16 at 13:06
  • @preceptron Maybe this makes it clearer: Imagine you are not in Moscow but in Brazil, so you could display the SAME instant with the offset of zone Sao-Paulo resulting in a local timestamp for Brazil. Local timestamp part and offset change, but the instant is still the same. And it is really up to you which offset you want to use for display. If you just instruct Joda-Time to use Moscow zone (without saying the explicit offset), then Joda-Time will use the offset as given by the contextual historic instant (which is to be printed). – Meno Hochschild Dec 10 '16 at 13:33
  • We keep time in UTC on server. And on iOS and new versions of Android timezone has correct offset (+3 for Moscow), but KitKat has offset +4 for Moscow (and it's wrong). So, I should fix it by side library. I have another problem. I try to get UTC string but if I get current time, I get string with -4 hours...But I need -3... For example now 10.12.2016 16:40 and I get UTC String like this one "2016-12-10T13:40:38.595" I tried different ways, and it's not works. Could you help me with such case? – preceptron Dec 10 '16 at 13:36
  • @preceptron See the update of my answer (bottom part). – Meno Hochschild Dec 10 '16 at 13:53
  • @preceptron About KitKat, well, Joda-Time-Android has its own timezone data. And if those are up-to-date then you should not experience a problem with display of offset +04:00. – Meno Hochschild Dec 10 '16 at 14:04
  • thank you for your answers. But now I need format my date to ISO String. I want get current time and make iso string. JodaTime make string with offset 4 hours, but should be 3 hours. – preceptron Dec 10 '16 at 15:03
  • @preceptron "1990-05-03T23:00:00.000+0300" and "1990-05-04T00:00:00.000+0400" are both valid ISO and describe the same time. You can use code in answer to display current time with the offset you want. – Meno Hochschild Dec 10 '16 at 15:25
  • So, my case - time on my kitKat phone is 20.00 (and TimeZone Moscow +4) , and I want to send this time in UTC to server. It will be as 16:00Z. Another user (with correct Moscow time zone +3) will get time in UTC (16.00Z) and parse it to local time, it will be as 19:00. I want to format time on old devices to UTC using correct TimeZone offset. It should be 20.00 (With incorrect system timezone +4) to 17.00Z using JodaTime. And other users (with correct timezone data) will see correct time 20.00. – preceptron Dec 10 '16 at 15:41
  • @preceptron Provided the user has not manipulated the mobile phone clock the app could simply send the current instant/moment to the server (for example as millisecs since UNIX epoch without any need to look at the mobile phone zone or local time), but I think it is much better if the server logs the time of receiving client messages using its own server clock. – Meno Hochschild Dec 10 '16 at 16:06
  • @preceptron Am just curious. If your zone offset on KitKat is wrong, what about your local time 20:00? Is it still correct? If so, how do you manage it as user, manipulating the mobile clock, manipulating the zone??? My point is, you have to do something to achieve the correct local time if the zone information is wrong. – Meno Hochschild Dec 10 '16 at 16:12
  • Local time is correct. It is 20:00, timezone is MSK +4, but UTC time is not same as on another devices with correct timezone. For example on KitKat device UTC time is 16:00 and timezone +4, so user see 20:00. But on another devices UTC time is 17:00 and timezone +3, and users see 20:00 too. – preceptron Dec 10 '16 at 16:29
  • @preceptron Have you also checked the value of `System.currentTimeMillis()` on all devices? Still different by magnitude of one hour? – Meno Hochschild Dec 10 '16 at 16:40
  • @preceptron I still try to figure out your problem. How do you conclude that UTC-time is different on your devices? By doing your combination of observed correct local time and wrong offset (and then sending this combination to server), or by obtaining the device clock value of `System.currentTimeMillis()`? – Meno Hochschild Dec 10 '16 at 17:39
  • different between `System.currentTimeMillis()` on two devices (+4 and +3 offsets) is 3608499, it is about 1 hour. – preceptron Dec 10 '16 at 17:51
  • @preceptron Thank you very much for your information. Am soon offline however. Will try to answer tomorrow. For now, we have correct local time, wrong clock device and wrong sytem timezone data compensating each other (although the tz-offset of Joda-Time should still be correct). That said, you could combine the displayed local device time in terms of fields like year-month-day-hour-minute and the tz-information of Joda-Time to gain an UTC-instant. – Meno Hochschild Dec 10 '16 at 18:41
  • Thank you for advice. I will try it. – preceptron Dec 10 '16 at 19:35
  • @preceptron Have now updated my answer and hope it helps. – Meno Hochschild Dec 12 '16 at 12:09
  • @preceptron Thanks, the informations given in your comments were also very useful for me as a developer of a time library so I can better imagine some standard problems on Android and how to find a workaround. – Meno Hochschild Dec 14 '16 at 12:24