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
- Common functions in the
stringspackage (trimming, splitting, replacing, searching) - strconv type conversion (Atoi/Itoa/Parse)
- strings.Builder: Efficient سلسلة concatenation
- time.Now / Parse / Format
- time.Duration Time Calculation
- time.Timer / time.Ticker timers
- Comprehensive Example: Log Timestamp Parser
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:00Zand07/08/2026 10:00:00 AM. I wrote 20 lines of code using Python'sdatetimemodule, 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:
// 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
// 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:
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 |
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
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)
}
Output:
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
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
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
}
| 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?
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
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))
}
Output:
Name,Age,City
Alice,28,Shanghai
Bob,32,Beijing
Length: 52
+ 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
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
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)
}
Output:
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
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
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
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
}
}
}
Output:
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
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
// 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:
=== 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 = 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
strings package?strconv and type casting?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.time.Parse?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.Duration?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()..C channel and support the .Stop() طريقة to stop execution.datetime.timedelta?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).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.strings.Builder than +?+ 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
- The
stringspackage includes over 40 functions that cover all سلسلة operations - strconv handles conversions between strings ↔ numbers/booleans (which type casting cannot do)
strings.Builderprovides efficient سلسلة concatenation and avoids the O(n²) complexity of the+operatortime.Nowretrieves the current time;Format/Parseformats/parses it- The Go date format is based on the reference time
2006-01-02 15:04:05 time.Durationis a number of nanoseconds; it supports arithmetic operations and comparisonsTimerfor one-time timing,Tickerfor periodic timing- Time zone handling:
LoadLocation+In()طريقة
📝 Exercises
-
Basic Problem (Difficulty ⭐): Implement a
wordCount(s سلسلة) map[سلسلة]intدالة using thestringspackage to count the number of times each word appears in a سلسلة. You must useFields, a حلقة, and a map. -
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 usetime.Since()combined with aDurationconditional. -
Challenge Problem (Difficulty ⭐⭐⭐): Implement a log aggregation tool: Given a سلسلة input (one timestamp + level + message per line), use
strings.Split,time.Parse,strings.Builder, andsort.Sliceto: (1) Parse and filter out ERROR-level logs; (2) Sort them by time; (3) UseBuilderto generate a report containing aggregated statistics. The tool must support at least 3 time formats.