Menu

Czas w Golang: Now, Sleep, Format, Parse i Duration

Jak pracować z datami i czasem w Go: time.Now i time.Sleep, arytmetyka na Duration, układ wzorcowy 2006-01-02 15:04:05 dla Format i Parse, strefy czasowe, znaczniki czasu Unix oraz timery i tickery.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Podstawy w jednym programie

time.Now() zwraca bieżący czas lokalny, time.Sleep wstrzymuje bieżącą goroutine, time.Since mierzy upływ czasu, a time.Date tworzy konkretną chwilę. Reszta tej strony omawia każdy element po kolei. Przykłady używają stałych dat i UTC, więc ich wynik jest taki sam, gdziekolwiek je uruchomisz.

Sleep i Duration

time.Duration to liczba nanosekund typu int64. Pakiet definiuje stałe do tworzenia czytelnych wartości:

StałaWartość
time.Nanosecond1
time.Microsecond1000 ns
time.Millisecond1000 µs
time.Second1000 ms
time.Minute60 s
time.Hour60 min

Nie ma time.Day, bo doba nie zawsze ma 24 godziny, gdy strefa czasowa zmienia się przy przejściu na czas letni. Do dni kalendarzowych używaj AddDate (patrz niżej).

Dwa częste błędy wynikają z tego, że Duration to liczba całkowita:

  • time.Sleep(5) usypia na 5 nanosekund. Zawsze mnóż przez jednostkę: time.Sleep(5 * time.Second).
  • time.Sleep(n * time.Second) się nie kompiluje, gdy n jest zmienną typu int. Skonwertuj ją: time.Duration(n) * time.Second. Stała taka jak 5 działa bez konwersji, bo stałe bez typu dopasowują się do typu Duration.

ParseDuration przyjmuje ns, us (lub µs), ms, s, m i h, łączone w stylu "2h45m" lub "1.5s". Nie ma jednostki dla dni.

Formatowanie: układ wzorcowy 2006-01-02

Go nie używa %Y-%m-%d ani yyyy-MM-dd. Układ to czas wzorcowy

Mon Jan 2 15:04:05 MST 2006

zapisany tak, jak ma wyglądać wynik. Wartości dobrano tak, by każda była unikalna: miesiąc 1, dzień 2, godzina 3 (lub 15), minuta 4, sekunda 5, rok 6 (2006), przesunięcie strefy -7 (-0700). Czytaj to jako 01/02 03:04:05PM '06 -0700.

Najczęściej potrzebne tokeny układu:

TokenZnaczeniePrzykład
2006 / 06rok, 4 lub 2 cyfry2026 / 26
01 / 1 / Jan / Januarymiesiąc03 / 3 / Mar / March
02 / 2 / _2dzień miesiąca (z zerem, bez dopełnienia, ze spacją)05 / 5 / " 5"
Mon / Mondaydzień tygodniaThu / Thursday
15godzina, zegar 24-godzinny09
03 / 3godzina, zegar 12-godzinny09 / 9
04 / 4minuta07 / 7
05 / 5sekunda03 / 3
PM / pmznacznik AM lub PMAM
.000 / .999ułamki sekund (stała liczba cyfr / bez końcowych zer).250 / .25
MSTskrót strefyUTC
-0700 / -07:00liczbowe przesunięcie strefy+0000 / +00:00
Z07:00jak -07:00, ale dla UTC wypisuje ZZ

Predefiniowane układy:

StałaUkład
time.RFC33392006-01-02T15:04:05Z07:00 (używaj go w API i JSON)
time.RFC3339Nano2006-01-02T15:04:05.999999999Z07:00
time.DateTime (Go 1.20)2006-01-02 15:04:05
time.DateOnly (Go 1.20)2006-01-02
time.TimeOnly (Go 1.20)15:04:05
time.Kitchen3:04PM
time.RFC1123Mon, 02 Jan 2006 15:04:05 MST (daty HTTP używają zamiast tego http.TimeFormat)

Klasyczny błąd to układ zapisany złymi liczbami, na przykład "2023-01-01". Go go nie odrzuca. Kopiuje znaki, których nie rozpoznaje, i podstawia te, które rozpoznaje: każda 2 to dzień, 3 to godzina w zegarze 12-godzinnym, a oba 01 to miesiąc, więc 5 marca o 9:07 zostaje sformatowany jako 5059-03-03. Układ taki jak "YYYY-MM-DD" nie zawiera żadnych tokenów i wypisuje się bez zmian. Jeśli sformatowane daty wyglądają dziwnie, sprawdź, czy układ używa dokładnie wartości wzorcowych.

Parsowanie stringów na czas

time.Parse(layout, value) używa tych samych układów i zwraca błąd, który musisz sprawdzić:

Bez strefy w danych wejściowych Parse zwraca UTC. Ten sam zapis czasu zegarowego może oznaczać różne chwile w zależności od strefy, dlatego istnieje ParseInLocation. Komunikat błędu wskazuje część, która nie pasuje, co pomaga przy debugowaniu układu.

Strefy czasowe

time.Time to chwila plus lokalizacja używana do wyświetlania. Zmiana lokalizacji przez In zmienia sposób wypisywania, a nie to, jaka to chwila.

  • time.UTC jest zawsze dostępne. Przechowuj i przesyłaj czasy w UTC (albo w RFC 3339 z przesunięciem) i konwertuj tylko do wyświetlania.
  • time.Local to strefa maszyny. Na serwerach i w kontenerach to często UTC, a na laptopie nie, więc kod, który od niej zależy, zachowuje się różnie w każdym miejscu.
  • time.LoadLocation("Europe/Berlin") czyta bazę IANA z systemu operacyjnego. Minimalne obrazy kontenerów często jej nie mają i wtedy wywołanie zwraca błąd. Pusty import _ "time/tzdata" osadza bazę w binarce (około 450 KB), więc zawsze działa. Zawsze obsługuj błąd.
  • time.FixedZone(name, offsetSeconds) tworzy strefę ze stałym przesunięciem. Nie ma reguł czasu letniego, więc używaj jej dla otrzymanych przesunięć, a nie dla nazwanych regionów.

Arytmetyka i porównywanie

Na co zwrócić uwagę w wyniku:

  • AddDate(0, 1, 0) dla 31 stycznia daje 3 marca, a nie 28 lutego. Go dodaje jeden miesiąc, otrzymuje „31 lutego”, a potem normalizuje nadmiar. Jeśli potrzebujesz „tego samego dnia w następnym miesiącu, przyciętego do jego końca”, napisz tę logikę samodzielnie.
  • Sub zwraca Duration, która kończy się na około 292 latach. Dla różnic w dniach kalendarzowych porównuj daty o północy UTC i dziel przez 24 godziny.
  • Aby dostać początek dnia, zbuduj czas na nowo z Date(). t.Truncate(24 * time.Hour) zaokrągla względem czasu zerowego w UTC, więc daje złą odpowiedź dla każdej strefy innej niż UTC.

Porównuj przez Equal, Before, After, nigdy przez ==. time.Now() zawiera odczyt zegara monotonicznego, dzięki któremu time.Since pozostaje poprawne, nawet gdy zegar systemowy zostanie przestawiony. == porównuje ten odczyt, a także lokalizację, więc dwie wartości Time dla tej samej chwili mogą być różne. Z tego samego powodu nie używaj time.Time jako klucza mapy bez wcześniejszej normalizacji (t.UTC().Round(0) usuwa odczyt monotoniczny).

Zerowy time.Time to 1 stycznia roku 1, 00:00 UTC. Sprawdzaj go przez t.IsZero().

Mierzenie upływu czasu

start := time.Now()
doWork()
log.Printf("doWork took %v", time.Since(start))

time.Since(start) to time.Now().Sub(start), a time.Until(deadline) to deadline.Sub(time.Now()). Oba używają zegara monotonicznego, gdy jest dostępny, więc są odporne na zmiany zegara systemowego. Do benchmarkowania kodu używaj wsparcia dla benchmarków w pakiecie testing zamiast ręcznego mierzenia czasu.

Timery i tickery

time.After(d) zwraca kanał, który odbiera wartość raz, po czasie d. time.Timer to to samo, ale z metodą Stop. time.Ticker dostarcza wartość co określony okres, dopóki go nie zatrzymasz.

Trzy tyknięcia po 20, 40 i 60 ms przychodzą długo przed timerem 110 ms, więc program kończy się po trzecim tyknięciu. time.AfterFunc(d, f) uruchamia f we własnej goroutine po czasie d, co przydaje się do jednorazowej opóźnionej pracy.

Od Go 1.23 timery i tickery, do których nie ma już odwołań, są zbierane przez garbage collector, nawet jeśli nigdy nie wywołasz Stop, a kanał zatrzymanego lub zresetowanego timera nie dostarcza już przestarzałej wartości. Wywołanie Stop przez defer nadal jasno mówi, że życie tickera się skończyło. Gdy limit czasu dotyczy całej operacji, a nie jednego oczekiwania, context.WithTimeout zwykle czyta się lepiej niż timer.

Czas w JSON

time.Time automatycznie serializuje się do stringów RFC 3339 w JSON i z nich deserializuje, z precyzją do nanosekund:

type Event struct {
	Name string    `json:"name"`
	At   time.Time `json:"at"`
}
// {"name":"deploy","at":"2026-09-23T14:30:00Z"}

String JSON w innym formacie nie da się zdeserializować. Dla znaczników czasu Unix lub własnych formatów przechowuj int64 albo string i konwertuj, albo zdefiniuj typ z własną metodą UnmarshalJSON.

Typowe błędy

  • time.Sleep(1) lub time.Sleep(n) z samą liczbą. To nanosekundy.
  • Układy ze złymi cyframi. "2023-01-01" ani "YYYY-MM-DD" nie są układami. Użyj "2006-01-02".
  • Mylenie minut z miesiącami. Minuty to 04, miesiące to 01. "15:01" wypisuje godzinę, a potem miesiąc, a nie minutę.
  • Porównywanie przez ==. Użyj Equal.
  • Poleganie na time.Local. Różni się między maszynami. Jawnie używaj time.UTC albo wczytanej lokalizacji.
  • Ignorowanie błędu z LoadLocation lub Parse. Oba zawodzą na prawdziwych danych. Nieudane Parse zwraca czas zerowy, który wypisuje się jako rok 1, a nieudane LoadLocation zwraca lokalizację nil, przez którą t.In(loc) wywołuje panic.

Najczęściej zadawane pytania

Jak zrobić sleep w Go?

Wywołaj time.Sleep z wartością time.Duration: time.Sleep(2 * time.Second) albo time.Sleep(500 * time.Millisecond). Sama liczba, jak time.Sleep(2), się skompiluje, ale uśpi program na 2 nanosekundy, bo Duration liczy nanosekundy. Sleep blokuje tylko bieżącą goroutine.

Dlaczego Go używa 2006-01-02 15:04:05 w formatach dat?

Go formatuje daty na przykładzie. Układ to czas wzorcowy Mon Jan 2 15:04:05 MST 2006 zapisany tak, jak ma wyglądać wynik. Jego części rosną w amerykańskiej kolejności: miesiąc 1, dzień 2, godzina 3 (15 w zegarze 24-godzinnym), minuta 4, sekunda 5, rok 6 (2006), przesunięcie strefy 7 (-0700). Dlatego "2006-01-02" oznacza rok-miesiąc-dzień, a "02/01/2006" dzień/miesiąc/rok.

Jak sparsować string z datą w Go?

Użyj time.Parse(layout, value) z układem zapisanym czasem wzorcowym: t, err := time.Parse("2006-01-02", "2026-09-23"). Zawsze sprawdzaj err. Bez informacji o strefie w stringu wynik jest w UTC; użyj time.ParseInLocation, żeby zinterpretować go w innej strefie.

Jak pobrać znacznik czasu Unix w Go?

time.Now().Unix() zwraca liczbę sekund od 1 stycznia 1970 UTC jako int64. UnixMilli(), UnixMicro() i UnixNano() dają mniejsze jednostki. W drugą stronę użyj time.Unix(sec, 0) albo time.UnixMilli(ms).

Jak porównać dwa czasy w Go?

Użyj t1.Before(t2), t1.After(t2) i t1.Equal(t2). Nie używaj ==: porównuje też lokalizację i odczyt zegara monotonicznego, więc dwie wartości dla tej samej chwili mogą być różne. t2.Sub(t1) daje różnicę jako Duration.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ