java.time
Хорошее решение - пропустить классы Calendar
и GregorianCalendar
и использовать вместо него LocalDate
из java.time, современного Java-API даты и времени.Calendar
и GregorianCalendar
давно устарели и плохо спроектированы.Современный API намного приятнее работать.И LocalDate
- это дата без времени и без часового пояса, поэтому, если подозрение, что я транслирую ниже, верное, это гарантирует, что проблема вашего часового пояса / летнего времени останется позади.Чтобы использовать его на старых Android, см. Ниже.
Что пошло не так?Спекулятивное объяснение
Следующее объяснение чисто теоретическое, но лучшее, что я смог придумать.Он основан на нескольких предположениях, которые я не смог проверить:
- Вы находитесь (или одно из ваших устройств) в часовом поясе, где в последние дни началось летнее время (DST).от марта 1994 года.
- В Android 101 и 5.0 может быть ошибка в
GregorianCalendar
, так что currentCalendar.add(Calendar.DAY_OF_WEEK, - (weekDay - 1));
просто добавляет это много раз за 24 часа.
Это чистое предположение,но если есть такая ошибка, ваш GregorianCalendar
закончится в 23:00 вечера перед целевой датой, что объяснит ваши результаты.Например, страны в ЕС начинают летнее время в последнее воскресенье марта.Это также имело место в 1994 году. Это очень хорошо подошло бы к вашей целевой дате воскресенья, 27 марта 1994 года, и также объяснило бы ваши неверные результаты за 1992 и 1993 годы. Я сделал краткий поиск в Интернете для упоминания такой ошибки вAndroid GregorianCalendar
и не нашел ничего, что могло бы его поддержать.
Для того, чтобы мои подозрения объяснили ваши наблюдения, нам понадобится еще пара деталей:
- Ошибка, которую я подозреваюбудет только в некоторых версиях Android (4.4, 5.0) и исправлена в более поздних версиях (8.0) (в противном случае ваше устройство Android 8.0 будет работать в другом часовом поясе).Кроме того, среда, в которой вы запускаете свои тесты, либо не содержит ошибок, либо имеет другой часовой пояс по умолчанию (либо объясняет, почему тесты проходят).
GregorianCalendar
, который вы получаете от getInstance
, имеет времядня в этом.И держит его после того, как вы установите дату.Чтобы объяснить разницу между двумя способами установки даты: допустим, вы запускаете код в 9:05.new GregorianCalendar(1994, Calendar.APRIL, 1)
даст вам 1 апреля 1994 года в 00:00.Calendar.getInstance()
, за которым следует currentCalendar.set(year, month, 1);
, дает вам 1 апреля 1994 года в 09:05.Разница между ними чуть больше 9 часов.В последнем случае предполагаемая ошибка приведет к тому, что вы наберете 8:05 27 марта, который все еще находится на правильной дате, поэтому вы не увидите ошибку.Если вы запустили свой код, скажем, в 0:35 ночью, вы попали в 23:35 26 марта, так что вы увидите ошибку и в этом случае.
Как яуже сказано, LocalDate
, java.time и ThreeTenABP сформируют хорошее решение.Если вы решите не полагаться на внешнюю библиотеку, а скорее пробиться через устаревшие классы, я думаю, что поможет следующее:
GregorianCalendar currentCalendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
currentCalendar.set(year, month, 1);
TimeZone
- это еще один старый и плохо разработанный классВ частности, метод getTimeZone
, который я использую, имеет несколько неприятных сюрпризов, но я считаю, что вышеприведенное работает (скрестив пальцы).Идея состоит в том, чтобы указать Calendar
использовать время UTC.UTC не имеет летнего времени, что уклоняется от проблемы.
Другая и более хакерская вещь, которую вы могли бы попробовать, была бы:
currentCalendar.set(year, month, 1, 6, 0);
Это устанавливает час дня на 6, что означает, чтоКогда вы вернетесь назад к переходу на летнее время, вы нажмете 5 часов утра, который все равно будет в правильную дату (вышеупомянутый вызов не устанавливает секунды и миллисекунды; за один прогон Iполучено 1 апреля 1994 года в 06:00: 40,213 UTC).
Вопрос: Можно ли использовать java.time на Android?
Да, java.time прекрасно работает на старых и новых устройствах Android,Для этого требуется как минимум Java 6 .
- В Java 8 и более поздних версиях и на более новых устройствах Android (от уровня API 26) современный API поставляется встроенным.
- В Java 6 и 7 получить ThreeTen Backport, бэкпорт новых классов (ThreeTen для JSR 310; см. Ссылки внизу).
- On (older) Android использует Android-версию ThreeTen Backport.Это называется ThreeTenABP.И убедитесь, что вы импортируете классы даты и времени из
org.threeten.bp
с подпакетами.
Ссылки