I am new for using RxJava and Room. What I trying to do is run a for loop to get data from database. The for loop iterate from first day of month to the last day of month.
Here is the Dao for this query.
#Query("SELECT SUM(duration) FROM xxx WHERE timeStamp >= :start and timeStamp <= :end and userId = :userId")
Flowable<Integer> getDuration(String userId, long start, long end);
And Here is how i using RxJava to get the result.
Calendar day1 = Calendar.getInstance();
Calendar day2 = Calendar.getInstance();
int maxLoopIndex = day1.getActualMaximum(Calendar.DAY_OF_MONTH);
day1.setFirstDayOfWeek(Calendar.MONDAY);
day2.setFirstDayOfWeek(Calendar.MONDAY);
day1.set(Calendar.DATE, day1.getActualMinimum(Calendar.DATE));
day2.set(Calendar.DATE, day2.getActualMinimum(Calendar.DATE));
day1.set(Calendar.HOUR_OF_DAY, 0);
day1.set(Calendar.MINUTE, 0);
day1.set(Calendar.SECOND, 0);
day1.set(Calendar.MILLISECOND, 0);
day2.set(Calendar.HOUR_OF_DAY, 23);
day2.set(Calendar.MINUTE, 59);
day2.set(Calendar.SECOND, 59);
day2.set(Calendar.MILLISECOND, 999);
ArrayList<Pair<Long, Long>> maxDayCount = new ArrayList<>();
//Get all the timeStamp in a month, where maxDayCount can be 30, 31, 28, 29.
for (int i = 0; i < maxDayCount; i++) {
Pair<Long, Long> P = Pair.create(day1.getTimeInMillis(), day2.getTimeInMillis());
pairArrayList.add(P);
day1.add(Calendar.DATE, 1);
day2.add(Calendar.DATE, 1);
}
// Using Flowable.formIterable to run through the list and get the data from room
Flowable.fromIterable(pairArrayList)
.flatMap(new Function<Pair<Long, Long>, Flowable<Integer>>() {
#Override
public Flowable<Integer> apply(#NonNull Pair<Long, Long> date) throws Exception {
return roomdb.Dao().getDuration(
User.getCurUser().getId(), date.first, date.second
);
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Integer>() {
#Override
public void accept(#NonNull Integer source) throws Exception {
Log.d(TAG, "Duration: "+source);
// I want to get the index of pairArrayList to store the duration in
// corresponding array
}
});
However in subscribe I can get the result return by room however I can not get which index is run in pairArrayList. Is there any way I can get the index? Furthermore is there any better way to get data from room with the loop?
Let's begin with the final structure. It should contain the day of month and duration:
class DayDuration {
public Integer day;
public Long duration;
public DayDuration(Integer day, Long duration) {
this.day = day;
this.duration = duration;
}
#Override
public boolean equals(Object o) { /* implementation */ }
#Override
public int hashCode() { /* implementation */ }
}
Creation of final Flowable what emits requested items might look like the following code. I have used ThreetenBP library to handle date/time operations because Android Calendar API is pure hell. Recommend you do the same:
class SO64870062 {
private Flowable<Long> getDuration(String userId, long start, long end) {
return Flowable.fromCallable(() -> start); // mock data
}
#NotNull
private Flowable<LocalDate> getDaysInMonth(YearMonth yearMonth) { // (1)
LocalDate start = LocalDate.of(yearMonth.getYear(), yearMonth.getMonthValue(), 1);
LocalDate end = start.with(TemporalAdjusters.lastDayOfMonth());
return Flowable.create(emitter -> {
LocalDate current = start;
while (!current.isAfter(end)) { // (2)
emitter.onNext(current);
current = current.plusDays(1);
}
emitter.onComplete();
}, BackpressureStrategy.BUFFER);
}
#NotNull
private Flowable<DayDuration> getDurationForDay(String userId, LocalDate localDate) {
long startDayMillis = localDate.atStartOfDay().atZone(ZoneOffset.UTC) // (3)
.toInstant()
.toEpochMilli();
long endDayMillis = localDate.atTime(LocalTime.MAX).atZone(ZoneOffset.UTC)
.toInstant()
.toEpochMilli();
return getDuration(userId, startDayMillis, endDayMillis) // (4)
.map(duration -> new DayDuration(localDate.getDayOfMonth(), duration));
}
public Flowable<DayDuration> getDayDurations(String userId, YearMonth yearMonth) {
return getDaysInMonth(yearMonth)
.flatMap(localDate -> getDurationForDay(userId, localDate));
}
}
Important and interesting parts:
Function getDaysInMonth() creates Flowable what emits all days of requested month.
Iteration from start (first day of a month) to end (last day of a month) date and emitting all of the days.
Make sure you set the zone you use within timestamps in your database. I have used UTC for simplicity.
Combine duration from a database with the current date.
Last but not least, let's check if it works correctly:
public class SO64870062Test {
#Test
public void whenDaysRequestedForApril2020ThenEmitted() {
SO64870062 tested = new SO64870062();
TestSubscriber<DayDuration> testSubscriber = tested
.getDayDurations("userId", YearMonth.of(2020, 11))
.test();
testSubscriber.assertValueCount(30);
testSubscriber.assertValueAt(1, new DayDuration(2, 1604275200000L));
testSubscriber.assertComplete();
}
}
Related
I'm making a exchange rate app and I have a screen with a graph that shows changes of the selected currency in the last 7 days.
Now what I wanna get is to emit items in strict order.
Here is my code:
public class GraphInteractorImpl implements GraphInteractor {
private final Retrofit retrofit;
#Inject
public GraphInteractorImpl(Retrofit retrofit) {
this.retrofit = retrofit;
}
#Override
public void downloadData(GraphListener listener) {
RestAPI api = retrofit.create(RestAPI.class);
List<String> listDates = getDates();
for (String date : listDates) {
Observable<List<ExchangeRate>> observable = api.getExchangeRatesForLast7days(date);
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
listener::onSuccess,
listener::onFailure
);
}
}
private List<String> getDates() { //returns last 7 days in a list
List<String> listDate = new ArrayList<>();
Calendar calendarToday = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
String today = simpleDateFormat.format(calendarToday.getTime());
Calendar calendarDayBefore = Calendar.getInstance();
calendarDayBefore.setTime(calendarDayBefore.getTime());
int daysCounter = 0;
while (daysCounter <= 7) {
if (daysCounter == 0) { // means that its present day
listDate.add(today);
} else { // subtracts 1 day after each pass
calendarDayBefore.add(Calendar.DAY_OF_MONTH, -1);
Date dateMinusOneDay = calendarDayBefore.getTime();
String oneDayAgo = simpleDateFormat.format(dateMinusOneDay);
listDate.add(oneDayAgo);
}
daysCounter++;
}
return listDate;
}
}
This code gets me the right values but they are not in order so I'm getting wrong values for specific days.
So what I have to do is execute 7 calls simultaneously, I'm guessing with zip operator but I didnt come up with a solution for this yet so any type of help would be appreciated.
API docs can be found here: http://hnbex.eu/api/v1/
So what I did to solve this is I added all the 7 observables in a list and then I just called the zipIterable() on that list
I set time/date as a x value in my real-time graph, but the time didn't match with my android's time, can someone check my code
final DateFormat sdf = android.text.format.DateFormat.getTimeFormat(this);
graph.getGridLabelRenderer().setLabelFormatter(new DefaultLabelFormatter() {
#Override
public String formatLabel(double value, boolean isValueX) {
if (isValueX) {
long Valuemilis = (new Double(value)).longValue();
return sdf.format(Valuemilis*1000);
}
else {
return null;
}
}
});
app's picture
To get your system time in milliseconds you can use Date.java class.
The below snippet of code may solve your problem.
long Valuemilis = new Date().getTime();
How to sort my list through Rx function, My list contain three type of different source then I want to display my list sorted using date, how to apply that using RxAndroid?
subscriptions.add(complaintsAPI.getComplaintsAPI(userDetails.getUsername())
.compose(ReactiveUtils.applySchedulers())
.map(list -> {
List<ComplaintsRowModel> rowModel = new ArrayList<>();
for (Complaint complaint : list.getComplaints()) {
rowModel.add(new ComplaintsRowModel(complaint.getDisputeNo(),
complaint.getOpenDate(), complaint.getArea(), complaint.getStatus()));
model.complaintsList.put(complaint.getDisputeNo(), complaint);
}
for (OnlineRequest onlineRequest : list.getOnlineRequests()) {
rowModel.add(new ComplaintsRowModel(onlineRequest.getRequestNo(), onlineRequest.getOpenDate(),
onlineRequest.getArea(), onlineRequest.getStatus()));
model.complaintsList.put(onlineRequest.getRequestNo(), onlineRequest);
}
for (LlTickets llTickets : list.getLlTickets()) {
rowModel.add(new ComplaintsRowModel(llTickets.getTicketNo(), llTickets.getOpenDate(),
llTickets.getType(), llTickets.getStatus()));
model.complaintsList.put(llTickets.getTicketNo(), llTickets);
}
return rowModel;}
).toSortedList(){
//how to sort here
}).subscribe(new RequestSubscriber<List<ComplaintsRowModel>>(view.getContext(), view.progressView) {
#Override
public void onFailure(RequestException requestException) {
view.showError(requestException);
}
#Override
public void onNoData() {
super.onNoData();
isAllDataLoaded = true;
view.noDataFound();
model.setNoDataFound(true);
}
#Override
public void onNext(List<ComplaintsRowModel> complaintsRowModels) {
isAllDataLoaded = true;
model.setRowModel(complaintsRowModels);
view.buildList(complaintsRowModels);
}
}));
I think in toSortedList() can I sort my list but I don't know the way to apply that.
The toSortedList operator would only work on Observable<ComplaintRowModel> while what you have is Observable<List<ComplaintRowModel>>. So first you have to transform your observable with
flatMapIterable(complaintRowModels -> complaintRowModels)
to map it to an observable of the list elements. Then you can apply the sorting something like
toSortedList((complaintRowModel, complaintRowModel2) -> {
Date date = complaintRowModel.getDate();
Date date2 = complaintRowModel2.getDate();
// comparing dates is very much dependent on your implementation
if (date <before> date2) {
return -1;
} else if (date <equal> date2) {
return 0;
} else {
return 1;
}
})
Then you get an observable of sorted list.
As per you don't want to provide specific information about your problem, there is generic answer.
When data object which need to be sorted implements Comparable or it's primitive type.
Observable.just(3, 2, 1)
.toSortedList()
.subscribe(list -> System.out.print(Arrays.toString(list.toArray())));
[1, 2, 3]
When data object which need to be sorted doesn't implement Comparable or implements it, but you need to specify how you'd like to sort data.
That sample illustrate how to sort list of objects by val field in descended order.
static class ToSort {
Integer val;
public ToSort(Integer val) {
this.val = val;
}
#Override
public String toString() {
return "ToSort{" +
"val=" + val +
'}';
}
}
public static void main(String[] args) {
Observable.just(new ToSort(1), new ToSort(2), new ToSort(3))
.toSortedList((o1, o2) -> (-1) * o1.val.compareTo(o2.val))
.subscribe(list -> System.out.print(Arrays.toString(list.toArray())));
}
[ToSort{val=3}, ToSort{val=2}, ToSort{val=1}]
Different approach to replce the
// comparing dates is very much dependent on your implementation
if (date <before> date2) {
return -1;
} else if (date <equal> date2) {
return 0;
} else {
return 1;
}
Using a static import java.util.Comparator.comparing;
you can do comparing using method references found inside of your date object.
Ex.
In this instance unsortedDevices is a standard ObservableList to be used in a TablView.
SortedList<HistoryDisplayItem> sortedItems = new SortedList<>(unsortedDevices,
comparingInt(HistoryDisplayItem::getNumber));
Hi I have this array list of hashmaps
[{EndTime=09:00 AM, UserId=48, StartTime=08:00 AM, AppointmentId=79, Date=11/18/13},
{EndTime=09:00 AM, UserId=48, StartTime=08:00 AM, AppointmentId=80, Date=11/18/13},
{EndTime=09:00 AM, UserId=48, StartTime=08:00 AM, AppointmentId=81, Date=11/18/13},
{EndTime=09:00 AM, UserId=48, StartTime=08:00 AM, AppointmentId=82, Date=11/18/13},
{EndTime=09:00 AM, UserId=48, StartTime=08:00 AM, AppointmentId=83, Date=11/18/13},
{EndTime=09:00 AM, UserId=48, StartTime=08:00 AM, AppointmentId=85, Date=11/18/13}]
I want to check particular entry from here using "AppoinmentID" and i want to get that record for a diferent hashmap and all the others to a different one.. how can I do it? Thanks in advance.
storing these values in a hashmap is not a good idea. why don't you create an appointment class. removing an appointment object will be easy in this case.
public class Appointment
{
private int appointmentId;
private int userId; // or private User user
private Date start;
private Date end;
public Appointment(int id)
{
this.appointmentId = id;
}
// getters and setters
#Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (this.getClass() != obj.getClass())
{
return false;
}
Appointment other = (Appointment) obj;
if (this.appointmentId != other.appointmentId)
{
return false;
}
return true;
}
}
now if you want to delete a specific item with a certain ID:
List<Appointment> appointments = new ArrayList<Appointment>();
appointments.remove(new Appointment(theIdYouWantToDelete));
or an even better way:
Store them like this
Map<Integer, Appointment> appointments = new HashMap<Integer, Appointment>();
appointments.put(appointment.getAppointmentId(), appointment);
and remove them like this:
appointments.remove(theIdYouWantToDelete);
with this approach, you do not need the equals method.
Why it works:
When you want to remove an Object from a List or a Map, Java uses the equals method to identify them. As You can see i only check for the appointmentId. So if the IDs of 2 object are the same, Java says they are the same object. If you don't override equals, check only checks for == (same object in the memory) which mostly isn't the case.
1.Create a class
public class Appointment
{
public int appointmentId;
public int userId;
public Date startTime;
public Date endTime;
public Appointment(int id,int aUserID,Date aStartTime,Date aEndTime)
{
this.appointmentId = id;
this.userId = aUserID;
this.startTime = aStartTime;
thiis.endTime = aEndTime;
}
}
2. Creating Appointment Object and Storing in HashMap
String dateFormat = "MMMM d, yyyy HH:mm"; //any date format
DateFormat df = new SimpleDateFormat(dateFormat);
Date startDate = df.parse("January 2, 2010 13:00");
Date endDate = df.parse("January 2, 2010 20:00");
Appointment appointment1 = new Appointment(1,23,startDate,endDate);
...
Map<Integer, Appointment> appointments = new HashMap<Integer, Appointment>();
// add to hashmap making appointment id as key
appointments.put(appointment1.appointmentId,appointment1);
......
...
appointments.put(appointmentN.appointmentId,appointmentN);
3. deleting an Appointment Object
appointments.remove(aAppointmentId)
4. getting an Appointment Object
Appointment ap = appointments.get(aAppointmentId);
System.out.printLn("id "+ ap.appointmentId);
System.out.printLn("userId "+ ap.userId);
DateFormat df = new SimpleDateFormat(dateFormat);
System.out.printLn("starttime "+ df.format(ap.startTime));
System.out.printLn("endtime "+ df.format(ap.endTime));
You can have class:
public class Apointment{
Stirng EndTime="09:00 AM";
int AppointmentId=79;
...
...
}
and have one hashmap with apointmentId as key
HashMap<Integer,Apointment> map=new HashMap<Integer,Apointment>();
Apointment ap=new Apointment(...);
map.put(ap.getAppointmentId(),ap);
..
..
..
And if you have apointmentID you can get apointment object by:
Apointment ap=map.get(79);
Using Joda 1.6.2 with Android
The following code hangs for about 15 seconds.
DateTime dt = new DateTime();
Originally posted this post
Android Java - Joda Date is slow in Eclipse/Emulator -
Just tried it again and its still not any better. Does anyone else have this problem or know how to fix it?
I also ran into this problem. Jon Skeet's suspicions were correct, the problem is that the time zones are being loaded really inefficiently, opening a jar file and then reading the manifest to try to get this information.
However, simply calling DateTimeZone.setProvider([custom provider instance ...]) is not sufficient because, for reasons that don't make sense to me, DateTimeZone has a static initializer where it calls getDefaultProvider().
To be completely safe, you can override this default by setting this system property before you ever call anything in the joda.
In your activity, for example, add this:
#Override
public void onCreate(Bundle savedInstanceState) {
System.setProperty("org.joda.time.DateTimeZone.Provider",
"com.your.package.FastDateTimeZoneProvider");
}
Then all you have to do is define FastDateTimeZoneProvider. I wrote the following:
package com.your.package;
public class FastDateTimeZoneProvider implements Provider {
public static final Set<String> AVAILABLE_IDS = new HashSet<String>();
static {
AVAILABLE_IDS.addAll(Arrays.asList(TimeZone.getAvailableIDs()));
}
public DateTimeZone getZone(String id) {
if (id == null) {
return DateTimeZone.UTC;
}
TimeZone tz = TimeZone.getTimeZone(id);
if (tz == null) {
return DateTimeZone.UTC;
}
int rawOffset = tz.getRawOffset();
//sub-optimal. could be improved to only create a new Date every few minutes
if (tz.inDaylightTime(new Date())) {
rawOffset += tz.getDSTSavings();
}
return DateTimeZone.forOffsetMillis(rawOffset);
}
public Set getAvailableIDs() {
return AVAILABLE_IDS;
}
}
I've tested this and it appears to work on Android SDK 2.1+ with joda version 1.6.2. It can of course be optimized further, but while profiling my app (mogwee), this decreased the DateTimeZone initialize time from ~500ms to ~18ms.
If you are using proguard to build your app, you'll have to add this line to proguard.cfg because Joda expects the class name to be exactly as you specify:
-keep class com.your.package.FastDateTimeZoneProvider
I strongly suspect it's because it's having to build the ISO chronology for the default time zone, which probably involves reading all the time zone information in.
You could verify this by calling ISOChronology.getInstance() first - time that, and then time a subsequent call to new DateTime(). I suspect it'll be fast.
Do you know which time zones are going to be relevant in your application? You may find you can make the whole thing much quicker by rebuilding Joda Time with a very much reduced time zone database. Alternatively, call DateTimeZone.setProvider() with your own implementation of Provider which doesn't do as much work.
It's worth checking whether that's actually the problem first, of course :) You may also want to try explicitly passing in the UTC time zone, which won't require reading in the time zone database... although you never know when you'll accidentally trigger a call which does require the default time zone, at which point you'll incur the same cost.
I only need UTC in my application. So, following unchek's advice, I used
System.setProperty("org.joda.time.DateTimeZone.Provider", "org.joda.time.tz.UTCProvider");
org.joda.time.tz.UTCProvider is actually used by JodaTime as the secondary backup, so I thought why not use it for primary use? So far so good. It loads fast.
The top answer provided by plowman is not reliable if you must have precise timezone computations for your dates. Here is an example of problem that can happen:
Suppose your DateTime object is set for 4:00am, one hour after daylight savings have started that day. When Joda checks the FastDateTimeZoneProvider provider before 3:00am (i.e., before daylight savings) it will get a DateTimeZone object with the wrong offset because the tz.inDaylightTime(new Date()) check will return false.
My solution was to adopt the recently published joda-time-android library. It uses the core of Joda but makes sure to load a time zone only as needed from the raw folder. Setting up is easy with gradle. In your project, extend the Application class and add the following on its onCreate():
public class MyApp extends Application {
#Override
public void onCreate() {
super.onCreate();
JodaTimeAndroid.init(this);
}
}
The author wrote a blog post about it last year.
I can confirm this issue with version 1, 1.5 and 1.62 of joda. Date4J is working well for me as an alternative.
http://www.date4j.net/
I just performed the test that #"Name is carl" posted, on several devices. I must note that the test is not completely valid and the results are misleading (in that it only reflects a single instance of DateTime).
From his test, When comparing DateTime to Date, DateTime is forced to parse the String ts, where Date does not parse anything.
While the initial creation of the DateTime was accurate, it ONLY takes that much time on the very FIRST creation... every instance after that was 0ms (or very near 0ms)
To verify this, I used the following code and created 1000 new instances of DateTime on an OLD Android 2.3 device
int iterations = 1000;
long totalTime = 0;
// Test Joda Date
for (int i = 0; i < iterations; i++) {
long d1 = System.currentTimeMillis();
DateTime d = new DateTime();
long d2 = System.currentTimeMillis();
long duration = (d2 - d1);
totalTime += duration;
log.i(TAG, "datetime : " + duration);
}
log.i(TAG, "Average datetime : " + ((double) totalTime/ (double) iterations));
My results showed:
datetime : 264
datetime : 0
datetime : 0
datetime : 0
datetime : 0
datetime : 0
datetime : 0
...
datetime : 0
datetime : 0
datetime : 1
datetime : 0
...
datetime : 0
datetime : 0
datetime : 0
So, the result was that the first instance was 264ms and more than 95% of the following were 0ms (I occasionally had a 1ms, but never had a value larger than 1ms).
Hope this gives a clearer picture of the cost of using Joda.
NOTE: I was using joda-time version 2.1
Using dlew/joda-time-android gradle dependency it takes only 22.82 ms (milliseconds). So I recommend you using it instead of overriding anything.
I found solution for me. I load UTC and default time zone. So it's loads very fast. And I think in this case I need catch broadcast TIME ZONE CHANGE and reload default time zone.
public class FastDateTimeZoneProvider implements Provider {
public static final Set<String> AVAILABLE_IDS = new HashSet<String>();
static {
AVAILABLE_IDS.add("UTC");
AVAILABLE_IDS.add(TimeZone.getDefault().getID());
}
public DateTimeZone getZone(String id) {
int rawOffset = 0;
if (id == null) {
return DateTimeZone.getDefault();
}
TimeZone tz = TimeZone.getTimeZone(id);
if (tz == null) {
return DateTimeZone.getDefault();
}
rawOffset = tz.getRawOffset();
//sub-optimal. could be improved to only create a new Date every few minutes
if (tz.inDaylightTime(new Date())) {
rawOffset += tz.getDSTSavings();
}
return DateTimeZone.forOffsetMillis(rawOffset);
}
public Set getAvailableIDs() {
return AVAILABLE_IDS;
}
}
This quick note to complete the answer about date4j from #Steven
I ran a quick and dirty benchmark comparing java.util.Date, jodatime and date4j on the weakest android device I have (HTC Dream/Sapphire 2.3.5).
Details : normal build (no proguard), implementing the FastDateTimeZoneProvider for jodatime.
Here's the code:
String ts = "2010-01-19T23:59:59.123456789";
long d1 = System.currentTimeMillis();
DateTime d = new DateTime(ts);
long d2 = System.currentTimeMillis();
System.err.println("datetime : " + dateUtils.durationtoString(d2 - d1));
d1 = System.currentTimeMillis();
Date dd = new Date();
d2 = System.currentTimeMillis();
System.err.println("date : " + dateUtils.durationtoString(d2 - d1));
d1 = System.currentTimeMillis();
hirondelle.date4j.DateTime ddd = new hirondelle.date4j.DateTime(ts);
d2 = System.currentTimeMillis();
System.err.println("date4j : " + dateUtils.durationtoString(d2 - d1));
Here are the results :
debug | normal
joda : 3s (3577ms) | 0s (284ms)
date : 0s (0) | 0s (0s)
date4j : 0s (55ms) | 0s (2ms)
One last thing, the jar sizes :
jodatime 2.1 : 558 kb
date4j : 35 kb
I think I'll give date4j a try.
You could also checkout Jake Wharton's JSR-310 backport of the java.time.* packages.
This library places the timezone information as a standard Android asset and provides a custom loader for parsing it efficiently. [It] offers the standard APIs in Java 8 as a much smaller package in not only binary size and method count, but also in API size.
Thus, this solution provides a smaller binary-size library with a smaller method count footprint, combined with an efficient loader for Timezone data.
As already mentioned you could use the joda-time-android library.
Do not use FastDateTimeZoneProvider proposed by #ElijahSh and #plowman. Because it is treat DST offset as standart offset for the selected timezone. As it will give "right" results for the today and for the rest of a half of a year before the next DST transition occurs. But it will defenetly give wrong result for the day before DST transition, and for the day after next DST transition.
The right way to utilize system's timezones with JodaTime:
public class AndroidDateTimeZoneProvider implements org.joda.time.tz.Provider {
#Override
public Set<String> getAvailableIDs() {
return new HashSet<>(Arrays.asList(TimeZone.getAvailableIDs()));
}
#Override
public DateTimeZone getZone(String id) {
return id == null
? null
: id.equals("UTC")
? DateTimeZone.UTC
: Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
? new AndroidNewDateTimeZone(id)
: new AndroidOldDateTimeZone(id);
}
}
Where AndroidOldDateTimeZone:
public class AndroidOldDateTimeZone extends DateTimeZone {
private final TimeZone mTz;
private final Calendar mCalendar;
private long[] mTransition;
public AndroidOldDateTimeZone(final String id) {
super(id);
mTz = TimeZone.getTimeZone(id);
mCalendar = GregorianCalendar.getInstance(mTz);
mTransition = new long[0];
try {
final Class tzClass = mTz.getClass();
final Field field = tzClass.getDeclaredField("mTransitions");
field.setAccessible(true);
final Object transitions = field.get(mTz);
if (transitions instanceof long[]) {
mTransition = (long[]) transitions;
} else if (transitions instanceof int[]) {
final int[] intArray = (int[]) transitions;
final int size = intArray.length;
mTransition = new long[size];
for (int i = 0; i < size; i++) {
mTransition[i] = intArray[i];
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public TimeZone getTz() {
return mTz;
}
#Override
public long previousTransition(final long instant) {
if (mTransition.length == 0) {
return instant;
}
final int index = findTransitionIndex(instant, false);
if (index <= 0) {
return instant;
}
return mTransition[index - 1] * 1000;
}
#Override
public long nextTransition(final long instant) {
if (mTransition.length == 0) {
return instant;
}
final int index = findTransitionIndex(instant, true);
if (index > mTransition.length - 2) {
return instant;
}
return mTransition[index + 1] * 1000;
}
#Override
public boolean isFixed() {
return mTransition.length > 0 &&
mCalendar.getMinimum(Calendar.DST_OFFSET) == mCalendar.getMaximum(Calendar.DST_OFFSET) &&
mCalendar.getMinimum(Calendar.ZONE_OFFSET) == mCalendar.getMaximum(Calendar.ZONE_OFFSET);
}
#Override
public boolean isStandardOffset(final long instant) {
mCalendar.setTimeInMillis(instant);
return mCalendar.get(Calendar.DST_OFFSET) == 0;
}
#Override
public int getStandardOffset(final long instant) {
mCalendar.setTimeInMillis(instant);
return mCalendar.get(Calendar.ZONE_OFFSET);
}
#Override
public int getOffset(final long instant) {
return mTz.getOffset(instant);
}
#Override
public String getShortName(final long instant, final Locale locale) {
return getName(instant, locale, true);
}
#Override
public String getName(final long instant, final Locale locale) {
return getName(instant, locale, false);
}
private String getName(final long instant, final Locale locale, final boolean isShort) {
return mTz.getDisplayName(!isStandardOffset(instant),
isShort ? TimeZone.SHORT : TimeZone.LONG,
locale == null ? Locale.getDefault() : locale);
}
#Override
public String getNameKey(final long instant) {
return null;
}
#Override
public TimeZone toTimeZone() {
return (TimeZone) mTz.clone();
}
#Override
public String toString() {
return mTz.getClass().getSimpleName();
}
#Override
public boolean equals(final Object o) {
return (o instanceof AndroidOldDateTimeZone) && mTz == ((AndroidOldDateTimeZone) o).getTz();
}
#Override
public int hashCode() {
return 31 * super.hashCode() + mTz.hashCode();
}
private long roundDownMillisToSeconds(final long millis) {
return millis < 0 ? (millis - 999) / 1000 : millis / 1000;
}
private int findTransitionIndex(final long millis, final boolean isNext) {
final long seconds = roundDownMillisToSeconds(millis);
int index = isNext ? mTransition.length : -1;
for (int i = 0; i < mTransition.length; i++) {
if (mTransition[i] == seconds) {
index = i;
}
}
return index;
}
}
The AndroidNewDateTimeZone.java same as "Old" one but based on android.icu.util.TimeZone instead.
I have created a fork of Joda Time especially for this. It loads for only ~29 ms in debug mode and ~2ms in release mode. Also it has less weight as it doesn't include timezone database.