Календарь дает неправильный день - PullRequest
0 голосов
/ 11 декабря 2018

Я получаю от своего GPS-трекера строку времени в формате UTC, например: hhmmss.ssss.

Я хочу преобразовать время UTC в местное время пользователя, используя календарь .Поэтому я извлекаю часы, минуты и секунды из строки времени с помощью substring(int start, int end) и устанавливаю ее с помощью функции Calendar.set(int field, int value).После этого я конвертирую Calendar в Date, но знаю, что у меня неправильный день.

Например, timestamp = 091215.0000, если я регистрирую Calendar.getInstance(), я получаю: Thu Dec 11 10:12:15 GMT+01:00 2018 Но когда я конвертирую его с помощью своей функции, я получаю: Thu Dec 13 10:12:15 GMT+01:00 2018

Моя функция

   public static Date utcToLocalTimeFromLock(String timestamp) {
    Calendar calendar = Calendar.getInstance();

    if (timestamp.charAt(0) == '0') {
        calendar.set(Calendar.HOUR_OF_DAY, timestamp.charAt(1) + 1);
    } else {
        calendar.set(Calendar.HOUR_OF_DAY, Integer.valueOf(timestamp.substring(0, 2) + 1));
    }


    calendar.set(Calendar.MINUTE, Integer.valueOf(timestamp.substring(2, 4)));
    calendar.set(Calendar.SECOND, Integer.valueOf(timestamp.substring(4, 6)));

    Date date = calendar.getTime();
    Log.d(LOG_TAG, "utcToLocalTimeFromLock: " + date);
   return date;
}

Ответы [ 2 ]

0 голосов
/ 11 декабря 2018

Что пошло не так в вашем коде?

Ваша ошибка в этой строке:

        calendar.set(Calendar.HOUR_OF_DAY, timestamp.charAt(1) + 1);

Символы представлены в компьютерах цифрами.Это сбивает с толку тот факт, что во многих случаях символы и числа могут использоваться взаимозаменяемо.Если timestamp.charAt(1) - это символ '9', как в вашем примере, он представлен как число 57. Когда вы добавляете 1, вы получаете 58. Когда вы устанавливаете час дня на 58 - если вы ожидали, что Calendarкласс с настройками по умолчанию для сообщения об ошибке, вы были не правы, это запутанная вещь об этом классе и только одна из многих причин, почему мы рекомендуем избегать его использования.Он просто продолжает считать часы в следующие дни и по совпадению заканчивается в нужный час, 10, только через два дня (два дня - 48 часов и 48 + 10 = 58).

Другая рекомендация:

  • Не разбирайте вручную строку времени.Оставьте разбор в классе библиотеки.
  • Не переводите в центральноевропейское время, добавив час.Это подвержено ошибкам.В летнее время (DST) это даст неверный результат.Это не переносимо на другие часовые пояса.Вместо этого снова оставьте преобразование в библиотечные классы.

Как исправить?

Базиль Бурк уже показал хороший способ решения вашей проблемы с помощью java.time, современной Java-даты ивремя API.Я хотел бы показать вам, что с java.time вы также можете включить долю секунды без особых проблем, если хотите.

static DateTimeFormatter timeFormatter = new DateTimeFormatterBuilder()
        .appendPattern("HHmmss")
        .appendFraction(ChronoField.NANO_OF_SECOND, 3, 4, true)
        .toFormatter(Locale.ROOT);
static ZoneId zone = ZoneId.of("Europe/Paris");

public static ZonedDateTime utcToLocalTimeFromLock(String timestamp) {
    return LocalDate.now(ZoneOffset.UTC)
            .atTime(LocalTime.parse(timestamp, timeFormatter))
            .atOffset(ZoneOffset.UTC)
            .atZoneSameInstant(zone);
}

Давайте попробуем:

    System.out.println(utcToLocalTimeFromLock(timestamp));

Вывод при запуске только сейчас:

2018-12-11T10: 12: 15 + 01: 00 [Европа / Париж]

Метод appendFraction требует минимумаи максимальное количество знаков после запятой, поэтому я указал 3 и 4 соответственно.В зависимости от ваших потребностей вы можете указать минимум до 0 и максимум до 9 цифр.

Конечно, замените свой часовой пояс, если это не произошло в Европе / Париже.

Если вам необходим старомодный объект Date для устаревшего API, который вы не хотите обновлять прямо сейчас:

public static Date utcToLocalTimeFromLock(String timestamp) {
    Instant inst= LocalDate.now(ZoneOffset.UTC)
            .atTime(LocalTime.parse(timestamp, timeFormatter))
            .atOffset(ZoneOffset.UTC)
            .toInstant();
    return Date.from(inst);
}

Вт 11 дек. 10:12:15 CET 2018

Если используется обратный порт (ThreeTen Backport и / или ThreeTenABP, см. Ниже) для преобразования из Instant в Date вместо Date.from(inst), используйте это:

    return DateTimeUtils.toDate(inst);

Поскольку Date не имеет часового пояса, в этом случае преобразование часового пояса не требуется.Только потому, что я тоже нахожусь в часовом поясе Центральной Европы, вывод совпадает с тем, что вы ожидаете - это будет время в часовом поясе JVM.

Вопрос: Могу ли я использовать java.time на Android?

Да, java.time прекрасно работает на старых и новых устройствах Android.Для этого требуется как минимум Java 6 .

  • В Java 8 и более поздних версиях и на более новых устройствах Android (от уровня API 26) современный API поставляется встроенным.
  • В Java 6 и 7 получают ThreeTen Backport, бэкпорт новых классов (ThreeTen для JSR 310; см. Ссылки внизу).
  • На (более старых) Android используется версия ThreeTen для AndroidBackport.Это называется ThreeTenABP.И убедитесь, что вы импортируете классы даты и времени из org.threeten.bp с подпакетами.

Ссылки

0 голосов
/ 11 декабря 2018

java.time

Вы используете ужасные старые классы даты и времени, которые были вытеснены годами назад ведущими в отрасли java.time классами.

Записать текущий момент в UTC как объект OffsetDateTime.

OffsetDateTime now = OffsetDateTime.now( ZoneOffset.UTC ) ;

Разобрать время дня как LocalTime.

String input = "091215.000" ;
DateTimeFormatter f = DateTimeFormatter.ofPattern( "HHmmss.SSS" ) ;
LocalTime lt = LocalTime.parse( input ) ;

Применить наше время суток.

OffsetDateTime odt = now.with( lt ) ;

У вас может быть проблема около удара в полночь.Возможно, вы захотите добавить какой-нибудь код, чтобы увидеть, находится ли отснятое текущее время в новом дне, но ваше время дня незадолго до полуночи вчерашнего дня.Если это так, вычтите день из now.Используйте любые значения времени дня в границах, которые имеют смысл в вашей ситуации.

if ( 
    lt.isAfter( LocalTime.of( 23 , 55 ) ) 
    && 
    odt.toLocalTime().isBefore( LocalTime.of( 0 , 5 ) ) 
) {
    now = now.minusDays( 1 ) ;
}

Настройте UTC на нужный часовой пояс.

ZoneId z = ZoneId( "Africa/Tunis" ) ;
ZonedDateTime zdt = odt.atZoneSameInstant( z ) ;

Для Android <26 см. Проект <em>ThreeTenABP .


О java.time

Среда java.time встроена в Java 8 и более поздние версии.Эти классы заменяют проблемные старые устаревшие классы даты и времени, такие как java.util.Date, Calendar, & SimpleDateFormat.

Проект Joda-Time , теперь в режиме обслуживания , рекомендует перейти на классы java.time .

Чтобы узнать больше, см. Oracle Tutorial .И поиск переполнения стека для многих примеров и объяснений.Спецификация: JSR 310 .

Вы можете обмениваться java.time объектами непосредственно с вашей базой данных.Используйте драйвер JDBC , совместимый с JDBC 4.2 или более поздней версии.Нет необходимости в строках, нет необходимости в java.sql.* классах.

Где получить классы java.time?

  • Java SE 8 , Java SE 9 , Java SE 10, Java SE 11 и более поздние версии - часть стандартного Java API с связанной реализацией.
    • Java 9 добавляет некоторые незначительные функции и исправления.
  • Java SE 6 и JavaSE 7
    • Большинство функций java.time перенесены в Java 6 & 7 в ThreeTen-Backport .
  • Android
    • Более поздние версии реализации связки Android java.time классы.
    • Для более ранних версий Android (<26) проект <a href="https://github.com/JakeWharton/ThreeTenABP" rel="nofollow noreferrer"> ThreeTenABP адаптируется ThreeTen-Backport (упомянуто выше).См. Как использовать ThreeTenABP… .

ThreeTen-Extra Проект расширяет java.time дополнительными классами.Этот проект является полигоном для возможных будущих дополнений к java.time.Здесь вы можете найти несколько полезных классов, таких как Interval, YearWeek, YearQuarter и more .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...