1

I get from my GPS tracker a time String in UTC-Format like this: hhmmss.ssss.

I want to convert the UTC time to the local time of the user by using calendar. So I extract the hours, minutes and seconds from the time String via substring(int start, int end) and set it via the Calendar.set(int field, int value) function. After this I convert the Calendar to a Date, but know I have a wrong day.

For example timestamp = 091215.0000, If I log Calendar.getInstance()I get: Thu Dec 11 10:12:15 GMT+01:00 2018 But when I convert it with my function I get: Thu Dec 13 10:12:15 GMT+01:00 2018

My function

   public static Date utcToLocalTimeFromLock(String timestamp) {
    Calendar calendar = Calendar.getInstance();

    if (timestamp.charAt(0) == '0') {
        calendar.set(Calendar.HOUR_OF_DAY, timestamp.charAt(1) + 1);
    } else {
        calendar.set(Calendar.HOUR_OF_DAY, Integer.valueOf(timestamp.substring(0, 2) + 1));
    }


    calendar.set(Calendar.MINUTE, Integer.valueOf(timestamp.substring(2, 4)));
    calendar.set(Calendar.SECOND, Integer.valueOf(timestamp.substring(4, 6)));

    Date date = calendar.getTime();
    Log.d(LOG_TAG, "utcToLocalTimeFromLock: " + date);
   return date;
}
developKinberg
  • 363
  • 4
  • 18
  • 5
    I'd strongly recommend avoiding writing the text handling code yourself - there's plenty of code already in Java to do this for you. Next, avoid java.util.Date and java.util.Calendar - prefer the java.time types instead. They're *much* easier to work with. (For example, your use of `Calendar` means it's interpreting the values in the default time zone, which isn't what you're trying to achieve as far as I can see...) – Jon Skeet Dec 11 '18 at 13:58
  • Is java.time a lib that I have to add to my Android Project ? I can't find it and I write it by my own because the time from the GPS Tracker is not a normal timestamp in second or milliseconds. I also use Date because this is the format how i save it in my DB. But I take a look on java.time :) – developKinberg Dec 11 '18 at 14:03
  • It's part of normal Java. You'll need to check which version of Android supports it vs the version of Android you're targeting. If you really, really need to use `Date`, I'd suggest you use `SimpleDateFormatter` to do the parsing, and make sure you set the time zone to UTC. – Jon Skeet Dec 11 '18 at 14:04
  • Why does your timestamp not contain a date? That seems incredibly error-prone. Are you using a public API for GPS tracking? Is there a separate `date` field available? – Bryan Dec 11 '18 at 14:25
  • 1
    For Android API levels under 26 use the backport of java.time. It has been adapted for Android in [ThreeTenABP](https://github.com/JakeWharton/ThreeTenABP). – Ole V.V. Dec 11 '18 at 15:02
  • Related: [Convert Java string to Time, NOT Date](https://stackoverflow.com/questions/18604408/convert-java-string-to-time-not-date). – Ole V.V. Dec 11 '18 at 15:05
  • The results you say you get look completely correct to me. 09:12:15 UTC is the same time as 10:12:15 at GMT offset +01:00. What result did you expect? I am very much in doubt how you managed to get `Thu Dec 11 10:12:15 GMT+01:00 2018`, though, since December 11 this year is a Tuesday. – Ole V.V. Dec 11 '18 at 15:06
  • When you say the format is `hhmmss.ssss` and give `091215.000` as an example, are there three or four decimals on the seconds, or are both possible? – Ole V.V. Dec 11 '18 at 15:23
  • @Bryan I don't know why the timestamp contain not a date, this is my gps modul: https://www.quectel.com/product/l80r.htm – developKinberg Dec 11 '18 at 16:24
  • Four or three decimal places in the fractional second? Your Question is inconsistent. – Basil Bourque Dec 11 '18 at 16:24
  • @OleV.V. No the correct result would be `Tue Dec 11 10:12:15 GMT+01:00 2018` not Thu 13. And I add a + 1 to the `HOUR_OF_DAY` because my timezone is UTC + 1 (European Time). The time that I get from the Tracker is UTC – developKinberg Dec 11 '18 at 16:27
  • @BasilBourque this is the format that I get from the Tracker and I only use the `hhmmss` not the other numbers after the point. I write the whole format to this question for completeness. – developKinberg Dec 11 '18 at 16:29
  • @OleV.V.And both are possible but see my comment below. – developKinberg Dec 11 '18 at 16:32
  • Do you have a link to the documentation, not just the product page? – Bryan Dec 11 '18 at 16:46
  • Your example data does not match your specified format with regard to number of decimal places, `.ssss` versus `.000`. Edit your Question to be consistent. – Basil Bourque Dec 11 '18 at 16:56
  • I edit my question – developKinberg Dec 11 '18 at 17:36

2 Answers2

3

What went wrong in your code?

Your bug is in this line:

        calendar.set(Calendar.HOUR_OF_DAY, timestamp.charAt(1) + 1);

Characters are represented as numbers in computers. It’s a confusing fact of Java that characters and numbers can be used interchangeably in many cases. If timestamp.charAt(1) is the char '9' as in your example, it is represented as the number 57. When you add 1, you get 58. When you set the hour of day to 58 — if you had expected the Calendar class with default settings to report an error, you were wrong, this a confusing thing about that class and just one of the many reasons why we recommend you avoid using it. It just keeps counting hours into the next days and coincidentally ends up at the right hour of day, 10, only two days later (two days is 48 hours, and 48 + 10 = 58).

Other recommendation:

  • Don’t “hand parse” your time string. Leave parsing to a library class.
  • Don’t convert to Central European Time by adding an hour. It’s error-prone. During summer time (DST) it will give an incorrect result. It’s not portable to other time zones. Instead again leave the conversion to the library classes.

How to fix?

Basil Bourque already showed the good way to solve your problem using java.time, the modern Java date and time API. I’d like to show you that with java.time you can also include the fraction of second without much trouble if you like, though.

static DateTimeFormatter timeFormatter = new DateTimeFormatterBuilder()
        .appendPattern("HHmmss")
        .appendFraction(ChronoField.NANO_OF_SECOND, 3, 4, true)
        .toFormatter(Locale.ROOT);
static ZoneId zone = ZoneId.of("Europe/Paris");

public static ZonedDateTime utcToLocalTimeFromLock(String timestamp) {
    return LocalDate.now(ZoneOffset.UTC)
            .atTime(LocalTime.parse(timestamp, timeFormatter))
            .atOffset(ZoneOffset.UTC)
            .atZoneSameInstant(zone);
}

Let’s try:

    System.out.println(utcToLocalTimeFromLock(timestamp));

Output when running just now:

2018-12-11T10:12:15+01:00[Europe/Paris]

The appendFraction method takes a minimum and maximum number of decimals after the point, so I specified 3 and 4, respectively. Depending on your needs you may specify a minimum down to 0 and a maximum up to 9 digits.

Of course replace your own time zone if it didn’t happen to Europe/Paris.

If you indispensably need an old-fashioned Date object for a legacy API that you don’t want to upgrade just now:

public static Date utcToLocalTimeFromLock(String timestamp) {
    Instant inst= LocalDate.now(ZoneOffset.UTC)
            .atTime(LocalTime.parse(timestamp, timeFormatter))
            .atOffset(ZoneOffset.UTC)
            .toInstant();
    return Date.from(inst);
}

Tue Dec 11 10:12:15 CET 2018

If using the backport (ThreeTen Backport and/or ThreeTenABP, see below) for conversion from Instant to Date instead of Date.from(inst) use this:

    return DateTimeUtils.toDate(inst);

Because a Date doesn’t have a time zone, no time zone conversion is necessary in this case. Only because I happen to be in Central European Time zone too, does the output agree with what you expect — it will be the time in the JVM’s time zone.

Question: Can I use java.time on Android?

Yes, java.time works nicely on 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 Java 6 and 7 get the ThreeTen Backport, the backport of the new 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
2

java.time

You are using terrible old date-time classes that were supplanted years ago by the industry-leading java.time classes.

Capture the current moment in UTC as an OffsetDateTime object.

OffsetDateTime now = OffsetDateTime.now( ZoneOffset.UTC ) ;

Parse the time-of-day as a LocalTime.

String input = "091215.000" ;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "HHmmss.SSS" ) ;
LocalTime lt = LocalTime.parse( input ) ;

Apply our time of day.

OffsetDateTime odt = now.with( lt ) ;

You may have a problem near the stroke of midnight. You might want to add some code to see if the captured current time is in a new day but your time-of-day is shortly before midnight yesterday. If so subtract a day from now. Use whatever boundary time-of-day values make sense in your situation.

if ( 
    lt.isAfter( LocalTime.of( 23 , 55 ) ) 
    && 
    odt.toLocalTime().isBefore( LocalTime.of( 0 , 5 ) ) 
) {
    now = now.minusDays( 1 ) ;
}

Adjust from UTC to your desired time zone.

ZoneId z = ZoneId( "Africa/Tunis" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;

For Android <26, see the ThreeTenABP project.


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • Thanks for your answer, I implement it tomorrow and see how it works for me :) And I use `Date` because I want to save the Date, when I get data from my GPS Tracker, in my Room Database. When I searching for type converting I found this: https://developer.android.com/training/data-storage/room/referencing-data. So this is the reason why I use `Date`. – developKinberg Dec 11 '18 at 17:45
  • @developKinberg If you must use `java.util.Date` to interoperate with old code not yet updated to *java.time* classes, you can easily convert back-and-forth using new conversion methods added to the old classes. If using the *ThreeTen-Backport* & *ThreeTenABP* projects for older Android before 26, use the `DateTimeUtils` class to convert back-and-forth. – Basil Bourque Dec 11 '18 at 19:00