четверг, 30 апреля 2020 г.

Отложить вызов функции (с возвращаемым значением) в Golang

Оператор defer откладывает выполнение функции до тех пор, пока не вернется окружающая функция, либо в обычном режиме, либо через panic.

func main() {
    defer fmt.Println("World")
    fmt.Println("Hello")
}

Вывод:

Hello
World

Отложенные вызовы выполняются даже при панике функции:

func main() {
    defer fmt.Println("World")
    panic("Stop")
    fmt.Println("Hello")
}

Вывод:

World
panic: Stop

goroutine 1 [running]:
main.main()
    ../main.go:3 +0xa0

Порядок исполнения

Аргументы отложенного вызова вычисляются немедленно, даже если вызов функции не выполняется до тех пор, пока не вернется окружающая функция.

Если имеется несколько отложенных вызовов функций, они выполняются в порядке "последний пришел - первым вышел".

func main() {
    fmt.Println("Hello")
    for i := 1; i <= 3; i++ {
        defer fmt.Println(i)
    }
    fmt.Println("World")
}

Вывод:

Hello
World
3
2
1

Используйте func для возврата значения

Отложенные анонимные функции могут получать доступ и изменять именованные возвращаемые параметры окружающей функции.

В этом примере функция foo возвращает "Change World".

func foo() (result string) {
    defer func() {
        // изменяет значение в самый последний момент
        result = "Change World" 
    }()
    return "Hello World"
}

Применение defer

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

Закрытие файла

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

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

    return io.Copy(dst, src)
}

Обработка ошибок: поймать panic

Defer также может быть использован для восстановления (recover) после panic и обновления возвращаемого значения.


Читайте также:


среда, 29 апреля 2020 г.

Указатели по-простому в Golang

Указатель (pointer) - это переменная, которая содержит адрес объекта.

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

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

Указатель имеет тип *T. Ключевое слово new выделяет новый объект и возвращает его адрес.

type Human struct {
    Name string
}

var ps *Human = new(Human) 
// ps содержит адрес новой структуры

Объявление переменной можно записать более компактно.

ps := new(Human)

Адресный оператор

Оператор & возвращает адрес объекта.

s := Human{"Алиса"} // s содержит саму структуру
ps := &s            // ps содержит адрес структуры

Оператор & также может использоваться с составными литералами. Две строки выше могут быть записаны как

ps := &Human{"Алиса"}

Разыменование указателя

Для указателя x разыменование указателя *x обозначает значение, на которое указывает x. Разыменование указателя используется редко, поскольку Go может автоматически получить адрес переменной.

ps := new(Human)
ps.Name = "Алиса" 
// то же что и (*ps).Name = "Алиса"

Указатели как параметры

Используя указатель для изменения объекта, вы влияете на весь код, который использует объект.

// Ivan - это функция, которая не имеет никакого эффекта.
func Ivan(s Human) {
    s.Name = "Иван" // изменяет только локальную копию
}

// Oleg устанавливает pp.Name равным "Олег".
func Oleg(ps *Human) {
    ps.Name = "Олег"
}

func main() {
    s := Human{"Алиса"}

    Ivan(s)
    fmt.Println(s) // печатает {Алиса}

    Oleg(&s)
    fmt.Println(s) // печатает {Олег}
}


Читайте также:


вторник, 28 апреля 2020 г.

Четыре базовых range цикла (for-each) в Golang

Базовый for-each цикл (срез или массив)

a := []string{"Foo", "Bar"}
for i, s := range a {
    fmt.Println(i, s)
}

Вывод:

0 Foo
1 Bar

  • Выражение range, a вычисляется один раз перед началом цикла.
  • Значения итерации присваиваются соответствующим переменным итерации, i и s, как в операторе присваивания.
  • Вторая итерационная переменная является необязательной.
  • Для нулевого среза (nil slice) число итераций равно 0.

Итерация строк: руны или байты

Для строк цикл диапазона перебирает Unicode кодовые точки.

for i, ch := range "日本語" {
    fmt.Printf("%#U начинается с позиции байта %d\n", ch, i)
}

Вывод:

U+65E5 '日' начинается с позиции байта 0
U+672C '本' начинается с позиции байта 3
U+8A9E '語' начинается с позиции байта 6

  • Индекс является первым байтом кодированной точки в кодировке UTF-8; второе значение типа rune - это значение кодовой точки.
  • Для недопустимой последовательности UTF-8 второе значение будет 0xFFFD, и итерация продвинется на один байт.

Чтобы перебрать отдельные байты, просто используйте нормальный for цикл и индексацию строки:

const s = "日本語"
for i := 0; i < len(s); i++ {
    fmt.Printf("%x ", s[i])
}

Вывод:

e6 97 a5 e6 9c ac e8 aa 9e

Итерация карт: ключи и значения

Порядок итераций над картами не указан и не гарантируется, что он будет одинаковым для каждой итерации.

m := map[string]int{
    "one":   1,
    "two":   2,
    "three": 3,
}
for k, v := range m {
    fmt.Println(k, v)
}

Вывод:

two 2
three 3
one 1

  • Если запись карты, которая еще не была достигнута, будет удалена во время итерации, это значение не будет создано.
  • Если запись карты создается во время итерации, эта запись может создаваться или не создаваться.
  • Для нулевой карты (nil map) число итераций равно 0.

Итерация канала

Для каналов значения итерации являются последовательными значениями, отправляемыми в канале до его закрытия.

ch := make(chan int)
go func() {
    ch <- 1
    ch <- 2
    ch <- 3
    close(ch)
}()
for n := range ch {
    fmt.Println(n)
}

Вывод:

1
2
3

Для нулевого канала (nil channel) range цикл блокируется навсегда.


Читайте также:


Проверка является ли число простым в Golang

Для целочисленных типов используйте ProbiablePrime(0) из пакета math/big. Этот тест на простоту на 100% точен для входных данных менее чем 2 в 64 степени (18446744073709551616).

const n = 1212121
if big.NewInt(n).ProbablyPrime(0) {
    fmt.Println(n, "- простое число")
} else {
    fmt.Println(n, "- не простое число")
}

Вывод:

1212121 - простое число

Большие числа

Для больших чисел вам необходимо указать желаемое количество тестов для ProbiablePrime(n). Для n тестов вероятность возврата истины для случайно выбранного не простого числа составляет не более (1/4)n. Распространенным выбором является использование n = 20; это дает ложную положительную оценку в 0.000,000,000,001 случаев.

z := new(big.Int)
fmt.Sscan("170141183460469231731687303715884105727", z)
if z.ProbablyPrime(20) {
    fmt.Println(z, "- возможно простое число")
} else {
    fmt.Println(z, "- не простое число")
}

Вывод:

170141183460469231731687303715884105727 - возможно простое число


Читайте также:


Два паттерна для цикла do-while в Golang

В Go нет цикла do-while чтобы эмулировать C/Java код

do {
    work();
} while (condition);

Вы можете использовать цикл for одним из следующих двух способов:

for ok := true; ok; ok = condition {
    work()
}

for {
    work()
    if !condition {
        break
    }
}

Цикл повторения-до (repeat-until)

Чтобы написать цикл повторения-до

repeat
    work();
until condition;

Просто измените условие (condition) в приведенном выше коде на его дополнение:

for ok := true; ok; ok = !condition {
    work()
}

for {
    work()
    if condition {
        break
    }
}


Читайте также:


понедельник, 27 апреля 2020 г.

Доступ к приватным полям с помощью отражения в Golang

С помощью отражения можно читать, но не записывать неэкспортированные поля структуры, определенной в другом пакете.

В этом примере мы получаем доступ к неэкспортированному полю len в структуре List в пакете container/list:

package list

type List struct {
    root Element
    len  int
}

Этот код читает значение len с помощью отражения.

package main

import (
    "container/list"
    "fmt"
    "reflect"
)

func main() {
    l := list.New()
    l.PushFront("foo")
    l.PushFront("bar")

    // Получаем reflect.Value fv 
    // для неэкспортированного len.
    fv := reflect.ValueOf(l).Elem().FieldByName("len")
    fmt.Println(fv.Int()) // 2

    // Пытаемся задать значение len.
    fv.Set(reflect.ValueOf(3)) // НЕДОПУСТИМО
}

Вывод (получаем panic из-за недопустимого действия):

2
panic: reflect: reflect.Value.Set using value obtained using unexported field

goroutine 1 [running]:
reflect.flag.mustBeAssignable(0x1a2, 0x285a)
 /usr/local/go/src/reflect/value.go:225 +0x280
reflect.Value.Set(0xee2c0, 0x10444254, 0x1a2, 0xee2c0, 0x1280c0, 0x82)
 /usr/local/go/src/reflect/value.go:1345 +0x40
main.main()
 ../main.go:18 +0x280


Читайте также:


Доступ к переменным среды в Golang

Используйте функции os.Setenv, os.Getenv и os.Unsetenv для доступа к переменным среды.

fmt.Printf("%q\n", os.Getenv("SHELL")) // "/bin/bash"

os.Unsetenv("SHELL")
fmt.Printf("%q\n", os.Getenv("SHELL")) // ""

os.Setenv("SHELL", "/bin/dash")
fmt.Printf("%q\n", os.Getenv("SHELL")) // "/bin/dash"

Функция os.Environ возвращает срез строк "key=value", в котором перечислены все переменные среды.

for _, s := range os.Environ() {
    // распаковываем "key=value"
    kv := strings.SplitN(s, "=", 2) 
    fmt.Printf("key:%q value:%q\n", kv[0], kv[1])
}

Вывод:

key:"SHELL" value:"/bin/bash"
key:"SESSION" value:"ubuntu"
key:"TERM" value:"xterm-256color"
key:"LANG" value:"en_US.UTF-8"
key:"XMODIFIERS" value:"@im=ibus"
…


Читайте также:


Генерация уникальной строки (UUID, GUID) в Golang

Универсально уникальный идентификатор (UUID) или глобально уникальный идентификатор (GUID) - это 128-битное число, используемое для идентификации информации.

  • UUID уникален для практических целей: вероятность его дублирования очень близка к нулю.
  • UUID не зависят от центрального органа или координации между теми, кто их генерирует.

Строковое представление UUID состоит из 32 шестнадцатеричных цифр, отображаемых в 5 группах, разделенных дефисами. Например:

123e4567-e89b-12d3-a456-426655440000

Пример генератора UUID

Вы можете использовать функцию rand.Read из пакета crypto/rand для генерации базового UUID.

b := make([]byte, 16)
_, err := rand.Read(b)
if err != nil {
    log.Fatal(err)
}
uuid := fmt.Sprintf("%x-%x-%x-%x-%x",
    b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
fmt.Println(uuid)

Вывод:

9438167c-9493-4993-fd48-950b27aad7c9

Ограничения

Этот UUID не соответствует RFC 4122. В частности, он не содержит номеров версий или вариантов.

Предупреждение: вызов rand.Read возвращает ошибку в случае сбоя основного системного вызова. Например, если он не может прочитать /dev/urandom в системе Unix, или если CryptAcquireContext не работает в системе Windows.


Читайте также:


Перемешать срез или массив в Golang

Функция rand.Shuffle в пакете math/rand перемешивает входную последовательность, используя заданную функцию подкачки.

a := []int{1, 2, 3, 4, 5, 6, 7, 8}
rand.Seed(time.Now().UnixNano())
rand.Shuffle(len(a), 
             func(i, j int) { a[i], a[j] = a[j], a[i] })

Вывод:

[5 8 6 4 3 7 2 1]

Предупреждение: без вызова rand.Seed вы будете получать одинаковую последовательность псевдослучайных чисел при каждом запуске программы.


Читайте также:


Две базовых реализации набора (set) в Golang

Идиоматический способ реализации набора в Go - использовать карту.

set := make(map[string]bool) // Новый пустой набор
set["Foo"] = true            // Добавить
for k := range set {         // Пройти в цикле
    fmt.Println(k)
}
delete(set, "Foo")    // Удалить
size := len(set)      // Размер
exists := set["Foo"]  // Наличие элемента

Если память, используемая логическими значениями, является проблемой, которая кажется маловероятной, вы можете заменить их пустыми структурами. В Go пустая структура обычно не использует память.

type void struct{}
var member void

set := make(map[string]void) // Новый пустой набор
set["Foo"] = member          // Добавить
for k := range set {         // Пройти в цикле
    fmt.Println(k)
}
delete(set, "Foo")      // Удалить
size := len(set)        // Размер
_, exists := set["Foo"] // Наличие элемента


Читайте также:


воскресенье, 26 апреля 2020 г.

Удобный доступ к crypto/rand для создания случайных чисел в Golang

Go имеет два пакета для случайных чисел:

  • math/rand реализует большой выбор генераторов псевдослучайных чисел.
  • crypto/rand реализует криптографически безопасный генератор псевдослучайных чисел с ограниченным интерфейсом.

Два пакета можно объединить, вызвав rand.New в пакете math/rand с источником, который получает данные из crypto/rand.

import (
    crand "crypto/rand"
    rand "math/rand"

    "encoding/binary"
    "fmt"
    "log"
)

func main() {
    var src cryptoSource
    rnd := rand.New(src)

    // действительно случайное число от 0 до 999
    fmt.Println(rnd.Intn(1000)) 
}

type cryptoSource struct{}

func (s cryptoSource) Seed(seed int64) {}

func (s cryptoSource) Int63() int64 {
    return int64(s.Uint64() & ^uint64(1<<63))
}

func (s cryptoSource) Uint64() (v uint64) {
    err := binary.Read(crand.Reader, binary.BigEndian, &v)
    if err != nil {
        log.Fatal(err)
    }
    return v
}

Предупреждение: crand.Reader возвращает ошибку в случае сбоя основного системного вызова. Например, если он не может прочитать /dev/urandom в системе Unix, или если CryptAcquireContext не работает в системе Windows.


Читайте также:


Создать случайную строку (пароль) в Golang

Случайная строка

Этот код генерирует случайную строку цифр и символов из шведского алфавита (которая включает символы не ASCII å, ä и ö).

rand.Seed(time.Now().UnixNano())
chars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZÅÄÖ" +
    "abcdefghijklmnopqrstuvwxyzåäö" +
    "0123456789")
length := 8
var b strings.Builder
for i := 0; i < length; i++ {
    b.WriteRune(chars[rand.Intn(len(chars))])
}
str := b.String() // Например "ExcbsVQs"

Предупреждение: чтобы сгенерировать пароль, вы должны использовать криптографически безопасные псевдослучайные числа.

Случайная строка с ограничениями

Этот код генерирует случайную строку ASCII по крайней мере с одной цифрой и одним специальным символом.

rand.Seed(time.Now().UnixNano())
digits := "0123456789"
specials := "~=+%^*/()[]{}/!@#$?|"
all := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
    "abcdefghijklmnopqrstuvwxyz" +
    digits + specials
length := 8
buf := make([]byte, length)
buf[0] = digits[rand.Intn(len(digits))]
buf[1] = specials[rand.Intn(len(specials))]
for i := 2; i < length; i++ {
    buf[i] = all[rand.Intn(len(all))]
}
rand.Shuffle(len(buf), func(i, j int) {
    buf[i], buf[j] = buf[j], buf[i]
})
str := string(buf) // Например "3i[g0|)z"

До версии Go 1.10

В коде до версии Go 1.10 замените вызов rand.Shuffle следующим кодом:

// Тасование Фишера — Йетса
for i := len(buf) - 1; i > 0; i-- { 
    j := rand.Intn(i + 1)
    buf[i], buf[j] = buf[j], buf[i]
}


Читайте также:


Что такое seed (начальное число, семя) в генераторе случайных чисел?

В действительности псевдослучайные числа вовсе не случайны. Они вычисляются с использованием фиксированного детерминированного алгоритма.

Начальное число является отправной точкой для последовательности псевдослучайных чисел. Если вы начинаете с того же семени, вы получаете ту же последовательность. Это может быть очень полезно для отладки.

Если вам нужна разная последовательность чисел каждый раз, вы можете использовать текущее время в качестве начального числа.

Пример

Этот генератор производит последовательность из 97 различных чисел, затем он запускается снова. seed решает, с какого числа начнется последовательность.

// New возвращает генератор псевдослучайных чисел Rand 
// с заданным начальным числом.
// Каждый раз, когда вы вызываете Rand, 
// вы получаете новое "случайное" число.
func New(seed int) (Rand func() int) {
    current := seed
    return func() int {
        next := (17 * current) % 97
        current = next
        return next
    }
}

func main() {
    rand1 := New(1)
    fmt.Println(rand1(), rand1(), rand1())

    rand2 := New(2)
    fmt.Println(rand2(), rand2(), rand2())
}

Вывод:

17 95 63
34 93 29

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


Читайте также:


Генерация случайных чисел, символов и элементов срезов в Golang

Используйте функции rand.Seed и rand.Int63 в пакете math/rand для генерации неотрицательного псевдослучайного числа типа int64:

rand.Seed(time.Now().UnixNano())
n := rand.Int63() // например 4601851300195147788

Аналогично, rand.Float64 генерирует псевдослучайное число с плавающей точкой x, где 0 <= x < 1:

x := rand.Float64() // например 0.49893371771268225

Предупреждение: без первоначального вызова rand.Seed вы будете получать одинаковую последовательность чисел при каждом запуске программы.

Несколько случайных источников

Все функции в пакете math/rand используют один случайный источник.

При необходимости вы можете создать новый генератор случайных чисел типа Rand со своим собственным источником, а затем использовать его методы для генерации случайных чисел:

generator := rand.New(rand.NewSource(time.Now().UnixNano()))
n := generator.Int63()
x := generator.Float64()

Целые числа и символы в заданном диапазоне

Число между a и b

Используйте rand.Intn(m), который возвращает псевдослучайное число n, где 0 <= n < m.

n := a + rand.Intn(b-a+1) // a ≤ n ≤ b

Символ между 'a' и 'z'

c := 'a' + rune(rand.Intn('z'-'a'+1)) // 'a' ≤ c ≤ 'z'

Случайный элемент из среза

Чтобы сгенерировать символ из произвольного набора, выберите случайный индекс из среза символов:

chars := []rune("AB⌘")
c := chars[rand.Intn(len(chars))] // например '⌘'


Читайте также:


суббота, 25 апреля 2020 г.

Измерение времени исполнения в Golang

Вы можете измерить время исполнения части кода, используя функцию time.Since:

start := time.Now()
// Код для измерения
duration := time.Since(start)

// Отформатированная строка, 
// например, "2h3m0.5s" или "4.503μs"
fmt.Println(duration)

// Nanoseconds как int64
fmt.Println(duration.Nanoseconds())

Измерение вызова функции

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

func foo() {
    defer duration(track("foo"))
    // Код для измерения
}

func track(msg string) (string, time.Time) {
    return msg, time.Now()
}

func duration(msg string, start time.Time) {
    log.Printf("%v: %v\n", msg, time.Since(start))
}

Тесты производительности (бенчмарки)

Пакет testing поддерживает тестирование производительности (benchmarking), которое можно использовать для проверки производительности вашего кода.


Читайте также:


Дни в месяце в Golang

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

Чтобы рассчитать количество дней в феврале, посмотрите на день до 1 марта.

func main() {
    t := Date(2000, 3, 0) // день до 2000-03-01
    fmt.Println(t)        // 2000-02-29 00:00:00 +0000 UTC
    fmt.Println(t.Day())  // 29
}

func Date(year, month, day int) time.Time {
    return time.Date(year, time.Month(month), day, 
                     0, 0, 0, 0, time.UTC)
}

AddDate нормализует свой результат таким же образом. Например, добавление одного месяца к 31 октября приводит к 1 декабря, нормализованной форме от 31 ноября.

// через месяц после 31 октября
t = Date(2000, 10, 31).AddDate(0, 1, 0)
fmt.Println(t) 
// 2000-12-01 00:00:00 +0000 UTC


Читайте также:


Количество дней между двумя датами в Golang

Чтобы вычислить количество дней между двумя датами используйте функцию Sub из пакета time.

func main() {
    // В високосном году 2016 было 366 дней.
    t1 := Date(2016, 1, 1)
    t2 := Date(2017, 1, 1)
    days := t2.Sub(t1).Hours() / 24
    fmt.Println(days) // 366
}

func Date(year, month, day int) time.Time {
    return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
}


Читайте также:


пятница, 24 апреля 2020 г.

Как найти день недели в Golang

Функция Weekday возвращает день недели для time.Time.

func (t Time) Weekday() Weekday

В использовании:

weekday := time.Now().Weekday()
fmt.Println(weekday)      // "Tuesday"
fmt.Println(int(weekday)) // "2"

Тип Weekday

Тип time.Weekday указывает день недели (Sunday = 0, Monday = 1, Tuesday = 2, Wednesday = 3, …).

type Weekday int

const (
    Sunday Weekday = iota // Воскресенье
    Monday                // Понедельник
    Tuesday               // Вторник
    Wednesday             // Среда
    Thursday              // Четверг
    Friday                // Пятница
    Saturday              // Суббота
)


Читайте также:


Получить год, месяц, день из пакета time в Golang

Функция Date возвращает год, месяц и день в time.Time.

func (t Time) Date() (year int, month Month, day int)

В использовании:

year, month, day := time.Now().Date()
fmt.Println(year, month, day)      // Например 2020 April 24
fmt.Println(year, int(month), day) // Например 2020 4 24

Вы также можете извлечь информацию с помощью отдельных вызовов:

t := time.Now()
year := t.Year()   // type int
month := t.Month() // type time.Month
day := t.Day()     // type int

Тип time.Month указывает месяц года (January = 1, February = 2, March = 3, …).

type Month int

const (
 January Month = 1 + iota
 February
 March
 April
 May
 June
 July
 August
 September
 October
 November
 December
)


Читайте также:


четверг, 23 апреля 2020 г.

Как получить текущую метку времени (timestamp) в Golang

Используйте time.Now и один из time.Unix или time.UnixNano, чтобы получить метку времени (timestamp).

// текущее местное время
now := time.Now()      

// количество секунд с 1 января 1970 года по Гринвичу (UTC)
sec := now.Unix() 

// количество наносекунд с 1 января 1970 года 
// по Гринвичу (UTC)
nsec := now.UnixNano() 

fmt.Println(now)  // time.Time
fmt.Println(sec)  // int64
fmt.Println(nsec) // int64

2020-04-23 11:05:19.9605992 +0500 +05 m=+0.007998301
1587621919
1587621919960599200


Читайте также:


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

Каждое Time имеет соответствующее Location, которое используется для отображения.

Метод In возвращает время с определенным местоположением (location). Изменение местоположения таким образом изменяет только представление; это не меняет момент во времени.

Вот удобная функция, которая изменяет местоположение, связанное со временем.

// TimeIn возвращает время в UTC, если name "" или "UTC".
// Возвращает местное время, если name "Local".
// В противном случае name принимается 
// за name местоположения в базе данных часового пояса IANA, 
// например, "Africa/Lagos".
func TimeIn(t time.Time, name string) (time.Time, error) {
    loc, err := time.LoadLocation(name)
    if err == nil {
        t = t.In(loc)
    }
    return t, err
}

В использовании:

for _, name := range []string{
    "",
    "Local",
    "Asia/Shanghai",
    "America/Metropolis",
} {
    t, err := TimeIn(time.Now(), name)
    if err == nil {
        fmt.Println(t.Location(), t.Format("15:04"))
    } else {
        fmt.Println(name, "<time unknown>")
    }
}

UTC 19:32
Local 20:32
Asia/Shanghai 03:32
America/Metropolis <time unknown>

Предупреждение. Переход на летнее время пропускается или повторяется время. Например, в Соединенных Штатах 13 марта 2011 г. 2:15am никогда не происходило, а 6 ноября 2011 г. 1:15am происходило дважды. В таких случаях выбор часового пояса и, следовательно, времени не является четко определенным. Date возвращает время, которое является правильным в одной из двух зон, участвующих в переходе, но не гарантирует, какая именно.


Читайте также:


среда, 22 апреля 2020 г.

Преобразования в Golang

Выражение T(x) преобразует значение x в тип T.

x := 5.1
n := int(x) // преобразует float в int

Правила преобразования обширны, но предсказуемы:

  • все преобразования между типизированными выражениями должны быть явно указаны,
  • незаконные преобразования обнаруживаются компилятором.

Преобразование в и из чисел и строк может изменить представление и иметь стоимость во время выполнения. Все остальные преобразования изменяют только тип, но не представление x.

Интерфейсы

Чтобы "преобразовать" интерфейс в строку, структуру или карту (map), вы должны использовать утверждение типа или переключатель типа. Утверждение типа в действительности не преобразует интерфейс в другой тип данных, но обеспечивает доступ к конкретному значению интерфейса, которое обычно является тем, что вам нужно.

Целые числа

  • При преобразовании в более короткий целочисленный тип значение усекается, чтобы соответствовать размеру типа результата.
  • При преобразовании в более длинный целочисленный тип,
    • если значение является целым числом со знаком, оно расширяется знаком;
    • в противном случае оно расширяется нулями.

a := uint16(0x10fe) // 0001 0000 1111 1110
b := int8(a)        //           1111 1110 (усечено к -2)
c := uint16(b)      // 1111 1111 1111 1110 (расширено знаком к 0xfffe)

Числа с плавающей запятой

  • При преобразовании числа с плавающей запятой в целое число дробь отбрасывается (усечение до нуля).
  • При преобразовании целого числа или числа с плавающей запятой в ​​тип с плавающей запятой результирующее значение округляется до точности, указанной типом назначения.

var x float64 = 1.9
n := int64(x) // 1
n = int64(-x) // -1

n = 1234567890
y := float32(n) // 1.234568e+09

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

Целое число в строку

  • При преобразовании целого числа в строку значение интерпретируется как кодовая точка Unicode, и результирующая строка будет содержать символ, представленный этой кодовой точкой, закодированный в UTF-8.
  • Если значение не представляет допустимую кодовую точку (например, если оно отрицательное), результатом будет "\ufffd", символ замены Unicode �.

string(97) // "a"
string(-1) // "\ufffd" == "\xef\xbf\xbd"

Используйте strconv.Itoa, чтобы получить десятичное строковое представление целого числа.

strconv.Itoa(97) // "97"

Строки и срезы байтов

  • Преобразование среза байтов в строковый тип дает строку, последовательные байты которой являются элементами среза.
  • Преобразование значения типа строки в тип среза байтов дает срез, последовательными элементами которого являются байты строки.

string([]byte{97, 230, 151, 165}) // "a日"
[]byte("a日")                     // []byte{97, 230, 151, 165}

Строки и срезы рун

  • Преобразование среза рун в строковый тип дает строку, которая является объединением отдельных значений рун, преобразованных в строки.
  • Преобразование значения типа строки в тип среза рун дает срез, содержащий отдельные кодовые точки Unicode строки.

string([]rune{97, 26085}) // "a日"
[]rune("a日")             // []rune{97, 26085}

Подлежащий тип

Неконстантное значение может быть преобразовано в тип T, если оно имеет тот же подлежащий тип, что и T.

В этом примере базовый тип int64, T1 и T2 - int64.

type (
 T1 int64
 T2 T1
)

В Go идиоматично преобразовывать тип выражения для доступа к определенному методу.

var n int64 = 12345
fmt.Println(n)                // 12345
fmt.Println(time.Duration(n)) // 12.345µs

Подлежащий тип time.Duration - int64, а тип time.Duration имеет метод String, который возвращает продолжительность (duration), отформатированную как время.

Неявные преобразования

Единственное неявное преобразование в Go - это когда нетипизированная константа используется в ситуации, когда требуется тип.

В этом примере нетипизированные литералы 1 и 2 неявно преобразуются.

var x float64
x = 1 // То же, что и x = float64(1)

t := 2 * time.Second 
// То же, что и t := time.Duration(2) * time.Second

Неявные преобразования необходимы, поскольку в Go нет смешивания числовых типов. Вы можете умножить time.Duration только на другое time.Duration.

Когда тип не может быть выведен из контекста, нетипизированная константа преобразуется в bool, int, float64, complex128, string или rune в зависимости от синтаксического формата константы.

n := 1   // То же, что и n := int(1)
x := 1.0 // То же, что и x := float64(1.0)
s := "A" // То же, что и s := string("A")
c := 'A' // То же, что и c := rune('A')

Незаконные неявные преобразования перехватываются компилятором.

var b byte = 256 // То же, что и var b byte = byte(256)

../main.go:2:6: constant 256 overflows byte

Указатели

Компилятор Go не допускает преобразования между указателями и целыми числами. Пакет unsafe реализует эту функцию в ограниченных условиях.

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


Читайте также:


вторник, 21 апреля 2020 г.

Создание временного файла или каталога в Golang

Создание временного файла

Используйте ioutil.TempFile в пакете io/ioutil, чтобы создать глобально уникальный временный файл. Это ваша собственная задача - удалить файл, когда он больше не нужен.

file, err := ioutil.TempFile("dir", "prefix")
if err != nil {
    log.Fatal(err)
}
defer os.Remove(file.Name())

fmt.Println(file.Name()) // Например "dir/prefix054003078"

Вызов ioutil.TempFile

  • создает новый файл с именем, начинающимся с "prefix" в каталоге "dir",
  • открывает файл для чтения и записи,
  • и возвращает новый *os.File.

Чтобы поместить новый файл в os.TempDir(), каталог по умолчанию для временных файлов, вызовите ioutil.TempFile с пустой строкой каталога.

Добавить суффикс к имени временного файла (с версии Go 1.11)

Начиная с Go 1.11, если вторая строка, заданная для TempFile, содержит "*", случайная строка заменяет эту "*".

file, err := ioutil.TempFile("dir", "myname.*.bat")
if err != nil {
    log.Fatal(err)
}
defer os.Remove(file.Name())

fmt.Println(file.Name()) 
// Например "dir/myname.054003078.bat"

Если "*" не включено, старое поведение сохраняется, и случайные цифры добавляются в конец.

Создание временного каталога

Используйте ioutil.TempDir в пакете io/ioutil, чтобы создать глобально уникальный временный каталог.

dir, err := ioutil.TempDir("dir", "prefix")
if err != nil {
 log.Fatal(err)
}
defer os.RemoveAll(dir)

Вызов ioutil.TempDir

  • создает новый каталог с именем, начинающимся с "prefix" в каталоге "dir"
  • и возвращает путь к новому каталогу.

Чтобы поместить новый каталог в os.TempDir(), каталог по умолчанию для временных файлов, вызовите ioutil.TempDir с пустой строкой каталога.


Читайте также:


понедельник, 20 апреля 2020 г.

Получить список всех файлов (рекурсивно) в каталоге в Golang

Список каталогов

Используйте функцию ioutil.ReadDir в пакете io/ioutil. Возвращает отсортированный срез, содержащий элементы типа os.FileInfo.

Код в следующем примере печатает отсортированный список всех имен файлов в текущем каталоге.

files, err := ioutil.ReadDir(".")
if err != nil {
    log.Fatal(err)
}
for _, f := range files {
    fmt.Println(f.Name())
}

Пример вывода:

dev
etc
tmp
usr

Посетить все файлы и папки в дереве каталогов

Используйте функцию filepath.Walk в пакете path/filepath.

  • Она просматривает файловое дерево, вызывая функцию типа filepath.WalkFunc для каждого файла или каталога в дереве, включая корень.
  • Файлы идут в лексическом порядке.
  • Не следует по символическим ссылкам.

Код в следующем примере перечисляет пути и размеры всех файлов и каталогов в дереве файлов с корнем в текущем каталоге.

err := filepath.Walk(".",
    func(path string, info os.FileInfo, err error) error {
    if err != nil {
        return err
    }
    fmt.Println(path, info.Size())
    return nil
})
if err != nil {
    log.Println(err)
}

Пример вывода:

. 1644
dev 1644
dev/null 0
dev/random 0
dev/urandom 0
dev/zero 0
etc 1644
etc/group 116
etc/hosts 20
etc/passwd 0
etc/resolv.conf 0
tmp 548
usr 822
usr/local 822
usr/local/go 822
usr/local/go/lib 822
usr/local/go/lib/time 822
usr/local/go/lib/time/zoneinfo.zip 366776


Читайте также:


воскресенье, 19 апреля 2020 г.

Найти текущий рабочий каталог в Golang

Текущий каталог

Используйте os.Getwd, чтобы найти путь к текущему каталогу.

path, err := os.Getwd()
if err != nil {
    log.Println(err)
}
fmt.Println(path)  // например /home/user

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

Текущий исполняемый файл

Используйте os.Executable, чтобы найти путь к исполняемому файлу, который запустил текущий процесс.

path, err := os.Executable()
if err != nil {
    log.Println(err)
}
fmt.Println(path) 
// например /tmp/go-build872132473/b001/exe/main

Предупреждение: нет никакой гарантии, что путь все еще указывает на правильный исполняемый файл. Если для запуска процесса использовалась символическая ссылка, в зависимости от операционной системы, результатом может быть символическая ссылка или путь, на который она указала. Если требуется стабильный результат, может помочь path/filepath.EvalSymlinks.


Читайте также:


суббота, 18 апреля 2020 г.

Добавить текст в файл, записать лог в Golang

Этот код добавляет строку текста в файл text.log. Он создает файл, если он еще не существует.

f, err := os.OpenFile("text.log",
    os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
    log.Println(err)
}
defer f.Close()
if _, err := f.WriteString("текст\n"); err != nil {
    log.Println(err)
}

Если вы добавляете текст в файл для целей ведения журнала, тогда используйте следующий пример. Этот код добавляет сообщение журнала в файл text.log. Он создает файл, если он еще не существует.

f, err := os.OpenFile("text.log",
    os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
    log.Println(err)
}
defer f.Close()

logger := log.New(f, "префикс: ", log.LstdFlags)
logger.Println("текст для добавления")
logger.Println("еще текст для добавления")

Содержимое text.log:

префикс: 2020/04/18 07:52:58 текст для добавления
префикс: 2020/04/18 07:52:58 еще текст для добавления

  • log.New создает новый log.Logger, который пишет в f.
  • "префикс: " появляется в начале каждой сгенерированной строки журнала.
  • Аргумент flag определяет, какой текст добавлять к каждой записи журнала. В приведенном примере LstdFlags - это флаг добавляющий запись даты и времени.

Отключить ведение журнала

Чтобы отключить все выходные данные из log.Logger, задайте в качестве места назначения вывода ioutil.Discard, средство записи, для которого все вызовы завершаются успешно, ничего не делая.

log.SetOutput(ioutil.Discard)


Читайте также:


пятница, 17 апреля 2020 г.

Читать файл (stdin) построчно в Golang

Читать из файла

Используйте bufio.Scanner, чтобы прочитать файл построчно.

file, err := os.Open("file.txt")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

scanner := bufio.NewScanner(file)
for scanner.Scan() {
    fmt.Println(scanner.Text())
}

if err := scanner.Err(); err != nil {
    log.Fatal(err)
}

Читать со стандартного ввода

Используйте os.Stdin для чтения из стандартного потока ввода.

scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
 fmt.Println(scanner.Text())
}

if err := scanner.Err(); err != nil {
 log.Println(err)
}

Читать из любого потока

Bufio.Scanner может читать из любого потока байтов, если он реализует интерфейс io.Reader.


Читайте также:


Последний элемент в срезе/массиве в Golang

Читать последней элемент

Используйте индекс len(a)-1 для доступа к последнему элементу среза или массива a.

a := []string{"A", "B", "C"}
s := a[len(a)-1] // C

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

Удалить последний элемент

a := []string{"A", "B", "C"}
a = a[:len(a)-1] // [A B]

Остерегайтесь утечек памяти.

Предупреждение: если срез является постоянным, а элемент временным, вы можете удалить ссылку на элемент, прежде чем вырезать его.

a[len(a)-1] = "" // Удалить элемент (записать нулевое значение)
a = a[:len(a)-1] // [A B]


Читайте также:


четверг, 16 апреля 2020 г.

Компиляция кода Go

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

Возьмем для примера следующий файл example.go:

package main

import (
 "fmt"
)

func main() {
 fmt.Println("Hello, I am example program!")
}

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

$ go build example.go

После этого у вас будет новый исполняемый файл с именем example:

$ file example
example: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, with debug_info, not stripped
$ ls -l example
-rwxr-xr-x 1 user user 2011612 apr 16 22:37 example
$ ./example
Hello, I am example program!

Основная причина того, что example такой большой (почти 2Мб), заключается в том, что он статически связанный, что означает, что он не требует никаких внешних библиотек для исполнения.


Читайте также:


Найти элемент в срезе/массиве линейным или бинарным поиском в Golang

Линейный поиск

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

// Find возвращает наименьший индекс i, 
// при котором x == a[i],
// или len(a), если такого индекса нет.
func Find(a []string, x string) int {
    for i, n := range a {
        if x == n {
            return i
        }
    }
    return len(a)
}

// Contains указывает, содержится ли x в a.
func Contains(a []string, x string) bool {
    for _, n := range a {
        if x == n {
            return true
        }
    }
    return false
}

Бинарный поиск

"Бинарный поиск быстрее линейного, но работает, только если ваши данные в порядке. Это сортировка." - Дэн Бентли

Если массив отсортирован, вы можете использовать бинарный поиск. Это будет намного эффективнее, поскольку бинарный поиск выполняется в наихудшем логарифмическом времени, делая O(log n) сравнений, где n - размер среза.

Существует три пользовательские функции бинарного поиска: sort.SearchInts, sort.SearchStrings или sort.SearchFloat64s.

Все они имеют сигнатуру

func SearchType(a []Type, x Type) int

и возвращают

  • наименьший индекс i, при котором x <= a[i]
  • или len(a), если такого индекса нет.

Срез должен быть отсортирован в порядке возрастания.

a := []string{"A", "C", "C"}

fmt.Println(sort.SearchStrings(a, "A")) // 0
fmt.Println(sort.SearchStrings(a, "B")) // 1
fmt.Println(sort.SearchStrings(a, "C")) // 1
fmt.Println(sort.SearchStrings(a, "D")) // 3

Общий бинарный поиск

Существует также универсальная функция бинарного поиска sort.Search.

func Search(n int, f func(int) bool) int

Она возвращает:

  • наименьший индекс i, при котором f(i) истинно (равно true)
  • или n, если такого индекса нет.

Требуется, чтобы f было false для некоторого (возможно, пустого) префикса входного диапазона, а затем true для остальной части.

Этот пример отражает предыдущий, но использует общую sort.Search вместо sort.SearchInts.

a := []string{"A", "C", "C"}
x := "C"

i := sort.Search(len(a), func(i int) bool { return x <= a[i] })
if i < len(a) && a[i] == x {
    fmt.Printf("Найдено %s по индексу %d в %v.\n", x, i, a)
} else {
    fmt.Printf("Не найдено %s в %v.\n", x, a)
}
// Вывод: Найдено C по индексу 1 в [A C C].

Вариант карты

Если вы делаете повторный поиск и обновление, вы можете использовать карту (map) вместо среза. Карта обеспечивает операции поиска, вставки и удаления за O(1) ожидаемое амортизированное время.


Читайте также:


среда, 15 апреля 2020 г.

Два способа удалить элемент из среза в Golang

Быстрая версия (меняет порядок)

a := []string{"A", "B", "C", "D", "E"}
i := 2

// Удалить элемент по индексу i из a.

// 1. Копировать последний элемент в индекс i.
a[i] = a[len(a)-1] 

// 2. Удалить последний элемент (записать нулевое значение).
a[len(a)-1] = ""  

// 3. Усечь срез.
a = a[:len(a)-1]  

fmt.Println(a) // [A B E D]

Код копирует один элемент и выполняется за постоянное время.

Медленная версия (сохраняет порядок)

a := []string{"A", "B", "C", "D", "E"}
i := 2

// Удалить элемент по индексу i из a.

// 1. Выполнить сдвиг a[i+1:] влево на один индекс.
copy(a[i:], a[i+1:])

// 2. Удалить последний элемент (записать нулевое значение).
a[len(a)-1] = ""

// 3. Усечь срез.
a = a[:len(a)-1]

fmt.Println(a) // [A B D E]

Код копирует (len(a) - i - 1) элементов и выполняется за линейное время.


Читайте также:


Как лучше всего очистить срез в Golang: пустой против нулевого

Удалить все элементы

Чтобы удалить все элементы, просто установите срез равным nil.

a := []string{"A", "B", "C", "D", "E"}
a = nil
fmt.Println(a, len(a), cap(a)) // [] 0 0

Это освободит подлежащий массив для сборщика мусора (при условии, что нет других ссылок).

Сохранить выделенную память

Чтобы сохранить подлежащий массив, нарежьте срез до нулевой длины.

a := []string{"A", "B", "C", "D", "E"}
a = a[:0]
fmt.Println(a, len(a), cap(a)) // [] 0 5

Если срез снова расширяется, исходные данные появляются снова.

fmt.Println(a[:2]) // [A B]

Пустой срез против нулевого среза

На практике нулевые срезы и пустые срезы часто можно обрабатывать одинаково:

  • они имеют нулевую длину и емкость,
  • они могут использоваться с одинаковым эффектом в циклах и функциях append,
  • и они даже выглядят одинаково при печати.

var a []int = nil
fmt.Println(len(a)) // 0
fmt.Println(cap(a)) // 0
fmt.Println(a)      // []

b := []int{}
fmt.Println(len(b)) // 0
fmt.Println(cap(b)) // 0
fmt.Println(b)      // []

Однако при необходимости вы можете заметить разницу.

var a []int = nil
var a0 []int = make([]int, 0)

fmt.Println(a == nil)  // true
fmt.Println(a0 == nil) // false

fmt.Printf("%#v\n", a)  // []int(nil)
fmt.Printf("%#v\n", a0) // []int{}

Официальная Go wiki рекомендует использовать нулевые срезы вместо пустых срезов.

Нулевой срез является предпочтительным стилем.

Обратите внимание, что существуют ограниченные обстоятельства, когда предпочтителен не nil срез, а срез нулевой длины, например, при кодировании объектов JSON (nil срез кодируется в null, а []string{} кодируется в массив JSON []).

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


Читайте также:


вторник, 14 апреля 2020 г.

Конструкторы в Golang

Go не имеет явных конструкторов. Идиоматический способ установки новых структур данных - это использование правильных нулевых значений в сочетании с фабричными функциями (factory functions).

Нулевое значение

Попробуйте сделать значение по умолчанию полезным и задокументируйте его поведение. Иногда это все, что нужно.

// StopWatch - это простая утилита часов.
// Его нулевое значение - это бездействующие часы 
// с total временем 0.
type StopWatch struct {
    start   time.Time
    total   time.Duration
    running bool
}

var clock StopWatch 
// Готов к использованию, инициализация не требуется.

StopWatch использует полезные нулевые значения time.Time, time.Duration и bool.

В свою очередь, пользователи StopWatch могут извлечь выгоду из его полезного нулевого значения.

Фабрика (Factory)

Если нулевого значения недостаточно, используйте фабричные функции с именем NewFoo или просто New.

Пример из пакета bufio стандартной библиотеки:

// NewScanner возвращает новый Scanner для чтения из r.
func NewScanner(r io.Reader) *Scanner {
 return &Scanner{
  r:            r,
  split:        ScanLines,
  maxTokenSize: MaxScanTokenSize,
 }
}

Использование NewScanner из пакета bufio:

scanner := bufio.NewScanner(os.Stdin)

Пример из пакета errors стандартной библиотеки:

// New возвращает error с данным text.
// Каждый вызов New возвращает отдельное error значение, 
// даже если text идентичный.
func New(text string) error {
 return &errorString{text}
}

Использование New из пакета errors:

err := errors.New("Произошла ошибка!")


Читайте также:


понедельник, 13 апреля 2020 г.

Форматирование времени и даты в Golang

Go не использует формат yyyy-mm-dd для форматирования времени. Вместо этого вы форматируете специальный параметр макета (layout)

Mon Jan 2 15:04:05 MST 2006

так же, как время или дата должны быть отформатированы. (Эту дату легче запомнить, когда она написана как 01/02 03:04:05PM ‘06 -0700.)

const (
    layoutISO = "2006-01-02"
    layoutUS  = "January 2, 2006"
)
date := "1999-12-31"
t, _ := time.Parse(layoutISO, date)
fmt.Println(t)                  
// 1999-12-31 00:00:00 +0000 UTC

fmt.Println(t.Format(layoutUS)) 
// December 31, 1999

  • Функция time.Parse разбирает строку даты
  • Функция Format форматирует time.Time

Они имеют следующие сигнатуры:

func Parse(layout, value string) (Time, error)
func (t Time) Format(layout string) string

Стандартные форматы времени и даты

Go layout Примечание
January 2, 2006
01/02/06
Jan-02-06
Date
15:04:05
3:04:05 PM
Time
Jan _2 15:04:05
Jan _2 15:04:05.000000
Timestamp с микросекундами
2006-01-02T15:04:05-0700
2006-01-02
15:04:05
ISO 8601 (RFC 3339)
02 Jan 06 15:04 MST
02 Jan 06 15:04 -0700
RFC 822 с числовой зоной
Mon, 02 Jan 2006 15:04:05 MST
Mon, 02 Jan 2006 15:04:05 -0700
RFC 1123 с числовой зоной

Также доступны следующие предопределенные константы формата даты и времени.

ANSIC       = "Mon Jan _2 15:04:05 2006"
UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
RFC822      = "02 Jan 06 15:04 MST"
RFC822Z     = "02 Jan 06 15:04 -0700"
RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700"
RFC3339     = "2006-01-02T15:04:05Z07:00"
RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
Kitchen     = "3:04PM"
// Удобные метки времени.
Stamp      = "Jan _2 15:04:05"
StampMilli = "Jan _2 15:04:05.000"
StampMicro = "Jan _2 15:04:05.000000"
StampNano  = "Jan _2 15:04:05.000000000"

Параметры макета

Тип Параметры
Год 06 2006
Месяц 01 1 Jan January
День 02 2 _2 (ширина 2, выравнивание справа)
День недели Mon Monday
Часы 03 3 15
Минуты 04 4
Секунды 05 5
ms μs ns .000 .000000 .000000000
ms μs ns .999 .999999 .999999999 (завершающие нули удалены)
am/pm PM pm
Timezone (временная зона) MST
Смещение -0700 -07 -07:00 Z0700 Z07:00

Крайние случаи

Невозможно указать, что час должен отображаться без начального нуля в 24-часовом формате времени.

Нельзя указывать полночь как 24:00 вместо 00:00. Типичное использование для этого будет давать часы работы, заканчивающиеся в полночь, например, с 07:00 до 24:00.

Невозможно указать время, содержащее високосную секунду: 23:59:60. Фактически, пакет времени предполагает григорианский календарь без високосных секунд.


Читайте также: