0

I took a date from a web service in UNIX timestamp. I milltuplied it by 1000L then I added the timezone to it in seconds (also provided by the web service) milltiplied by 1000 to obtain the date according to the country in which the application will run and not the UTC date. In the emulator the date time provided is correct but when I tested on a real device it provided me the time with 1 hour more which does not correspond to the local time. Where is the problem?

long numberOfsecondsRise = json.getJSONObject("city").getInt("timezone");

long res=(json.getJSONObject("city").getLong("sunrise")*1000L +numberOfsecondsRise*1000) ;
 Date rise=new java.util.Date(res);
 DateFormat dfa = DateFormat.getTimeInstance();
 sunFiled.setText(getResources().getString(R.string.sunrise)+": " + dfa.format(rise));
Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
Delgado
  • 216
  • 3
  • 16
  • 2
    If at all possible, use the new `java.time` APIs instead. You can use ThreeTen if for some reason you can't use Java 8. – chrylis -cautiouslyoptimistic- May 15 '20 at 21:15
  • A Unix timestamp is independent of time zone. So you can safely ignore `json.getJSONObject("city").getInt("timezone")`. – Ole V.V. May 16 '20 at 13:21
  • As an aside consider throwing away the long outmoded and notoriously troublesome `DateFormat` and friends, and adding [ThreeTenABP](https://github.com/JakeWharton/ThreeTenABP) to your Android project in order to use java.time, the modern Java date and time API. It is so much nicer to work with. – Ole V.V. May 16 '20 at 13:21

2 Answers2

3

java.time and ThreeTenABP

Consider using java.time, the modern Java date and time API, for your time work. If for minSDK below API level 26, then through the backport, I will get back to that. First the code:

    DateTimeFormatter timeFormatter
            = DateTimeFormatter.ofLocalizedTime(FormatStyle.MEDIUM);

    long sunriseUnixTs = 1_589_581_234;
    ZonedDateTime sunriseApplicationTz = Instant.ofEpochSecond(sunriseUnixTs)
            .atZone(ZoneId.systemDefault());

    System.out.println("Sunrise: " + sunriseApplicationTz.format(timeFormatter));

Output from this example snippet in my time zone and locale:

Sunrise: 03.50.34

One of the things I find great about java.time is that the code makes it explicit that we are getting the time in the default time zone of the JVM where the application is running.

What went wrong in your code?

Adding the time zone offset of the city you are inquiring about is wrong. A Unix timestamp is independent of time zone. So if you multiply by 1000 and feed to new Date(long), you are getting a Date that holds the correct point in time. If you add a non-zero offset, you are getting a wrong point in time. Your emulator gave you the expected result, why, then? It might be because the offset from JSON was 0 (zero) or because the error was balanced out by the emulator using a different default time zone from what you had expected.

Question: Doesn’t java.time require Android API level 26?

java.time works nicely on both older and newer Android devices. It just requires at least Java 6.

  • In Java 8 and later and on newer Android devices (from API level 26) the modern API comes built-in.
  • In non-Android Java 6 and 7 get the ThreeTen Backport, the backport of the modern classes (ThreeTen for JSR 310; see the links at the bottom).
  • On (older) Android use the Android edition of ThreeTen Backport. It’s called ThreeTenABP. And make sure you import the date and time classes from org.threeten.bp with subpackages.

Links

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
1

Date (long date) constructor documentation says:

Allocates a Date object and initializes it to represent the specified number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT.

This means the value is supposed to be in UTC. The time offset in seconds must be applied when formatting the date for display.

long numberOfsecondsRise = json.getJSONObject("city").getInt("timezone");
Date rise = new java.util.Date(json.getJSONObject("city").getLong("sunrise") * 1000L);

int offsetMinutes = numberOfsecondsRise / 60;
String sign = (offsetMinutes < 0 ? "-" : "+");
offsetMinutes = Math.abs(offsetMinutes);
String timeZoneID = String.format("GMT%s%d:%02d", sign, offsetMinutes / 60, offsetMinutes % 60);

DateFormat dfa = DateFormat.getTimeInstance();
dfa.setTimeZone(TimeZone.getTimeZone(timeZoneID));
sunFiled.setText(getResources().getString(R.string.sunrise) + ": " + dfa.format(rise));
Andreas
  • 154,647
  • 11
  • 152
  • 247
  • If you want to obtain an instance of the old-fashioned and poorly designed `TimeZone` class from the count of seoncds from JSON, there are simpler ways: (1) `TimeZone.getTimeZone(ZoneOffset.ofTotalSeconds(offsetSeconds))`. (2) `new SimpleTimeZone(Math.toIntExact(TimeUnit.SECONDS.toMillis(offsetSeconds)), "My-time-zone")`. (3) Using ThreeTenABP: `DateTimeUtils.toTimeZone(ZoneOffset.ofTotalSeconds(offsetSeconds))`. – Ole V.V. May 16 '20 at 14:34
  • @OleV.V. Question is [tag:android], so `ZoneOffset` is likely not available, which is why the question is using `Date` in the first place. --- Didn't know about [`SimpleTimeZone`](https://docs.oracle.com/javase/8/docs/api/java/util/SimpleTimeZone.html), so thanks for that, but why not just do `offsetSeconds * 1000`? – Andreas May 16 '20 at 14:50
  • Matter of taste — do that if you prefer short code over self-explanatory :-) I agree that mine is longer than one would have hoped. – Ole V.V. May 16 '20 at 15:03
  • 1
    @OleV.V. LOL If someone doesn't understand that `seconds * 1000` becomes a *millisecond* value, then they sure wouldn't understand what `offsetMinutes / 60` and `offsetMinutes % 60` does either. No, I'm generally not coding for such individuals; I do expect *some* level of competency of anyone reading code. – Andreas May 16 '20 at 15:07