Go: Go Strings and Date/Time Handling

Last updated: 2026-08-26

Strings and dates are the two fundamental data types in all programs—the Go standard library provides a comprehensive set of tools for both, without the need for third-party dependencies.

The strings package contains over 40 functions; the strconv package handles all type conversions; and the time package covers all time-related operations. In this lesson, you'll master the full suite of tools for working with strings and time in Go.

1. You will learn



2. A True Story of an Operations Engineer

(1) Pain Point: Manually parsing timestamps results in errors on every line of the log

Bob is an operations engineer. He needs to extract timestamps from خادم logs to calculate the QPS:

"500,000 log lines per day, with timestamps in two formats: 2026-07-08T10:00:00Z and 07/08/2026 10:00:00 AM. I wrote 20 lines of code using Python's datetime module, but my colleague couldn't make sense of it. I switched to Go, and ended up having to consult the documentation 10 times just to figure out the date-time format strings."

The first version he wrote:

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)
}

Colleagues often get confused by Go's timestamp format ("2006-01-02 15:04:05").

(2) Go Solution: Full Coverage of the Standard Library

GO
// log_parser.go
package main

import (
    "fmt"
    "strings"
    "time"
)

// Clean log line + extract timestamp
func extractTimestamp(logLine سلسلة) (time.Time, خطأ) {
    // 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 := []سلسلة{
        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 := []سلسلة{
        "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))
    }
}

Output:

TEXT 📖 Display only
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) Benefits: Completeness of the standard library

Package Core Capabilities Replaces Third-Party Libraries
strings 40+ سلسلة functions No need for lodash/underscore
strconv Type conversion No manual parsing required
strings.Builder Efficient concatenation bytes.Buffer
time Time Parsing/Formatting/Timers moment.js / date-fns
💡 Tip: Go uses the "reference time" format Mon Jan 2 15:04:05 MST 2006 (where 01/02/03/04/05/06 correspond to month/day/hour/minute/second/year), so just remember 2006-01-02 15:04:05.



3. Core Functions of the strings Package

▶ Example: 12 Common String Functions

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)
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
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) Quick Reference for the strings Function

Category Function Purpose
Trimming TrimSpace / Trim / TrimPrefix / TrimSuffix Remove whitespace or specified characters
Split Split / SplitN / Fields Split into slices
Join Join Merge slices into a string
Search Contains / Index / LastIndex / Count Substring Check
Replace Replace / ReplaceAll Substring replacement
Case ToUpper / ToLower / Title Case Conversion
Construction Builder / Repeat Efficient string concatenation / string repetition


4. strconv Type Conversion

(1) String ↔ Number

GO
package main

import (
    "fmt"
    "strconv"
)

func main() {
    // سلسلة → int
    n, err := strconv.Atoi("42")
    fmt.Printf("Atoi: %d, err=%v\n", n, err)

    // int → سلسلة
    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))
}

▶ Example: The Difference Between strconv and Type Conversion

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
}
▶ Try it Yourself
Scenario Using type conversion Using strconv
int ↔ float64 float64(n)
int ↔ سلسلة strconv.Itoa / Atoi
سلسلة ↔ float64 ParseFloat / FormatFloat
Number Base Parsing ParseInt("FF", 16, 64)
Integer ↔ uint/int32


5. Efficient String Concatenation with strings.Builder

(1) Why use a 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)
}

▶ Example: Complete Usage of Builder

GO
package main

import (
    "fmt"
    "strings"
)

func buildCSV(data [][]سلسلة) سلسلة {
    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 := [][]سلسلة{
        {"Name", "Age", "City"},
        {"Alice", "28", "Shanghai"},
        {"Bob", "32", "Beijing"},
    }

    csv := buildCSV(data)
    fmt.Println(csv)
    fmt.Printf("Length: %d\n", len(csv))
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
Name,Age,City
Alice,28,Shanghai
Bob,32,Beijing

Length: 52
🔥 Common Mistake: Using + to concatenate strings creates a new سلسلة each time, resulting in O(n²) performance. strings.Builder maintains a mutable مخزن مؤقت internally, resulting in O(n) performance. When performing a large number of concatenations, the performance difference can be as much as 1,000 times.



6. time package: Time handling

(1) time.Now and Time Formats

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"))
}

▶ Example: Time Parsing

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)
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
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) time.Duration Time Calculation

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) Table of Duration Units

Constant Meaning
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. Timer / Ticker

(1) time.Timer: One-time timer

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!")
}

▶ Example: time.Ticker: Periodic Timer

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
        }
    }
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
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) Timer vs Ticker

Feature Timer Ticker
Triggered One-time Recurring
Stop timer.Stop() ticker.Stop()
Signal Channel .C .C
Reset timer.Reset(d)
Common Scenarios Timeout Control / Delayed Execution Heartbeat / Scheduled Tasks


8. Time Zone Handling

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))
}
Method Description
time.LoadLocation("Asia/Shanghai") IANA Time Zone Name
time.FixedZone("CST", 8*3600) Fixed offset
t.In(loc) Convert to target time zone
t.UTC() Convert to UTC
t.Local() Convert to local time zone


9. Complete Example: Log Timestamp Parser

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)
}

Expected Output:

TEXT 📖 Display only
=== 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
🔥 Common Mistake: Go's date and time format is based on the numerical pattern of the reference time Mon Jan 2 15:04:05 MST 2006. 2006 = year, 01 = month, 02 = day, 15 = hour (24-hour), 03 = hour (12-hour), 04 = minute, 05 = second. This design is unique, but it feels natural once you get used to it.


❓ FAQ

Q What are some commonly used functions in the strings package?
A Trimming (Trim/TrimSpace), splitting (Split/Fields), joining (Join), searching (Contains/Index/Count), replacing (Replace/ReplaceAll), case conversion (ToUpper/ToLower), and construction (Builder/Repeat)—these 15 functions cover 90% of common needs.
Q What is the difference between strconv and type casting?
A Type casting is used for conversions within the same data type (e.g., int to float64), while strconv is used for conversions between strings and numbers. You cannot use type casting between سلسلة and int or float—you must use strconv.
Q How do I write a format سلسلة for time.Parse?
A Remember the reference time 2006-01-02 15:04:05 (month/day/hour/minute/second/year in that order). 2006 = year, 01 = month, 02 = day, 15 = 24-hour clock, 03 = 12-hour clock, 04 = minutes, 05 = seconds.
Q How do I use Duration?
A time.Duration is an int64 value representing the number of nanoseconds. Creation: 5 * time.Second; Operations: t.Add(d), t.Sub(t2); Retrieval: d.Hours(), d.Minutes(), d.Seconds().
Q What is the difference between a Timer and a Ticker?
A A Timer executes once (delayed execution/timeout control), while a Ticker executes repeatedly (scheduled tasks/heartbeats). Both receive signals via the .C channel and support the .Stop() طريقة to stop execution.
Q Does Go have something similar to Python's datetime.timedelta?
A Yes, time.Duration is exactly that. Creation: d := 2*time.Hour + 30*time.Minute; Addition and subtraction: t.Add(d), t.Add(-d); Difference between two times: t2.Sub(t1).
Q How do I handle times across time zones?
A Load the time zone using time.LoadLocation("Asia/Shanghai"), then convert it with t.In(loc). Times are stored internally in UTC and converted to the target time zone upon output.
Q How much faster is strings.Builder than +?
A + creates a new سلسلة each time => O(n²); Builder's internal مخزن مؤقت => O(n). When concatenating 10,000 times, Builder is over 1,000 times faster.

📖 Summary


📝 Exercises

  1. Basic Problem (Difficulty ⭐): Implement a wordCount(s سلسلة) map[سلسلة]int دالة using the strings package to count the number of times each word appears in a سلسلة. You must use Fields, a حلقة, and a map.

  2. Advanced Problem (Difficulty ⭐⭐): Implement a timeAgo(t time.Time) سلسلة دالة that returns a human-readable description ("3 minutes ago" / "2 hours ago" / "yesterday" / "3 days ago"). You must use time.Since() combined with a Duration conditional.

  3. Challenge Problem (Difficulty ⭐⭐⭐): Implement a log aggregation tool: Given a سلسلة input (one timestamp + level + message per line), use strings.Split, time.Parse, strings.Builder, and sort.Slice to: (1) Parse and filter out ERROR-level logs; (2) Sort them by time; (3) Use Builder to generate a report containing aggregated statistics. The tool must support at least 3 time formats.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏