Parcelable java.util.Date - android

I am trying to make one of my classes Parcelable, and one of its attributes is a Date object.
In the writeToParcel() method I have:
out.writeLong(myDate.getTime());
And in my createFromParcel() method I have
person.setDate(new Date(in.readLong() * 1000));
The Object that I am passing to my Intent has a Date created like this:
new Date(2000,12,06)
But, when I read it out on the other side, in the other activity:
myDate.getYear()+"-"+myDate.getMonth()+"-"+myDate.getDay()
It prints out "2001-0-0"
I'm assuming that something is getting screwed up during the parcel process?

You're writing out milliseconds since the Unix epoch (see the documentation for getTime()).
The Date constructor that takes a long value takes in milliseconds since the Unix epoch (see the documentation for that constructor).
You, instead, are passing, microseconds since the Unix epoch to the constructor. Just pass the readLong() value to the Date constructor, and see if that helps.

Your error is in the way you create the Date object, check the documentation for more information about it.
Parameters
year, 0 is 1900 (e.g 100 is 2000).
month of the year, 0 - 11 (e.g. June is 5).
day of the month, 1 - 31 (don't think it needs example).
as you can see, when you do this "new Date(2000,12,06)", you are not creating the
date you thought you were creating on the first place. Also please not that the method
was deprecated since API 1, so please don't use it, unless you are targeting API 1, that
i don't think anyone is doing it anymore.
Also, as CommonsWare told you, remove the 1000 at the end while reading.

Related

How to convert LocalDateTime to UTC and back, without loading zones?

Background
I'm using threetenbp backport for Android (here), to handle various time related data operations.
One of them is to convert a time to a different timezone (current to UTC and back).
I know this is possible if you use something like that:
LocalDateTime now = LocalDateTime.now();
LocalDateTime nowInUtc = now.atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneId.of("UTC")).toLocalDateTime();
This works just fine, and it's also quite easy to do the opposite.
The problem
I'm trying to avoid initialization of the library, which loads quite a large file of zones into it. I've already figured out how to handle various date/time related operations without this, except this case of converting to UTC and back.
What I got has an error of a whole 1 hour off from the correct conversion.
What I've tried
This is what I've found and tried:
// getting the current time, using current time zone:
Calendar cal = Calendar.getInstance();
LocalDateTime now = LocalDateTime.of(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY),
cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND), cal.get(Calendar.MILLISECOND) * 1000000);
//the conversion itself, which is wrong by 1 hour in my tests:
LocalDateTime alternativeNowInUtc = now.atZone(ZoneOffset.ofTotalSeconds(TimeZone.getDefault().getRawOffset() / 1000)).withZoneSameInstant(ZoneId.ofOffset("UTC", ZoneOffset.ofHours(0))).toLocalDateTime();
The question
What's wrong exactly with what I wrote? How can I get an alternative code for converting the time without initialization of the library?
Given an instance of LocalDateTime as input, how can I convert it from current timezone to UTC, and from UTC to current timezone ?
This is probably happening because your JVM's default timezone is in Daylight Saving Time (DST).
To get the correct offset, you should check if the timezone is in DST and add this to the offset:
Calendar cal = Calendar.getInstance();
TimeZone zone = TimeZone.getDefault();
// if in DST, add the offset, otherwise add zero
int dst = zone.inDaylightTime(cal.getTime()) ? zone.getDSTSavings() : 0;
int offset = (zone.getRawOffset() + dst) / 1000;
LocalDateTime alternativeNowInUtc = now.atZone(ZoneOffset.ofTotalSeconds(offset))
.withZoneSameInstant(ZoneId.ofOffset("UTC", ZoneOffset.ofHours(0)))
.toLocalDateTime();
Another way to create the nowInUtc as a LocalDateTime is to create an Instant from the Calendar:
LocalDateTime nowInUtc = Instant.ofEpochMilli(cal.getTimeInMillis())
.atOffset(ZoneOffset.ofHours(0)).toLocalDateTime();
Actually, you don't need the Calendar at all, just use Instant.now() to get the current instant:
LocalDateTime nowInUtc = Instant.now().atOffset(ZoneOffset.ofHours(0)).toLocalDateTime();
Or, even shorter, use an OffsetDateTime directly:
LocalDateTime nowInUtc = OffsetDateTime.now(ZoneOffset.ofHours(0)).toLocalDateTime();
Not sure if any of those loads timezone data, it's up to you to test.
And I think that the constant ZoneOffset.UTC can be used instead of ZoneOffset.ofHours(0), because it won't load tz data as well (but I haven't tested it).
Final solution
Assuming the default timezone is in Israel (TimeZone.getDefault() is Asia/Jerusalem):
// April 11th 2018, 3 PM (current date/time in Israel)
LocalDateTime now = LocalDateTime.of(2018, 4, 11, 15, 0, 0);
TimeZone zone = TimeZone.getDefault();
// translate DayOfWeek values to Calendar's
int dayOfWeek;
switch (now.getDayOfWeek().getValue()) {
case 7:
dayOfWeek = 1;
break;
default:
dayOfWeek = now.getDayOfWeek().getValue() + 1;
}
// get the offset used in the timezone, at the specified date
int offset = zone.getOffset(1, now.getYear(), now.getMonthValue() - 1,
now.getDayOfMonth(), dayOfWeek, now.getNano() / 1000000);
ZoneOffset tzOffset = ZoneOffset.ofTotalSeconds(offset / 1000);
// convert to UTC
LocalDateTime nowInUtc = now
// conver to timezone's offset
.atOffset(tzOffset)
// convert to UTC
.withOffsetSameInstant(ZoneOffset.UTC)
// get LocalDateTime
.toLocalDateTime();
// convert back to timezone
LocalDateTime localTime = nowInUtc
// first convert to UTC
.atOffset(ZoneOffset.UTC)
// then convert to your timezone's offset
.withOffsetSameInstant(tzOffset)
// then convert to LocalDateTime
.toLocalDateTime();
The answer of carlBjqsd is okay, just awkward and should maybe a little bit clearer.
Why one hour difference
See the final solution of #carlBjqsd: It uses the expression
int offset = zone.getOffset(1, now.getYear(), now.getMonthValue() - 1, now.getDayOfMonth(), dayOfWeek, now.getNano() / 1000000);
instead of
getRawOffset().
That has caused the difference of one hour you observed. Applications have normally no need only to calculate with the raw offset which leaves out the dst-offset for some periods of the year. It is only the total offset which matters in any conversion from local timestamp to UTC and back. The main purpose of the fine-granular differentiation of partial offsets like raw offsets or dst offsets is just proper naming of the zone (shall we call it standard time or not?).
Misleading title of question: "without loading zones"
No, you can never avoid loading zones if you want to convert between local timestamps and UTC using zones. Your real question is rather: How to avoid loading the zones of ThreetenABP and to use/load the zones of the Android platform instead. And your motivation seems to be:
I'm trying to avoid initialization of the library, which loads quite a
large file of zones into it
Well, I have not measured which zone data have more impact on performance. I can only say based on my studies and knowledge of the source code of involved libraries that java.time and ThreetenBP load the whole file TZDB.dat into a binary array cache in memory (as first step) and then pick out the relevant part for a single zone (i.e. interprete a part of the binary data array via deserialization into a set of zone rules and finally a single ZoneId). Old Java platforms instead work with a set of different zi-files (one for each zone), and I suspect that Android zones behave in a similar way (but please correct me if you know that detail better).
If only ONE zone shall be used at all then the traditional approach of using separate zone files might be better but once you want to iterate over all available zones then it is better to have only one zone file at all.
Personally, I think that the performance aspect is neglectable. If you use the Android zones you will also have some loading times, inevitably. In case you really want to speed up the initialization time of ThreetenABP, you should consider to load it in a background thread.
Are Android zones and ThreetenABP zones equivalent?
Generally not. Both timezone repositories might give the same offset for a concrete zone. And often they do so but sometimes there will be differences which are not under your control. Although both timezone repositories use the data of iana.org/tz in final consequence, differences are mainly caused by possible different versions of tzdb-data. And you cannot control which version of zone data exists on the Android platform because this is up to the user of mobile phone how often he/she updates the Android OS. And this is also true for the data of ThreetenABP. You can offer the latest version of your app including the latest version of ThreetenABP but you cannot control if the mobile device user really updates the app.
Other reasons why to care about choosing the proper tz repository?
Beyond performance and initialization times, there is indeed one special scenario which might be interesting for the choice. If the Android OS is somehow old and uses an outdated version of zone rules then some mobile phone users do not update their operating system but manipulate the device clock in order to compensate the wrong timezone data. This way, they still get the correct local times on the mobile phone (in ALL apps).
In this scenario, ThreetenABP does not offer a good solution because combining their correct zone data with wrong device clock will result in wrong local timestamps (annoying the user). This has been a problem for example in Turkey which changed the dst-rules not a long time ago.
Using just the old calendar and timezone API of Android (in the package java.util) can take into account the problem so correct local timestamps are created. However, if an app communicates UTC-times (for example as count of millisecs since 1970-01-01T00Z) to other hosts (for example servers) then the wrong device clock is still a problem.
We could say why bother because the user has done "nonsense" with the device configuration but we also live in real world and should think about how to make even such users happy. So when thinking about a solution I had introduced at least in my calendar library Time4A methods like SystemClock.inPlatformView() which uses the (probably) most actual zone data and obtains the correct UTC clock based on the assumption that the user will at least observe correct local device time (whatever he/she had done to achieve this goal, either by updating the OS or by clock/zone configuration). I am quite happy with avoiding the old calendar and zone API altogether this way. My API even allows to simultaneously use both zone repositories:
Timezone.of("java.util.TimeZone~Asia/Jerusalem") // uses Android data
Timezone.of("Asia/Jerusalem") // uses Time4A data
Maybe you can profit from these ideas when to find/develop suitable helper classes for your usage of ThreetenABP. Time4A is open source.

SimpleDateFormat.parse() ignoring timezone?

I am trying to parse date string with timezone using this code for tests:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mmZZZZZ", Locale.US);
Calendar calendar = Calendar.getInstance();
calendar.setTime(sdf.parse("2017-07-26T06:00-06:00"));
int offset = calendar.getTimeZone().getRawOffset();
I am trying to change timezone from -06 to +09, but offset always contains 10800000.
How to parse date with timezone correctly (I need time and timezone both)?
Note: -06:00 is an offset, not a timezone - those 2 concepts are related, but they are different things (more on that below).
The problem with SimpleDateFormat and Calendar is that they use the system's default timezone, so even though you parse a date with a different offset (like -06:00), the resulting Calendar will have the default timezone (you can check what zone is by calling TimeZone.getDefault()).
That's just one of the many problems and design issues of this old API.
Fortunately, there's a better alternative, if you don't mind adding a dependency to your project (in this case, I think it's totally worth it). In Android you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, you'll also need the ThreeTenABP to make it work (more on how to use it here).
To work with offsets, you can use the org.threeten.bp.OffsetDateTime class:
// parse the String
OffsetDateTime odt = OffsetDateTime.parse("2017-07-26T06:00-06:00");
This will parse all the fields correctly (date/time and offset). To get the offset value, similar to calendar.getTimeZone().getRawOffset(), you can do:
// get offset in milliseconds
int totalSeconds = odt.getOffset().getTotalSeconds() * 1000;
I had to multiply by 1000 because calendar returns the value in milliseconds, but ZoneOffset returns in seconds.
To convert this to another offset (+09:00), it's straightforward:
// convert to +09:00 offset
OffsetDateTime other = odt.withOffsetSameInstant(ZoneOffset.ofHours(9));
As I said, timezone and offset are different things:
offset is the difference from UTC: -06:00 means "6 hours behind UTC" and +09:00 means "9 hours ahead UTC"
timezone is a set of all the different offsets that a region had, has and will have during its history (and also when those changes occur). The most common cases are Daylight Saving Time shifts, when clocks change 1 hour back or forward in a certain region. All these rules about when to change (and what's the offset before and after the change) are encapsulated by the timezone concept.
So, the code above works fine if you're working with offsets and wants to convert to a different one. But if you want to work with a timezone, you must convert the OffsetDateTime to a ZonedDateTime:
// convert to a timezone
ZonedDateTime zdt = odt.atZoneSameInstant(ZoneId.of("Asia/Tokyo"));
// get the offset
totalSeconds = zdt.getOffset().getTotalSeconds() * 1000;
The getOffset() method above will check the history of the specified timezone and get the offset that was active in that corresponding instant (so, if you take a date during DST, for example, the offset (and also date and time) will be adjusted accordingly).
The API uses IANA timezones names (always in the format Region/City, like America/Sao_Paulo or Europe/Berlin).
Avoid using the 3-letter abbreviations (like CST or PST) because they are ambiguous and not standard.
You can get a list of available timezones (and choose the one that fits best your system) by calling ZoneId.getAvailableZoneIds().
You can also use the system's default timezone with ZoneId.systemDefault(), but this can be changed without notice, even at runtime, so it's better to explicity use a specific one.

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().

GregorianCalendar and SQLite

I'm trying to setup a RPG that will keep track of a virtual time. After some reading GregorianCalendar seems to be the best way to do this but I have some questions and hoping someone with experience could give me some insight.
Here is what I'm trying to do. The game will start in the year 1675 and run for about 30 years. At that point the character will retire if they have survived that long. The character will be able to choose from actions I've preset for them through the coarse of the game. Some actions will be short and take a hour, others may take a week or a month. The real question comes from me using SQLite heavily. I want to save the current time as well as how long an action will take in my database. My first thought when setting this up was, if I want to start the game in Aug 15, 1675 to have my data base with 3 rows, set those fields to 8, 15, 1675. Then also have 2 more columns for the time. Pull these int via cursor and set them with something like
GregorianCalendar time = new GregorianCalendar();
time.set(year, month, date, hour, minute);
I figured I would pull how long an action takes in a similar fashion. Have an int X, and then a string to tell whether this time is in minutes, hours, days etc. Then either add this int to the int pulled from database to add to the calendar directly.
Q: If I add it directly to the calendar how would I pull int values from the calendar to store in database to load at a later time (when the player loads their game)?
Q: If I add it to the int stored in the database and set the calendar with this new int what will happen when I add enough to make the int out of scope for the calendar (Date is set to 31 but I add another day)?
You can convert from the GregorianCalendar object to/from UNIX time for example using getTimeInMillis() setTimeInMillis(). GregorianCalendar also has a roll() method:
Adds the specified amount to the specified field and wraps the value
of the field when it goes beyond the maximum or minimum value for the
current date. Other fields will be adjusted as required to maintain a
consistent date.
I would recommend using Joda Time as a substitute for the (somewhat lacking) standard java date and time utilities. It's much more flexible.
It has functions to do date math, it supports several different calendars (ISO8601, Buddhist, Coptic, Ethiopic, Gregorian, GregorianJulian, Islamic, Julian), has support for intervals, durations and periods. It has built in formatters that let you make your output look like just about anything you wish.
If it were me, I would use Joda and store the date in the native format presented by Joda (I don;t remember what that is right offhand) and then pull it out again and use Joda to do all the date math, as well as having it convert it to whatever calendar you wish to use for display to the user.
Otherwise, it seems to me you'd be re-inventing the wheel.

Date.getTime in Android Eclipse

Here is my code to assign a value to the Date:
long saveddatevalue = new Date().getTime();
I just want to know, what does this saved long variable actually show? I wish to use this to output values within a certain number of days.
Thanks!
It will basically give you the same result as System.currentTimeMillis() - the number of milliseconds since the Unix epoch of midnight January 1st 1970, UTC.
It is far easier to work with java.util.Calendar.add(int, int) for manipulating dates. Here is some sample code.

Categories

Resources