Go: Go Strings e Manipulação de Data/Hora
Última atualização: 2026-08-26
Strings e datas são os dois tipos de dados fundamentais em todos os programas — a biblioteca padrão do Go oferece um conjunto abrangente de ferramentas para ambos, sem a necessidade de dependências de terceiros.
O pacote strings contém mais de 40 funções; o pacote strconv lida com todas as conversões de tipo; e o pacote time abrange todas as operações relacionadas a tempo. Nesta lição, você vai dominar o conjunto completo de ferramentas para trabalhar com strings e tempo em Go.
1. Você aprenderá
- Funções comuns no pacote
strings(corte, divisão, substituição, pesquisa) - conversão de tipos com strconv (Atoi/Itoa/Parse)
- strings.Builder: Concatenação eficiente de strings
- time.Now / Analisar / Formatar
- Cálculo da duração do tempo (time.Duration)
- temporizadores time.Timer / time.Ticker
- Exemplo completo: Analisador de carimbos de data/hora de logs
2. Uma história real de um engenheiro de operações
(1) Problema: A análise manual dos carimbos de data e hora gera erros em todas as linhas do log
Bob é engenheiro de operações. Ele precisa extrair registros de data e hora dos logs do servidor para calcular o QPS:
“500 mil linhas de log por dia, com carimbos de data e hora em dois formatos:
2026-07-08T10:00:00Ze07/08/2026 10:00:00 AM. Escrevi 20 linhas de código usando o módulodatetimedo Python, mas meu colega não conseguiu entender nada. Mudei para o Go e acabei tendo que consultar a documentação dez vezes só para entender as strings de formato de data e hora.”
A primeira versão que ele escreveu:
// Bad code: hardcoded time format, inflexible
func parseTimestamp(raw string) (time.Time, error) {
// Remember: Go's time format is 2006-01-02 15:04:05, not arbitrary
return time.Parse("2006-01-02 15:04:05", raw)
}
Os colegas costumam ficar confusos com o formato de data e hora do Go (“2006-01-02 15:04:05”).
(2) Go Solution: Cobertura completa da biblioteca padrão
// log_parser.go
package main
import (
"fmt"
"strings"
"time"
)
// Clean log line + extract timestamp
func extractTimestamp(logLine string) (time.Time, error) {
// 1. strings package: TrimSpace removes whitespace
line := strings.TrimSpace(logLine)
if line == "" {
return time.Time{}, fmt.Errorf("empty log line")
}
// 2. strings package: Split to get the first field
parts := strings.Fields(line)
if len(parts) < 1 {
return time.Time{}, fmt.Errorf("no fields")
}
// 3. strconv / strings: extract the time field
rawTime := strings.Trim(parts[0], "[]")
// 4. time package: parse time
formats := []string{
time.RFC3339,
"01/02/2006 03:04:05 PM",
"2006-01-02 15:04:05",
}
for _, f := range formats {
if t, err := time.Parse(f, rawTime); err == nil {
return t, nil
}
}
return time.Time{}, fmt.Errorf("unrecognized time format: %s", rawTime)
}
func main() {
lines := []string{
"2026-07-08T10:00:00Z [INFO] Server started",
"07/08/2026 10:05:00 AM [ERROR] Connection timeout",
}
for _, line := range lines {
t, err := extractTimestamp(line)
if err != nil {
fmt.Printf("Error: %v\n", err)
continue
}
fmt.Printf("Parsed: %s -> %s\n", line, t.Format(time.RFC822))
}
}
Resultado:
Parsed: 2026-07-08T10:00:00Z [INFO] Server started -> 08 Jul 26 10:00 UTC
Parsed: 07/08/2026 10:05:00 AM [ERROR] Connection timeout -> 08 Jul 26 10:05 UTC
(3) Benefícios: Exaustividade da biblioteca padrão
| Pacote | Principais funcionalidades | Substitui bibliotecas de terceiros |
|---|---|---|
| strings | Mais de 40 funções para strings | Não é necessário usar o lodash/underscore |
| strconv | Conversão de tipos | Não requer análise manual |
| strings.Builder | Concatenação eficiente | bytes.Buffer |
| tempo | Análise/formatação de tempo/temporizadores | moment.js / date-fns |
Mon Jan 2 15:04:05 MST 2006 (em que 01/02/03/04/05/06 correspondem a mês/dia/hora/minuto/segundo/ano), então basta lembrar-se de 2006-01-02 15:04:05.
3. Funções principais do pacote strings
(1) ▶ Exemplo: 12 funções comuns para strings
package main
import (
"fmt"
"strings"
)
func main() {
s := " Hello, Go World! "
// Trimming
fmt.Printf("TrimSpace: [%s]\n", strings.TrimSpace(s))
fmt.Printf("Trim: [%s]\n", strings.Trim(s, " !"))
fmt.Printf("TrimPrefix: %s\n", strings.TrimPrefix(s, " Hello"))
// Split and Join
parts := strings.Split("a,b,c", ",")
fmt.Printf("Split: %v\n", parts)
fmt.Printf("Join: %s\n", strings.Join(parts, "-"))
// Search
fmt.Printf("Contains: %v\n", strings.Contains(s, "Go"))
fmt.Printf("Index: %d\n", strings.Index(s, "Go"))
fmt.Printf("Count: %d\n", strings.Count(s, "o"))
// Replace
fmt.Printf("Replace: %s\n", strings.Replace(s, "o", "0", 1))
fmt.Printf("ReplaceAll: %s\n", strings.ReplaceAll(s, "o", "0"))
// Case conversion
fmt.Printf("ToUpper: %s\n", strings.ToUpper(s))
fmt.Printf("ToLower: %s\n", strings.ToLower(s))
// Field splitting (automatically handles whitespace)
fields := strings.Fields(" hello go world ")
fmt.Printf("Fields: %v\n", fields)
}
Resultado:
TrimSpace: [Hello, Go World!]
Trim: [Hello, Go World]
TrimPrefix: , Go World!
Split: [a b c]
Join: a-b-c
Contains: true
Index: 8
Count: 3
Replace: Hell0, Go World!
ReplaceAll: Hell0, G0 W0rld!
ToUpper: HELLO, GO WORLD!
ToLower: hello, go world!
Fields: [hello go world]
(2) Referência rápida para a função strings
| Categoria | Função | Finalidade |
|---|---|---|
| Corte | TrimSpace / Trim / TrimPrefix / TrimSuffix |
Remover espaços em branco ou caracteres especificados |
| Dividir | Split / SplitN / Fields |
Dividir em fatias |
| Participar | Join |
Unir fatias em uma sequência |
| Pesquisar | Contains / Index / LastIndex / Count |
Verificação de subcadeia |
| Substituir | Replace / ReplaceAll |
Substituição de subcadeia |
| Caso | ToUpper / ToLower / Title |
Conversão de maiúsculas e minúsculas |
| Construção | Builder / Repeat |
Concatenação eficiente de strings / repetição de strings |
4. Conversão de tipos com strconv
(1) String ↔ Número
package main
import (
"fmt"
"strconv"
)
func main() {
// string → int
n, err := strconv.Atoi("42")
fmt.Printf("Atoi: %d, err=%v\n", n, err)
// int → string
s := strconv.Itoa(42)
fmt.Printf("Itoa: %s\n", s)
// ParseInt (with base and bit size)
v, _ := strconv.ParseInt("FF", 16, 64)
fmt.Printf("ParseInt(hex): %d\n", v)
// ParseFloat
f, _ := strconv.ParseFloat("3.14", 64)
fmt.Printf("ParseFloat: %f\n", f)
// FormatInt / FormatFloat
fmt.Printf("FormatInt(hex): %s\n", strconv.FormatInt(255, 16))
}
(2) ▶ Exemplo: A diferença entre strconv e a conversão de tipos
package main
import (
"fmt"
"strconv"
)
func main() {
// int → float64 (using type conversion)
var age int = 28
fAge := float64(age)
fmt.Printf("Type conversion: %f\n", fAge)
// string → int (using strconv)
numStr := "42"
if num, err := strconv.Atoi(numStr); err == nil {
fmt.Printf("strconv: %d\n", num)
}
// int → string (using strconv)
s := strconv.Itoa(42)
fmt.Printf("Itoa: %s\n", s)
// Cannot use type conversion to convert string to int (compilation error)
// n := int(numStr) ❌ compilation error
}
| Cenário | Usando conversão de tipo | Usando strconv |
|---|---|---|
| int ↔ float64 | ✅ float64(n) |
❌ |
| int ↔ string | ❌ | ✅ strconv.Itoa / Atoi |
| string ↔ float64 | ❌ | ✅ ParseFloat / FormatFloat |
| Análise da base numérica | ❌ | ✅ ParseInt("FF", 16, 64) |
| Inteiro ↔ uint/int32 | ✅ | ❌ |
5. Concatenação eficiente de strings com strings.Builder
(1) Por que usar um Builder?
package main
import (
"fmt"
"strings"
)
func main() {
// Inefficient: each + creates a new string
s1 := ""
for i := 0; i < 1000; i++ {
s1 += "a" // O(n²) performance
}
// Efficient: Builder's internal buffer
var sb strings.Builder
for i := 0; i < 1000; i++ {
sb.WriteString("a") // O(n) performance
}
s2 := sb.String()
fmt.Printf("len=%d, equal=%v\n", len(s2), s1 == s2)
}
(2) ▶ Exemplo: Como usar o Builder
package main
import (
"fmt"
"strings"
)
func buildCSV(data [][]string) string {
var sb strings.Builder
sb.Grow(1024) // pre-allocate memory
for i, row := range data {
rowStr := strings.Join(row, ",")
sb.WriteString(rowStr)
if i < len(data)-1 {
sb.WriteByte('\n') // write a single byte
}
}
return sb.String()
}
func main() {
data := [][]string{
{"Name", "Age", "City"},
{"Alice", "28", "Shanghai"},
{"Bob", "32", "Beijing"},
}
csv := buildCSV(data)
fmt.Println(csv)
fmt.Printf("Length: %d\n", len(csv))
}
Resultado:
Name,Age,City
Alice,28,Shanghai
Bob,32,Beijing
Length: 52
+ para concatenar strings cria uma nova string a cada vez, resultando em um desempenho de O(n²). strings.Builder mantém um buffer mutável internamente, resultando em um desempenho de O(n). Ao realizar um grande número de concatenações, a diferença de desempenho pode chegar a 1.000 vezes.
6. Pacote time: Manipulação de tempo
(1) Hora. Agora e formatos de hora
package main
import (
"fmt"
"time"
)
func main() {
// Current time
now := time.Now()
fmt.Printf("Now: %v\n", now)
// Common formatting
fmt.Printf("RFC3339: %s\n", now.Format(time.RFC3339))
fmt.Printf("RFC822: %s\n", now.Format(time.RFC822))
fmt.Printf("Custom: %s\n", now.Format("2006-01-02 15:04:05"))
fmt.Printf("Date: %s\n", now.Format("2006-01-02"))
fmt.Printf("Time: %s\n", now.Format("15:04:05"))
}
(2) ▶ Exemplo: Análise de horários
package main
import (
"fmt"
"time"
)
func main() {
// Parse standard format
t1, _ := time.Parse(time.RFC3339, "2026-07-08T10:00:00Z")
fmt.Printf("RFC3339: %v\n", t1)
// Parse custom format (2006-01-02 15:04:05 = reference time)
t2, _ := time.Parse("2006-01-02 15:04:05", "2026-07-08 10:30:00")
fmt.Printf("Custom: %v\n", t2)
// Parse with time zone
t3, _ := time.Parse("2006-01-02T15:04:05-07:00", "2026-07-08T10:00:00+08:00")
fmt.Printf("With TZ: %v\n", t3)
// Parse English month and day format
t4, _ := time.Parse("January 2, 2006", "July 8, 2026")
fmt.Printf("English: %v\n", t4)
}
Resultado:
RFC3339: 2026-07-08 10:00:00 +0000 UTC
Custom: 2026-07-08 10:30:00 +0000 UTC
With TZ: 2026-07-08 10:00:00 +0800 CST
English: 2026-07-08 00:00:00 +0000 UTC
(3) Cálculo da duração do tempo
package main
import (
"fmt"
"time"
)
func main() {
// Create Duration
d1 := 5 * time.Second
d2 := 100 * time.Millisecond
d3 := 2*time.Hour + 30*time.Minute
fmt.Printf("5s = %d ns\n", d1.Nanoseconds())
fmt.Printf("100ms = %v\n", d2)
fmt.Printf("2h30m = %v\n", d3)
// Time arithmetic
now := time.Now()
later := now.Add(2 * time.Hour)
duration := later.Sub(now)
fmt.Printf("Difference: %v\n", duration)
// Comparison
fmt.Printf("5s > 100ms? %v\n", d1 > d2)
fmt.Printf("d1.String(): %s\n", d1)
}
(4) Tabela de unidades de duração
| Constante | Significado |
|---|---|
time.Nanosecond |
1 ns |
time.Microsecond |
1 µs = 1000 ns |
time.Millisecond |
1 ms = 1000 µs |
time.Second |
1 s = 1000 ms |
time.Minute |
60 s |
time.Hour |
60 min |
7. Cronômetro / Contador
(1) time.Timer: Temporizador de uso único
package main
import (
"fmt"
"time"
)
func main() {
timer := time.NewTimer(2 * time.Second)
fmt.Println("Waiting 2 seconds...")
<-timer.C // blocks until timeout
fmt.Println("Time's up!")
// Alternatively, use time.After (more concise, but does not support Stop)
fmt.Println("Waiting another 1 second...")
<-time.After(1 * time.Second)
fmt.Println("Done!")
}
(2) ▶ Exemplo: time.Ticker: Temporizador periódico
package main
import (
"fmt"
"time"
)
func main() {
ticker := time.NewTicker(1 * time.Second)
done := make(chan bool)
go func() {
time.Sleep(5 * time.Second)
done <- true
}()
for count := 1; ; count++ {
select {
case t := <-ticker.C:
fmt.Printf("Tick %d at %s\n", count, t.Format("15:04:05"))
case <-done:
ticker.Stop() // stop the ticker
fmt.Println("Done!")
return
}
}
}
Resultado:
Tick 1 at 10:00:01
Tick 2 at 10:00:02
Tick 3 at 10:00:03
Tick 4 at 10:00:04
Tick 5 at 10:00:05
Done!
(3) Temporizador x Ticker
| Recurso | Cronômetro | Ticker |
|---|---|---|
| Acionado | Único | Recorrente |
| Parar | timer.Stop() |
ticker.Stop() |
| Canal de sinal | .C |
.C |
| Reiniciar | timer.Reset(d) |
❌ |
| Cenários comuns | Controle de tempo limite / Execução diferida | Heartbeat / Tarefas programadas |
8. Tratamento de fusos horários
package main
import (
"fmt"
"time"
)
func main() {
// Load time zone
loc, _ := time.LoadLocation("America/New_York")
// Parse in specified time zone
t := time.Date(2026, 7, 8, 10, 0, 0, 0, loc)
fmt.Printf("New York time: %s\n", t.Format(time.RFC3339))
// Convert to other time zone
shanghai := t.In(time.FixedZone("CST", 8*3600))
fmt.Printf("Shanghai time: %s\n", shanghai.Format(time.RFC3339))
// UTC
utc := t.UTC()
fmt.Printf("UTC time: %s\n", utc.Format(time.RFC3339))
}
| Método | Descrição |
|---|---|
time.LoadLocation("Asia/Shanghai") |
Nome do fuso horário da IANA |
time.FixedZone("CST", 8*3600) |
Deslocamento fixo |
t.In(loc) |
Converter para o fuso horário de destino |
t.UTC() |
Converter para UTC |
t.Local() |
Converter para o fuso horário local |
9. Exemplo completo: Analisador de carimbos de data/hora de log
// log_analyzer.go
package main
import (
"fmt"
"sort"
"strings"
"time"
)
// LogEntry represents a log entry
type LogEntry struct {
Timestamp time.Time
Level string // INFO / ERROR / WARN
Message string
}
// LogParser parses log lines
type LogParser struct {
timeFormats []string
}
func NewLogParser() *LogParser {
return &LogParser{
timeFormats: []string{
time.RFC3339,
"2006-01-02 15:04:05",
"01/02/2006 03:04:05 PM",
"2006/01/02 15:04:05",
"Jan 2 15:04:05",
},
}
}
// Parse parses a single log line
func (p *LogParser) Parse(line string) (LogEntry, error) {
line = strings.TrimSpace(line)
if line == "" {
return LogEntry{}, fmt.Errorf("empty line")
}
parts := strings.Fields(line)
if len(parts) < 3 {
return LogEntry{}, fmt.Errorf("too few fields")
}
// Try to parse timestamp (supports multiple formats)
var ts time.Time
var tsLen int
for i := 0; i < len(parts); i++ {
candidate := strings.Trim(parts[i], "[]")
for _, format := range p.timeFormats {
if t, err := time.Parse(format, candidate); err == nil {
ts = t
tsLen = i + 1
break
}
}
if !ts.IsZero() {
break
}
}
if ts.IsZero() {
return LogEntry{}, fmt.Errorf("no timestamp found in: %s", line)
}
remaining := parts[tsLen:]
if len(remaining) < 2 {
return LogEntry{}, fmt.Errorf("no level/message after timestamp")
}
level := strings.Trim(remaining[0], "[]")
message := strings.Join(remaining[1:], " ")
return LogEntry{
Timestamp: ts,
Level: level,
Message: message,
}, nil
}
// Analyze analyzes a collection of log lines
func (p *LogParser) Analyze(lines []string) {
var entries []LogEntry
var errors int
// Use strings.Builder to construct the error report
var errBuf strings.Builder
errBuf.Grow(1024)
for i, line := range lines {
entry, err := p.Parse(line)
if err != nil {
errors++
errBuf.WriteString(fmt.Sprintf(" Line %d: %v\n", i+1, err))
continue
}
entries = append(entries, entry)
}
// Sort by time
sort.Slice(entries, func(i, j int) bool {
return entries[i].Timestamp.Before(entries[j].Timestamp)
})
// Count
var infoCount, errorCount, warnCount int
for _, e := range entries {
switch e.Level {
case "INFO":
infoCount++
case "ERROR":
errorCount++
case "WARN":
warnCount++
}
}
// Output report
var report strings.Builder
report.Grow(2048)
report.WriteString("=== Log Analysis Report ===\n")
report.WriteString(fmt.Sprintf("Total lines: %d\n", len(lines)))
report.WriteString(fmt.Sprintf("Parsed successfully: %d\n", len(entries)))
report.WriteString(fmt.Sprintf("Parse failures: %d\n", errors))
report.WriteString(fmt.Sprintf("INFO: %d, ERROR: %d, WARN: %d\n",
infoCount, errorCount, warnCount))
report.WriteString(fmt.Sprintf("Time range: %s ~ %s\n",
entries[0].Timestamp.Format(time.RFC3339),
entries[len(entries)-1].Timestamp.Format(time.RFC3339)))
if errors > 0 {
report.WriteString("\n=== Error Details ===\n")
report.WriteString(errBuf.String())
}
fmt.Print(report.String())
}
func main() {
logLines := []string{
"2026-07-08T10:00:00Z [INFO] Server started",
"2026-07-08T10:01:15Z [ERROR] Connection timeout to db",
"2026-07-08T10:02:30Z [WARN] Memory usage 85%",
"2026-07-08T10:03:45Z [INFO] Request processed in 120ms",
"invalid line without timestamp",
"2026-07-08T10:05:00Z [ERROR] Disk space low",
}
parser := NewLogParser()
parser.Analyze(logLines)
}
Resultado esperado:
=== Log Analysis Report ===
Total lines: 6
Parsed successfully: 5
Parse failures: 1
INFO: 2, ERROR: 2, WARN: 1
Time range: 2026-07-08T10:00:00Z ~ 2026-07-08T10:05:00Z
=== Error Details ===
Line 5: no timestamp found in: invalid line without timestamp
graph TB
now[time.Now] --> format[Format]
now --> sub[Sub/Duration]
now --> add[Add]
parse[time.Parse] --> t[time.Time]
t --> format
t --> sub
t --> add
sub --> duration[time.Duration]
duration --> hours[Hours/Minutes/Seconds]
timer[time.Timer] --> C[.C channel]
ticker[time.Ticker] --> C
style now fill:#e1f5fe
style t fill:#e1f5fe
style duration fill:#f3e5f5
Mon Jan 2 15:04:05 MST 2006. 2006 = ano, 01 = mês, 02 = dia, 15 = hora (24 horas), 03 = hora (12 horas), 04 = minuto, 05 = segundo. Esse formato é único, mas parece natural assim que você se acostuma.
❓ Perguntas Frequentes
P: Quais são algumas das funções mais utilizadas no pacote
strings? R: Corte (Trim/TrimSpace), divisão (Split/Fields), junção (Join), pesquisa (Contains/Index/Count), substituição (Replace/ReplaceAll), conversão de maiúsculas e minúsculas (ToUpper/ToLower) e construção (Builder/Repeat) — essas 15 funções atendem a 90% das necessidades comuns.
P: Qual é a diferença entre
strconve a conversão de tipos? R: A conversão de tipos é usada para conversões dentro do mesmo tipo de dados (por exemplo, deintparafloat64), enquantostrconvé usado para conversões entre strings e números. Não é possível usar conversão de tipo entrestringeintoufloat— é necessário usarstrconv.
P: Como escrevo uma string de formatação para
time.Parse? R: Lembre-se da data de referência2006-01-02 15:04:05(mês/dia/hora/minuto/segundo/ano, nessa ordem).2006= ano,01= mês,02= dia,15= formato de 24 horas,03= formato de 12 horas,04= minutos,05= segundos.
P: Como utilizo
Duration? R:time.Durationé um valorint64que representa o número de nanossegundos. Criação:5 * time.Second; Operações:t.Add(d),t.Sub(t2); Recuperação:d.Hours(),d.Minutes(),d.Seconds().
P: Qual é a diferença entre um Timer e um Ticker? R: Um Timer é executado uma única vez (execução adiada/controle de tempo limite), enquanto um Ticker é executado repetidamente (tarefas agendadas/heartbeats). Ambos recebem sinais pelo canal
.Ce suportam o método.Stop()para interromper a execução.
P: O Go tem algo semelhante ao
datetime.timedeltado Python? R: Sim,d := 2*time.Hour + 30*time.Minuteé exatamente isso. Criação:time.Duration; Adição e subtração:t.Add(d),t.Add(-d); Diferença entre dois horários:t2.Sub(t1).
P: Como faço para lidar com horários em diferentes fusos horários? R: Carregue o fuso horário usando
time.LoadLocation("Asia/Shanghai")e, em seguida, converta-o comt.In(loc). Os horários são armazenados internamente em UTC e convertidos para o fuso horário de destino no momento da saída.
P: Quão mais rápido é o
strings.Builderem relação ao+? R: O+cria uma nova string a cada vez => O(n²); o buffer interno do Builder => O(n). Ao concatenar 10.000 vezes, o Builder é mais de 1.000 vezes mais rápido.
📖 Resumo
- O pacote
stringsinclui mais de 40 funções que abrangem todas as operações com strings - A função strconv realiza conversões entre strings ↔ números/booleanos (o que a conversão de tipo não consegue fazer)
strings.Builderoferece uma concatenação eficiente de strings e evita a complexidade O(n²) do operador+time.Nowrecupera a hora atual;Format/Parsea formata/analisa- O formato de data do Go baseia-se na hora de referência
2006-01-02 15:04:05 time.Durationé um número expresso em nanossegundos; permite operações aritméticas e comparaçõesTimerpara temporização única,Tickerpara temporização periódica- Tratamento de fusos horários: método
LoadLocation+In()
📝 Exercícios
-
Problema Básico (Dificuldade ⭐): Implemente uma função
wordCount(s string) map[string]intusando o pacotestringspara contar o número de vezes que cada palavra aparece em uma sequência de caracteres. Você deve usarFields, um loop e um mapa. -
Problema avançado (Dificuldade ⭐⭐): Implemente uma função
timeAgo(t time.Time) stringque retorne uma descrição legível para humanos (“há 3 minutos” / “há 2 horas” / “ontem” / “há 3 dias”). Você deve usartime.Since()combinada com uma condiçãoDuration. -
Problema de desafio (Dificuldade ⭐⭐⭐): Implemente uma ferramenta de agregação de logs: Dada uma entrada em forma de string (um timestamp + nível + mensagem por linha), use
strings.Split,time.Parse,strings.Builderesort.Slicepara: (1) Analisar e filtrar os logs de nível ERROR; (2) Classificá-los por hora; (3) UsarBuilderpara gerar um relatório contendo estatísticas agregadas. A ferramenta deve suportar pelo menos 3 formatos de hora.