4

I want to store a string into a databse (SQLite) for an Android App with the current time and date. For that purpose I am using SimpleDateFormat. Unfortunately it does not show the correct time when. I tried two options.

First Option (from SimpleDateFormat with TimeZone)

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.getDefault());
        sdf.format(new Date());

Second option (from Java SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") gives timezone as IST)

    SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss'Z'");
    sdf2.setTimeZone(TimeZone.getTimeZone("CEST"));

In both cases the time is just wrong. It is not the local time that my laptop or phone is showing but the output time is 2 hours earlier. How can I change that? I would like to have the current time of Berlin (CEST) that is also shown on my computer. I appreciate every comment.

VanessaF
  • 515
  • 11
  • 36
  • Why not store the current time as unix timestamp? Then convert the ms into a date string when needed. Saves some bytes and can be converted to any other timezone without much hassle. – Viatcheslav Ehrmann Sep 27 '20 at 13:20
  • Store `System.currentTimeMillis();` parsed as a `String`. Use it along with a `Calendar` to display it as text again. If you'd like to know how to do this, Please comment... I'll post an answer. – Vishnu Sep 27 '20 at 13:24
  • Thanks Viatcheslav and Vishnu for your comments. I already have an entry in the database with the current time in milliseconds. This should be a second entry. The first entry (in milliseconds) is used for quering entries between two time points. As the entry in milliseconds in not 'readable' for humans, I would also like to have a second readable entry in the database – VanessaF Sep 27 '20 at 13:24
  • @VanessaF I'll post an answer now to make *human-readable* time from MilliSeconds.. – Vishnu Sep 27 '20 at 13:28
  • 1
    As an aside consider throwing away the long outmoded and notoriously troublesome `SimpleDateFormat` and friends. See if you either can use [desugaring](https://developer.android.com/studio/write/java8-support-table) or add [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. Sep 27 '20 at 13:39
  • Can you be sure that your app will never ever be used in any other place than Berlin or at least Germany? If only 99.9 % sure, consider storing the time in UTC (that’s probably the time you got), it’s time zone neutral, and convert to the time zone of the device only for display. – Ole V.V. Sep 27 '20 at 13:43
  • @Vishnu tanks for the answer but it was a really bad one. The time was wrong and also the format was not nice at all. – VanessaF Sep 27 '20 at 13:47
  • @OleV.V.: Thanks for your comment. Ideally I would like to store the local time on which the device is running. – VanessaF Sep 27 '20 at 13:47
  • I would recommend Ole V.V. 's stance. If your app is gonna have a new column dedicated to record time better opt for Java.time – MdBasha Sep 27 '20 at 13:48
  • @OleV.V.: Basically I tried to use the new Java Time API. Unfotunately when I use it I get an error message or a warning in Android Studio telling me : " Call requires API level 26 (current min is 15): java.time.LocalDateTime#format " This means I can only use the Java Time API in new devices which is definetely not what I want – VanessaF Sep 27 '20 at 13:49
  • No, @VanessaF, it does not mean that. You will, however, need some external dependency for using java.time on older Android devices, either desugaring or ThreeTenABP as explained in my answer. On the other hand, I would certainly consider java.time not only the programmer-friendly but also the future-proof choice, so it’s probably worth paying that price. – Ole V.V. Sep 27 '20 at 13:57

3 Answers3

6

Use Europe/Berlin instead of CEST and you will get the expected result.

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

public class Main {
    public static void main(String[] args) {
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
        sdf2.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
        System.out.println(sdf2.format(new Date()));
    }
}

Output:

2020-09-27 18:38:04 +0200

A piece of advice:

I recommend you switch from the outdated and error-prone java.util date-time API and SimpleDateFormat to the modern java.time date-time API and the corresponding formatting API (package, java.time.format). Learn more about the modern date-time API from Trail: Date Time. If your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.

Using the modern date-time API:

import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

public class Main {
    public static void main(String[] args) {
        ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Europe/Berlin"));

        // Default format
        System.out.println(zdt);

        // Some custom format
        System.out.println(zdt.format(DateTimeFormatter.ofPattern("EEEE dd uuuu hh:mm:ss a z")));
    }
}

Output:

2020-09-27T18:42:53.620168+02:00[Europe/Berlin]
Sunday 27 2020 06:42:53 pm CEST

The modern API will alert you whereas legacy API may failover:

import java.time.ZoneId;
import java.time.ZonedDateTime;

public class Main {
    public static void main(String[] args) {
        ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("CEST"));
        // ...
    }
}

Output:

Exception in thread "main" java.time.zone.ZoneRulesException: Unknown time-zone ID: CEST
    at java.base/java.time.zone.ZoneRulesProvider.getProvider(ZoneRulesProvider.java:279)
    at java.base/java.time.zone.ZoneRulesProvider.getRules(ZoneRulesProvider.java:234)
    at java.base/java.time.ZoneRegion.ofId(ZoneRegion.java:120)
    at java.base/java.time.ZoneId.of(ZoneId.java:408)
    at java.base/java.time.ZoneId.of(ZoneId.java:356)
    at Main.main(Main.java:6)

As you can see, you get an exception in this case whereas SimpleDateFormat will give you undesirable result as shown below:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

public class Main {
    public static void main(String[] args) {
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
        sdf2.setTimeZone(TimeZone.getTimeZone("CEST"));
        System.out.println(sdf2.format(new Date()));
    }
}

Output:

2020-09-27 16:47:45 +0000

You might be wondering what this undesirable result refers to. The answer is: when SimpleDateFormat doesn't understand a time-zone, it failovers (defaults) to GMT (same as UTC) i.e. it has ignored CEST and applied GMT in this case (not a good feature IMHO ).

Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
  • Thanks Arvind for your answer. Is it possible to get the timezone from the local device meaning that the time should be equal to the clock of the running device? – VanessaF Sep 27 '20 at 15:08
  • 1
    Thanks Arvind. When using the SimpleDateFormat only for storing a value into a database that is even non-functional (meaning that nothing is done with it). What problems might occur (in my example) when using the SimpleDateFormat? – VanessaF Sep 27 '20 at 15:10
  • Don’t hardcode `Z` as a literal in the format pattern string. It will be understood as UTC, and the time is *not* in UTC, you made sure. So people will be sure to misread the time. Instead print the offset just like VanessaF does in the first snippet in the question. @VanessaF You have already had your problems with `SimpleDateFormat`. If you get it to work both for times in summer and winter, I don’t expect further problems until the day you or someone else touches the code, which may happen sooner than you expect, then the trouble could start over. – Ole V.V. Sep 27 '20 at 16:24
  • @VanessaF - I've updated my answer to show where `SimpleDateFormat` has failed to give you the desired result whereas the modern date-time API has smartly alerted you by throwing the relevant exception. – Arvind Kumar Avinash Sep 27 '20 at 16:52
  • Thanks for your answer and huge effort Arvind. I really appreciate it. I use the solution with SimpleDateFormat and the time is being displayed correctly. I have 3 additional questions? 1) Will the time automatically change when we do not have summer time any more in Germany (End of October)? 2) Is the entry there is an additional information +0200. Can I get rid of that because I don't need it and I think it is confusing? 3) Is there an option to get the timezone from the device the App is running at meaning that I do not have to specify "Europe/Berlin"? – VanessaF Sep 28 '20 at 19:58
  • As stated in the chat I decided NOT to use the Java Time API because it has cruical drawbacks regarding the compability with older Android Versions. While I fully support your view that it is basically the better option compared to SimpleDateFormat I am convinced that for my application the good old SimpleDateFormat is better as it can be used with older Android Versions and ThreadSafty is not important for my application. – VanessaF Sep 28 '20 at 20:00
  • @VanessaF - 1) Will the time automatically change when we do not have summer time any more in Germany (End of October)? - `Yes`. 2) Is the entry there is an additional information +0200. Can I get rid of that because I don't need it and I think it is confusing? - `Use z instead of Z`. 3) Is there an option to get the timezone from the device the App is running at meaning that I do not have to specify "Europe/Berlin"? - `Check` https://stackoverflow.com/a/63799256/10819573 – Arvind Kumar Avinash Sep 28 '20 at 20:30
  • Thanks a lot Arvind for your answers and efforts and your great help. Regarding 3): Can I use the solution together with SimpleDateFormat? Because the solution itself is a Format object this is why I am not sure whether I can combine it with SimpleDateFormat – VanessaF Sep 28 '20 at 20:34
  • @VanessaF - Check https://stackoverflow.com/questions/63579862/android-how-to-get-default-date-time-format – Arvind Kumar Avinash Sep 28 '20 at 20:36
  • Further I do not use JSON or an URL. Is there no native way to tell the application to use just the time of the computer clock? – VanessaF Sep 28 '20 at 20:36
  • Is there no native way to tell the application to use just the time of the computer clock? - You can use `TimeZone.getDefault()`. – Arvind Kumar Avinash Sep 28 '20 at 20:38
  • Thanks Arvind for your comments. Regarding question 2) I used z instead of Z but this did not change the additional information. It is still being displayed. I'd like to get completely rid of it. – VanessaF Sep 28 '20 at 20:38
  • 1
    Then use just `yyyy-MM-dd HH:mm:ss`. – Arvind Kumar Avinash Sep 28 '20 at 20:39
  • Thanks Arvind for your comments and effort. I really appreciate it. Regarding 3) I tried your solution with TimeZone.getDefault() but the daytime is WRONG (2 hours earlier). I'd like to get the real current time not something wrong. – VanessaF Sep 28 '20 at 20:41
  • Thanks Arvind for your comment. "yyyy-MM-dd HH:mm:ss" works .fine :-). But I still have the problem getting the correct localTime automatically without specifying the TimeZone – VanessaF Sep 28 '20 at 20:46
  • Hi Arvind, do you have any solution for the problem with the wrong time when using your suggested solution "TimeZone.getDefault()". As said before I get the wrong time (by 2 hours). I'd appreciate further comments. – VanessaF Sep 29 '20 at 17:27
  • @VanessaF If there’s a possibility that something interesting will happen in the night bewteen 24 and 25 October, you will not want to do without the offset (+02:00 or +01:00). The clocks are turned back from 03:00 to 02:00, and if you read, say, 02:17 in your database without the offset, you cannot know *which* 02:17. And having it there does no harm. – Ole V.V. Sep 29 '20 at 19:33
  • Thanks Ole for your answer. I understand what you say. But my question to Arvind was how to get the local time automatically. His suggested solution "TImeZone.getDefault()" does not provide the correct local time. This is why I am looking for another way how to realize that. – VanessaF Sep 30 '20 at 19:47
  • Apart from that I generally do not want to use the +02:00 z information because I do not really need it. In my database I have two time entries so I from the other entry (in Milliseconds) I can see (or query) the correct information. – VanessaF Sep 30 '20 at 19:52
  • @ArvindKumarAvinash: Thanks for your answer Arvind. Your suggested code "TimeZone.getDefault()" does not provide the correct local time. Do you know how I can get the correct local time? I'd appreciate any further comment from you. – VanessaF Oct 01 '20 at 19:10
  • @VanessaF - I retract my suggestion as it was made in a hurry. I'm sorry for it. The `TImeZone.getDefault()` will return the timezone of the server whereas you want the timezone of the device. So, I'm going back to my original suggestion i.e. to use https://stackoverflow.com/questions/63793425/android-how-to-get-the-default-date-format-based-on-device-timezone/63799256#63799256. – Arvind Kumar Avinash Oct 01 '20 at 19:50
  • Thanks Arvind for your answer and effort. Is there no alternative because I do not want to use JSON and thus increase the complexity of the application (I have never worked with JSON). Further, I can imagine that the Geolocation API can't be used in older Android Devices. So I would favour a solution with SimpleDateFormat that gives me the local time of the device (without JSON). – VanessaF Oct 02 '20 at 15:24
  • Is there also a solution without JSON? – VanessaF Oct 06 '20 at 19:43
  • @VanessaF - Not that I am aware of. There is another one [here](https://github.com/graphhopper/timezone) – Arvind Kumar Avinash Oct 07 '20 at 12:19
  • Thanks Arvind for your answer. The answer really suprises me that there is no option to get the local time of a device without using some complicated JSON application and new libraries. But I trust you on that and I would like to thank you for your great help and effort. – VanessaF Oct 08 '20 at 17:31
  • @VanessaF - After some search, I found [this Q/A](https://stackoverflow.com/questions/13/determine-a-users-timezone) which can be useful to you. – Arvind Kumar Avinash Oct 09 '20 at 23:04
4

ISO 8601

Assuming that your SQLite hasn’t got a datetime datatype I recommend that you use ISO 8601 format, the international standard, for storing your date-times as strings to SQLite. Next, consider using java.time, the modern Java date and time API, for your date and time work. The two suggestions go nicely hand in hand. Common recommendations say to store date and time in UTC, but I understand that you prefer Europe/Berlin time.

    ZoneId databaseTimeZone = ZoneId.of("Europe/Berlin");
    
    ZonedDateTime now = ZonedDateTime.now(databaseTimeZone);
    String databaseTime = now.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
    
    System.out.println(databaseTime);

Output from the above when running just now:

2020-09-27T15:54:21.53+02:00

I have on purpose included the offset from UTC in the string. This will allow anyone retrieving the string to convert the time to UTC or the time zone of their preference. If the user travels to India to see Taj Mahal and retrieves the data there, converting to India Standard Time is no problem. The offset also disambiguates times in the night in October when Berlin changes from summer time (DST) to standard time and the same clock times repeat. Times before the change will have offset +02:00, times after the change will have +01:00.

How can I change the format(?)

Edit: If you insist on your own format for information and human readability, build a formatter for that. The ZonedDateTime already has the time in your chosen time zone, so that time is also the one you will have when you format it:

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss Z");
    String databaseTime = now.format(formatter);

Now the result is:

2020-09-27 16:22:23 +0200

Further edit: Since human readability is the only requirement for that column, go all-in on that and use java’s predefined localized format, for example:

    DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)
            .withLocale(Locale.GERMAN);
  1. September 2020 19:06:03 MESZ

If it’s too long for you, use FormatStyle.MEDIUM instead.

Further further edit: And why? The question is whether 27. September 2020 19:06:03 MESZ is easier to read and understand correctly than 2020-09-27 16:22:23 +0200. You should make it as easy for yourself as you reasonably can. There is a point in including the offset, +0200, though, since it is unambiguous whereas a time zone abbreviation like MESZ is not guaranteed to be (many time zone abbreviations are ambiguous).

What went wrong in your code?

You are probably running your code on a computer with its time zone set to UTC (or some other time zone that is currently two hours behind Berlin time). In your second snippet you are trying to make up for this fact by setting the time zone of you formatter to CEST (Central European Summer Time). The way you are doing that is not what you want, and it also does not work. Both have to do with the fact that CEST is not a time zone. CEST is two hours ahead of UTC, and if it had worked, you would have got two hours ahead of UTC also during the standard time of year where Berlin is only 1 hour ahead of UTC, that is, the wrong time. Since CEST is not a time zone, TimeZone does not recognize it as a time zone. And this is as confusing as the TimeZone class is: instead of objecting, it tacitly gives you GMT, so you have got nowhere. I really recommend avoiding using that class. The correct time zone identifier for Berlin is Europe/Berlin, the one I am also using in my code. Time zone identifiers come in the region/city format.

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 either use desugaring or the Android edition of ThreeTen Backport. It’s called ThreeTenABP. In the latter case make sure you import the date and time classes from org.threeten.bp with subpackages.

Links

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • Thanks Ole for your detailed answer and your effort. First of all I want to say that I am not using the Datatype datetime in my SQLite database. Basically I have 2 database entries for storing the data. The first stores the date in Milliseconds (for quering) and the second one store the data as a String. – VanessaF Sep 27 '20 at 14:05
  • I personally do not really like the output format "2020-09-27T15:54:21.53+02:00". I would like not to have the "T" and to have the real time instead of "+02:00". Is it not possible in Java or Android just to store the time of the clock that is running on the device? – VanessaF Sep 27 '20 at 14:08
  • Ooh, beware of redundancy in your database. If you want to use ISO 8601 for querying including queries involvong *before* and *after*, it takes two measures, though: store all times with the same number of decimals (you can build the correct formatter for doing that) and more imprtantly, store all times at the same UTC offset (again I suggest UTC itself). – Ole V.V. Sep 27 '20 at 14:09
  • Yes, you can have any format you like. Following standards is usually recommended over inventing your own format, but it’s up to you, obviously. Most people get used to the `T` pretty quickly. – Ole V.V. Sep 27 '20 at 14:10
  • Thanks Ole for your answer. When you store the time in Milliseconds it is quite easy to query. You do not need a correct formatter. This is why I have one database entry as Milliseconds and the other one as String. The String entry just servers for me personally to have a look at the entries. For quering I use the Milliseconds entry. As I have already created several methods for that, I do not necessarily want to change this format. – VanessaF Sep 27 '20 at 14:12
  • Basically here in this question it is recommended to store the time as milliseconds in SQLite https://stackoverflow.com/questions/7363112/best-way-to-work-with-dates-in-android-sqlite This is why I also choose this as I clearly see the benefits. – VanessaF Sep 27 '20 at 14:15
  • It may not pertain very precisely to your exact situation, but I still think it’s worth reading: [The Problem of redundancy in Database](https://www.geeksforgeeks.org/the-problem-of-redundancy-in-database/). – Ole V.V. Sep 27 '20 at 14:15
  • Thanks for your comments and effort. I really appreciate it. You wrote "Yes, you can have any format you like. " This is exactly my question. How can I change the format. I tried it (see my question) but the time is not displayed correctly when using SimpleDateFormat. – VanessaF Sep 27 '20 at 14:17
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/222150/discussion-between-ole-v-v-and-vanessaf). – Ole V.V. Sep 27 '20 at 14:17
  • Thanks Ole for your additional answer and your huge effort and great help. I really appreciate it. As stated in the chat I decided NOT to use the Java Time API because it has cruical drawbacks regarding the compability with older Android Versions. While I fully support your view that it is basically the better option compared to SimpleDateFormat I am convinced that for my application the good old SimpleDateFormat is better as it can be used with older Android Versions and ThreadSafty is not important for my application. – VanessaF Sep 28 '20 at 20:01
  • You posted something about "DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG) .withLocale(Locale.GERMAN);" Why is this solution for the readability better than the SimpleDateFormat solution? The SimpleDateFormat solution is correct and the time is easy to read (can't imagine how it could be easier to read). I use 2 time columns in my database. One for readability (here I use the SimpleDateFormat) and the other one is basically for quering where I store the date in Milliseconds as recommended in a StackOverFlow discussion. – VanessaF Sep 28 '20 at 20:10
  • Thanks Ole for your comment and effort and great help. I tried to use your new solution but it has the same cruical drawback as the Java Time API. You can't use it in older Android versions so I will stick to the SimpleDateFormat – VanessaF Sep 29 '20 at 17:25
1

Well, i faced the same issue a week ago and I figured out that the problem is in the TimeZone settings

If you are getting the date as a string and you need to format it to another format use the code below

public String getCalendarDate(String inputDate){
        Date date = getDateFromSource(inputDate);
        SimpleDateFormat formatter = new SimpleDateFormat("EEEE, d MMMM yyyy", Locale.getDefault());
        formatter.setTimeZone(TimeZone.getDefault());
        return formatter.format(date);
    }

    Date getDateFromSource(String apiDate){
        Date newFormattedDate = null;
        SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH);
        parser.setTimeZone(TimeZone.getTimeZone("UTC"));
        try {
             newFormattedDate = parser.parse(apiDate);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return newFormattedDate;
    }

In the getDateFromSource function change the date format to the source format, while in the getCalendarDate function, change the format to your required format.

If you already have the Date object, you can ignore the getDateFromSource function and put it directly in the second one

For those who use Kotlin this is the equivalent code

    fun getCalendarDate(apiDate: String): String{
        val date = getDateFromApi(apiDate)
        val formatter = SimpleDateFormat("EEEE, d MMMM yyyy", Locale.getDefault())
        formatter.timeZone = TimeZone.getDefault()
        return formatter.format(date)
    }

    private fun getDateFromApi(apiDate: String) :Date{
        val parser = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH)
        parser.timeZone = TimeZone.getTimeZone("UTC")
        return parser.parse(apiDate)!!
    }
Hamza Sharaf
  • 811
  • 9
  • 25
  • Don’t hardcode ´Z` as a literal in the format pattern string. It’s an offset (of zero) from UTC, so parse it as that to get the correct result. I know that when there is no other zone or offset in the string and the formatter’s time zone is UTC, you will still get the correct result, but one day a junior programmer changes one of those prerequisites and will not understand that the times are now wrong. So for the sake of maintainable code. – Ole V.V. Sep 27 '20 at 16:42