time и context стандартной библиотеки Go. n дней. Ещё благодаря этому пакету можно создать таймер, который будет напоминать, что пора сделать паузу в работе. И это далеко не все возможности.time используется, даже если функциональность программы не связана со временем напрямую. Если приложение просто копирует файлы из одной папки в другую, time может потребоваться для записи логов, сбора метрик, приостановки программы или запуска горутин по таймеру. context. Без него не обходится ни один серьёзный проект. Пакет context даёт возможность установить дополнительные признаки для завершения работы горутины — например, когда пользователь отменил запущенную задачу или истекло время ожидания соединения с сервером. context вы увидите, как могут применяться на практике функции для работы со временем.context для отмены исполнения ветвей программы.Time и методы для работы с датой и временем;Duration и методы для работы с интервалами;Timer, Ticker и функции для работы с таймерами;Location и методы для работы с часовыми поясами.time.Now(), которая возвращает переменную типа Time:package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println(now)
} 2021-11-10 18:48:38.08749 +0300 MSK m=+0.000092583. Тип Time реализует интерфейс fmt.Stringer, поэтому функция fmt.Println() выводит время в виде строки.Time хранится информация о часовом поясе — по умолчанию это часовой пояс операционной системы, в которой запущен код. Для переменной типа Time можно вызвать множество методов, чтобы работать с содержащимся в ней временем.time.Date():func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time package main
import (
"fmt"
"time"
)
func main() {
t := time.Date(2003, time.May, 1, 7, 1, 24, 0, time.UTC)
fmt.Println(t)
} 1 — месяц;2 — день;3, 15 — час в 12- и 24-часовом формате соответственно;4 — минуты;5 — секунды;06, 2006 — год;-0700 — часовой пояс;pm, PM — время суток;MST — аббревиатура часового пояса.Format(). Он возвращает текстовое представление времени в соответствии с переданным макетом: func (t Time) Format(layout string) string package main
import (
"fmt"
"time"
)
func main() {
t := time.Date(2003, time.May, 1, 17, 1, 21, 0, time.UTC)
fmt.Println("Сложно воспринимать: ", t.Format("2.1.06 3:4:5 PM"))
fmt.Println("Привычный для нас формат: ", t.Format("02.01.06 15:04:05"))
} Сложно воспринимать: 1.5.03 5:1:21 PM
Привычный для нас формат: 01.05.03 17:01:21 __), то программа выведет день относительно начала года — в формате трёх символов с нулями или пробелами:package main
import (
"fmt"
"time"
)
func main() {
t := time.Date(2003, time.May, 1, 17, 1, 21, 0, time.UTC)
fmt.Printf("%s — это %s-й день в году", t.Format("02.01.06"), t.Format("__2"))
} 01.05.03 — это 121-й день в году 01/02 03:04:05PM '06 -0700
MM/DD HH:MM:SSPM 'YY -ZONE Jan, January — месяц в виде строки на английском;Mon, Monday — день в виде строки на английском;.0-000000000, .9-999999999 — доли секунды, будет выводиться столько цифр, сколько указано;-0700, -07:00, -07, Z0700, Z07:00, Z07 — часовой пояс;MST — аббревиатура часового пояса.time с готовыми макетами:| Константа | Макет | Пример результата |
|---|---|---|
| UnixDate | Mon Jan _2 15:04:05 MST 2006 | Thu May 1 17:01:21 UTC 2003 |
| RFC822 | 02 Jan 06 15:04 MST | 01 May 03 17:01 UTC |
| RFC850 | Monday, 02-Jan-06 15:04:05 MST | Thursday, 01-May-03 17:01:21 UTC |
| RFC1123 | Mon, 02 Jan 2006 15:04:05 MST | Thu, 01 May 2003 17:01:21 UTC |
| RFC3339 | 2006-01-02T15:04:05Z07:00 | 2003-05-01T17:01:21Z |
2006-01-02 15:04:05.999999999 -0700 MST. Для этого достаточно воспользоваться методом (t Time) String() string.Time. Это можно сделать функцией Parse():func Parse(layout, value string) (Time, error) Parse() позволяет получить время из строки, но для этого нужно знать макет, который соответствует данному строковому представлению:package main
import (
"fmt"
"time"
)
func parseTime(layout, value string) {
t, err := time.Parse(layout, value)
if err != nil {
panic(err)
}
fmt.Println(t.Format(`02.01.06 15:04:05`))
}
func main() {
parseTime(time.UnixDate, "Tue Jun 1 22:16:03 MSK 2022")
parseTime("01/02/2006 15:04:05", "01/26/2023 14:43:00")
} 01.06.22 22:16:03
26.01.23 14:43:00 Time есть метод String(), он реализует интерфейс fmt.Stringer. Благодаря этому тип Time можно выводить в консоль и преобразовывать в строку. fmt.Stringer, тип Time реализует следующие интерфейсы:encoding.BinaryMarshaler;encoding.BinaryUnmarshaler;json.Marshaler;json.Unmarshaler;encoding.TextMarshaler;encoding.TextUnmarshaler.Time может участвовать в преобразовании структуры в двоичное, текстовое или JSON-представление. По умолчанию для текстового и JSON-преобразования используется макет time.RFC3339.Time можно получить год, месяц, число, день недели, время, часовой пояс и timestamp с помощью одноимённых методов. Наглядно это можно увидеть на примере:package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println("Год:", now.Year())
fmt.Println("Месяц:", now.Month())
fmt.Println("Число:", now.Day())
fmt.Println("День недели:", now.Weekday())
hour, min, sec := now.Clock()
fmt.Printf("Время: %d:%d:%d\n", hour, min, sec)
fmt.Println("Часовой пояс:", now.Location())
fmt.Println("timestamp в секундах:", now.Unix())
fmt.Println("timestamp в наносекундах:", now.UnixNano())
} Год: 2021
Месяц: October
Число: 17
День недели: Sunday
Время: 22:41:8
Часовой пояс: Local
timestamp в секундах: 1634499668
timestamp в наносекундах: 1634499668786207000 Equal(), After() и Before():// Equal проверяет, равны ли два момента времени.
func (t Time) Equal(u Time) bool
// After проверяет, наступил ли момент времени t после u.
func (t Time) After(u Time) bool
// Before проверяет, наступил ли момент времени t перед u.
func (t Time) Before(u Time) bool Duration, который содержит количество наносекунд. Это переопределение типа int64.type Duration int64 // Sub возвращает интервал: t — u.
func (t Time) Sub(u Time) Duration
// Since возвращает интервал: текущее время — t.
func Since(t Time) Duration
// Until возвращает интервал: t — текущее время.
func Until(t Time) Duration Duration — замер времени выполнения функции:start := time.Now()
anyFunction()
duration := time.Since(start) time есть несколько предопределённых интервалов:const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
) Duration можно модифицировать дату и время: // Add добавляет интервал ко времени.
func (t Time) Add(d Duration) Time
// Round округляет время до начала ближайшего указанного интервала.
func (t Time) Round(d Duration) Time
// Truncate округляет время в меньшую сторону до начала
// ближайшего указанного интервала.
func (t Time) Truncate(d Duration) Time now := time.Now()
// добавим 20 секунд к текущему времени
fmt.Println(now.Add(20*time.Second))
// округлим время до часа
fmt.Println(now.Round(time.Hour))
// округлим время в меньшую сторону до начала трёхминутного интервала
fmt.Println(now.Truncate(3 * time.Minute)) 2021-10-17 22:58:34.280577 +0300 MSK m=+1.000122601
2021-10-17 23:00:00 +0300 MSK
2021-10-17 22:57:00 +0300 MSK Duration можно получить количественные интервалы — наносекунды, микросекунды, миллисекунды, секунды, минуты и часы — с помощью одноимённых методов. Стоит учитывать, что секунды, минуты и часы возвращаются в виде числа с плавающей точкой — float64.package main
import (
"fmt"
"time"
)
func main() {
trunc := time.Date(2022, 1, 1, 0, 0, 0, 0, time.Local).Truncate(24 * time.Hour)
fmt.Println(trunc)
} 2021-12-31 03:00:00 +0300 MSK Location предназначен для хранения информации о часовом поясе. Например, если в логе указано время, нужно учитывать часовой пояс, который использовался при записи в этот лог.LoadLocation():func LoadLocation(name string) (*Location, error) time есть два предопределённых часовых пояса:Local — часовой пояс операционной системы;UTC — часовой пояс UTC.time.UTC и time.Local. Но если на компьютере установлен Go, компилятор может брать информацию о часовом поясе из папки golang.time/tzdata:import _ "time/tzdata" golang (прибавит примерно 0,5 Мб к размеру итогового файла).package main
import (
"fmt"
"time"
)
func main() {
layout := "02.01.06 15:04:05 -07 MST"
now := time.Now()
fmt.Println(now.Format(layout))
loc, _ := time.LoadLocation("Europe/Moscow")
fmt.Println(now.In(loc).Format(layout))
} 22.11.22 17:18:27 +05 +05
22.11.22 15:18:27 +03 MSK now.In(loc) приводит время к нужному часовому поясу.FixedZone():func FixedZone(name string, offset int) *Location offset нужно поставить сдвиг в секундах:now := time.Now()
// 2021-07-14 22:30:27.478879 +0300 MSK
loc := time.FixedZone("My best zone", 30*60)
fmt.Println(now.In(loc))
// 2021-07-14 20:00:27.478879 +0030 My best zone Time и Location, вы можете создать собственную временную точку. Например, время начала 2023 года в UTC:newYear := time.Date(2023, time.January, 1, 0, 0, 0, 0, time.UTC) Until() возвращает интервал между текущим временем и временем в будущем. Передай ему дату ближайшего дня рождения, а количество часов из полученного интервала раздели на 24.time есть типы, которые упрощают работу с календарём, — Month и Weekday, переопределения типа int. Они представляют месяц и день недели:type Month int
const (
January Month = 1 + iota
February
March
April
May
June
July
August
September
October
November
December
)
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
) Month и Weekday реализуют интерфейс fmt.Stringer, а значит, их можно выводить в человекочитаемом формате.Hello, world! каждые две секунды десять раз подряд.Sleep() приостанавливает выполнение текущей горутины как минимум на интервал, переданный в качестве параметра. Реальное время остановки может быть чуть больше, но в большинстве практических задач этой погрешностью можно пренебречь. package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 10; i++ {
time.Sleep(2 * time.Second)
fmt.Println("Hello, world!")
}
} time есть функция AfterFunc(), которая запускает горутину спустя указанный промежуток времени:func AfterFunc(d Duration, f func()) *Timer AfterFunc() позволяет параллельно запустить горутину:package main
import (
"fmt"
"time"
)
func main() {
time.AfterFunc(1*time.Second, func() {
fmt.Println("Hi from AfterFunc")
})
fmt.Println("Hi")
// ожидаем 2 секунды, чтобы успела запуститься функция в AfterFunc
time.Sleep(2 * time.Second)
fmt.Println("Goodbye")
} time содержит типы Timer и Ticker, которые позволяют реализовать таймеры. Timer срабатывает только один раз, а Ticker будет работать до момента, пока его не закроют. C — канал, через который можно получать сигнал по истечении времени. Канал представляет собой «туннель», в который одна горутина может «положить» значение определённого типа, а другая — «взять». В обоих типах канал C позволяет только читать значение типа Time, которое равно времени события. *Timer создаётся функцией NewTimer() и отправляет время в канал C только один раз через указанный промежуток времени:NewTimer(d Duration) *Timer — создать таймер;(t *Timer) Stop() bool — остановить таймер.*Ticker создаётся функцией NewTicker() и отправляет время в канал C постоянно с указанным интервалом: NewTicker(d Duration) *Ticker — создать тикер;(t *Ticker) Stop() — остановить тикер.// прочитать значение из канала
v := <-ch
// прочитать, если получаемое значение не важно
<- ch package main
import (
"fmt"
"time"
)
func main() {
start := time.Now()
timer := time.NewTimer(2 * time.Second) // создаём таймер
t := <-timer.C // ожидаем срабатывания таймера
fmt.Println(t.Sub(start).Seconds()) // выводим разницу во времени
} Request в файле internal/models/models.go.Timezone, в которое, согласно документации по написанию навыков для Алисы, приходит строковое представление временной зоны пользователя. Также добавим новую вложенную структуру Session — в ней будут храниться данные о сессии пользователя:// Request описывает запрос пользователя.
// См. https://yandex.ru/dev/dialogs/alice/doc/request.html
type Request struct {
// тут будет, например, строка "Europe/Moscow" для часового пояса Москвы
Timezone string `json:"timezone"`
Request SimpleUtterance `json:"request"`
Session Session `json:"session"`
Version string `json:"version"`
}
type Session struct {
New bool `json:"new"`
} Session нас интересует только флаг New. Он сообщит нам, будет ли сессия новой.cmd/skill/main.go:func webhook(w http.ResponseWriter, r *http.Request) {
//...
text := "Для вас нет новых сообщений."
// первый запрос новой сессии
if req.Session.New {
// обрабатываем поле Timezone запроса
tz, err := time.LoadLocation(req.Timezone)
if err != nil {
logger.Log.Debug("cannot parse timezone")
w.WriteHeader(http.StatusBadRequest)
return
}
// получаем текущее время в часовом поясе пользователя
now := time.Now().In(tz)
hour, minute, _ := now.Clock()
// формируем текст ответа
text = fmt.Sprintf("Точное время %d часов, %d минут. %s", hour, minute, text)
}
// заполняем модель ответа
resp := models.Response{
Response: models.ResponsePayload{
Text: text, // Алиса проговорит новый текст
},
Version: "1.0",
}
w.Header().Set("Content-Type", "application/json")
// сериализуем ответ сервера
enc := json.NewEncoder(w)
if err := enc.Encode(resp); err != nil {
logger.Log.Debug("error encoding response", zap.Error(err))
return
}
logger.Log.Debug("sending HTTP 200 response")
} cmd/skill/main_test.go:package main
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/go-resty/resty/v2"
"github.com/stretchr/testify/assert"
)
func TestWebhook(t *testing.T) {
handler := http.HandlerFunc(webhook)
srv := httptest.NewServer(handler)
defer srv.Close()
testCases := []struct {
name string
method string
body string
expectedCode int
expectedBody string
}{
{
name: "method_get",
method: http.MethodGet,
expectedCode: http.StatusMethodNotAllowed,
expectedBody: "",
},
{
name: "method_put",
method: http.MethodPut,
expectedCode: http.StatusMethodNotAllowed,
expectedBody: "",
},
{
name: "method_delete",
method: http.MethodDelete,
expectedCode: http.StatusMethodNotAllowed,
expectedBody: "",
},
{
name: "method_post_without_body",
method: http.MethodPost,
expectedCode: http.StatusInternalServerError,
expectedBody: "",
},
{
name: "method_post_unsupported_type",
method: http.MethodPost,
body: `{"request": {"type": "idunno", "command": "do something"}, "version": "1.0"}`,
expectedCode: http.StatusUnprocessableEntity,
expectedBody: "",
},
{
name: "method_post_success",
method: http.MethodPost,
body: `{"request": {"type": "SimpleUtterance", "command": "sudo do something"}, "session": {"new": true}, "version": "1.0"}`,
expectedCode: http.StatusOK,
// ответ стал сложнее, поэтому сравниваем его с шаблоном вместо точной строки
expectedBody: `Точное время .* часов, .* минут. Для вас нет новых сообщений.`,
},
}
for _, tc := range testCases {
t.Run(tc.method, func(t *testing.T) {
req := resty.New().R()
req.Method = tc.method
req.URL = srv.URL
if len(tc.body) > 0 {
req.SetHeader("Content-Type", "application/json")
req.SetBody(tc.body)
}
resp, err := req.Send()
assert.NoError(t, err, "error making HTTP request")
assert.Equal(t, tc.expectedCode, resp.StatusCode(), "Response code didn't match expected")
if tc.expectedBody != "" {
// сравниваем тело ответа с ожидаемым шаблоном
assert.Regexp(t, tc.expectedBody, string(resp.Body()))
}
})
}
} time.