I'm using SimpleDateFormat
to turn my GregorianCalendar
instances into nice strings. I'm having a weird issue where in certain dates the time string is off by one, but not always.
Normally, if my GregorianCalendar
has values like HOUR_OF_DAY=0,MINUTE=11
then I get a string like "1:11 AM". But then I change the YEAR
, MONTH
, or DAY
and now HOUR_OF_DAY=0,MINUTE=11
gives me the string "2:11 AM".
This is my time to string formatting function (datetime is the GregorianCalendar
):
public String toTimeString() {
Log.i(TAG, "Datetime: "+ datetime.toString() + "Formatted as: " + SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT).format(datetime.getTime()));
return SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT).format(datetime.getTime());
}
This gives me these logs:
Working good at first:
Datetime: java.util.GregorianCalendar[time=1426637512725,areFieldsSet=true,lenient=true,zone=GMT,firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2015,MONTH=2,WEEK_OF_YEAR=12,WEEK_OF_MONTH=3,DAY_OF_MONTH=18,DAY_OF_YEAR=77,DAY_OF_WEEK=4,DAY_OF_WEEK_IN_MONTH=3,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=11,SECOND=52,MILLISECOND=725,ZONE_OFFSET=0,DST_OFFSET=0]
Formatted as: 1:11 AM
then I change the month from March to April (year and day do the same) and now this:
Datetime: java.util.GregorianCalendar[time=1429315912725,areFieldsSet=true,lenient=true,zone=GMT,firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2015,MONTH=3,WEEK_OF_YEAR=16,WEEK_OF_MONTH=3,DAY_OF_MONTH=18,DAY_OF_YEAR=108,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=3,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=11,SECOND=52,MILLISECOND=725,ZONE_OFFSET=0,DST_OFFSET=0]
Formatted as: 2:11 AM
How could this possibly be?
This off-by-one behavior only seems to happen in future months, not this month or last. 2015 and 2016 are fine, but 2017 causes it the problem, too. The actual date seems to trigger it when it's a week or more into the future (regardless of year).
I can change year, month, and date in different UI widgets, switching the time-string formatting from normal to off-by-one and back. I control the hour with a TimePicker
that calls the string formatter on TimePicker.OnTimeChangedListener()
. I don't think it's relevant, since it works when month and day are set to some values, but here is that code just in case. As you can see, there is some off-by-one handling, as GregorianCalendar
stores HOUR_OF_DAY
as a 0-23 value, and the TimePicker
uses values 1-24 matching what the widget actually displays. I think I've done this right, but would be happy to find out the problem is a mistake here.
private void setUpTimePicker() {
timePicker.setCurrentHour(schedule.getHourOfDay() + 1);
timePicker.setCurrentMinute(schedule.getMinute());
timePicker.setOnTimeChangedListener(new TimePicker.OnTimeChangedListener() {
public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
schedule.setHourOfDay(hourOfDay - 1);
schedule.setMinute(minute);
timeText.setText(schedule.toTimeString());
}
});
}
The time of day formatted to a string is always right (0 = 1am, 17 = 5pm, etc for every hour of the day) or always wrong (0 = 2am, 17 = 6pm, ect for every hour of the day) based on the other calendar fields. The consistency makes me think that it's not a thread issue.
Could it be a timezone issue like every other similar question? I can't imagine how. My GregorianCalandar
instances are always created with this function:
public static TimeZone TIMEZONE = TimeZone.getTimeZone("GMT");
private static GregorianCalendar makeCalendar(){
GregorianCalendar now = (GregorianCalendar) GregorianCalendar.getInstance(TIMEZONE);
now.setFirstDayOfWeek(Calendar.SUNDAY);
return now;
}
Any thoughts would be very appreciated! I've exhausted everything that I can think of, including sleeping on it for a few days. Thanks in advance to anyone who answers!!
UPDATE
Jon found the problem, but since the code change is hidden in comments, I'll copy it here, too.
The problem was that SimpleDateFormat
is using the system timezone, not the timezone of the given GregorianCalendar
, like I had assumed. I fixed this by changing this line:
return SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT).format(datetime.getTime());
to this:
SimpleDateFormat sdf = new SimpleDateFormat("hh:mm a", LOCALE);
sdf.setTimeZone(TIMEZONE);
return sdf.format(datetime.getTime());
Then I removed the +1
and -1
from the TimePicker
and it works!
You're setting the time zone of the calendar you're using, but that's not what you pass into the SimpleDateFormat
- you're just using the default system time zone when you format here:
SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT).format(datetime.getTime())
I would suggest that:
java.time
from Java 8. The java.util
classes are awful. I realize you're working on Android, so java.time
is out, but is Joda Time an option for you?UTC
rather than GMT
to indicate "no offset from UTC" as otherwise some people will think you mean the UK time zone.)See more on this question at Stackoverflow