Классы Date, Calendar, TimeZone

Для удобной работы с датой и временем в Java используются классы Date и Calendar. Оба класса находятся в библиотеке java.util. Класс TimeZone используется совместно с классами Calendar и DateFormat

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

На странице рассмотрены следующие классы :

Класс Date

Класс Date хранит время в миллисекундах начиная с 1 января 1970 года. Данный класс имеет конструктор по умолчанию, который возвращает текущее время. Кроме этого можно создать объект Date используя конструктор, который принимает количество миллисекунд начиная с 1 января 1970 года. Для получения этого внутреннего времени используется метод getTime(). Кроме этого уже после создания экземпляра класса можно изменить время с помощью setTime(long date).

Конструкторы класса Date :

Date()
Date(long milliseconds)

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

Методы класса Date:

  • 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 года.

Простой пример использования Date для вывода даты в консоль.

Date date = new Date();

System.out.println(date.toString());

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

Date date = new Date();

long millis = date.getTime();

System.out.println(String.valueOf(millis));

Класс SimpleDateFormat

Для того, чтобы отображать дату и время в удобном формате можно использовать класс SimpleDataFormat :

package ru.test;

import java.util.Date;
import java.text.SimpleDateFormat;

public class Test
{
    public void test()
    {
        Date d = new Date();
        SimpleDateFormat format1 = new SimpleDateFormat("dd.MM.yyyy hh:mm");
        SimpleDateFormat format2 = new SimpleDateFormat("День dd Месяц MM Год yyyy Время hh:mm");
        System.out.println(format1.format(d)); // 25.02.2013 09:03
        System.out.println(format2.format(d)); // День 25 Месяц 02 Год 2013 Время 09:03
    }
}

При создании шаблона представления даты SimpleDateFormat использовались следующие параметры :

  • dd — означает день;
  • MM — месяц;
  • yyyy — год;
  • hh — часы;
  • mm — минуты;

В качестве разделитель можно использовать любой текст.

Класс 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. В первом случае при необходимости для чисел 0..9 будет отображаться ноль перед цифрой.

Класс Calendar

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

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

Пример использования классов Calendar и GregorianCalendar

import java.util.Date;
import java.util.Calendar;
import java.util.GregorianCalendar;

import java.text.SimpleDateFormat;

public class Test
{
    public void test()
    {
        Calendar c = new GregorianCalendar();              // календарь на текущую дату
        Calendar c2 = new GregorianCalendar(2014, 12, 21); // календарь на 21.12.2014
        c2.add(Calendar.DAY_OF_YEAR, 1);                   // увеличиваем дату на 1 день
        System.out.println(c2.getTime());                  // 22.12.2014
        c2.add(Calendar.DAY_OF_YEAR, -1);                  // уменьшаем дату на 1 день
    }
}

Поля класса Calendar

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

Методы класса Calendar

НаименованиеОписание
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.

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

  • DAY_OF_YEAR — день года (0- 365);
  • DAY_OF_MONTH — день месяца( какой по счету день в месяце 0 — 31);
  • WEEK_OF_MONTH — неделя месяца;
  • WEEK_OF_YEAR — неделя в году;
  • MONTH — номер месяца;
  • Year — номер года;
  • Calendar.ERA — эра.

Пример Calendar, GregorianCalendar

import java.util.Date;
import java.util.Calendar;
import java.util.GregorianCalendar;

import java.text.SimpleDateFormat;

public class Test
{
    public void test()
    {
        Calendar c = new GregorianCalendar(2015, 01, 25);          // календарь на 25.01.2015
        System.out.println(c.get(Calendar.MONTH));                 // 01
        System.out.println(c.get(Calendar.YEAR));                  // 2015
        System.out.println(c.get(Calendar.DAY_OF_WEEK_IN_MONTH));
        System.out.println(c.get(Calendar.DAY_OF_WEEK));
        System.out.println(c.get(Calendar.DAY_OF_YEAR));
        System.out.println(c.get(Calendar.DAY_OF_MONTH));
    }
}

Класс GregorianCalendar

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

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

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

boolean isLeapYear(int year)

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

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

GregorianCalendar calendar = new GregorianCalendar(2012, Calendar.DECEMBER, 31);

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

int month = calendar.get(Calendar.MONTH);
System.out.println(month); // вернёт 11

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

GregorianCalendar calendar = new GregorianCalendar(2012, Calendar.DECEMBER, 31);
calendar.set(2013, Calendar.FEBRUARY, 23);
// Убедимся, что возвращает 1 - февраль
System.out.println(calendar.get(Calendar.MONTH));

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

GregorianCalendar calendar = new GregorianCalendar(2012, Calendar.DECEMBER, 31);
calendar.add(Calendar.MONTH, 2);
System.out.println(calendar.get(Calendar.MONTH));

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

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

Класс TimeZone

Класс TimeZone предназначен для совместного использования с классами Calendar и DateFormat. Этот класс абстрактный, поэтому от него порождать объекты нельзя. Вместо этого определен статический метод getDefault(), который возвращает экземпляр наследника TimeZone с настройками, взятыми из операционной системы, под управлением которой работает JVM.

TimeZone имеет статический метод getTimeZone(String ID), используя который можно указать наименование конкретного временного пояса, для которого необходимо получить объект TimeZone.

Набор полей, определяющих возможный набор параметров для getTimeZone, нигде явно не описывается. Но имеется статический метод getAvailableIds(), возвращающий список возможных значений наименование временных зон типа String[], который можно использовать в методе getTimeZone. Так можно определить набор возможных параметров для конкретного временного пояса относительно Гринвича String[] getAvailableIds(int offset).

Рассмотрим пример, в котором на консоль последовательно выводятся:
  - временная зона по умолчанию;
  - список всех возможных временных зон;
  - список временных зон, которые совпадают с временной зоной Москвы.

Пример получения списка TimeZone

import java.util.Arrays;
import java.util.TimeZone;

public class TimeZoneList
{
    private final int    hour       = 1000 * 60 * 60;
    private final String TEMPL_TZ   = "%s (%s)";
    private final String TEMPL_OFFS = "hour offset=(%d)";

    String align(String str, int len)
    {
        if (len - str.length() > 0){
            char[] buf = new char[len - str.length()];
            Arrays.fill (buf, ' ');
            return str + new String(buf);
        } else
            return str.substring(0, len);
    }
    private void drawTimeZoneParam(final TimeZone tz)
    {
        String line = null;
        line  = String.format(TEMPL_TZ, tz.getID(), tz.getDisplayName());
        line  = align(line, 64);
        line += String.format(TEMPL_OFFS, tz.getRawOffset() / hour);
        System.out.println(line);
    }
    public TimeZoneList() 
    {
        TimeZone tz = TimeZone.getDefault();
        int rawOffset = tz.getRawOffset();
        System.out.println("Текущая TimeZone : " + tz.getID() + 
                         " (" + tz.getDisplayName() + ")\n");

        // Display all available TimeZones
        System.out.println("Доступные TimeZone\n");
        String[] timezones = TimeZone.getAvailableIDs();

        for (int cnt = 0; cnt < timezones.length; cnt++){
            tz = TimeZone.getTimeZone(timezones[cnt]);
            drawTimeZoneParam(tz);
        }
        // Display all available TimeZones same as for Moscow
        System.out.println("Список TimeZone, соответствующие текущей");
        timezones = TimeZone.getAvailableIDs(rawOffset);
        for(int cnt = 0;cnt < timezones.length;cnt++){
            tz = TimeZone.getTimeZone(timezones[cnt]);
            drawTimeZoneParam(tz);
        }
    }
    public static void main(String[] args) {
        new TimeZoneList();
        System.exit(0);
    }
}

Код программы несложный. Метод align выполняет выравнивание для отображения смещения по времени TimeZone от среднего времени по Гринвичу GMT — времени меридиана, проходящего через прежнее место расположения Гринвичской обсерватории около Лондона. Метод drawTimeZoneParam отображает параметры TimeZone. В конструкторе класса TimeZoneList сначала определяется текущая TimeZone, после чего выводится список возможных TimeZone. И в заключении, используя метод getAvailableIDs получаем список TimeZone, у которых смещение по времени соответствует текущей зоне.

В результате работы программы в консоль будет выведена следующая информация (первый список в усеченном виде):


Текущая TimeZone : Europe/Moscow (Moscow Standard Time)

Доступные TimeZone

Etc/GMT+12 (GMT-12:00)                   hour offset=(-12)
Etc/GMT+11 (GMT-11:00)                   hour offset=(-11)
Pacific/Midway (Samoa Standard Time)     hour offset=(-11)
Pacific/Niue (Niue Time)                 hour offset=(-11)
Pacific/Pago_Pago (Samoa Standard Time)  hour offset=(-11)
Pacific/Samoa (Samoa Standard Time)      hour offset=(-11)
US/Samoa (Samoa Standard Time)           hour offset=(-11)

. . .
Список TimeZone, соответствующие текущей

Asia/Baku (Azerbaijan Time)              hour offset=(4)
Asia/Dubai (Gulf Standard Time)          hour offset=(4)
Asia/Muscat (Gulf Standard Time)         hour offset=(4)
Asia/Tbilisi (Georgia Time)              hour offset=(4)
Asia/Yerevan (Armenia Time)              hour offset=(4)
Etc/GMT-4 (GMT+04:00)                    hour offset=(4)
Europe/Moscow (Moscow Standard Time)     hour offset=(4)
Europe/Samara (Samara Time)              hour offset=(4)
Europe/Simferopol (Moscow Standard Time) hour offset=(4)
Europe/Volgograd (Volgograd Time)        hour offset=(4)
Indian/Mahe (Seychelles Time)            hour offset=(4)
Indian/Mauritius (Mauritius Time)        hour offset=(4)
Indian/Reunion (Reunion Time)            hour offset=(4)
NET (Armenia Time)                       hour offset=(4)
W-SU (Moscow Standard Time)              hour offset=(4)
 

TimeZone UTC

В практике не часто приходится использовать различные TimeZone. Задачи, где разработчику приходится учитывать временной сдвиг, относятся к специфической деятельности человечества. Так, например, в расписании движения пассажирских поездов РФ указывается привязка к московскому и местному времени. А по какому времени ведет пассажирский состав машинист дальнего следования? Эта несложная задача решается в рамках министерства ЖД транспорта. Если состав выходит за пределы страны, то время движения (расписание) должно быть согласовано с соответствующей стороной. Аналогично это касается и полетов воздушного транспорта, где все согласования между различными аэропортами выполняются по времени UTC - всеми́рное координи́рованное вре́мя (Coordinated Universal Time).

UTC было введено вместо устаревшего среднего времени по Гринвичу (GMT), поскольку GMT является неравномерной шкалой и связана с суточным вращением Земли. Шкала UTC основана на равномерной шкале атомного времени и является более удобной для гражданского использования.

Как может влиять TimeZone, с точки зрения разработчика, на результаты работы программы? Давайте представим себе ситуацию, что время формируется на сервере (объект типа Date) и отправляется удаленному пользователю по сети. Это типично для WEB-приложений, у которых сервер и пользователи (браузеры) могут быть расположены в разных временных зонах. Для отправки объекта Date по сети (Internet) используется сериализация, позволяющая упаковать объект в набор байт на сервере и восстановить объект типа Date на клиенте (браузере). И вот здесь Вас может ожидать «засада». Если сервер и клиент имеют разные TimeZone (часовые пояса), то на клиенте будет восстановлен объект Date с временем сервера. И наоборот, если объект будет отправлен с клиента на сервер, то на сервере объект десериализуется с временем клиента.

Конечно, объект Date можно конвертировать не сервере в текстовый вид, используя DateFormat, и на клиенте его восстановить. Но Java позволяет учитывать различные TimeZone, не оказывающие влияние на сериализацию/десериализацию объектов типа Date.

Давайте рассмотрим пример TimeZoneExample (код представлен ниже), в котором время будем привязывать к одной из сторон (желательно серверной), а на компьютере устанавливать различные зоны (UTC, Москва, Владивосток). Для установки определенной временной зоны необходимо открыть в панели управления окно «Date and Time» и выбрать соответствующую TimeZone.

import java.util.Date;
import java.util.TimeZone;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;

public class TimeZoneExample 
{
	private  final  String  TIMEZONE_utc    = "UTC";
	private  final  String  TIMEZONE_msc    = "Europe/Moscow";
	private  final  String  DATETIME_format = "yyyy-MM-dd HH:mm:ss.SS";
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	public TimeZoneExample()
	{
        // TimeZone.setDefault(TimeZone.getTimeZone(TIMEZONE_utc));

        Date date   = new Date();
        Date dt_msk = null;
		
        TimeZone tm_curr = TimeZone.getDefault();
        System.out.println ("Current TimeZone : \"" + 
                                       tm_curr.getID() + "\" (" +
                                       tm_curr.getDisplayName() + ")");
        System.out.println ("useDaylightTime : " +  
                                       tm_curr.useDaylightTime() + "\n");
        TimeZone tm_utc = TimeZone.getTimeZone(TIMEZONE_utc);
        TimeZone tm_msk = TimeZone.getTimeZone(TIMEZONE_msc);
		
        DateFormat df_utc = new SimpleDateFormat(DATETIME_format);
        DateFormat df_msk = new SimpleDateFormat(DATETIME_format);
		
        df_utc.setTimeZone(tm_utc);
        df_msk.setTimeZone(tm_msk);
		
        String date_utc = df_utc.format(date);
        String date_msk = df_msk.format(date);

        try {
            dt_msk = df_msk.parse(date_msk);
        } catch (ParseException e) {}

        System.out.println ("UTC\n" + date);
        System.out.println (date_utc);
        System.out.println (tm_utc.getRawOffset());
        System.out.println ();
        System.out.println ("Moscow\n" + dt_msk);
        System.out.println (date_msk);
    }
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    public static void main(String[] args) 
    {
        new TimeZoneExample();
        System.exit(0);
    }
}

В примере используется три TimeZone (tm_curr, tm_utc, tm_msk - текущая, зона UTC, зона Москвы). Для двух временных зон (tm_utc, tm_msk) выводим в консоль объект Date без форматирования и с форматированием с использованием tm_utc, tm_msk и DateFormat/SimpleDateFormat.

Первоначально на компьютере устанавливаем зону UTC и получаем в консоли следующий текст :


Current TimeZone : "UTC" (Coordinated Universal Time)
useDaylightTime : false

UTC
Fri Dec 23 08:50:24 UTC 2016
2016-12-23 08:50:24.547

Moscow
Fri Dec 23 08:50:24 UTC 2016
2016-12-23 11:50:24.547
 

Здесь следует сказать, что для зоны UTC и для зоны Москвы неформатированные даты (date, dt_msk) совпадают, но вот с учетом временной зоны форматированные даты отличаются на соответствующие 3 часа.

Теперь установим зону на компьютере зону Москвы и получим в консоли следующий текст :


Current TimeZone : "Europe/Moscow" (Moscow Standard Time)
useDaylightTime : false

UTC
Fri Dec 23 11:51:18 MSK 2016
2016-12-23 08:51:18.915

Moscow
Fri Dec 23 11:51:18 MSK 2016
2016-12-23 11:51:18.915
 

Все как по науке - смещения по времени для дат UTC и Москвы поменялись местами. В заключении устанавливаем временную зону Владивостока :


Current TimeZone : "Asia/Vladivostok" (Vladivostok Time)
useDaylightTime : false

UTC
Fri Dec 23 19:55:35 VLAT 2016
2016-12-23 09:55:35.924

Moscow
Fri Dec 23 19:55:35 VLAT 2016
2016-12-23 12:55:35.924
 

Все соответствует действительности - объект даты создается во временной зоне Владивостока. Время для TimeZone Москвы и UTC соответствуют.

Если приложение должно работать в определенной временной зоне TimeZone, то можно, используя метод setDefault класса TimeZone, установить соответствующую временную зону в приложении при работе с датой. В примере можно было бы снять комментарий с первой строки и установить сответствующую временную зону в приложении, не оказывая влияния на временную зону в настройках компьютера.

Скачать исходный код примера

Исходный код рассмотренных примеров можно скачать здесь (6.69 Кб).

Наверх
  Рейтинг@Mail.ru