I am trying to calculate the difference between any two given times in an android app. To accomplish this task I was using JodaTime and the Period class along with Period's getYears(), getDays(), getHours(), getMinutes(), and getSeconds() methods. However, I realized that it is giving what seems the be the absolute difference between each element of the date. For example, calling my function on the dates 2017-01-29 00:00:00 (yyyy-MM-dd HH:mm:ss) and 2018-01-28 00:00:00 will return:
0 years
2 days
0 hours
0 minutes
0 seconds
when it should return 364 days along with zeros in every other section. Of course, it is an android app so I am not printing them, I am handling the display separately. I was able to do this on my computer using LocalDateTime and Duration.between, but I cannot use these classes here. I am looking for either a fix for my current method (which is below) or suggestions for a completely new approach. Thank you in advance.
My current function:
public int[] getDifference(DateTime start, DateTime end){
Period p = new Period(start, end);
int[] differences = new int[5];
differences[0] = p.getYears();
differences[1] = p.getDays();
differences[2] = p.getHours();
differences[3] = p.getMinutes();
differences[4] = p.getSeconds();
return differences;
}
Why would you expect 364 days? Read the class doc for Period.
public int getDays()
Gets the days field part of the period.
The “field part” is the key here. A Period is a number of years, months, weeks, days, hours, minutes, seconds, and milliseconds. For example, a year and a half would have two parts, a years part of 1 and a months part of 6.
Your output skipped over the months part and the weeks part.
DateTime start = new DateTime ( 2017 , 1 , 29 , 0 , 0 , DateTimeZone.UTC );
DateTime stop = new DateTime ( 2018 , 1 , 28 , 0 , 0 , DateTimeZone.UTC );
Period p = new Period ( start , stop );
int[] differences = new int[ 7 ];
differences[ 0 ] = p.getYears ();
differences[ 1 ] = p.getMonths ();
differences[ 2 ] = p.getWeeks ();
differences[ 3 ] = p.getDays ();
differences[ 4 ] = p.getHours ();
differences[ 5 ] = p.getMinutes ();
differences[ 6 ] = p.getSeconds ();
System.out.println ( "differences: " + Arrays.toString ( differences ) );
When run, we see eleven months, four weeks, and two days. Running Joda-Time 2.8.2 in Java 8 Update 121 on macOS El Capitan.
differences: [0, 11, 4, 2, 0, 0, 0]
You might think of calling toStandardDays. But doing so results in an exception (java.lang.UnsupportedOperationException) saying: “Cannot convert to Days as this period contains months and months vary in length”.
java.time
FYI: The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
In java.time, a span of time unattached to the timeline is represented as either years-month-days in java.time.Period or hours-minutes-seconds-nanoseconds in java.time.Duration.
The LocalDate class represents a date-only value without time-of-day and without time zone.
The java.time classes eschew constructors, using factory methods instead.
LocalDate start = LocalDate.of ( 2017 , 1 , 29 );
LocalDate stop = LocalDate.of ( 2018 , 1 , 28 );
Period period = Period.between ( start , stop );
System.out.println ( "period.toString(): " + period );
The toString generates a piece of text in standard ISO 8601 format for durations. We see here eleven months and thirty days. Obviously Joda-Time and java.time count differently. Here we see different results. Joda-Time counts weeks while java.time does not.
period.toString(): P11M30D
Try replacing
Period p = new Period(start, end);
with
Period p = new Period(start, end, PeriodType.yearMonthDayTime());
You should also set the appropriate period type because you want a specific normalization to years, days, hours, minutes and seconds only, see also javadoc.
LocalDateTime start = new LocalDateTime(2017, 1, 29, 0, 0);
LocalDateTime end = new LocalDateTime(2018, 1, 28, 0, 0);
Period p = new Period(start, end, PeriodType.yearDayTime().withMillisRemoved());
System.out.println(p); // P364D (what you expected)
This example would also work with the type DateTime for start and end.
Please note that this code example (as the class Period of Joda-Time itself) cannot be fully migrated to new java.time-API contained in Java-8 because latter API cannot handle durations with date AND time components. It can also not specify the period type i.e. cannot control the normalization as PeriodType does.
Related
Context
This is an Android App running on API 29 (with a Min of 23).
I'm using ThirteenTenABP
Issue
I'm simply debugging some code and I do the following:
val zonedDateTime = ZonedDateTime.of(
LocalDateTime.of(1900, 1, 1, 15, 15, 0),
ZoneId.of("Europe/Amsterdam"))
This prints (toString() as:
1900-01-01T15:15+00:19:32[Europe/Amsterdam]
Expected
I would have expected to see 1900-01-01T15:15+02:00:00[Europe/Amsterdam] or similar.
The current Offset from UTC in Amsterdam is +2 due to daylight savings. Yet, I see these 19 minutes and 32 seconds.
This means that if I convert that Zoned date time to UTC using something like:
val utcZoned = zonedDateTime.withZoneSameInstant(ZoneOffset.UTC)
I get (consistently with the error above):
1900-01-01T14:55:28Z
So it's 14:55:28 or what is equivalent to the time 15:15 (3:15 pm) minus 19 minutes and thirty-two seconds.
Am I missing something here?
I'm running this on an Emulator. ZoneId.getSystemDefault() also returns the same ZoneId + Offset. I started hardcoding Amsterdam to see if I could spot a difference.
Another way to see this, is simply by doing:
ZonedDateTime.of(LocalDateTime.of(1900,01,01,15,15,0), ZoneId.of("Europe/Amsterdam")).offset
The result is a ZoneOffset
And consistently with the ninteen minutes above:
The offset is: +00:19:32
What am I doing wrong here?
Did I try java.time.*?
Yes, I removed ThirteenTenABP, and replaced all imports to java.time.* and ran this on an Android O (8.x) to see if the native Java8 time classes would yield a similar result and the answer is: yes.
The offsets come from the offset rules:
But why those numbers?
Looks like that's correct, Amsterdam's timezone used to be based off of the time in Westerkerk.
The reason for the specific offset of +0h 19m 32.13s was that the time zone was centered on the mean solar time of the Westertoren (4° 53' 01.95" E Longitude), the tower of the Westerkerk church in Amsterdam.
https://en.wikipedia.org/wiki/UTC%2B00:20
Bare in mind the docs for ZonedDateTime.of say
The local date-time is then resolved to a single instant on the time-line
So it will give you back a time using the timezone that was in use at that instant in time, not the current timezone in use in the Netherlands.
Objective:
Need to get the usage stats for today (total time for which the device was used today) ie. 12.00 am to current time.
Problem:
1.I get today's time + some other non explainable time
2.Non explainable time stamps(start and end of the usage stats as retrieved by the getTimestamp methods)
The time bucket is not relevant. I give the start time as 12.00 am and the end time as current time, but I get completely irrelevant ".firstTimeStamp" and ".lastTimeStamp" (which supposedly return the beginning and end of the usage stats data) for the usage statistics.
*already done the permission granting part, here is the function I'm using to get total time in minutes.
fun showtime(){
val time=Calendar.getInstance()
time.set(Calendar.HOUR_OF_DAY,0)
time.set(Calendar.MINUTE,0)
val start=time.timeInMillis
val end= System.currentTimeMillis()
val usageStatsManager = getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
var stats = usageStatsManager.queryAndAggregateUsageStats(start,end)
var x:Long=0
var ft:Long=0
var v:Long=0
var l:Long=0
for ((key,value) in stats) {
ft=value.totalTimeInForeground/60000
textField1.append("$key = $ft mins")
textField1.append("\n")
x=x+ft
v=value.firstTimeStamp
l=value.lastTimeStamp
}
textView.setText("YOU SPENT $x mins.")
textView2.setText("${Date(v)} to \n${Date(l)}")
}
As an example, when the above code runs at Wed 12 Dec 12.40 am, the result is:
(in textView):YOU SPENT 90 mins
(in textView2):Tue 11 Dec 16:23:19 GMT +05:30 2018 toTue 11 Dec 19:38:45 GMT +05:30 2018
How can I use my phone for 90 mins in just 40 mins?
And what does those apparently irrelevant timestamps mean?
Am I doing something wrong to achieve my objective?
I actually experience a similar problem:
According to my understanding of the documentation firstTimeStamp and lastTimeStamp should give the "beginning (end) of the time range this UsageStats represents".
They differ however from what I give as an argument in queryAndAggregateUsageStats as beginTime and endTime.
Also the result for the totalTimeInForegroundseems rather give back a result for the timespan given by firstTimeStamp / lastTimeStamp than for the requested one.
I filled a bug with google for this, please have a look at https://issuetracker.google.com/issues/118564471.
I noticed several problems with your approach.
You are missing time.set(Calendar.SECOND,0) and time.set(Calendar.MILLISECOND,0)
Precision is lost in the division ft=value.totalTimeInForeground/60000
I would recommend Java Time (ThreeTenBP) to handle DateTime and Duration more accurately. I create a new function to compare and indeed the results are different.
fun showtime2(){
val start = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()
val end = ZonedDateTime.now().toInstant().toEpochMilli()
val usageStatsManager = getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
val stats = usageStatsManager.queryAndAggregateUsageStats(start, end)
val total = Duration.ofMillis(stats.values.map { it.totalTimeInForeground }.sum())
println("YOU SPENT ${total.toMinutes()} mins.")
}
Your output
YOU SPENT 577 mins.
My output
YOU SPENT 582 mins.
I'm successfully registering a SipProfile with 1 hour expiration. I see the REGISTER message in Wireshark on the SIP server machine, re-sent with authorization.
Brekeke replies immediately with a STATUS: 200 OK, passing back 'Expires:3600'.
Both Xamarin and Android docs tell me that lExpire should be duration in seconds before the registration expires, so it represents an interval. I want to expose received values, so multiply by TimeSpan.TicksPerSecond (to convert to TimeSpan), 3600 would result in 0.01:00:00:
void ISipRegistrationListener.OnRegistrationDone( string lclPrfUri, long lExpire )
{
long l= DateTime.Now.Ticks;
double d= (double)l / lExpire;
string s= string.Format( "RegSucced( '{0}', {1} ), {2}, {3}", lclPrfUri, lExpire,
new TimeSpan( lExpire * TimeSpan.TicksPerSecond )
.ToString( "d\\.hh\\:mm\\:ss" ), d );
..
}
But i'm getting lExpire values in the range of 1.5 trillions (1'541'195'345'242)!
As i saw it constantly growing with time, i thought it might be related to Ticks, so i exposed both and gathered the following statistics (H == T + 1 hour -- expected expiration):
# DT.Now.Ticks (T) lExpire (E) T/E ratio T+36000000000 (H) H/E ratio
1 636767624266839520 1541183611836 413167.918 636767660266839520 413167.941
2 636767669188704010 1541188122398 413166.738 636767705188704010 413166.761
3 636767670260843180 1541188229643 413166.710 636767706260843180 413166.733
4 636767670974718350 1541188301027 413166.691 636767706974718350 413166.715
5 636767693193745790 1541190522922 413166.110 636767729193745790 413166.133
And the ratios look surprisingly consistent, though the magic of 413166 escapes me.. And from that lExpire looks more like a reference to a point in time, than an interval, no?
But according to docs i should get 3600 without any scaling factors, right? What is going on??!
UPDATE (2019-Jan-25)
Finally got closer to an answer. Digging through Android source files (e.g. https://android.googlesource.com/platform/frameworks/base/+/431bb2269532f2514861b908d5fafda8fa64da79/voip/java/com/android/server/sip/SipService.java) i found the following fragment:
#Override
public void onRegistrationDone(ISipSession session, int duration) {
if (DEBUG) Log.d(TAG, "onRegistrationDone(): " + session);
synchronized (SipService.this) {
if (notCurrentSession(session)) return;
mProxy.onRegistrationDone(session, duration);
if (duration > 0) {
mSession.clearReRegisterRequired();
mExpiryTime = SystemClock.elapsedRealtime() + (duration * 1000);
..
SystemClock.elapsedRealtime() returns milliseconds since boot, including time spent in sleep.
Time in UNIX/Linux/Java is kept as number of seconds since epoch (1970-01-01T00:00:00Z).
Wikipedia's page shows Current Unix time as 1548450313 (2019-01-25T21:05:13+00:00), which is only a 1000 times (s-to-ms multiplier!) different in range from lExpire values i observe. The formula for mExpiryTime in the last line kinda gives hope.. "Eureka!"?
Let's check if they conform to "# of ms since epoch" theory:
DateTime dtEpoch = new DateTime( 1970, 1, 1, 0, 0, 0, DateTimeKind.Utc );
void ISipRegistrationListener.OnRegistrationDone( string lclPrfUri, long lExpire )
{
DateTime dt = dtEpoch.AddMilliseconds( lExpire ).ToLocalTime( );
string s= string.Format( "RegSucced( '{0}', {1}, {2} )", lclPrfUri, lExpire,
dt.ToString( "yyyy-MM-dd HH:mm:ss.fff" ) );
..
}
Yesterday's screenshots - with that addition (magenta arrows mark exposed values):
As you can see, the difference between lExpire converted into time and DateTime.Now (captured in the log) is negligible - in ms range!
I actually like the idea of being given an expiration moment instead of duration (which need to be added to an undefined starting point). Was about to answer my question..
But still, there are follow-up unresolved mysteries:
Why documentation says the argument is duration in seconds?
Xamarin may be just copying Android docs, but the source is way wrong!
Is this really how lame Android devs are? Reckon that should be rethorical ((..
Does anybody care that requested expiration period may be trumped [down] by PBX /SIP server, and thus they have to re-register more often?
All calls to .onRegistrationDone(session, duration) in the source files specify duration, not mExpiryTime.
There are constant definitions (EXPIRY_TIME = 3600, SHORT_EXPIRY_TIME = 10, MIN_EXPIRY_TIME = 60) and comparisons of duration to these..
Range of values is drastically different between the two (3600 vs 15 trillion).
How/where does mExpiryTime (expiration moment) make it into the argument of my method?
ms since boot and s (or ms) since epoch are still very different, what makes that adjustment?
and finally, asking a 1 hr expiration and receiving it in the OK reply from SIP server, i expect that to be reflected here too, so lExpire should be 1 hr in the future, not now!!
Any ideas?
I am trying to add one day to calendar date but i am getting wrong output.
Below code i am using.
var cal2= Calendar.getInstance()
cal2!!.timeInMillis=cal.timeInMillis
Log.e("Time1",""+cal.timeInMillis);
cal2.add(Calendar.DATE, 1)
Log.e("Time2",""+cal2.timeInMillis);
Time1: 1526478465( Wednesday, 16 May 2018 19:17:45)
Time1: 1612878465(Tuesday, 9 February 2021 19:17:45 )
Assuming your cal.timeInMillis has proper value (say today's date), your code works fine:
var cal = Calendar.getInstance() <-- Assumption
var cal2 = Calendar.getInstance()
cal2!!.timeInMillis=cal.timeInMillis
println("Time1: "+cal.timeInMillis);
cal2.add(Calendar.DATE, 1)
println("Time2: "+cal2.timeInMillis);
Running above code gives following output:
Time1: 1527159971747 (Thursday, May 24, 2018 11:06:11.747 AM)
Time2: 1527246371747 (Friday, May 25, 2018 11:06:11.747 AM)
You’re somehow confusing seconds and milliseconds since the epoch. 1 526 478 465 is seconds. If you treat them as milliseconds, you get January 18, 1970 4:01:18 PM UTC. If you add 1 day to that, you get the next value you mention (I got 1 612 878 000, it comes close). When in turn you interpret 1 612 878 465 as seconds, you get February 9, 2021 1:47:45 PM UTC. This is the same as the date-time you mention, Tuesday, 9 February 2021 19:17:45, if I assume you’re at offset +05:30 (like Asia/Kolkata or Asia/Colombo time zone).
Since there are 1000 milliseconds in a second, your confusion has caused you to add 1000 days to your date instead of 1 day.
Tip: 10 digit values are usually seoncds. 13 digit values are usually milliseconds.
I have a program where it relies heavily on identifying the week number for the year. I have done the leg work and figured out all the problems that will cause and settle with this method. I works perfect for years that have 53 weeks and such. My only issue is that when I run it on my emulator for 2.2 it works perfect, like this is week 19 and its correct. when I run it on my phone a G1, the week shows 20. How do I fix this?
Here is my week code:
/**
* Format the date into a number that is the year*100 plus the week i.e. 2008 and its week 11
* would show as 811
* #param - String of a date to create a week id, must be in format of 2011-01-31 (YYYY-MM-DD)
* #return returns next weeks id
*/
public static int getWeekId(String date){
// Set the first day of week to Monday and set the starting new year weeks
// as a full first week in the new year.
Calendar c = Calendar.getInstance();
c.setFirstDayOfWeek(Calendar.MONDAY);
c.setMinimalDaysInFirstWeek(7);
if (!date.equalsIgnoreCase("")) {
String[] token = date.split("-", 3);
int year = Integer.parseInt(token[0]);
int month = Integer.parseInt(token[1])-1; // months are 0-11 stupid..
int day = Integer.parseInt(token[2]);
c.set(year, month, day);
}
int yearWeek = ( (c.get(Calendar.YEAR) - 2000)*100 + (c.get(Calendar.WEEK_OF_YEAR)));
Log.d("getWeekId()"," WEEK_OF_YEAR: " + c.get(Calendar.WEEK_OF_YEAR));
return yearWeek;
}
(I'd leave this in a comment, but my account does not yet have commenting permissions.)
When called with 2011-01-01, the code currently returns 1152. Is this as intended?
For what it's worth, there is likely more intricacy to it than you've written. I don't say this to be mean, it's just that there are probably lots of interesting weird cases that you haven't considered. There is a good Java library that knows a ton of stuff about times, and may have this code written already, Check it out: http://joda-time.sourceforge.net/
Apparently its a bug in the android phone...so watch out for this when using android 1.6.