Comparing string object dates in Android Kotlin? - android

I'm using XmlPullParser to fetch and parse an RSS feed and store entries in a database. I'm storing the date as a String object in a format of "Dec 15, 2020" for example.
While I'm parsing the RSS feed I want to do a comparison to set a flag if the entry is before/after the data "Jan 1, 2021."
What would be the best way to do this? Is there a way to do it as strings or am I better off using Date objects?

You would parse the string as a LocalDate (because it seems that you are only comparing dates without timezone information) using the DateTimeFormatter from the modern Java/Kotlin time API in the java.time.* package.
In your case, since you want to parse a specific pattern, you would use DateTimeFormatter.ofPattern(pattern: String) to create the formatter. The whole class docs are available here
Then you would compare the parsed LocalDate to another LocalDate of your choice with .isBefore(other) or .isAfter(other) calls.
import java.time.LocalDate
import java.time.Month
import java.time.format.DateTimeFormatter
// ...
val rssDate = "Dec 15, 2020"
// your pattern
val dateFormatter = DateTimeFormatter.ofPattern("MMM d, yyyy")
val inputDate = LocalDate.parse(rssDate, dateFormatter)
val targetDate = LocalDate.of(2021, Month.JANUARY, 1)
if (inputDate.isBefore(targetDate)) {
// before
} else {
// after
}
Comparing date strings as strings, or otherwise dealing with dates (or time in general) without using a good time API will only lead to bugs. Also using the outdated older Java time APIs will eventually lead to bugs. Use the modern java.time.* API and beware of falsehoods we believe about time.
If you have to support older Android API levels (lower than 26) you need Android Gradle plugin of at least v4.0.0+ for the java.time.* APIs to be available (support for core library desugaring).

Related

Parse date from string on Android Lollipop

I have a date string like 2021-02-20 12:24:10 and I need to parse it to Date. I have the solution, which works on Nougat (API 24) and higher, but I need need the solution for Lollipop (API 21). The solution for Nougat is below
val parser = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val entryDate: Date = parser.parse(ticketResponse.entryDate)
I tried to use DateFormat, but I get ParseException when I try to parse my date
val entryDate: Date = DateFormat.getInstance().parse(ticketResponse.entryDate)
I understand it happens because my input string is not a standard representation of date, but I cannot easy change it, I get this date from server. I also didn't find way to set pattern for input date like for SimpleDateFormat.
I am surprised that where is no answer here in some old questions, all answers recommend to use SimpleDateFormat. Of course I can split date with dividers, but I would like to use not so bold approach.
if you haven't noticed there are two SimpleDateFormat classes with a different package: java.text.SimpleDateFormat; and android.icu.text.SimpleDateFormat;
please try to use the first one it is added in api level 1 chek this link :
https://www.datetimeformatter.com/how-to-format-date-time-in-kotlin/

How to send Date formatted with Z and T to an API using Retrofit?

I have the following example path to my API -
base_url/path/{country}/path/path?from=2020-03-01T00:00:00Z&to=2020-03-02T00:00:00Z
So I need to pass 2 Date objects using the Z and T formatting and I can't really get how to format a new Kotlin Date() object into these Z and T formatting.
My current get method -
#GET("path/{country}/path/path/")
suspend fun getCountryModelByDate(
#Path("country") country: String,
#Query("from") from : String,
#Query("to") to : String
): Model
But when I try to test my method like the following -
class RemoteDataSource(private val api: Api) {
suspend fun getCountryModelByDate(): Resource<Model> {
return try {
Resource.Success(coronaVirusApi.getCovidDeathsByDeathFromCountry("italy", Date().toString(), Date().toString()))
} catch (exception: Exception) {
Resource.Exception(exception)
}
}
}
Which causes me to get the following 404 error, look at the URL that is being sent -
Response{protocol=h2, code=404, message=, url=https://api.covid19api.com/country/italy/status/deaths/?from=Tue%20Nov%2017%2010%3A47%3A30%20GMT%2B02%3A00%202020&to=Tue%20Nov%2017%2010%3A47%3A30%20GMT%2B02%3A00%202020}
So my questions are -
How do I format a Kotlin Date object to have a Z and T format like what I need for my API?
How can I send the formatted date into my query without it being gibrished out?
Instant.toString() from java.time
Your format with T and Z is ISO 8601. You may want to consult the link at the bottom. The classes of java.time, the modern Java date and time API, produce ISO 8601 from their toString methods. Use the Instant class. For a brief demonstration in Java:
Instant i = Instant.now();
System.out.println(i);
Example output:
2020-11-18T09:31:33.613965Z
If the Instant falls on midnight in UTC, as yours do, the output will be like:
2020-11-18T00:00:00Z
The presence of decimals on the seconds in the first case probably is no issue for your API since the fraction is optional according to the ISO 8601 standard. Should you want to get rid of it, it’s easiest to truncate the Instant:
Instant instantToPrint = i.truncatedTo(ChronoUnit.SECONDS);
System.out.println(instantToPrint);
2020-11-18T09:31:33Z
How can I make the instant object always return T00:00:00Z as this is
what my API requires?
Edit: Given that you have already got an instant that falls on the desired day in UTC, just truncate to whole days:
Instant instantToPrint = i.truncatedTo(ChronoUnit.DAYS);
2020-11-18T00:00:00Z
The question is, though, where your date comes from. Using java.time a day in the calendar would be represented by a LocalDate, so we’d need to convert.
LocalDate fromDate = LocalDate.of(2020, Month.MARCH, 1);
Instant fromDateTimeUtc = fromDate.atStartOfDay(ZoneOffset.UTC).toInstant();
System.out.println(fromDateTimeUtc);
2020-03-01T00:00:00Z
How can I send the formatted date into my query without it being gibrished out?
I believe that a colon in a URL needs to be URL encoded (except the colon after the protocol, the one in https://). So it should become %3A. The rest of the string from Instant should be clear to read.
Links
Wikipedia article: ISO 8601
Oracle tutorial: Date Time explaining how to use java.time.
Java Specification Request (JSR) 310, where java.time was first described.
ThreeTen Backport project, the backport of java.time to Java 6 and 7 (ThreeTen for JSR-310).
Java 8+ APIs available through desugaring
ThreeTenABP, Android edition of ThreeTen Backport
Question: How to use ThreeTenABP in Android Project, with a very thorough explanation.

How to save Timestamp value in Cloud Firestore and perform query on it?

I've followed this Stackoverflow post and there is clearly said how to store the timestamp, if we user the ServerTimestamp annotation. However, I want to ask about other alternatives, which can later also be used to perform queries on them.
With respect to the different time libraries (before/after Java 8) we can get the timestamp as a long variables, or as ZonedDateTime object. My question is:
Can we save ZonedDateTime object or a long variable as a String and then perform queries on it, or we must use in any case ServerTimestamp annotation?
If you want to write a timestamp field, and you can't use a server timestamp, you will need to either provide a regular Java Date object, or a Firebase Timestamp object. Either of these types of values will convert to a timestamp field in the document.
When you query for the document, the field will always show up in the document snapshot as a Timestamp type object.
I've figured it out. As you know when we're dealing with Time in Android, we need to pay attention to the different APIs i.e. APIs before API 26 and after that. There are many articles for this topic in Stackoverflow, but generalized, before Android 8 (Oreo, API 26) we need to use the library ThreeTenABP and after Android 8 we need to use java.time.
An implementation looks like this:
private Date getCurrentDate(){
if(Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O){
return Date.from(Instant.now());
} else {
AndroidThreeTen.init(this);
return DateTimeUtils.toDate(org.threeten.bp.Instant.now());
}
}
Of course, you can use LocalDateTime or ZonedDateTime etc.
Firebase expects Date object, or you can use the #ServerTimestamp annotation and provide no parameter in the constructor of your model. Firebase will then create the timestamp when request for saving data comes to it. Like this
//When you provide Date
class SomeClass {
private String someAttribute;
private Date someDate;
...
public SomeClass(String someAttribute, Date someDate, ...){
this.someAttribute = someAttribute;
this.someDate = someDate;
}
}
//When you don't provide Date
class SomeClass {
private String someAttribute;
private #ServerTimestamp Date someDate;
...
public SomeClass(String someAttribute, ...){
this.someAttribute = someAttribute;
...
}
}
(In may case I needed the Date object when I start an activity, but a request to the server may happen even after 2-3 hours. That's why #ServerTimestamp was inefficient for me.)
The retrieving happens in the same way. Create a model class, or use HashMap (your choice) but the type must be Date. It holds almost every kind of information according to date and time (time zone, the time is even up to microseconds etc).
Most of the time, the user wants to see only a date and a time in form of hours:minutes.
You can use DateTimeFormatter and provide the pattern which you want (there are plenty of standards). In my case I use it in this way:
private String formatDate(Date date){
if(Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O){
java.time.LocalDateTime ldt = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
java.time.format.DateTimeFormatter dtf = java.time.format.DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm");
return ldt.format(dtf);
} else {
LocalDateTime ldt = Instant.ofEpochMilli(date.getTime()).atZone(org.threeten.bp.ZoneId.systemDefault()).toLocalDateTime();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm");
return ldt.format(dtf);
}
}
As you see the method expects a Date object (what you retrieve from Firebase). Then it will remove most of the irrelevant data (in my case) and return you only the date and the time in the specified format.
(Note: in this post where UPDATE 2020/01/17 is, a colleague points that with Android Studio 4.0 we can use Java 8 in older APIs as well (like in my case - API 21), but I didn't dig into the topic concrete. You can check it also :)

How to prevent my strings from being translated to other languages than English?

I am trying to prevent my app from automatically being translated to other languages (such as Bulgarian in my case). I want all of my strings to be in English. I tried setting the timezone to "Europe\London" (because I am in the UK) but that did not quite work. Is there a way how I can ensure that my app's settings (all of them) are not being translated when somebody install my app in other than the UK country?
I am using dates in my app and I am using SimpleDateFormatter. I think this is causing the issue of translating some of the strings. So what I did is set the timezone to it as well before using the strings from it like that:
public static SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("Europe/London"));
String time = sdf.format(new Date());
mPurchasedDate.setText(day + " " + numDay + " " + mont + " at " + time);
But that did not work either.
PS: I have not added any localization in my app. I have only one strings.xml folder and the strings there are in English.
If you only want to use a specific Locale for SimpleDateFormat, use the constructor that takes a Locale: new SimpleDateFormat(String, Locale):
public static SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss", Locale.UK);
The date-time API of java.util and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern date-time API. For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and 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.
In order to stick to English, you need to specify Locale.ENGLISH with the date-time formatting/parsing API. Anyway, one of the best practices is to use Locale with the formatting/parsing API in every case.
Modern API:
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("HH:mm:ss", Locale.ENGLISH);
Legacy API:
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);
Note that the java.util.Date object is not a real date-time object like the modern date-time types; rather, it represents the milliseconds from the Epoch of January 1, 1970. When you print an object of java.util.Date, its toString method returns the date-time calculated from this milliseconds value. Since java.util.Date does not have timezone information, it applies the timezone of your JVM and displays the same. If you need to print the date-time in a different timezone, you will need to set the timezone to SimpleDateFomrat and obtain the formatted string from it.
In contrast, modern date-time API has specific classes representing just date, or time or date-time. And, for each of these, there are separate classes for with/without timezone information. Check out the following table for an overview of modern date-time types:
A quick demo:
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
LocalTime time = LocalTime.now(ZoneId.of("Europe/London"));
// Print its default format i.e. the value returned by time#toString
System.out.println(time);
// Custom format
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("hh:mm:ss", Locale.ENGLISH);
System.out.println(time.format(dtf));
}
}
Output:
12:39:07.627763
12:39:07
Learn more about the modern date-time API from Trail: Date Time.
set translatable to false in strings.xml for every string which u want not to be translated into any other language
<string name="account_setup_imap" translatable="false">IMAP</string>

How to get hours from JodaTime with timezone

I encounter strange behaviour with Jodatime and Android. I want to parse string:
2014-05-19T18:13:00.000+02:00
to DateTime, and get year, month, hours to int. I started with some test on IntelliJ Studio, and I done something like that:
String date = "2014-05-19T18:13:00.000+02:00";
DateTime dateTime = new DateTime(date);
System.out.println(dateTime.toString());
System.out.println(dateTime.getYear());
System.out.println(dateTime.getMonthOfYear());
System.out.println(dateTime.getDayOfMonth());
System.out.println(dateTime.getHourOfDay());
System.out.println(dateTime.getMinuteOfHour());
System.out.println(dateTime.getMillis());
Which gave me correct answers:
2014-05-19T18:13:00.000+02:00
2014
5
19
18
13
1400515980000
Now, when I changed IDE to Android Studio, and do the same:
String dateT = "2014-05-19T18:13:00.000+02:00";
DateTime dateTime = new DateTime(dateT);
Lo.g(dateTime.getHourOfDay() + "");
Lo.g(dateTime.toString());
my results are:
16
2014-05-19T16:13:00.000Z
For some reason DateTime on Android Studio / Android not take into account the timezone which is +2:00.
I can not find solution for this. Also there is no simple method "addTimeZone" in Joda.
How to display correct time with DataTime? I tried LocalDateTime, construct DateTime with DateTimeZone.getDefault() (which gaves me UTF...)
Since you said that you use the same Joda-Time version on both platforms and regarding the fact that Joda-Time has its own timezone repository independent from system timezone data, there is probably only one explanation left why you observe different behaviour: Different input either explicit or implicit. Let's go into details:
Well, you say, obviously there is the same input given the same input string:
"2014-05-19T18:13:00.000+02:00"
So we have the same (explicit) input. But wait, there is another thing: implicit default settings which can also be considered as kind of input in an abstract way. You use the constructor DateTime(Object). This constructor first delegates to super constructor of class BaseDateTime as you can see in the source code.
public DateTime(Object instant) {
super(instant, (Chronology) null);
}
The javadoc of this super-constructor says:
"Constructs an instance from an Object that represents a datetime,
using the specified chronology. If the chronology is null, ISO in the
default time zone is used.
The recognised object types are defined in ConverterManager and
include ReadableInstant, String, Calendar and Date."
So finally we see that Joda-Time uses the default timezone. This is really the only possibility for different behaviour I can see by studying the source code and the documentation. All other things are equal: Same library version and same explicit string input and same test scenario.
Conclusion: You have different default timezones on your platforms. Note that you get the same instant on both platforms however, just represented with different local timestamps and offsets due to different internal timezone/offset setting inside the DateTime-object.
Update: I have tested the zone overriding behaviour with this code:
String date = "2014-05-19T19:13:00.000+03:00"; // same instant as yours (just with offset +03)
DateTime dateTime = new DateTime(date);
System.out.println(dateTime.toString());
// output: 2014-05-19T18:13:00.000+02:00 (in my default timezone Europe/Berlin)
So the default timezone has precedence over any offset in string input. If you instead want to use the parsed offset then look at using the DateTimeFormatter and its method withOffsetParsed().

Categories

Resources