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á


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:00Z e 07/08/2026 10:00:00 AM. Escrevi 20 linhas de código usando o módulo datetime do 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:

GO
// 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

GO
// 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:

TEXT 📖 Somente leitura
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
💡 Dica: O Go usa o formato de “hora de referência” 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

GO
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)
}
▶ Experimente

Resultado:

TEXT 📖 Somente leitura
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

GO
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

GO
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
}
▶ Experimente
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?

GO
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

GO
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))
}
▶ Experimente

Resultado:

TEXT 📖 Somente leitura
Name,Age,City
Alice,28,Shanghai
Bob,32,Beijing

Length: 52
🔥 Erro comum: Usar + 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

GO
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

GO
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)
}
▶ Experimente

Resultado:

TEXT 📖 Somente leitura
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

GO
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

GO
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

GO
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
        }
    }
}
▶ Experimente

Resultado:

TEXT 📖 Somente leitura
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

GO
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

GO
// 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:

TEXT 📖 Somente leitura
=== 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
100%
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
🔥 Erro comum: O formato de data e hora do Go se baseia no padrão numérico da hora de referência 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 strconv e a conversão de tipos? R: A conversão de tipos é usada para conversões dentro do mesmo tipo de dados (por exemplo, de int para float64), enquanto strconv é usado para conversões entre strings e números. Não é possível usar conversão de tipo entre string e int ou float — é necessário usar strconv.

P: Como escrevo uma string de formatação para time.Parse? R: Lembre-se da data de referência 2006-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 valor int64 que 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 .C e suportam o método .Stop() para interromper a execução.

P: O Go tem algo semelhante ao datetime.timedelta do 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 com t.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.Builder em 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


📝 Exercícios

  1. Problema Básico (Dificuldade ⭐): Implemente uma função wordCount(s string) map[string]int usando o pacote strings para contar o número de vezes que cada palavra aparece em uma sequência de caracteres. Você deve usar Fields, um loop e um mapa.

  2. Problema avançado (Dificuldade ⭐⭐): Implemente uma função timeAgo(t time.Time) string que retorne uma descrição legível para humanos (“há 3 minutos” / “há 2 horas” / “ontem” / “há 3 dias”). Você deve usar time.Since() combinada com uma condição Duration.

  3. 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.Builder e sort.Slice para: (1) Analisar e filtrar os logs de nível ERROR; (2) Classificá-los por hora; (3) Usar Builder para gerar um relatório contendo estatísticas agregadas. A ferramenta deve suportar pelo menos 3 formatos de hora.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%