This question already has an answer here:
android timezone difference is 1 hour less then expected
(1 answer)
Closed 9 years ago.
I need an offset for "Europe/Russia" to UTC.. in hours. here is my code:
Calendar mCalendar = new GregorianCalendar();
mCalendar.setTimeZone( TimeZone.getTimeZone("Europe/Moscow"));
TimeZone mTimeZone = mCalendar.getTimeZone();
int remote_offset = mTimeZone.getRawOffset()/1000/60/60;
For UTC it should be -4 hours. BUT! some user got 3 hours difference!!
I think, the problem is, Russia doesn't use winter time. And some devices now that, but some not.. how could I implement allway to get "-4" hours?
Regards
First, Russia isn't UTC-4.
The problem has to do with Russia not having daylight saving time. But your issue is probably only happening with android 2.x device and less. The daylight saving time was removed before 4.x as far as I remember (if it's a user input). On the other hand, if you receive a date that was created by the device without user input, you don't have to convert it as it's already as UTC.
But as I said, Russia isn't -4. Russia/Moscow will be +4 hours. But Russia is larger than Moscow really!
Look here: http://en.wikipedia.org/wiki/Time_in_Russia
UTC+03:00 MSK−1: Kaliningrad Time Europe/Kaliningrad
UTC+04:00 MSK: Moscow Time Europe/Moscow, Europe/Volgograd, Europe/Samara
UTC+06:00 MSK+2: Yekaterinburg Time Asia/Yekaterinburg
UTC+07:00 MSK+3: Omsk Time Asia/Omsk, Asia/Novosibirsk, Asia/Novokuznetsk
UTC+08:00 MSK+4: Krasnoyarsk Time Asia/Krasnoyarsk
UTC+09:00 MSK+5: Irkutsk Time Asia/Irkutsk
UTC+10:00 MSK+6: Yakutsk Time Asia/Yakutsk
UTC+11:00 MSK+7: Vladivostok Time Asia/Vladivostok, Asia/Sakhalin
UTC+12:00 MSK+8: Magadan Time Asia/Magadan, Asia/Kamchatka, Asia/Anadyr
So what you'll have to do is to check if we're in winter and that the TimeZone is one of those. If the timezone is one of those, you can add one more hour when you want to show. And remove 1 hour when you want to convert to UTC.
I don't believe it's possible to update the TimeZone on the android phones and that also means that it's not exactly possible to do that unless you find an alternative library for Dates that has timezones built-in and which are updated.
You could subclass the DateObject with the functions that you use to behave just like the old date object, all you'll have to do is to make sure it behaves differently on android2.x and not on android 4.x+.
Also check this: http://www.joda.org/joda-time/
I checked there and I guess it could be usable and less hacky than my suggestion above. The TimeZones are up to date so it could just work for every phone since it shouldn't use the internal timezones. On the other hand, if you have functions that require the Date, it might get tricky.
My suggestion is make sure you use UTC everywhere and use JodaTime to format the date with timezones and to do "datetime" operations. If you make sure that your Java Date never contain a TimeZone other than UTC. It should work.
Related
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.
I'm really stuck with a certain problem and I'm hoping someone can help me understand the problem and come to a solution. I've looked online a fair bit but can't see an answer unless it's been staring me in the face :-/
Basically, I'm creating a very basic TV Guide app. It parses data from an RSS feed which has days offset (yesterday was -1. today is 0, tomorrow is 1, etc etc) and I'm trying to implement a DatePicker that allows the user to see what is on a particular channel when they select yesterday, today, tomorrow, etc.. but if they pick a date that is out-with the range (at the moment it's a week in advance), a simple Toast message will be displayed.
My questions I guess are, firstly, how do I use maybe an IF ELSE to either parse the specific channel data for the day the user wants or display an error Toast message, and, how do I go about converting the days from what the user has put in compared to the actual date today into integers? If they select yesterday's date it will go to URL "http://example.com/-1/channel", if they select tomorrow's date it will go to URL "http://example.com/1/channel" etc etc etc.
Code is available if anyone needs to see it, but I think if someone would be kind enough to explain the logic, I'd like to see if I can come to the answer myself...
Thanks a lot folks!!
You should use a DatePicker to allow the user to choose the when.
Time in Android is stored on a long (not an int). And the long time can easily be converted back and forth between long (always milli-seconds) and a Date object.
The Date object gives you all sorts of tools to compare before and after, look at months, minutes, hours, etc.
The current time is determined by:
long nowMs = System.currentTimeMillis();
int nowSec = (int)(nowMs / 1000);
There is also a very important Calendar object. This allows you to parse textual date formats as delivered by your http functions in and out of various dates.
For example:
Calendar cal = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd hh:mm:ss Z");
String text = sdf.format(cal.getTime();
You will have to put all these tools together with a DatePicker example such as the one here Create a DatePicker to complete your TV Guide application.
Reference:
Date
Calendar
DatePicker
EDIT : Check David's Answer its better.
First Filter the date selected with today's date. You can compare it by date.isbefore(date) or date.isafter(date) these booleans will let you tell know if a date is of past or future or present. then to further calculate the days inbetween you can make a method with switch statement that will basically convert the selected date and the current date into miliseconds(Date.getTimeinmiliseconds)
if the date is of past take the difference of present time in miliseconds and past date in miliseconds. If the date is of future do the opposite. Take the difference and convert it to days difference with appropriate sign(negative/positive).
Please refer this link for a better coding example
I have an Android app that is used by people in the UK and Ireland only, and there are no plans for this app to be used overseas.
In the app I store dates for various things, although I never need the full timestamp including the time of day, I only need the date.
So that I can compare dates easily, I've been creating calendar objects and clearing the values of the time, and using the milliseconds of that to store in the database.
public Calendar clearCalTime(long l) {
Calendar cal = Calendar.getInstance();
cal.setFirstDayOfWeek(Calendar.MONDAY);
cal.setTimeInMillis(l);
cal.clear(Calendar.HOUR);
cal.clear(Calendar.HOUR_OF_DAY);
cal.clear(Calendar.MINUTE);
cal.clear(Calendar.SECOND);
cal.clear(Calendar.MILLISECOND);
return cal;
}
The issue has been that if users change the timezone, for some reason the dates start messing up, e.g by saving things to the wrong day. The timezones could be anything, I have no control over what the users set.
I've tried setting the timezone to UTC but this doesn't work either. Is there any way to just disregard the timezones?
Nope. You are going to have to set TimeZone going in and out of your storage.
You'll need to do this:
Calendar c = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
in your code to make it consistent.
See this SO: Java.util.Calendar - milliseconds since Jan 1, 1970
Alternatively use Joda Time when dealing with complicated Date/Time math.
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.
No matter how I change the time zone of an Event in Google Calendar, there's always a "-04:00" string trailing behind the Start and End's DateTime attribute when pulled using the Google Calendar API on Android. What I do is I create an Event from the web and view the results on an Android emulator.
For example:
If I create an Event whose time zone is in Toronto, Canada (FYI, which is the Calendar's time zone), it will show
"dateTime" : "2012-04-25T10:00:00.000-04:00",
If I change it to Taipei, Taiwan, it will show
"dateTime" : "2012-04-24T22:00:00.000-04:00",
The hours change automatically, which is great for implementation, but what does that "-04:00" stand for? (FYI, Toronto's time zone is "GMT-05:00")
It's the UTC offset, telling you which time zone the event's time is being expressed in. That's probably coming from the time zone setting of the calendar. Toronto is currently at UTC-4:00, meaning it's four hours behind the UTC clock. So when you moved the event to Taipei, you kept it at the same local time (10 AM on the 25th), but the API expresses it in Toronto hours; i.e. the event will happen when it's 10 PM on the 24th in Toronto.
Toronto's time zone is GMT-5:00 (UTC and GMT are roughly equivalent) because without daylight savings, that's the offset. The API knows that Toronto is currently on daylight savings, though, and changes the offset accordingly.
It's also worth noting that this date/time format is ISO 8601 and is the usual way of representing time in APIs.