Android localdate to date

Преобразование даты в локальную дату или локальное время и обратно

Узнайте о возможных способах преобразования между старыми java.util.Классы даты и новый API java.time.

Автор: baeldung
Дата записи

1. Обзор

Начиная с Java 8, у нас есть новый API даты: java.time .

Однако иногда нам все еще нужно выполнять преобразования между новым и старым API и работать с представлениями данных из обоих.

Дальнейшее чтение:

Переход на новый API даты и времени Java 8

Введение в API даты и времени Java 8

2. Преобразование java.util.Дата на java.время.LocalDate

Давайте начнем с преобразования старого представления данных в новое.

Здесь мы можем воспользоваться новым методом |/to Instant () , который был добавлен в java.util.Дата в Java 8.

Когда мы преобразуем объект Instant |/, необходимо использовать ZoneId , потому что Instant объекты не зависят от часового пояса — просто точки на временной шкале.

atZone(ZoneId zone) API из Instant объекта возвращает ZonedDateTime , поэтому нам просто нужно извлечь Локальные данные из него с помощью метода toLocalDate () .

Во-первых, мы используем систему по умолчанию ZoneId :

И аналогичное решение, но с другим способом создания Instant объекта — с использованием метода ofEpochMilli() :

Прежде чем мы продолжим, давайте также быстро рассмотрим класс old java.sql.Date и то, как его можно преобразовать в Локальные данные .

Начиная с Java 8, мы можем найти дополнительный метод toLocalDate() в java.sql.Date , который также дает нам простой способ преобразования его в java.time.LocalDate .

В этом случае нам не нужно беспокоиться о часовом поясе:

Точно так же мы можем преобразовать старый объект Date в объект LocalDateTime . Давайте посмотрим на это дальше.

3. Преобразование java.util.Дата на java.время.LocalDateTime

Чтобы получить экземпляр LocalDateTime , мы можем аналогичным образом использовать посредника ZonedDateTime , а затем использовать toLocalDateTime() API.

Как и раньше, мы можем использовать два возможных решения для получения Instant объекта из java.util.Дата :

И, начиная с Java 8, мы также можем использовать java.sql.Timestamp для получения LocalDateTime :

4. Преобразуйте java.time.LocalDate в java.util.Date

Теперь, когда у нас есть хорошее понимание того, как преобразовать старое представление данных в новое, давайте посмотрим на преобразование в другом направлении.

Мы обсудим два возможных способа преобразования Локальной даты в Дату .

В первом случае мы используем новый метод valueOf(LocalDate date) , предоставленный в | java.sql.Date object, который принимает Локальные данные в качестве параметра:

Как мы видим, это легко и интуитивно. Он использует местный часовой пояс для преобразования (все делается под капотом, так что не нужно беспокоиться).

В другом примере Java 8 мы используем объект Instant , который мы передаем в from(Instant instant) | метод | java.util.Дата объект:

Обратите внимание, что мы используем здесь объект Instant и что нам также нужно заботиться о часовых поясах при выполнении этого преобразования.

Далее, давайте используем очень похожее решение для преобразования LocalDateTime в Date объект.

5. Конвертируйте java.time.LocalDateTime в java.util.Date

Самый простой способ получить a java.util.Дата из LocalDateTime должна использовать расширение для |/java.sql.Timestamp — доступно с Java 8:

Но, конечно, альтернативным решением является использование объекта an Instant , который мы получаем из ZonedDateTime :

6. Дополнения Java 9

В Java 9 доступны новые методы, упрощающие преобразование между java.util.Дата и java.time.LocalDate или java.time.LocalDateTime .

LocalDate.of Instant(Мгновенный момент, зона ZoneId) и LocalDateTime.ofInstant(Мгновенный момент, зона ZoneId) предоставляют удобные ярлыки:

7. Заключение

В этой статье мы рассмотрели возможные способы преобразования старого java.util.Дата в новый java.time.LocalDate и java.time.LocalDateTime , а также наоборот.

Полная реализация этой статьи доступна на GitHub .

Источник

Java и время: часть вторая

Эта статья написана в продолжение к первой части и посвящена новому Date Time API, который был введен в Java 8. Я изначально хотел оформить эту тему отдельно, поскольку она достаточно большая и серьезная. Я еще сам не в полной мере начал использовать этот API в проектах, поэтому разбираться будем вместе по ходу. В принципе в переходе на новый API нет никакой срочной необходимости, более того многие еще и не начинали проекты на Java 8, а это означает, что время на освоение еще есть.

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

История

Сравнение

Начать наверное стоит с того, что именно не устраивало многих в старом API. И тут же, чтобы не терять время сразу укажу, что в новом API изменилось к лучшему.

Разбиение классов по пакетам:

  • В старом API классы для работы со временем усажены в пакеты java.util и java.sql — среди большого множества других классов. Кроме того существуют еще классы java.util.concurrent.TimeUnit и java.text.DateFormat c наследниками.
  • В новом API для работы с временем выделен отдельный пакет java.time

Названия классов:

  • Названия классов в старом API не отражают суть происходящего. В старом API есть два класса которые способны обозначить точку на временной оси: java.util.Date и java.util.Calendar. Класс java.util.Date обозначает время в миллисекундах по Unix-time, а вовсе не дату (он назван так скорее всего из тех же соображений, по которым время в командной строке выдает утилита /bin/date). Класс java.util.Calendar также вовсе не календарь, у него есть состояние в виде временной зоны, календарных и временных полей.
  • В новом API названия классов даны более осмысленно. Есть классы аналогичные уже упомянутым: java.time.Instant и java.time.ZonedDateTime. Существует также множество других классов для более специализированного использования.
Читайте также:  Очистить память андроид huawei планшет

Неизменяемость и потокобезопасность:

    Класс java.util.Date не является immutable и отягощен большим количеством лишних методов, которые хоть уже и помечены как устаревшие, но вряд ли будут удалены в обозримом будущем. Изменяемость java.util.Date заставляет некоторых клонировать инстанты java.util.Date — для того чтобы враг не пробрался:

Класс java.util.Calendar также изменяем. Хотя это особых проблем это не доставляет поскольку большинство понимает что у него есть внутреннее состояние которое меняется, да и передавать его аргументами как-то не очень принято.

Поскольку классы в старом API изменяемые, использовать их в многопоточной среде нужно с осторожностью. В частности java.util.Date можно признать «эффективно» потоко-безопасным, если вы не вызываете у него устаревшие методы.

  • Все классы в новом API неизменяемые и как следствие потоко-безопасные
  • Точность:

    • Точность представления в времени составляет одну миллисекунду. Для большинства практических задач этого более чем достаточно, но иногда хочется иметь точность повыше.
    • В новом API представления времени составляет одну наносекунду, что в миллион раз точнее.

    Хранение меток времени и даты:

    • Классы для меток времени и даты (java.sql.Date и java.sql.Time) не являются чистым представлением меток времени и даты, поскольку унаследованы от java.util.Date и так или иначе хранят полное значение Unix-time с игнорированием части этого значения.
    • В новом API соответствующие классы java.time.LocalDate и java.time.LocalTime хранят чистые кортежи (yyyy,MM,dd) и (HH,mm,dd) соответственно, и никакой лишней информации или логики в этих классах нет. Также введен класс java.time.LocalDateTime который хранит оба кортежа.

    Указание временной зоны:

    • В старом API многие действия, где необходимо указание временной зоны, могут быть выполнены без ее указания. В этом случае берется временная зона по-умолчанию, а программист может даже и не догадаться о том, что он что-то упустил.
    • В новом API все действия, где необходимо указание временной зоны, требуют ее явно: либо в виде аргумента метода, либо временная зона отображена прямо в названии метода. Другими словами временная зона «по-умолчанию» нигде по умолчанию не используется.

    Тестирование:

    • Старый API очень сложно использовать в тестах, в которых нужно протестировать поведение логики с течением времени (об этом подробно расписано в предыдущей статье).
    • В новом API введен специальный абстрактный класс java.time.Clock, единый экземпляр которого можно инжектить в контекст или просто передавать в свою логику. Переопределив этот класс для тестов, можно контролировать течение времени для своего кода в ходе его выполнения.

    Нумерация месяцев:

    • В старом API номера месяцев идут с 0, что очень неинтуитивно.
    • В новом API номера месяцев идут с 1. Появилось новое перечисление java.time.Month.

    Установка меток:

    • В java.util.Calendar устанавливала год-месяц-день-час-минуту-секунду, но для сброса миллисекунд нужно было сделать отдельный вызов.
    • В java.time.ZonedDateTime устанавливаются все поля сразу, включая наносекунды.

    Обозначение длительности:

    • В старом API нет классов для определения длительности и промежутков времени. Обычно используется простой long и хранение длительности в виде миллисекунд.
    • В новом API определены специальные классы для длительности и периодов.

    Опасения

    Также наверное стоит рассказать о том, что я точно не стану называть «ухудшениями», а осторожно назову «опасениями»:

    • если раньше было два активно используемых класса: java.util.Date и java.util.Calendar, то сейчас классов стало сильно больше, плюс к ним добавилась иерархия интерфейсов и абстрактных классов.
    • отчасти из-за большого количества новых классов появились некоторые нюансы работы, которые я успел найти уже за несколько часов исследований и о которых мы поговорим позже.
    • новый API не контролирует правильность операций в compile-time. Многие проблемы с отсутствием временной зоны будут проявляться только в runtime — как это далее будет видно в примерах. Я бы предпочел возможно менее гибкий, но более строгий контракт.
    • в ходе работы генерируется большее количество объектов. Например получение текущей временной точки через Instance.now() кроме самого экземпляра java.time.Instance создает еще и java.time.Clock на каждый запрос, хотя это вообще ему ни к чему — в текущей реализации достаточно было бы вызова System.currentTimeMillis(). Также промежуточные объекты создаются и при многих других действиях. Но я не думаю, что для типичного бэкенда это будет представлять какую-нибудь проблему — ни по потреблению памяти, ни по времени исполнения. Хардкорщики все равно хранят время в long или даже пакуют в int.
    • проблему (проблема ли это?) с учетом leap second никак не решили. Фактически новая библиотека все также не отходит ни на шаг от Unix-time и полагается на внешний перевод секундной стрелки назад. Прошлое при этом все также каждый раз теряет секунду и сдвигается вперед. Библиотеку для точных научных расчетов мы так и не получили.

    Об этих и других, настороживших меня кейсах расскажу подробнее уже в примерах.

    Временные зоны

    Начнем как обычно с временных зон. Новый класс java.time.ZoneId обозначает временную зону. Два его сабкласса java.time.ZoneRegion и java.time.ZoneOffset реализуют два типа временных зон: временную зону по географическому принципу и временную зону по простому смещению относительно UTC, UT или GMT. Правила перевода стрелок вынесены в отдельных класс java.time.zone.ZoneRules, экземпляр которого доступен через метод java.time.ZoneId#getRules.

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

    Не очень понятно почему case-4, который фактически запрашивает тоже что и case-3, в результате создает java.time.ZoneRegion, а не java.time.ZoneOffset.

    Для временной зоны UTC заведена специальная константа java.time.ZoneOffset#UTC, но тем не менее запрос на ZoneId.of(«UTC») в новом API выдает уже объект класса java.util.ZoneRegion, а не эту константу.

    «Время — это часы» — как утверждают некоторые физики. И это фраза является ключевой для нового API, где класс java.time.Clock является краеугольным. И также как некоторые из наших часов, время для нас может быть: константным (неидущим), опаздывающим, идущим с различной степенью точности, двигающем стрелки по разному в разных часовых поясах. В общем в новом API можно использовать (либо определить самому) практически любой ход времени, в том числе и для проверки тестов.

    Читайте также:  Режим терминала android studio

    Стандартный экземпляр java.time.Clock можно создать только фабричными статическими методами (сам класс абстрактный).

    Стандартный экземпляр java.time.Clock всегда знает о временной зоне в которой его создали (хотя это бывает и ненужным).

    Пройдемся по фабричным методам:

    • java.time.Clock#systemDefaultZone — метод создает системные часы во временной зоне по-умолчанию.
    • java.time.Clock#systemUTC — метод создает системные часы во временной зоне UTC.
    • java.time.Clock#system — метод создает системные часы в указанной временной зоне.
    • java.time.Clock#fixed — метод создает часы константного времени, то есть часы не идут, а стоят на месте.
    • java.time.Clock#offset — метод создает прокси над указанными часами, который смещает время на указанную величину.
    • java.time.Clock#tickSeconds — метод создает системные часы в указанной временной зоне, значение которых округлено до целых секунд.
    • java.time.Clock#tickMinutes — метод создает системные часы в указанной временной зоне, значение которых округлено до целых минут.
    • java.time.Clock#tick — метод создает прокси над указанными часами, который округляет значения времени до указанного периода.
    • java.time.Clock#withZone — метод создает копию текущих часов в другой временной зоне.

    Можно переопределить java.time.Clock и написать любую свою логику выдачи времени, например часы которые выдают случайное время на каждый запрос, почему бы и нет?

    У объекта java.time.Clock всего три рабочих метода:

    • java.time.Clock#getZone — запросить временную зону в которой работают часы.
    • java.time.Clock#millis — запросить текущее время в миллисекундах по Unix-time
    • java.time.Clock#instant — запросить текущее время в самом общем смысле (по факту — в наносекундах по Unix-time)

    Теперь немного критики:

    • Я бы завел чистый интерфейс java.time.Clock и отдельно фабрику java.time.Clocks — но я не настаиваю.
    • Часам зачем-то в обязательном порядке навязывают временную зону. Самим часам она вообще не нужна: ни java.time.Clock#millis, ни java.time.Clock#instant временную зону не используют. Временная зона часов запрашивается в фабричных методах DateTime, но именно туда ее и можно было передавать отдельным параметром в методе, а не хранить балластом в java.time.Clock.
    • К сожалению класса MockClock для ручного управления временем для тестов нет, придется писать его самим — это не проблема, но было лучше бы если бы он был сразу.
    • У часов нет метода java.time.Clock#ticks для измерения вневременных наносекундных тиков (аналог java.lang.System#nanoTime). С одной стороны отсутствие такого метода объяснимо, потому как к исчислению времени не относится. Но с другой стороны, это относится к измерению длительности операций. Поэтому для управлениями вневременными тиками (и измерением длительности соответственно) в тестах было бы неплохо, если бы метод для тиков находился бы также в этом интерфейсе, хотя бы потому что ручное продвижение времени вперед в MockClock по умолчанию продвигало бы одновременно как время, так и тики.

    Instant

    java.time.Instant — это новый java.util.Date, только неизменяемый, с наносекундной точностью и корректным названием. Внутри хранит Unix-time в виде двух полей: long с количеством секунд, и int с количеством наносекунд внутри текущей секунды.

    Значение обоих полей можно запросить напрямую, а также можно попросить посчитать более привычное для старого API представление Unix-time в виде миллисекунд:

    Также как и java.util.Date (при правильном его использовании), объект класса java.time.Instant ничего не знает про временную зону.

    Отдельно стоит сказать про метод java.time.Instant.toString(). Если раньше java.util.Date.toString() работал с учетом текущей локали и временной зоны по умолчанию, то новый java.time.Instant.toString() всегда формирует текстовое представление во временной зоне UTC и одинаковым форматом ISO-8601 — это касается и вывода переменных в IDE при отладке:

    Базовые интерфейсы

    Посмотрим на базовый интерфейс java.time.temporal.TemporalAccessor. Интерфейс TemporalAccessor — это справочник для запроса отдельной частичной информации по текущей точке или метке и его реализуют все временные классы нового API.

    Попросим значение Unix-time у java.time.Instant:

    Получаем исключение с совершенно необъяснимым сообщением:

    Немного подебажив, становится ясна причина исключения: результат теоретически может не помещаться в диапазон int (хотя в данный момент помещается). Поле INSTANT_SECONDS надо запрашивать как long. Исправим запрос, попутно запросим дополнительную мета-информацию:

    Поле CLOCK_HOUR_OF_DAY не поддерживается типом Instant. Это совершенно ожидаемо, поскольку для выяснения часа дня по временной точке нам нужно указать временную зону, которой в java.time.Instant нет. Попробуем все таки запросить это значение:

    Все правильно — при запросе часа дня мы получаем исключение. Прекрасно, что метод запроса не стал использовать временную зону по умолчанию (которой в новом API и нет).

    Кроме запроса отдельных полей можно запрашивать значения с помощью более сложных алгоритмов-стратегий наследующих интерфейс java.time.TemporalQuery:

    java.time.temporal.Temporal — интерфейс является наследником интерфейса TemporalAccessor. Вводит операции сдвига временной точки/метки вперед и назад, операцию замены части временной информации, а также операцию вычисления расстояния до другой временной точки/метки. Реализуется почти всеми «полноценными» временными классами нового API.

    Пробуем сдвинуть метку на день вперед и посчитаем разницу:

    Поскольку все классы наконец-то стали неизменяемыми, то результаты операций надо не забыть присвоить другой переменной, поскольку оригинальная при операции не изменяется — все аналогично java.lang.String или java.math.BigDecimal.

    Попробуем изменить час дня в java.time.Instant:

    Ожидаемо получаем по рукам, поскольку для этой операции необходима временная зона.

    java.time.temporal.TemporalAdjuster — интерфейс стратегии коррекции временной точки/метки, например перемещение в первый день текущего кода. Раньше приходилось для этого писать свои вспомогательные классы для работы с полями java.util.Calendar — сейчас весь код можно оформить в виде стратегии, если нужной еще нет в стандартной поставке:

    Теперь можно перейти к временным классам.

    LocalTime, LocalDate, LocalDateTime

    java.time.LocalTime — это кортеж (час, минуты, секунды, наносекунды)
    java.time.LocalDate — это кортеж (год, месяц, день месяца)
    java.time.LocalDateTime — оба кортежа вместе

    К этим же классам я бы отнес еще и специфические классы для хранения части информации: java.time.MonthDay, java.time.Year, java.time.YearMonth

    Все эти классы объединяет то, что они содержат временные метки или их части, но временные точки на временной оси сами по себе определить не в состоянии (даже LocalDateTime) — поскольку ни в одном из них нет ни временной зоны, ни даже смещения.

    Эти классы, как и все другие, поддерживают интерфейс java.lang.Comparable, но нужно понимать, что это именно сравнение временных меток, а не временных точек:

    Нужно сказать, что несмотря на неизбежные параллели в использовании между java.time.LocalTime и java.sql.Time, а также между java.time.LocalDate и java.sql.Date — это совершенно различные классы. В старом API классы java.sql.Time и java.sql.Date являются наследниками java.util.Date, а это значит, что их интерпретация (получение значения часа например) зависит от временной зоны в которой объект этого класса был создан и от временной зоны в которой этот объект будет прочитан. В новом API классы java.time.LocalTime и java.time.LocalDate — это честные кортежи значений и при записи и чтении значения часа временная зона никак не участвует.

    Однако временная зона необходима при создании их из временной точки, поскольку интерпретация дней-часов от нее зависит:

    Исключение выбрасывается, по причине того, что временную зону взять просто неоткуда (в Instant ее нет, а зону по-умолчанию не берем). Но ее можно получить либо из часов java.time.Clock, либо передать дополнительно:

    Теперь все работает, но легкость, с которой можно сделать ошибку, несколько настораживает.

    В комментариях к предыдущей статье упомянули, что настоящие параноики должны еще указывать календарь при операциях с календарными значениями (что включает создание объектов всех временных классов кроме Instant). В новом API есть несколько календарей, которые названы хронологиями:

    Вообще сложно представить кейс, где может потребоваться отличная от ISO-8601 хронология IsoChronology (которая практически эквивалентна грегорианскому календарю), но, если что, новый API это поддерживает.

    ZonedDateTime

    java.time.ZonedDateTime — аналог java.util.Calendar. Это самый мощный класс с полной информацией о временном контексте, включает временную зону, поэтому все операции со сдвигами этот класс проводит правильно.

    Попробуем создать ZonedDateTime из LocalDateTime:

    Сразу же получаем по рукам за то, что в операции (в LocalDateTime) нет временной зоны, а использовать временную зону по-умолчанию новое API опять отказывается (это очень хорошо).

    Посмотрим, насколько ZonedDateTime строг по отношению к некорректно указанным датам. В java.util.Calendar есть переключатель lenient, который можно настроить как на «строгий», так и на «мягкий» режим. В новом API такого переключателя нет.

    29-е февраля не в високосном году не пройдет:

    60-ю секунду указать нельзя:

    Но указание метки в момент перевода стрелок на летнее время успешно проходит, а результат отличается от ожидаемого. В строгом режиме java.util.Calendar такое не пропускал (см. предыдущую статью).

    Про операции в ZonedDateTime я ничего писать не буду — можно посмотреть документацию.

    OffsetTime, OffsetDateTime

    java.time.OffsetTime — это LocalTime + ZoneOffset
    java.time.OffsetDateTime — это LocalDateTime + ZoneOffset

    Надо сказать, что также как смещение не является временной зоной (временная зона — это история смещений, плюс еще дополнительная информация), то также OffsetDateTime хранит меньше информации чем ZonedDateTime. OffsetDateTime может полноценно обозначать временную точку на временной оси, но полностью корректные сдвиги производить не в силах, поскольку о будущих и прошлых переводах стрелок этот класс ничего не знает.

    Эти классы можно использовать, если по ситуации известно только текущее смещение пользователя (например через JavaScript). Полностью корректные операции сдвигов они не позволяют сделать, поэтому лучше использовать ZonedDateTime — если есть способ выяснить полноценную временную зону пользователя. С другой стороны, между двумя экземплярами OffsetDateTime всегда можно успешно и правильно посчитать разницу в секундах.

    Модификации времени

    Из всех классов нового API временную точку на временной оси однозначно определяют только три: java.time.Instant, java.time.ZonedDateTime и java.time.OffsetTime.

    Операции сдвига и модификации времени в общем случае выполняются корректно только в java.time.ZonedDateTime, поскольку только он один знает про временные зоны.

    Выполним пример с расчетом прошедших часов в день перевода стрелок на зимнее время:

    Кейсы case#1 и case#2 выполняются на полноценном классе ZonedDateTime и выдают правильный результат, поскольку в этот день стрелки переводили назад в итоге получается 25 часов.

    Кейс case#3 показывает, что OffsetDateTime полноценно сохраняет информацию о точке на временной оси, но кейс case#4 показывает, что с потерей временной зоны этот класс производит вычисления уже по другому.

    То же с кейсами case#5 и case#6 — несмотря на то, что Instant полноценно определяет точку на временной оси, расчеты он производит без временной зоны.

    Кейсы case#7 и case#8 — показывают, что LocalDateTime не может ни полноценно отразить временную точку, ни произвести расчеты без временной зоны.

    Я ни в коем случае не хочу сказать, что эти примеры показывают ошибки в новом API (если кто-то так подумал). Все эффекты ожидаемы и объяснимы. Напрягает другое — насколько такое поведение будет осознано армией Java-разработчиков. В старом API такие потенциальные проблемы были невозможны, поскольку всеми расчетами занимался только один класс java.util.Calendar, а единственное, что в нем можно было сделать неправильно — забыть явно указать временную зону.

    Возможно стоило запретить большинство операций со временем во всех классах кроме ZonedDateTime, поскольку только он один в курсе переводов стрелок. Возможно стоило запретить расчет Duration с использованием LocalDateTime, поскольку без временной зоны он не определяет временную точку. Я не готов сейчас как-то серьезно дискутировать на тему возможности или невозможности таких решений, но ощущение опасности от нового API у меня есть.

    Period, Duration

    В новом API есть два класса для определения длительности.

    java.time.Period — описание календарной длительности (периода) в виде кортежа (год, месяц, день).

    java.time.Duration — описание точной длительности в виде целого количества секунд и долей текущей секунды в виде наносекунд.

    Разницу между двумя можно показать в примере с днем перевода стрелок на зимнее время. Из-за перевода стрелок назад этот календарный день состоит из 25 часов.

    Источник

    Читайте также:  Постапокалипсис рпг для андроид
    Оцените статью