I am using (maybe incorrectly) a Joda DateTime to store a reminder time in a reminders app. When the timezone changes on the device (because of DST or just moving to a different TZ) I want to be able to reset the reminders to use the current timezone but keep the time.
For example: I set a reminder for Friday May 15th, at 15:00. If the timezone changes before that time, I want to be able to recreate the reminder for Friday May 15th at 15:00 local time.
I have already tried to use withZoneRetainFields but I haven't been able to keep the time unchanged:
new DateTime(reminderTimeMillis, DateTimeZone.forID(previousTimeZone)).withZoneRetainFields(DateTimeZone.getDefault())
Well, that method should basically work:
#Test
public void change_timezone_of_reminder() {
DateTime originalReminderDateTime = LocalDateTime.parse("2015-05-15T15:00").toDateTime(
DateTimeZone.forID("Europe/Berlin"));
assertThat(originalReminderDateTime.toString(), equalTo("2015-05-15T15:00:00.000+02:00"));
long reminderMillis = originalReminderDateTime.getMillis();
DateTime updatedTime = new DateTime(reminderMillis, DateTimeZone.forID("Europe/Berlin"))
.withZoneRetainFields(DateTimeZone.forID("America/New_York"));
assertThat(updatedTime.toLocalDateTime(), equalTo(LocalDateTime.parse("2015-05-15T15:00")));
assertThat(updatedTime.toString(), equalTo("2015-05-15T15:00:00.000-04:00"));
}
So, how are you evaluating that the time field is not changing?
Also, if you are storing the reminder time as millis-after-epoch, you don't need to adjust it for DST shifts, if you're calculating the millis using a full timezone ("Europe/Berlin") rather than a fixed offset.
Related
so, let's say I want to save a data in my database and save the timestamp (date and time) for the time the data saved in database. for example in +3:00 GMT.
now the timezone changed and the saved time when I getting it is changing too but I don't want that.
I want it to show the time in that timezone not current
and I using currentTimeMillis() for getting time
it's my code of getting time
val timeStamp = System.currentTimeMillis()
and code for getting from database in my view holder
val tsLong = currentItem.timeStamp
System.currentTimeMillis will always report in UTC regardless of the user's timezone.
I would recommend using Instant to store all your timestamps. Instants can easily be converted to any timezone. Instant has the following advantages:
Explicitly always in UTC/GMT timezone
toString() to ISO date format that is much more readable than milliseconds from the epoch
Far too often when we use milliseconds from the epoch we can't easily read them and it's ambitious if the field is seconds from the epoch or milliseconds. using Instant removes this ambiguity and makes things easier for developers.
I have a logic that schedules reminders using AlarmManager. I need to implement the following:
Logic 1: when the user changes time zone, eg he travels from UK (UTC+0) to central Europe (UTC+1), alarms should follow the time zone.
Example, a reminder scheduled at 3PM UTC+0 should fire at 4PM UTC+1
Logic 2: when a time shift occurs, eg time shifts to daylight saving time in spring (from UTC+1 to UTC+2), alarms should keep the original time
Example, a reminder scheduled at 3PM UTC+1 should fire at 3PM UTC+2
How can I achieve this? As of now I have no particular logic in place and all the alarms follow Logic 1. I have found no way to identify when a time shift happens.
Scheduling logic is very simple:
LocalDateTime reminderTime = LocalDateTime.of(...)
ZoneOffset currentOffsetForMyZone = ZoneId.systemDefault().getRules().getOffset(Instant.now());
reminderTime.toInstant(currentOffsetForMyZone).toEpochMilli();
alarmManager.setExact(AlarmManager.RTC_WAKEUP, reminderTime, pendingIntent);
For each alarm store the time of day and the time zone in which it was set. This suffices for firing the alarm at the right time no matter if the user is currently in a different time zone. And Java will take summer time (DST) into account.
Your example times and UTC offsets correspond to standard time in those time zones, so let’s start with an example date in standard time even though it was a couple of days ago now:
LocalTime alarmTime = LocalTime.of(15, 0);
ZoneId alarmTimeZone = ZoneId.of("Europe/London");
// Travel to Paris and see the alarm go off at 4, assuming standard time
ZoneId currentTimeZone = ZoneId.of("Europe/Paris");
Instant actualAlarmTime = LocalDate.of(2021, Month.MARCH, 18)
.atTime(alarmTime)
.atZone(alarmTimeZone)
.toInstant();
ZonedDateTime timeOnLocation = actualAlarmTime.atZone(currentTimeZone);
System.out.format("Scheduled at %s or %d millis, goes off at %s local time%n",
actualAlarmTime, actualAlarmTime.toEpochMilli(), timeOnLocation);
The code prints:
Scheduled at 2021-03-18T15:00:00Z or 1616079600000 millis, goes off at
2021-03-18T16:00+01:00[Europe/Paris] local time
Let’s also try a date in the summer time part of the year. I have changed Paris to London and MARCH to APRIL:
// Stay back home in the UK
ZoneId currentTimeZone = ZoneId.of("Europe/London");
Instant actualAlarmTime = LocalDate.of(2021, Month.APRIL, 18)
.atTime(alarmTime)
.atZone(alarmTimeZone)
.toInstant();
Scheduled at 2021-04-18T14:00:00Z or 1618754400000 millis, goes off at
2021-04-18T15:00+01:00[Europe/London] local time
The basic trick is: don’t use the current offset for the time zone where you set the alarm. Let Java automatically apply the offset for the date and time where the alarm is to go off.
If anyone is interested, the fix was to apply the correct offset for the date and time where the alarm is to go of, as pointed out by Ole. My silly mistake was to apply always the current timezone.
LocalDateTime alarmTime = LocalDateTime.of(...)
ZoneId zone = ZoneId.systemDefault();
ZonedDateTime zoneDateTime = ZonedDateTime.of(alarmTime , zone);
long startAtMillis = zoneDateTime.toInstant().toEpochMilli();
//Fire alarm
notificationAlarm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, startAtMillis, pendingIntent);
I have a TimePickerDialog which lets the user pick a time. The TimePickerDialog has a onTimeSet method that gets called when the user finished picking the time.
I pass the arguments to a second method, setTime(int hour, int minute) which saves the values and displays a formatted time.
Here is a code snippet of the method:
java.text.DateFormat dateFormatter =
java.text.DateFormat.getTimeInstance(java.text.DateFormat.SHORT, Locale.getDefault());
DateTime dt = new DateTime();
dt = dt.hourOfDay().setCopy(hour);
dt = dt.minuteOfHour().setCopy(minute);
String text = dateFormatter.format(dt.toDate());
The Problem is that (sometimes, not always) the hour value is off by one or maybe even two hours. I think the cause of the problem has something to do with timezones, but I do not know what the exact cause is. I also think that the problem is caused by the Joda DateTime object, since I did not have any issues before I implemented Joda time AND because it also gets saved with one hour off.
Any ideas what happens / how to fix it?
For this answer, I'm using joda-time 2.7 (although it might work with previous versions).
I'm also assuming that your program only cares about hour and minute (I'm not an Android expert, but I saw docs from TimePickerDialog and it seems to be the case).
So, if you're manipulating only hour and minute, you don't need to use DateTime class (actually you shouldn't in this case).
That's because a DateTime object is "aware" of all date fields, including day, month, year and timezone. So, when you create a new DateTime(), it creates a new object with current date and time in your default timezone. And if the current date in default timezone is in summer time (aka daylight saving time), you can have these hour shifts.
(And I believe that Android takes the default timezone from the device's system, so it can vary according to the device running the code - at least that's how it works in computers, so it shouldn't be different for devices).
As you don't need to know day/month/year and timezone, you can use LocalTime class (org.joda.time.LocalTime) which is a class with only hour and minute fields (and seconds, if you want; if you don't care about seconds, they'll be set to zero). And the best part is: this class doesn't care about timezones, so 10:00 AM will always be 10:00 AM.
You also don't need to use java.text.DateFormat, as joda-time has its own formatters. So the code will be like this:
public void setTime(int hour, int minute) {
// shortTime() is equivalent to DateFormat.SHORT
DateTimeFormatter fmt = DateTimeFormat.shortTime().withLocale(Locale.getDefault());
LocalTime time = new LocalTime(hour, minute);
String text = fmt.print(time);
System.out.println(text);
}
Doing some tests (assuming default locale is en_US):
setTime(9, 30); // prints 9:30 AM
setTime(10, 0); // prints 10:00 AM
PS: the output may vary according to your default Locale (which I also believe it comes from the device's system). If you want a fixed format (independent from locales), you can use DateTimeFormat.forPattern("h:mm a"), which also results in the output above (and when you use formatters this way, the default locale doesn't change the output).
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 am currently using the following line to achieve the time: System.currentTimeInMilis.
I have noticed it doesn't consider time zones,or does it not match the android phone it self by the time, while on the emulator it does match.s
so is there another type of way to get the android clock it self? so when the user adjusts he's phones built in clock, it affects it too?
float getTime()
{
Calendar cal = new GregorianCalendar();
cal.setTimeInMillis(System.currentTimeMillis());
cal.setTimeZone(TimeZone.getDefault());
return cal.getTimeInMillis();
}
Read the documentation of currentTimeMillis. It has a time zone, which happens to be UTC (which is the default for Unix time stamps).
If you want to convert it to a different time zone you can make use of the Java Calendar and TimeZone classes:
Calendar cal = new GregorianCalendar();
cal.setTimeInMillis(System.currentTimeMillis());
cal.setTimeZone(TimeZone.getDefault());
Alternatively you can just create a new GregorianCalendar instance. By default its TimeZone will match the local one (as set on the device) and the time will be set to "now".
There are also other ways for retrieving the current time according the current time zone and locale as string. Take a look at DateUtils.
EDIT Explaining the usage of Calendar
Read the documentation for Calendar.getTimeMillis(). That method returns the Unix time stamp again which happens to have the time zone UTC.
You have to use the Calendar.get() method instead for getting the correct values. See following example for getting the current hour in the correct time zone via your calendar object:
int hour = cal.get(Calendar.HOUR_OF_HAY);
Read the documentation of Calendar. There are plenty of fields like HOUR_OF_DAY which help you getting values like the year, month, minute, seconds etc.