Преобразование строки в дату генерирует разные результаты на разных устройствах

Я получаю строку с именем 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 двухэтапного процесса:

  1. Разобрать дату, чтобы преобразовать ее в Date объект
  2. Возьми это разобрали 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 с подпакетами.

связи

Так что просто добавить немного обновления здесь. Ответы, которые дали люди, очень помогли, и теперь у меня есть код, который делает именно то, что я хочу, но термин "наследие" в одном из комментариев дает мне понять, что может быть лучший и более продолжительный путь. Вот что сейчас происходит в коде.

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;
    }

}

}

Другие вопросы по тегам