Освой Java играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Классы Date, Calendar, DateFormat

Date
Calendar
GregorianCalendar
Класс DateFormat
Класс SimpleDateFormat
TimeZone

Date

Класс Date предназначен для работы с текущими датой и временем и позволяет отталкиваться от них для решения своих задач. При выходе новых версий Java часть методов класса была перемещена в классы Calendar и DateFormat.

При импорте выбирайте java.util.Date, а не java.sql.Date.

У класса есть два конструктора:

Date()
Date(long milliseconds)

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

Методы:

  • boolean after(Date date) - если объект класса Date содержит более позднюю дату, чем указано в параметре, то возвращается true
  • boolean before(Date date) - если объект класса Date содержит более раннюю дату, чем указано в параметре, то возвращается true
  • int compareTo(Date date) - сравнивает даты. Возвращает 0, если совпадают, отрицательное значение - если вызывающая дата более ранняя, положительное значение - если вызывающая дата более поздняя, чем в параметре
  • boolean equals(Object object) - если даты совпадают, то возвращается true
  • long getTime() - возвращает количество миллисекунд, прошедших с полуночи 1 января 1970 года
  • void setTime(long milliseconds) - устанавливает время и дату в виде числа миллисекунд, прошедших с полночи 1 января 1970 года.

Если вы посмотрите документацию, то увидите, что существует множество методов для получения или установки отдельных компонентов времени и даты, например, getMinutes()/setMinutes() и др. Все они являются устаревшими и вместо них следует использовать класс Calendar.

Простой пример вывода даты на экран.


// Создадим объект Date
Date date = new Date();

mInfoTextView.setText(date.toString());

С помощью метода getTime() можно отобразить количество миллисекунд, прошедших с 1 января 1970 года. Обновим пример


// Создадим объект Date
Date date = new Date();

long millis = date.getTime();

infoTextView.setText(String.valueOf(millis));

Или так:


long msTime = System.currentTimeMillis();
Date curDateTime = new Date(msTime);
mInfoTextView.setText(curDateTime.toString());

Впрочем, предыдущий код избыточен, так как конструктор Date() без параметров уже содержит текущее время и дату.


Date anotherCurDate = new Date(); 
mInfoTextView.setText(anotherCurDate.toString());

При работе с датами важно учитывать локаль (регион), если хотим увидеть вывод даты на нужном языке. Локаль можно задать в строковых функциях. Выводим дату на немецком языке.


Locale locale = new Locale("de");
Locale.setDefault(locale);
Date now = new Date();
mInfoTextView.setText(
        String.format(locale, "%tc\n", now) + // длинная строка
                String.format(locale, "%tD\n", now) + //(MM/DD/YY)
                String.format(locale, "%tF\n", now) + //(YYYY-MM-DD)
                String.format(locale, "%tr\n", now) + //Full 12-hour time
                String.format(locale, "%tz\n", now) + //Time zone GMT offset
                String.format(locale, "%tZ\n", now)  //Localized time zone abbreviation
);

Calendar

Абстрактный класс Calendar позволяет преобразовать время в миллисекундах в более удобном виде - год, месяц, день, часы, минуты, секунды. Существуют также подклассы, например, GregorianCalendar.

Переменная типа boolean под именем areFieldsSet указывает, были установлены компоненты времени. Переменная fields - это массив целочисленных значений, содержащий компоненты времени. Переменная isSet - массив типа boolean, указывающий, был ли установлен специфический компонент времени. Переменная time (тип long) содержит текущее время объекта. Переменная isTimeSet (тип boolean) указывает, что было установлено текущее время.

У класса много методов. Вкратце опишем часть из них:

  • abstract void add(int field, int value) - добавляет value к компоненту времени или даты, указанному в параметре field (например, Calendar.HOUR). Чтобы отнять, используйте отрицательное значение.
  • boolean after(Object calendar) - возвращает значение true, если вызывающий объект класса Calendar содержит более позднюю дату, чем calendar.
  • boolean before(Object calendar) - возвращает значение true, если вызывающий объект класса Calendar содержит более раннюю дату, чем calendar.
  • final void clear() - обнуляет все компоненты времени в вызывающем объекте.
  • final void clear(int field) - обнуляет компонент, указанный в параметре field
  • int get(int field) - возвращает значение одного компонента, например, Calendar.MINUTE
  • synchronized static Locale[] getAvailableLocales() - возвращает массив объектов класса Locale, содержащий региональные данные
  • synchronized static Calendar getInstance() - возвращает объект класса Calendar для региональных данных и часового пояса по умолчанию. Есть и другие перегруженные версии.
  • final Date getTime() - возвращает объекта класса Date, содержащий время, эквивалентное вызывающему объекту
  • TimeZone getTimeZone() - возвращает часовой пояс
  • final boolean isSet(int field) - возвращает значение true, если указанный компонент времени указан.
  • void set(int field, int value) - устанавливает компоненты даты или времени. Есть перегруженные версии
  • final void setTime(Date date) - устанавливает различные компоненты даты и времени через объект класса Date
  • void setTimeZone(TimeZone timezone) - устанавливает часовой пояс через объект класса TimeZone

Также в календаре определены много различных констант: AUGUST и другие месяцы, SATURDAY и другие дни недели, HOUR и т.д.

С помощью специальных классов DateFormat и др., мы можем получить отформатированную строку (см. примеры ниже). Но мы можем и вручную обрабатывать получаемый результат.

Получим текущую дату.


Calendar now = Calendar.getInstance();
mInfoTextView.setText(now.toString());

Мы получим очень длинную строку, не слишком удобную для чтения: java.util.GregorianCalendar[time=1654754513863,areFieldsSet=true,lenient=true,zone=Europe/Moscow,firstDayOfWeek=2,minimalDaysInFirstWeek=1,ERA=1,YEAR=2022,MONTH=5,WEEK_OF_YEAR=24,WEEK_OF_MONTH=2,DAY_OF_MONTH=9,DAY_OF_YEAR=160,DAY_OF_WEEK=5,DAY_OF_WEEK_IN_MONTH=2,AM_PM=0,HOUR=9,HOUR_OF_DAY=9,MINUTE=1,SECOND=53,MILLISECOND=863,ZONE_OFFSET=10800000,DST_OFFSET=0].

Можно получить более удобный вывод даты, применив функцию getTime().


Calendar now = Calendar.getInstance();
mInfoTextView.setText(now.getTime().toString());

Получим более вразумительный ответ: Thu Jun 09 09:13:36 GMT+03:00 2022

Получить текущий месяц можно, например, так:


Calendar calendar = Calendar.getInstance();
int month = calendar.get(Calendar.MONTH);
mInfoTextView.setText(String.valueOf(month));

Помните, что отсчёт месяцев идёт от 0. Например, если хотите получить правильные номера месяцев (добавим также небольшое форматирование с ведущим нулём):


Calendar calendar = Calendar.getInstance();
String month = calendar.get(Calendar.MONTH) + 1 + "";
if (month.length() < 2) {
    month = "0" + month;
}
mInfoTextView.setText(month);

Даже профессиональные программисты забывают добавлять единицу к месяцу. Однажды в одной версий Android была проблема с месяцами, когда кто-то из команды Google забыл про правило.

Получим текущий день и текущую минуту:


Calendar calendar = Calendar.getInstance();
// текущая минута
int currentMinute = calendar.get(Calendar.MINUTE);
mInfoTextView.setText(String.valueOf(currentMinute));

Оформим в виде метода получение сегодняшней даты в определённом формате.


public void onClick(View view) {
    mInfoTextView.setText(getToday());
}

private String getToday() {
    // Сегодняшняя дата в формате MM/DD/YYYY
    final Calendar calendar = Calendar.getInstance();

    return (new StringBuilder().append(calendar.get(Calendar.MONTH) + 1)
            .append("/").append(calendar.get(Calendar.DAY_OF_MONTH)).append("/")
            .append(calendar.get(Calendar.YEAR)).append(" ")).toString();
}

Текущее время.


public void onClick(View view) {
    mInfoTextView.setText(getCurrentTime());
}

private String getCurrentTime() {
    // Текущее время в формате HH:MM:SS
    final Calendar calendar = Calendar.getInstance();

    return (new StringBuilder().append(calendar.get(Calendar.HOUR_OF_DAY))
            .append(":").append(calendar.get(Calendar.MINUTE)).append(":")
            .append(calendar.get(Calendar.SECOND)).append(" ")).toString();
}
Не забывайте, что текущее время берётся из настроек телефона и может не совпадать с реальным временем и датой.

Напишем вспомогательную функцию для вывода основных данных, которые можно получить из класса Calendar:


private String getCalendarInfo() {
    Calendar calendar = Calendar.getInstance();

    return calendar + "\n" +
            calendar.get(Calendar.YEAR)
            + "-"
            + calendar.getDisplayName(Calendar.MONTH, Calendar.SHORT,
            Locale.US) + "-" + calendar.get(Calendar.DATE) + "\n" +
            "День года: " + calendar.get(Calendar.DAY_OF_YEAR) + "\n" +
            "День месяца: " + calendar.get(Calendar.DAY_OF_MONTH) + "\n" +
            "День недели: " + calendar.get(Calendar.DAY_OF_WEEK) + "\n" +
            "getTime(): " + calendar.getTime() + "\n" +
            "getTimeInMillis(): " + calendar.getTimeInMillis() + "\n";
}

GregorianCalendar

Класс GregorianCalendar является подклассом Calendar, который представляет обычный Григорианский календарь. Метод getInstance() класса Calendar обычно возвращает объект класса GregorianCalendar, инициированный текущей датой и временем согласно региональным настройкам.

У класса есть два поля AD и BC - до нашей эры и наша эра.

Кроме стандартных методов, которые есть в классе Calendar, у GregorianCalendar есть метод isLeapYear() для проверки високосного года.


boolean isLeapYear(int year)

Если год високосный, то возвращается true.

Отсчёт месяцев идёт от нуля, поэтому декабрь будет одиннадцатым месяцем. Чтобы не путаться с такими случаями, проще использовать понятные константы:


GregorianCalendar catDay = new GregorianCalendar(2022, Calendar.MARCH,
        1);
int dayOfWeek = catDay.get(Calendar.DAY_OF_WEEK);
mInfoTextView.setText("1 марта 2022 года " + (--dayOfWeek) + " день недели");

А получать нужные отрезки времени можно через метод get(). Например, узнать, какой месяц содержится в созданной нами дате можно так:


GregorianCalendar catDay = new GregorianCalendar(2022, Calendar.MARCH,
        1);
int month = catDay.get(Calendar.MONTH);
mInfoTextView.setText(String.valueOf(month)); // не забываем затем прибавить единицу в реальном проекте

Изменить состояние объекта можно через метод set(). Например, установим новую дату у нашего объекта.


GregorianCalendar calendar = new GregorianCalendar(1975,
        Calendar.DECEMBER, 31);
calendar.set(1976, Calendar.FEBRUARY, 23);
// Убедимся, что возвращает 1 - февраль
mInfoTextView.setText(String.valueOf(calendar.get(Calendar.MONTH)));

Можно сдвинуть дату на определённый период с помощью метода add(). Отодвинем дату на два месяца.


GregorianCalendar calendar = new GregorianCalendar(1975,
        Calendar.DECEMBER, 31);
calendar.add(Calendar.MONTH, 2);
mInfoTextView.setText(String.valueOf(calendar.get(Calendar.MONTH)));

Методы getTime() и setTime() работают с объектами Date и полезны для преобразования.


GregorianCalendar calendar = new GregorianCalendar(year, month, day);
Date hireDay = calendar.getTime();

Класс DateFormat

Будьте осторожны, существуют два класса в разных пакетах: java.text.format.DateFormat/android.text.format.DateFormat и java.text.DateFormat/android.text.format.DateFormat. И иногда возникает конфликт имён при сокращённой записи. Поэтому нужно быть осторожным и использовать правильные имена. Методы getXXXFormat() относятся к классу android.text.format.DateFormat, а возвращаемый результат к java.text.DateFormat.

java.text.DateFormat

Класс DateFormat является абстрактным классом, с помощью которого можно форматировать и анализировать показания даты и времени. метод getDateInstance() возвращает экземпляр класса DateFormat, который может форматировать информацию о дате.

Чаще всего используется метод format(), позволяющий вывести дату в нужном формате.


Date now = Calendar.getInstance().getTime();
String formattedDate = DateFormat.getDateInstance().format(now);
mInfoTextView.setText(formattedDate);

Результат (текущая дата на запуск примера): 9 июня 2022 г.

Можно получить больше информации, например, получить день недели. Для этого вызываем перегруженную версию getDateInstance().


String formattedDate = DateFormat.getDateInstance(DateFormat.FULL).format(now);

Результат: четверг, 9 июня 2022 г.

Мы получили строку, где данные разделены запятой. Строковая функция split() с использованием регулярного выражения поможет разбить строку на подстроки.


Date now = Calendar.getInstance().getTime();
String formattedDate = DateFormat.getDateInstance(DateFormat.FULL).format(now);
String[] splitDate = formattedDate.split(",");
mInfoTextView.setText(splitDate[0]); // выводим первый элемент массива

Результат: четверг

Правила форматирования зависят от записи. Небольшая шпаргалка для общего понимания (для июля).

M -> 7
MM -> 07
MMM -> Июл
MMMM -> Июль

Для минут (7 минут)

m -> 7
mm -> 07
mmm -> 007
mmmm -> 0007

Подставляя нужный вариант, вы формируете дату по вашему вкусу. Для дней недели используются E, EEEE. Посторонние слова, которые не будут распознаны как элементы даты, будут выводиться без изменений.

android.text.format.DateFormat

Метод System.currentTimeMillis() возвращает текущую дату в миллисекундах. С помощью метода DateFormat.format() можно привести дату в нужный формат. Например:


CharSequence date = DateFormat.format("MM/dd/yy h:mmaa", System.currentTimeMillis());
mInfoTextView.setText(date);

Усложним пример, добавим собственную информацию. Допустим, сегодня у нас родился котёнок, фиксируем событие.


CharSequence date = DateFormat.format(
    "День рождения кота: EEEE MMMM/dd/yy h:mmaa",
    System.currentTimeMillis());
mInfoTextView.setText(date);

java.text.format.DateFormat

Формально мы можем использовать DateFormat из пакета Java, а не Android. Пример работает, но лучше избегать такого кода.


public void onClick(View view) {
    java.text.DateFormat timeFormat = DateFormat.getTimeFormat(this); // 18:10

    java.text.DateFormat numericDateFormat = DateFormat.getDateFormat(this); // 11.11.2016

    java.text.DateFormat mediumDateFormat = DateFormat.getMediumDateFormat(this); // 11 нояб. 2016 г.

    java.text.DateFormat longDateFormat = DateFormat.getLongDateFormat(this); // 11 ноября 2016 г.

    mInfoTextView.setText(timeFormat.format(new Date()) + "\n" +
            numericDateFormat.format(new Date()) + "\n" +
            mediumDateFormat.format(new Date()) + "\n" +
            longDateFormat.format(new Date()));
}

Класс SimpleDateFormat

Класс SimpleDateFormat является подклассом класса DateFormat и позволяет определять собственные шаблоны форматирования для отображения даты и времени.

Символы форматирования строки

  • A - AM или PM
  • d - день месяца (1-31)
  • D - день в году (1-366)
  • H - часы в формате AM/PM (1-12)
  • K - часы в формате суток (1-24)
  • M - минуты (0-59)
  • S - секунды (0-59)
  • W - неделя в году (1-53)
  • y - год
  • z - часовой пояс

и так далее.

Количество повторений символа определяет способ представления даты. Например, можно указать hh:mm:ss, а можно h:m:s. В первом случае будет отображаться ноль перед цифрой.

Напомним, что в Java дата хранится в миллисекундах, которые отсчитываются от 1 января 1970 года. Простейший способ перевести непонятное большое число типа long в нормальную дату (есть и другие способы):


SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy"); 
String dateString = formatter.format(new Date(dateInMillis)));

Соответственно, получить текущее время и дату можно так:


Calendar calendar = Calendar.getInstance();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String formattedDate = formatter.format(calendar.getTime());
mInfoTextView.setText(formattedDate);

Результат: 2022-06-09 15:50:59

TimeZone

Класс TimeZone позволяет работать с часовыми поясами, смещёнными относительно Гринвича, известного как универсальное глобальное время (UTC). Класс также учитывает летнее время.

Если необходимо определить установленный текущий часовой пояс на устройстве, то воспользуйтесь методом TimeZone.getDefault():


TimeZone timezone = TimeZone.getDefault();

String timeZoneName = timezone.getDisplayName();
int timeZoneOffset = timezone.getRawOffset() / (60 * 60 * 1000);
mInfoTextView.setText("Мой часовой пояс " + timeZoneName + ": " + timeZoneOffset);

Метод getAvailableIDs() возвращает список установленных в системе идентификаторов часовых поясов, из которых можно извлечь полезную информацию, в частности через метод getTimeZone(). Наш испытательный стенд для этой задачи не слишком подойдёт для примера, поэтому временно изменим макет приложения.


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Spinner
        android:id="@+id/spinner"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/timezone_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

Получаемый список поместим в Spinner - список получается очень большой.


package ru.alexanderklimov.expresscourse;

import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import java.util.TimeZone;


public class MainActivity extends AppCompatActivity {

    private TextView timeZoneTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Spinner spinner = findViewById(R.id.spinner);
        timeZoneTextView = findViewById(R.id.timezone_text);
        String[] idArray = TimeZone.getAvailableIDs();

        ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(this,
                android.R.layout.simple_spinner_item, idArray);
        spinnerAdapter
                .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

        spinner.setAdapter(spinnerAdapter);

        spinner
                .setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

                    @Override
                    public void onItemSelected(AdapterView<?> parent,
                                               View view, int position, long id) {

                        String selectedId = (String) (parent
                                .getItemAtPosition(position));

                        TimeZone timezone = TimeZone.getTimeZone(selectedId);
                        String TimeZoneName = timezone.getDisplayName();

                        int TimeZoneOffset = timezone.getRawOffset()
                                / (60 * 60 * 1000);

                        timeZoneTextView.setText(TimeZoneName + ": " + TimeZoneOffset);
                    }

                    @Override
                    public void onNothingSelected(AdapterView<?> arg0) {
                        // TODO Auto-generated method stub
                    }
                });
    }
}

Часовые пояса Часовые пояса

SimpleTimeZone

Класс SimpleTimeZone - подкласс класса TimeZone и позволяет работать с часовыми поясами в Григорианском календаре.

Реклама