🦫 Самоучитель по Go для начинающих. Часть 13. Работа с датой и временем. Пакет time
В этой части самоучителя изучим способы работы с датами и временем в языке Go, разберем полезные функции пакета time и в заключение решим парочку интересных задач.
Как в Go хранится время
Язык программирования Go хранит время в соответствии с эпохой UNIX, которая берет свое начало 1 января 1970 года в 00:00:00 UTC. Системы на базе UNIX отслеживают время путем подсчета секунд, прошедших с этого особенного дня. Счетчик прошедших секунд хранится в виде 32-битного целого числа, которое изменяется в диапазоне от -2^32 до 2^31 – 1. Возникает логичный вопрос: зачем для подсчета времени использовать знаковый тип данных int32, если он содержит отрицательные значения? Дело в том, что отрицательными целыми числами представляется время до эпохи UNIX, а положительными – после неё. Таким образом, значение счетчика -100 означает момент времени за 100 секунд до 1 января 1970 года, а +100 секунд указывает на 100 секунд после этой даты.
Пакет time
Для работы с временем в Go используется пакет time стандартной библиотеки, содержащий обширный набор полезных функций и методов. Давайте на конкретных примерах рассмотрим основные из них.
Получить текущее локальное время можно с помощью функции time.Now
. Однако стоит учитывать, что для каждого запуска вывод времени будет отличаться. В статье приведены значения времени, актуальные на момент её написания:
Давайте детально разберем каждую часть выведенного времени:
- Первая часть (2024-04-11 17:39:17.756388243) представляет собой дату и время в формате
год-месяц-день час:минута:секунда.миллисекунда
- Вторая часть (+0300) указывает смещение временной зоны относительно UTC (Всемирного координированного времени) – это основной стандарт времени, используемый в авиации, картах, планах полетов, прогнозах погоды и других областях.
- Третья часть (MSK) указывает на часовой пояс. В примере это московское время.
- Четвертая часть (m=+0.000053694) содержит вспомогательную информацию, предоставляемую самим языком. В данном случае
m
обозначает момент времени, а+0.000053694
представляет его как количество секунд с начала выполнения программы или другого опорного момента.
Для вывода текущего времени в определенном формате следует использовать встроенные функции, такие как UTC(), Unix() и другие:
Каждый объект встроенной структуры time.Time
связан с конкретным местоположением, которое по своей сути является часовым поясом. Все локации определяются структурой time.Location
с неэкспортируемыми полями. Для получения информации о часовом поясе объекта времени используется метод Location
структуры Time, а задание определенной локации производится с помощью функции time.LoadLocation
и последующим вызовом метода In
:
Вывод выглядит следующим образом:
Если название локации содержит пустую строку или "UTC", то функция LoadLocation возвращает время в соответствии с UTC. При указании "Local" вернется локальное время. Во всех иных случаях наименование локации должно соответствовать названиям из базы данных часовых поясов IANA. Например, "Europe/Moscow", "Asia/Tomsk" и так далее.
Компоненты времени
Часто возникает необходимость взять только определенный отрезок времени, например, год, месяц или день. Этого можно добиться с помощью довольно очевидных функций:
Задание момента времени
Если необходимо создать временной объект для определенной даты, то следует воспользоваться функцией time.Date
со следующей сигнатурой:
func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time
Функция time.Date
принимает в качестве параметров компоненты времени в формате год, месяц, день, час, минута, секунда, наносекунда, местоположение
. Стоит отметить, что эти параметры могут находиться за пределами допустимых значений, но в результате все равно будут сконвертированы. Например, 34 апреля будет воспринято как 4 мая:
Форматирование времени
Форматирование времени в Go производится с помощью метода time.Format()
, который конвертирует объект структуры Time в строку, соответствующую по параметру layout:
func (t Time) Format(layout string) string
Стоит отметить, что шаблоны для time.Parse
и time.Format
предопределены в пакете time. Базовое время, используемое в них, представляет собой конкретную отметку: 01/02 03:04:05PM '06 -0700
, что в формате Unix выглядит как Mon Jan 2 15:04:05 MST 2006
.
Документация пакета time допускает использование следующих форм времени:
Для форматирования произвольного объекта времени следует использовать константы из эталонного времени, переупорядочив их в необходимом порядке. К примеру, написанный ниже код выведет на экран время у заданной даты в формате "15:04:05":
Форматирование может производиться по предопределенным шаблонам, таким как DateOnly ("2006-01-02"), DateTime ("2006-01-02 15:04:05"), TimeOnly ("15:04:05") и другим:
На экран будет выведено следующее:
Парсинг времени
Функция time.Parse
парсит форматированную строку и возвращает значение времени, которое она представляет. Иначе говоря, конвертирует строку в структуру Time:
func Parse(layout, value string) (Time, error)
Для корректного использования функции time.Parse
стоит учитывать несколько нюансов. Во-первых, второй параметр функции должен быть доступен для парсинга с использованием шаблона, указанного в качестве первого аргумента. Во-вторых, при работе с time.Parse
допускается использовать только предопределенную в языке модель для описания ввода и вывода, рассмотренную ранее. В-третьих, time.Parse
при отсутствии часового пояса в переданной строке возвращает время в формате UTC:
Длительность
Продолжительность между двумя промежутками времени в наносекундах представляет тип Duration: type Duration int64
, ограниченный примерно 290 годами.
Вычислить продолжительность промежутка времени можно с помощью функции time.Sub
:
func (t Time) Sub(u Time) Duration
Рассмотрим применение функции time.Sub
на примере, где зададим три отметки времени с разницей в 2 часа 4 минуты и вычислим разницу между ними:
На основе time.Sub
работают две вспомогательные функции:
time.Since
– вычисляет период между текущим и прошлым моментами времени, сокращение дляtime.Now().Sub(t)
time.Until
– вычисляет период между текущим и будущим моментами времени, сокращение дляt.Sub(time.Now())
Рассмотрим эти функции на примере программы, в которой создадим два момента времени (будущее и прошлое) с разницей в три часа по сравнению с текущим и вычислим временные промежутки, округлив окончательный результат до секунд с помощью метода Round:
Арифметика времени
Ранее для добавления или вычитания времени мы использовали явное указание его компонентов в функции time.Date. Такой подход довольно неудобный и подвержен ошибкам.
Для более точной и корректной временной арифметики следует использовать функции time.Add
и time.AddDate
:
func (t Time) Add(d Duration) Timefunc (t Time) AddDate(years int, months int, days int) Time
При указании положительных параметров будет производиться добавление времени, при отрицательных – вычитание:
В результате выполнения кода увидим следующее:
Сравнить два временных объекта t1 и t2 можно с помощью следующих функций: time.Equal
(t1 равен t2), time.Before
(t1 произошел до t2) и time.After
(t1 произошел после t2):
Приостановка программы
В некоторых ситуациях требуется приостановить выполнение программы на определенный промежуток времени. В этом помогает функция time.Sleep
, которая останавливает текущую горутину на указанное в параметрах время. При этом отрицательное или нулевое значение параметра приводит к немедленному завершению time.Sleep и, соответственно, игнорированию задержки.
Следующий код выведет строку "start...", остановит выполнение главной горутины main на 3 секунды и по прошествии этого времени выведет строку "end after 3 seconds":
Задачи
Самое время закрепить изученную теорию на практике, решив несколько несложных задач. Настоятельно рекомендуется не игнорировать возможные ошибки, а обрабатывать их с помощью изученных ранее конструкций, например, log.Fatal(err)
.
Преобразование времени
Напишите программу для преобразования строкового представления времени в формате 2000-05-14T07:30:00+07:00
в структуру Time формата Unix Date: Sun May 14 07:30:00 +0700 2000
. Для нахождения правильного шаблона обратитесь к списку констант пакета time.
Пример входных данных
Выходные данные для примера
Решение
Запись к врачу
Напишите программу для имитации системы записи на прием к врачу. На вход подается строка с датой в формате “Mon Jan _2 15:04:05 2006” (time.ANSIC). Необходимо проверить соответствие даты следующим условиям:
- Дата приема не назначена на выходной день. Если это не так, выведите сообщение об ошибке с текстом: "Нельзя записаться на выходной день!".
- Дата приема не просрочена. Если это не так, выведите сообщение об ошибке с текстом: "Запись просрочена!".
- Время записи лежит в промежутке от 8 до 20 включительно. Если это не так, выведите сообщение об ошибке с текстом: "Врач работает с 8 до 20 часов!".
В случае соответствия даты всем условиям выведите сообщение с текстом: "Вы успешно записались на %s\n", где вместо спецификатора %s находится дата записи, отформатированная в соответствии с шаблоном "Monday, January 2, 2006, at 15:04".
Пример входных данных
Выходные данные для примера
Решение
Сколько лет Гоше?
Первоклассник Гоша очень хочет узнать, сколько ему полных лет в данный момент времени, но не может сделать это с большой точностью. Помогите мальчику вычислить свой возраст, а именно: количество лет, месяцев, недель, дней, часов и минут, прошедших с заданной во входных параметрах даты в формате "2006-01-02 15:04:05" (time.DateTime).
Пример входных данных
Выходные данные для примера
Решение
Сколько еще работать?
Напишите программу для подсчета количества рабочих дней между начальной и конечной датами (включительно). Данные вводятся в формате "2006-01-02" (time.DateOnly
). Также требуется добавить проверку того, что начальная дата была раньше конечной. Если это условие не выполняется, завершите программу с текстом: "Начальная дата должна быть раньше конечной".
Пример входных данных
Выходные данные для примера
Решение
Заключение
В этом уроке мы подробно изучили способ хранения времени в Go, а также основные функции и методы пакета time: Now, Date, Format, Parse, Sub, Since, Until и некоторые другие. Их использование откроет перед вами новые возможности для обработки временных объектов в программах. Стоит отметить, что пакет time не ограничивается рассмотренными в этой статье функциями и предоставляет широкий набор полезных инструментов.
В следующей части самоучителя научимся с помощью встроенных пакетов обрабатывать текстовые данные, работать с файлами и взаимодействовать с операционной системой для выполнения часто встречающихся задач.
Содержание самоучителя
- Особенности и сфера применения Go, установка, настройка
- Ресурсы для изучения Go с нуля
- Организация кода. Пакеты, импорты, модули. Ввод-вывод текста.
- Переменные. Типы данных и их преобразования. Основные операторы
- Условные конструкции if-else и switch-case. Цикл for. Вложенные и бесконечные циклы
- Функции и аргументы. Области видимости. Рекурсия. Defer
- Массивы и слайсы. Append и сopy. Пакет slices
- Строки, руны, байты. Пакет strings. Хеш-таблица (map)
- Структуры и методы. Интерфейсы. Указатели. Основы ООП
- Наследование, абстракция, полиморфизм, инкапсуляция
- Обработка ошибок. Паника. Восстановление. Логирование
- Обобщенное программирование. Дженерики
- Работа с датой и временем. Пакет time
- Интерфейсы ввода-вывода. Буферизация. Работа с файлами. Пакеты io, bufio, os
- Конкурентность. Горутины. Каналы
- Тестирование кода и его виды. Table-driven подход. Параллельные тесты
- Основы сетевого программирования. Стек TCP/IP. Сокеты. Пакет net