Expressões Regulares e Data
Lição 24: Expressões Regulares e Data
Analogia da Vida
Imagine que você é um trabalhador de classificação de pacotes:
- Expressões regulares são como regras de classificação — padrões como "endereço contém 'São Paulo' e o número tem 3 dígitos" ajudam você a filtrar rapidamente o lote de pacotes que correspondem de um volume enorme.
- Data e hora são como carimbos de tempo nos comprovantes de entrega — você precisa saber "a que horas este pacote chegou", "quanto tempo desde que foi assinado", "pode chegar até às 15h de amanhã".
Em programação, regex ajuda você a localizar e extrair com precisão informações de texto, enquanto o pacote time ajuda você a medir, calcular e exibir tempo.
Conceitos Centrais
Expressões Regulares (Pacote regexp)
O pacote regexp do Go é baseado no mecanismo de sintaxe RE2, suportando a maioria das sintaxes de regex comuns, mas não suporta backtracking e backreferências (uma escolha de design priorizando desempenho).
Interfaces centrais:
- Compile: regexp.Compile(pattern) → (*Regexp, error)
- Match: MatchString(s) → bool
- Find: FindString / FindAllString / FindStringSubmatch
- Replace: ReplaceAllString / ReplaceAllStringFunc
Data e Hora (Pacote time)
O tratamento de tempo do Go gira em torno da struct time.Time, usando um tempo de referência fixo como template de formatação:
Tempo de referência: Mon Jan 2 15:04:05 MST 2006
Mnemônico numérico: 01/02 03:04:05PM 2006 -0700
1 2 3 4 5 6 7 (Mês 1, Dia 2, Hora 3, Minuto 4, Segundo 5, Ano 6, Fuso Horário 7) para facilitar a memorização.
Sintaxe Básica e Uso
1. Fundamentos de Regex
package main
import (
"fmt"
"regexp"
)
func main() {
// Compilar expressão regular
re, err := regexp.Compile(`\d{3}-\d{4}-\d{4}`)
if err != nil {
fmt.Println("Falha na compilação da regex:", err)
return
}
// Verificar se corresponde
phone := "11-9876-5432"
fmt.Println("Corresponde:", re.MatchString(phone)) // true
// Encontrar primeira correspondência
text := "Contato: 11-9876-5432 ou 21-1234-5678"
fmt.Println("Primeira:", re.FindString(text)) // 11-9876-5432
// Encontrar todas as correspondências
all := re.FindAllString(text, -1)
fmt.Println("Todas:", all) // [11-9876-5432 21-1234-5678]
}
MustCompile vs Compile: MustCompile entra em pânico diretamente na falha de compilação, adequado para regexes de constantes globais; Compile retorna um erro, adequado para regexes construídas dinamicamente em tempo de execução.
2. Grupos de Captura Nomeados
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`(?P<user>\w+)@(?P<domain>\w+\.\w+)`)
email := "joao@example.com"
match := re.FindStringSubmatch(email)
names := re.SubexpNames()
for i, name := range names {
if i > 0 && name != "" {
fmt.Printf("%s = %s\n", name, match[i])
}
}
// user = joao
// domain = example.com
}
3. Operações de Substituição
package main
import (
"fmt"
"regexp"
"strings"
)
func main() {
re := regexp.MustCompile(`\s+`)
text := " hello world go "
result := re.ReplaceAllString(text, " ")
fmt.Printf("[%s]\n", result) // [hello world go]
re2 := regexp.MustCompile(`\b\w`)
title := re2.ReplaceAllStringFunc("hello world go", func(s string) string {
return strings.ToUpper(s)
})
fmt.Println(title) // Hello World Go
}
4. Operações Básicas com Tempo
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println("Tempo atual:", now)
fmt.Println("Ano:", now.Year())
fmt.Println("Mês:", now.Month())
fmt.Println("Dia:", now.Day())
fmt.Println("Hora:", now.Hour())
fmt.Println("Minuto:", now.Minute())
fmt.Println("Segundo:", now.Second())
fmt.Println("Dia da semana:", now.Weekday())
}
time.Now().UTC().
5. Formatação e Análise de Tempo
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
// Formatação: usar tempo de referência como template
fmt.Println(now.Format("2006-01-02"))
fmt.Println(now.Format("2006/01/02 15:04:05"))
fmt.Println(now.Format("03:04PM"))
// Formatos predefinidos comuns
fmt.Println(now.Format(time.RFC3339))
// Analisar string para tempo
t, err := time.Parse("2006-01-02", "2025-12-25")
if err != nil {
fmt.Println("Falha na análise:", err)
return
}
fmt.Println("Resultado da análise:", t)
// Analisar com fuso horário
t2, _ := time.ParseInLocation("2006-01-02 15:04:05",
"2025-12-25 08:00:00", time.Local)
fmt.Println("Com fuso horário:", t2)
}
2006-01-02 15:04:05.
6. Duration e Cálculos com Tempo
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
tomorrow := now.Add(24 * time.Hour)
fmt.Println("Amanhã:", tomorrow.Format("2006-01-02"))
twoHoursLater := now.Add(2 * time.Hour)
fmt.Println("Duas horas depois:", twoHoursLater.Format("15:04:05"))
diff := tomorrow.Sub(now)
fmt.Println("Diferença de tempo:", diff)
fmt.Println("Horas:", diff.Hours())
fmt.Println("Minutos:", diff.Minutes())
fmt.Println("Amanhã é depois:", tomorrow.After(now))
fmt.Println("Hoje é antes:", now.Before(tomorrow))
floored := now.Truncate(time.Hour)
fmt.Println("Truncado para hora:", floored.Format("15:04:05"))
}
7. Temporizadores
package main
import (
"fmt"
"time"
)
func main() {
// Temporizador de disparo único
timer := time.NewTimer(2 * time.Second)
fmt.Println("Aguardando 2 segundos...")
<-timer.C
fmt.Println("Tempo esgotado!")
// Temporizador periódico (Ticker)
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
count := 0
for t := range ticker.C {
count++
fmt.Println("Tick:", t.Format("15:04:05.000"))
if count >= 3 {
break
}
}
<-time.After(1 * time.Second)
fmt.Println("Executado após 1 segundo")
}
time.After em loops — cada iteração cria um novo canal, e temporizadores antigos não serão coletados pelo garbage collector. Em loops, use time.NewTimer e manualmente Reset.
Código de Exemplo
Exemplo: Validação e Extração — Análise de Log (Dificuldade ⭐)
package main
import (
"fmt"
"regexp"
)
func main() {
logs := []string{
"[2025-06-27 14:30:00] ERROR Falha na conexão com banco de dados",
"[2025-06-27 14:30:01] INFO Serviço iniciado com sucesso",
"[2025-06-27 14:30:05] WARN Espaço em disco baixo",
"[2025-06-27 14:31:00] ERROR Timeout de requisição",
}
re := regexp.MustCompile(`\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] (\w+)\s+(.+)`)
levelCount := make(map[string]int)
for _, log := range logs {
match := re.FindStringSubmatch(log)
if match == nil {
continue
}
timestamp := match[1]
level := match[2]
message := match[3]
fmt.Printf("Hora: %s | Nível: %-5s | Mensagem: %s\n",
timestamp, level, message)
levelCount[level]++
}
fmt.Println("\nEstatísticas por nível:")
for level, count := range levelCount {
fmt.Printf(" %s: %d entradas\n", level, count)
}
}
Saída:
Hora: 2025-06-27 14:30:00 | Nível: ERROR | Mensagem: Falha na conexão com banco de dados
Hora: 2025-06-27 14:30:01 | Nível: INFO | Mensagem: Serviço iniciado com sucesso
Hora: 2025-06-27 14:30:05 | Nível: WARN | Mensagem: Espaço em disco baixo
Hora: 2025-06-27 14:31:00 | Nível: ERROR | Mensagem: Timeout de requisição
Estatísticas por nível:
ERROR: 2 entradas
INFO: 1 entradas
WARN: 1 entradas
Exemplo: Motor de Templates — Sistema de Substituição de Texto (Dificuldade ⭐⭐)
package main
import (
"fmt"
"regexp"
"strings"
"time"
)
func main() {
template := `Prezado {{name}}:
Seu pedido {{order}} foi enviado em {{date}}.
Previsão de entrega em {{days}} dias.
Hora atual: {{now}}`
vars := map[string]string{
"name": "Alice",
"order": "PED-20250627-001",
"date": "2025-06-27",
"days": "3",
}
re := regexp.MustCompile(`\{\{(\w+)\}\}`)
result := re.ReplaceAllStringFunc(template, func(match string) string {
key := match[2 : len(match)-2]
if key == "now" {
return time.Now().Format("2006-01-02 15:04:05")
}
if val, ok := vars[key]; ok {
return val
}
return match
})
fmt.Println(result)
allVars := re.FindAllString(template, -1)
varNames := make([]string, 0, len(allVars))
for _, v := range allVars {
varNames = append(varNames, v[2:len(v)-2])
}
fmt.Printf("\nVariáveis do template: %s\n", strings.Join(varNames, ", "))
}
Saída:
Prezado Alice:
Seu pedido PED-20250627-001 foi enviado em 2025-06-27.
Previsão de entrega em 3 dias.
Hora atual: 2025-06-27 14:30:00
Variáveis do template: name, order, date, days, now
Exemplo: Temporizador de Contagem Regressiva (Dificuldade ⭐⭐⭐)
package main
import (
"fmt"
"regexp"
"strings"
"time"
)
func formatDuration(d time.Duration) string {
if d <= 0 {
return "Expirado"
}
days := int(d.Hours()) / 24
hours := int(d.Hours()) % 24
minutes := int(d.Minutes()) % 60
seconds := int(d.Seconds()) % 60
parts := []string{}
if days > 0 {
parts = append(parts, fmt.Sprintf("%dd", days))
}
if hours > 0 {
parts = append(parts, fmt.Sprintf("%dh", hours))
}
if minutes > 0 {
parts = append(parts, fmt.Sprintf("%dm", minutes))
}
parts = append(parts, fmt.Sprintf("%02ds", seconds))
return strings.Join(parts, " ")
}
func parseDeadline(s string) (time.Time, error) {
formats := []string{
"2006-01-02 15:04:05",
"2006-01-02",
"2006/01/02 15:04",
"01-02 15:04",
}
re := regexp.MustCompile(`^\+(\d+)([hms])`)
if match := re.FindStringSubmatch(s); match != nil {
return time.Now().Add(2 * time.Hour), nil
}
for _, format := range formats {
if t, err := time.ParseInLocation(format, s, time.Local); err == nil {
return t, nil
}
}
return time.Time{}, fmt.Errorf("não foi possível analisar o horário: %s", s)
}
func main() {
deadline, err := parseDeadline("2025-12-31 23:59:59")
if err != nil {
fmt.Println("Erro:", err)
return
}
fmt.Printf("Prazo: %s\n", deadline.Format("2006-01-02 15:04:05"))
fmt.Println(strings.Repeat("─", 40))
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
count := 0
for now := range ticker.C {
remaining := deadline.Sub(now)
fmt.Printf("\rRestante: %-30s", formatDuration(remaining))
count++
if count >= 5 || remaining <= 0 {
break
}
}
fmt.Println("\n\nDemonstração de contagem regressiva concluída")
re := regexp.MustCompile(`^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$`)
formatted := deadline.Format("2006-01-02 15:04:05")
fmt.Printf("Verificação de formato: %v\n", re.MatchString(formatted))
}
Cenários de Aplicação Prática
Cenário 1: Validação de Dados de Formulário
package main
import (
"fmt"
"regexp"
)
type Validator struct {
rules map[string]*regexp.Regexp
}
func NewValidator() *Validator {
rules := map[string]*regexp.Regexp{
"phone": regexp.MustCompile(`^1[3-9]\d{9}$`),
"email": regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`),
"id_card": regexp.MustCompile(`^\d{17}[\dXx]$`),
"username": regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_]{3,15}$`),
"password": regexp.MustCompile(`^.{8,}$`),
"ip": regexp.MustCompile(`^(\d{1,3}\.){3}\d{1,3}$`),
"date": regexp.MustCompile(`^\d{4}[-/](0[1-9]|1[0-2])[-/](0[1-9]|[12]\d|3[01])$`),
}
return &Validator{rules: rules}
}
func (v *Validator) Validate(field, value string) bool {
re, ok := v.rules[field]
if !ok {
return true
}
return re.MatchString(value)
}
func (v *Validator) ValidateAll(data map[string]string) []string {
var errors []string
for field, value := range data {
if !v.Validate(field, value) {
errors = append(errors, fmt.Sprintf("formato de %s incorreto: %s", field, value))
}
}
return errors
}
func main() {
v := NewValidator()
data := map[string]string{
"phone": "11987654321",
"email": "test@example.com",
"username": "go_dev",
"date": "2025-06-27",
}
errors := v.ValidateAll(data)
if len(errors) == 0 {
fmt.Println("✓ Todos os campos validados com sucesso")
} else {
fmt.Println("Falha na validação:")
for _, err := range errors {
fmt.Printf(" ✗ %s\n", err)
}
}
invalidData := map[string]string{
"phone": "12345678901",
"email": "not-an-email",
}
fmt.Println("\nTeste de dados inválidos:")
for field, value := range invalidData {
result := "✓"
if !v.Validate(field, value) {
result = "✗"
}
fmt.Printf(" %s %s: %s\n", result, field, value)
}
}
Cenário 2: Consulta por Intervalo de Tempo — Gerenciamento de Status de Atividades
package main
import (
"fmt"
"strings"
"time"
)
type Activity struct {
Name string
StartTime time.Time
EndTime time.Time
}
func (a Activity) Status(now time.Time) string {
switch {
case now.Before(a.StartTime):
return "Não iniciada"
case now.After(a.EndTime):
return "Encerrada"
default:
return "Em andamento"
}
}
func (a Activity) Remaining(now time.Time) string {
if now.Before(a.StartTime) {
d := a.StartTime.Sub(now)
return fmt.Sprintf("Inicia em: %s", formatDur(d))
}
if now.Before(a.EndTime) {
d := a.EndTime.Sub(now)
return fmt.Sprintf("Restante: %s", formatDur(d))
}
return "Expirado"
}
func formatDur(d time.Duration) string {
hours := int(d.Hours())
minutes := int(d.Minutes()) % 60
if hours > 24 {
days := hours / 24
hours = hours % 24
return fmt.Sprintf("%dd %dh %dm", days, hours, minutes)
}
return fmt.Sprintf("%dh %dm", hours, minutes)
}
func main() {
now := time.Now()
activities := []Activity{
{
Name: "Promoção de Verão",
StartTime: mustParse("2025-06-01 00:00:00"),
EndTime: mustParse("2025-06-18 23:59:59"),
},
{
Name: "Volta às Aulas",
StartTime: mustParse("2025-07-01 00:00:00"),
EndTime: mustParse("2025-08-31 23:59:59"),
},
{
Name: "Black Friday Preview",
StartTime: mustParse("2025-11-01 00:00:00"),
EndTime: mustParse("2025-11-11 23:59:59"),
},
}
fmt.Printf("Hora atual: %s\n", now.Format("2006-01-02 15:04:05"))
fmt.Println(strings.Repeat("─", 50))
for _, act := range activities {
status := act.Status(now)
remaining := act.Remaining(now)
fmt.Printf("Atividade: %s\n", act.Name)
fmt.Printf(" Período: %s ~ %s\n",
act.StartTime.Format("2006-01-02"),
act.EndTime.Format("2006-01-02"))
fmt.Printf(" Status: %s | %s\n\n", status, remaining)
}
fmt.Println("Atividades em andamento:")
found := false
for _, act := range activities {
if act.Status(now) == "Em andamento" {
fmt.Printf(" - %s\n", act.Name)
found = true
}
}
if !found {
fmt.Println(" Nenhuma atividade em andamento no momento")
}
}
func mustParse(s string) time.Time {
t, err := time.ParseInLocation("2006-01-02 15:04:05", s, time.Local)
if err != nil {
panic(err)
}
return t
}
❓ Perguntas Frequentes
P1: Por que minha regex não consegue corresponder caracteres chineses?
O pacote regexp do Go lida com codificação UTF-8 por padrão, então caracteres chineses podem ser correspondidos normalmente. O problema comum é não usar a classe de caracteres correta:
// Errado: \w não corresponde chinês
re := regexp.MustCompile(`^\w+$`)
re.MatchString("你好") // false
// Correto: usar classe Unicode
re2 := regexp.MustCompile(`^[\p{Han}]+$`)
re2.MatchString("你好") // true
// Ou correspondência mista
re3 := regexp.MustCompile(`^[\w\p{Han}]+$`)
re3.MatchString("hello你好123") // true
P2: Qual a diferença entre time.Parse e time.ParseInLocation?
// Parse usa UTC quando nenhuma informação de fuso horário está presente
t1, _ := time.Parse("2006-01-02", "2025-06-27")
fmt.Println(t1.Location()) // UTC
// ParseInLocation especifica um fuso horário padrão
t2, _ := time.ParseInLocation("2006-01-02", "2025-06-27", time.Local)
fmt.Println(t2.Location()) // Local
ParseInLocation e especifique explicitamente o fuso horário.
P3: E se o desempenho da regex for ruim?
// Errado: recompilar a cada chamada
func validatePhone(phone string) bool {
re := regexp.MustCompile(`^1[3-9]\d{9}$`)
return re.MatchString(phone)
}
// Correto: pré-compilar como variável de nível de pacote
var phoneRe = regexp.MustCompile(`^1[3-9]\d{9}$`)
func validatePhone2(phone string) bool {
return phoneRe.MatchString(phone)
}
P4: Como lidar com conversão de fuso horário?
package main
import (
"fmt"
"time"
)
func main() {
saoPaulo, _ := time.LoadLocation("America/Sao_Paulo")
tokyo, _ := time.LoadLocation("Asia/Tokyo")
newyork, _ := time.LoadLocation("America/New_York")
t := time.Date(2025, 6, 27, 14, 0, 0, 0, saoPaulo)
fmt.Println("São Paulo:", t.Format("15:04 MST"))
fmt.Println("Tóquio:", t.In(tokyo).Format("15:04 MST"))
fmt.Println("Nova York:", t.In(newyork).Format("15:04 MST"))
}
time.LoadLocation pode exigir instalação de tzdata em alguns sistemas. Go 1.15+ pode incorporar dados de fuso horário importando _ "time/tzdata".
📖 Resumo
| Tópico | Pacote | Tipos Centrais | Operações Principais |
|---|---|---|---|
| Expressões Regulares | regexp |
Regexp |
Compile, Match, Find, Replace |
| Data e Hora | time |
Time, Duration |
Format, Parse, Calculate, Timer |
Pontos-chave de Regex:
- Usar
MustCompilepara pré-compilar regexes constantes - Grupos de captura nomeados
(?P<name>...)melhoram a legibilidade \p{Han}corresponde chinês,\p{L}corresponde todas as letras Unicode- Mecanismo RE2 não suporta backreferences, mas garante complexidade temporal linear
Pontos-chave de Tempo:
- Template de formatação é o tempo de referência
2006-01-02 15:04:05 Parsepadrão é UTC,ParseInLocationespecifica fuso horárioDurationrepresenta intervalos de tempo, suporta métodos ricos de unidade- Temporizadores devem ser
Stop()ados após uso para evitar vazamentos de goroutine
📝 Exercícios
Exercício 1: Extrator de Links Markdown
Escreva um programa que extrai todos os links de texto Markdown, exibindo no formato Título -> URL.
// Dica: corresponder formato [Título](URL)
// Entrada: `Visite [Go官网](https://golang.org) ou [GitHub](https://github.com)`
// Saída:
// Go官网 -> https://golang.org
// GitHub -> https://github.com
Exercício 2: Calculadora de Dias Úteis
Escreva uma função AddBusinessDays(t time.Time, n int) time.Time que calcula a data após n dias úteis de uma data fornecida (pulando sábado e domingo).
// Testes:
// AddBusinessDays(2025-06-27 Sexta, 1) → 2025-06-30 Segunda
// AddBusinessDays(2025-06-27 Sexta, 5) → 2025-07-04 Sexta
Exercício 3: Sistema de Filtro de Palavras Sensíveis
Implementar um filtro de palavras sensíveis:
- Carregar lista de palavras sensíveis da configuração, compilar para regex
- Suportar modos de substituição por
*e remoção completa - Suportar detecção de variantes de palavras sensíveis (ex.: espaços inseridos no meio)
// Texto de entrada: "Este é um site de jogos, oferecendo serviços de jo gos"
// Saída do modo substituição: "Este é um site de *, oferecendo serviços de *"
// Saída do modo remoção: "Este é um site de , oferecendo serviços de"
Próxima Lição
Na próxima lição, aprenderemos sobre Desenvolvimento de Programas de Linha de Comando, entendendo como usar Go para construir ferramentas CLI poderosas, incluindo análise de argumentos, subcomandos, entrada interativa e outras habilidades práticas.



