Преобразование строки в дату генерирует разные результаты на разных устройствах
Я получаю строку с именем date
в виде 2018-09-20T17:00:00Z
например, и преобразовать его в дату в формате Thu Oct 20 17:00:00 GMT+01:00 2018
с помощью
SimpleDateFormat dateConvert = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'", Locale.US);
convertedDate = new Date();
try {
convertedDate = dateConvert.parse(date);
} catch (ParseException e) {
e.printStackTrace();
}
Однако на разных устройствах я получаю разные результаты. Один дает Thu Oct 20 17:00:00 BST 2018
(Британское летнее время, эквивалентно GMT+01:00), но это оказывается проблематичным позже. Есть ли способ убедиться, что даты отформатированы в терминах смещения по Гринвичу, то есть по Гринвичу + 01: 00 вместо BST?
3 ответа
Вы просто делаете шаг 1 двухэтапного процесса:
- Разобрать дату, чтобы преобразовать ее в
Date
объект - Возьми это разобрали
Date
объект иformat
это с помощьюSimpleDateFormat
снова.
Итак, вы сделали первый шаг правильно, вот что вы должны сделать с шагом 2, попробуйте это:
final String formattedDateString = new SimpleDateFormat("EEE MMM dd HH:mm:ss 'GMT'XXX yyyy").format(convertedDate);
java.time
Instant convertInstant = Instant.parse(date);
Instant
(так же, как Date
) представляет момент времени независимо от часового пояса. Так что ты в порядке. В качестве дополнительного бонуса ваша строка 2018-09-20T17:00:00Z
на мгновение в формате ISO 8601, поэтому Instant
Класс анализирует его без необходимости указывать формат.
РЕДАКТИРОВАТЬ: Для форматирования в удобочитаемую строку в британском летнем времени с однозначным смещением UTC используйте, например:
DateTimeFormatter formatter
= DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss Z yyyy", Locale.UK);
ZonedDateTime dateTime = convertInstant.atZone(ZoneId.of("Europe/London"));
String formatted = dateTime.format(formatter);
System.out.println(formatted);
Этот фрагмент кода напечатан:
Чт 20 сентября 18:00:00 +0100 2018
18:00 - правильное время по смещению +01:00. Z
в конце исходной строки означает нулевое смещение, AKA "часовой пояс Зулу", а 17 в нулевом смещении - это тот же момент времени, что и 18:00 со смещением +01:00. Я взял строку шаблона формата из вашего собственного ответа.
РЕДАКТИРОВАТЬ 2
Я хотел бы представить вам мое предложение переписать Fixture
класс из вашего собственного ответа:
public class Fixture implements Comparable<Fixture> {
private static DateTimeFormatter formatter
= DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss Z yyyy", Locale.UK);
public Instant date;
/** @param date Date string from either web service or persistence */
public Fixture(String date) {
this.date = Instant.parse(date);
}
/** @return a string for persistence, e.g., Firebase */
public String getDateForPersistence() {
return date.toString();
}
/** @return a string for the user in the default time zone of the device */
public String getFormattedDate() {
return date.atZone(ZoneId.systemDefault()).format(formatter);
}
@Override
public int compareTo(Fixture other) {
return date.compareTo(other.date);
}
@Override
public String toString() {
return "Fixture [date=" + date + "]";
}
}
Этот класс имеет естественный порядок (а именно по дате и времени) в том, что он реализует Comparable
Это означает, что вам больше не нужен ваш DateSorter
учебный класс. Несколько строк кода, чтобы продемонстрировать использование нового getXx
методы:
String date = "2018-09-24T11:30:00Z";
Fixture fixture = new Fixture(date);
System.out.println("Date for user: " + fixture.getFormattedDate());
System.out.println("Date for Firebase: " + fixture.getDateForPersistence());
Когда я запустил этот фрагмент в часовом поясе Европы / Лондона, я получил:
Date for user: Mon Sep 24 12:30:00 +0100 2018
Date for Firebase: 2018-09-24T11:30:00Z
Таким образом, пользователь получает дату и время со своим собственным смещением от UTC, как я думаю, вы просили. Попробуем тот же фрагмент в часовом поясе Европа / Берлин:
Date for user: Mon Sep 24 13:30:00 +0200 2018
Date for Firebase: 2018-09-24T11:30:00Z
Мы видим, что пользователю в Германии говорят, что матч происходит в 13:30, а не в 12:30, что согласуется с его часами. Дата, которая будет сохранена в Firebase, не изменилась, что также является вашим желанием.
Что пошло не так в вашем коде
В строке шаблона формата есть две ошибки, yyyy-MM-dd'T'hh:mm:ss'Z'
:
- Строчные
hh
для часа в пределах AM или PM с 01 по 12 и имеет смысл только с маркером AM/PM. На практике вы получите правильный результат, за исключением анализа 12 часов, которые будут пониматься как 00. - Разбор
Z
как литерал, вы не получаете информацию о смещении UTC из строки. ВместоSimpleDateFormat
будет использовать настройку часового пояса JVM. Это очевидно отличается от одного устройства к другому и объясняет, почему вы получили разные и противоречивые результаты на разных устройствах.
Другая вещь, происходящая в вашем коде, это своеобразное поведение Date.toString
: этот метод получает настройку часового пояса JVM и использует ее для генерации строки. Поэтому, когда одно устройство установлено на Европа / Лондон, а другое на GMT+01:00, тогда оно равно Date
объекты будут отображаться по-разному на этих устройствах. Такое поведение многих смутило.
Вопрос: Могу ли я использовать java.time на Android?
Да, java.time
прекрасно работает на старых и новых устройствах Android. Это просто требует как минимум Java 6.
- В Java 8 и более поздних версиях и на более новых устройствах Android (от 26 уровня API, как мне говорят) современный API поставляется встроенным.
- В Java 6 и 7 получите ThreeTen Backport, бэкпорт новых классов (ThreeTen для JSR 310; см. Ссылки внизу). Код выше был разработан и запущен с
org.threeten.bp.Duration
из бэкпорта. - На (более старой) версии Android используется версия Android ThreeTen Backport. Это называется ThreeTenABP. И убедитесь, что вы импортируете классы даты и времени из
org.threeten.bp
с подпакетами.
связи
- Учебник Oracle: Дата и время, объясняющие, как использовать
java.time
, - Запрос спецификации Java (JSR) 310, где
java.time
был впервые описан. - ThreeTen Backport проект, бэкпорт
java.time
в Java 6 и 7 (ThreeTen для JSR-310). - ThreeTenABP, Android-версия ThreeTen Backport
- Вопрос: Как использовать ThreeTenABP в Android Project, с очень подробным объяснением.
- Статья в Википедии: ISO 8601
Так что просто добавить немного обновления здесь. Ответы, которые дали люди, очень помогли, и теперь у меня есть код, который делает именно то, что я хочу, но термин "наследие" в одном из комментариев дает мне понять, что может быть лучший и более продолжительный путь. Вот что сейчас происходит в коде.
1) Я беру футбольное приспособление, которое идет с датой String utc в форме 2018-09-22T11:30:00Z
2) Затем я анализирую дату, используя SimpleDateFormat convertUtcDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
а также convertUtcDate.setTimeZone(TimeZone.getTimeZone("GMT"));
3) я тогда получаю текущее время используя currentTime = Calendar.getInstance(TimeZone.getTimeZone("GMT")).getTime();
и сравнить два, используя if(convertedDate.after(currentTime))
найти следующий матч команды. В этот момент я обнаружил, что устройство будет иметь эти две даты в одной и той же форме, либо с BST, либо с GMT+01:00, но в любом случае эти даты можно точно сравнить.
4) Затем я форматирую дату, чтобы она соответствовала смещению по Гринвичу, используя SimpleDateFormat convertToGmt = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US);
а также String dateString = convertToGmt.format(convertedDate);
5) Для даты utc в 1) это возвращает Sat Sep 22 12:30:00 GMT+01:00 2018
независимо от устройства. Обратите внимание, что время отличается от даты utc. Не совсем уверен, почему это так (может быть потому, что парень, работающий с API, базируется в Германии, что на час впереди меня здесь, в Англии), но важно то, что это время правильное (это относится к Фулхэму - Уотфорду игра завтра, которая действительно в 12:30 BST / GMT+01:00).
6) Затем я отправляю эту строку вместе с несколькими другими частями информации о приборе в Firebase. На этом этапе важно указывать дату в формате GMT + 01: 00, а не в формате BST, поскольку другие устройства могут не распознавать форму BST при чтении этой информации.
7) Когда дело доходит до вызова этой информации из Firebase, я конвертирую ее обратно в дату, анализируя ее с помощью SimpleDateFormat String2Date = new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy", Locale.US);
и тогда я могу расположить матчи в хронологическом порядке, сравнивая их даты.
Я просто хотел бы повторить, что этот метод работает. Я проверил время, когда часовой пояс в Англии меняется на GMT+00:00, и он все еще работает нормально. Я постарался убедиться, что все сделано в терминах GMT, чтобы оно работало где угодно. Я не могу быть уверен, что это будет так. Кто-нибудь видит какие-либо недостатки в этом методе? Можно ли это улучшить?
Изменить: Вот фрагмент кода, который, я надеюсь, просто и точно отражает то, что я делаю.
public class FragmentFixture extends Fragment {
SimpleDateFormat convertUtcDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
SimpleDateFormat String2Date = new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy", Locale.US);
SimpleDateFormat convertToGmt = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US);
private List<Fixture> fixtureList;
Date date1;
Date date2;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
convertUtcDate.setTimeZone(TimeZone.getTimeZone("GMT"));
fixtureList = new ArrayList<>();
// retrieve fixtures from API and get date for a certain fixture. I will provide an example
String date = "2018-09-22T11:30:00Z";
Date convertedDate = new Date();
try {
convertedDate = convertUtcDate.parse(date);
} catch (ParseException e) {
e.printStackTrace();
}
Date currentTime = Calendar.getInstance(TimeZone.getTimeZone("GMT")).getTime();
if (convertedDate.after(currentTime)) {
String dateString = convertToGmt.format(convertedDate);
Fixture fixture = new Fixture(dateString);
fixtureList.add(fixture);
Collections.sort(fixtureList, new DateSorter());
}
}
public class DateSorter implements Comparator<Fixture> {
@Override
public int compare(Fixture fixture, Fixture t1) {
try {
date1 = String2Date.parse(fixture.getDate());
} catch (ParseException e) {
e.printStackTrace();
}
try {
date2 = String2Date.parse(t1.getDate());
} catch (ParseException e) {
e.printStackTrace();
}
return date1.compareTo(date2);
}
}
public class Fixture {
public String date;
public Fixture() {
}
public Fixture(String date) {
this.date = date;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
}
}