Regex and Date
Lesson 24: Regex and Date
Life Analogy
Imagine you are a package sorting worker:
- Regular expressions are like sorting rules — patterns like "address contains 'Beijing' and the house number is 3 digits" help you quickly filter out the batch of packages that match from a huge volume.
- Date and time are like timestamps on delivery slips — you need to know "what time did this package arrive," "how long since it was signed for," "can it arrive by 3 PM tomorrow."
In programming, regex helps you precisely locate and extract information from text, while the time package helps you measure, calculate, and display time.
Core Concepts
Regular Expressions (regexp Package)
Go's regexp package is based on the RE2 syntax engine, supporting most common regex syntax, but does not support backtracking and backreferences (a performance-first design choice).
Core interfaces:
- Compile: regexp.Compile(pattern) → (*Regexp, error)
- Match: MatchString(s) → bool
- Find: FindString / FindAllString / FindStringSubmatch
- Replace: ReplaceAllString / ReplaceAllStringFunc
Date and Time (time Package)
Go's time handling centers on the time.Time struct, using a fixed reference time as the formatting template:
Reference time: Mon Jan 2 15:04:05 MST 2006
Numeric mnemonic: 01/02 03:04:05PM 2006 -0700
1 2 3 4 5 6 7 (Month 1, Day 2, Hour 3, Minute 4, Second 5, Year 6, Timezone 7) for easy memorization.
Basic Syntax and Usage
1. Regex Basics
package main
import (
"fmt"
"regexp"
)
func main() {
// Compile regular expression
re, err := regexp.Compile(`\d{3}-\d{4}-\d{4}`)
if err != nil {
fmt.Println("Regex compile failed:", err)
return
}
// Check if it matches
phone := "138-1234-5678"
fmt.Println("Matches:", re.MatchString(phone)) // true
// Find first match
text := "Contact: 138-1234-5678 or 139-8765-4321"
fmt.Println("First:", re.FindString(text)) // 138-1234-5678
// Find all matches
all := re.FindAllString(text, -1)
fmt.Println("All:", all) // [138-1234-5678 139-8765-4321]
}
MustCompile vs Compile: MustCompile panics directly on compile failure, suitable for global constant regexes; Compile returns an error, suitable for regexes dynamically built at runtime.
2. Named Capture Groups
package main
import (
"fmt"
"regexp"
)
func main() {
// Use named capture groups to extract parts of an email
re := regexp.MustCompile(`(?P<user>\w+)@(?P<domain>\w+\.\w+)`)
email := "zhangsan@example.com"
// Get match results
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 = zhangsan
// domain = example.com
}
3. Replacement Operations
package main
import (
"fmt"
"regexp"
"strings"
)
func main() {
re := regexp.MustCompile(`\s+`)
// Replace all whitespace with a single space
text := " hello world go "
result := re.ReplaceAllString(text, " ")
fmt.Printf("[%s]\n", result) // [hello world go]
// Use function for dynamic replacement
re2 := regexp.MustCompile(`\b\w`)
title := re2.ReplaceAllStringFunc("hello world go", func(s string) string {
// Capitalize first letter
return strings.ToUpper(s)
})
fmt.Println(title) // Hello World Go
}
4. Basic Time Operations
package main
import (
"fmt"
"time"
)
func main() {
// Get current time
now := time.Now()
fmt.Println("Current time:", now)
// Extract individual components
fmt.Println("Year:", now.Year())
fmt.Println("Month:", now.Month())
fmt.Println("Day:", now.Day())
fmt.Println("Hour:", now.Hour())
fmt.Println("Minute:", now.Minute())
fmt.Println("Second:", now.Second())
fmt.Println("Weekday:", now.Weekday())
}
time.Now().UTC().
5. Time Formatting and Parsing
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
// Formatting: use reference time as template
fmt.Println(now.Format("2006-01-02")) // 2025-06-27
fmt.Println(now.Format("2006/01/02 15:04:05")) // 2025/06/27 14:30:00
fmt.Println(now.Format("03:04PM")) // 02:30PM
// Common predefined formats
fmt.Println(now.Format(time.RFC3339)) // 2025-06-27T14:30:00+08:00
// Parse string to time
t, err := time.Parse("2006-01-02", "2025-12-25")
if err != nil {
fmt.Println("Parse failed:", err)
return
}
fmt.Println("Parse result:", t)
// Parse with timezone
t2, _ := time.ParseInLocation("2006-01-02 15:04:05",
"2025-12-25 08:00:00", time.Local)
fmt.Println("With timezone:", t2)
}
2006-01-02 15:04:05.
6. Duration and Time Calculations
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
// Time addition and subtraction
tomorrow := now.Add(24 * time.Hour)
fmt.Println("Tomorrow:", tomorrow.Format("2006-01-02"))
twoHoursLater := now.Add(2 * time.Hour)
fmt.Println("Two hours later:", twoHoursLater.Format("15:04:05"))
// Calculate time difference
diff := tomorrow.Sub(now)
fmt.Println("Time difference:", diff) // 24h0m0s
fmt.Println("Hours:", diff.Hours()) // 24
fmt.Println("Minutes:", diff.Minutes()) // 1440
// Compare order
fmt.Println("Tomorrow is after:", tomorrow.After(now)) // true
fmt.Println("Today is before:", now.Before(tomorrow)) // true
// Truncate to specified precision
floored := now.Truncate(time.Hour)
fmt.Println("Truncated to hour:", floored.Format("15:04:05"))
}
7. Timers
package main
import (
"fmt"
"time"
)
func main() {
// Single-shot timer
timer := time.NewTimer(2 * time.Second)
fmt.Println("Waiting 2 seconds...")
<-timer.C
fmt.Println("Time's up!")
// Periodic timer (Ticker)
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop() // Remember to stop to avoid leaks
count := 0
for t := range ticker.C {
count++
fmt.Println("Tick:", t.Format("15:04:05.000"))
if count >= 3 {
break
}
}
// time.After simplified version (single wait)
<-time.After(1 * time.Second)
fmt.Println("Executed after 1 second")
}
time.After in loops — each iteration creates a new channel, and old timers won't be garbage collected, potentially causing memory leaks. In loops, use time.NewTimer and manually Reset.
Example Code
Example: Validation and Extraction — Log Parsing (Difficulty ⭐)
package main
import (
"fmt"
"regexp"
)
func main() {
// Simulated log lines
logs := []string{
"[2025-06-27 14:30:00] ERROR Database connection failed",
"[2025-06-27 14:30:01] INFO Service started successfully",
"[2025-06-27 14:30:05] WARN Disk space low",
"[2025-06-27 14:31:00] ERROR Request timeout",
}
// Match log format: time + level + message
re := regexp.MustCompile(`\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] (\w+)\s+(.+)`)
// Count by level
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("Time: %s | Level: %-5s | Message: %s\n",
timestamp, level, message)
levelCount[level]++
}
fmt.Println("\nLevel statistics:")
for level, count := range levelCount {
fmt.Printf(" %s: %d entries\n", level, count)
}
}
Output:
Time: 2025-06-27 14:30:00 | Level: ERROR | Message: Database connection failed
Time: 2025-06-27 14:30:01 | Level: INFO | Message: Service started successfully
Time: 2025-06-27 14:30:05 | Level: WARN | Message: Disk space low
Time: 2025-06-27 14:31:00 | Level: ERROR | Message: Request timeout
Level statistics:
ERROR: 2 entries
INFO: 1 entries
WARN: 1 entries
Example: Template Engine — Simple Text Replacement System (Difficulty ⭐⭐)
package main
import (
"fmt"
"regexp"
"strings"
"time"
)
func main() {
template := `Dear {{name}}:
Your order {{order}} was shipped on {{date}}.
Expected delivery in {{days}} days.
Current time: {{now}}`
// Define variable mapping
vars := map[string]string{
"name": "Alice",
"order": "ORD-20250627-001",
"date": "2025-06-27",
"days": "3",
}
// Match {{variable_name}} pattern
re := regexp.MustCompile(`\{\{(\w+)\}\}`)
// Replace variables
result := re.ReplaceAllStringFunc(template, func(match string) string {
// Extract variable name (remove {{ and }})
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 // Variable not found, keep as-is
})
fmt.Println(result)
// Count variables in template
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("\nTemplate variables: %s\n", strings.Join(varNames, ", "))
}
Output:
Dear Alice:
Your order ORD-20250627-001 was shipped on 2025-06-27.
Expected delivery in 3 days.
Current time: 2025-06-27 14:30:00
Template variables: name, order, date, days, now
Example: Countdown Timer — Real-Time Display with Formatting (Difficulty ⭐⭐⭐)
package main
import (
"fmt"
"regexp"
"strings"
"time"
)
// formatDuration formats a Duration as "Xd Xh Xm Xs"
func formatDuration(d time.Duration) string {
if d <= 0 {
return "Expired"
}
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, " ")
}
// parseDeadline parses multiple date formats
func parseDeadline(s string) (time.Time, error) {
// Try multiple formats
formats := []string{
"2006-01-02 15:04:05",
"2006-01-02",
"2006/01/02 15:04",
"01-02 15:04",
}
// Check if it's a relative time (e.g., "+2h30m")
re := regexp.MustCompile(`^\+(\d+)([hms])`)
if match := re.FindStringSubmatch(s); match != nil {
// Parse relative time (simplified, single unit only)
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("unable to parse time: %s", s)
}
func main() {
// Parse deadline
deadline, err := parseDeadline("2025-12-31 23:59:59")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Deadline: %s\n", deadline.Format("2006-01-02 15:04:05"))
fmt.Println(strings.Repeat("─", 40))
// Simulate countdown (updates every second, 5 times total)
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
count := 0
for now := range ticker.C {
remaining := deadline.Sub(now)
// Screen clearing effect: overwrite current line with carriage return
fmt.Printf("\rRemaining: %-30s", formatDuration(remaining))
count++
if count >= 5 || remaining <= 0 {
break
}
}
fmt.Println("\n\nCountdown demo complete")
// Verify time format
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("Format verification: %v\n", re.MatchString(formatted)) // true
}
Practical Application Scenarios
Scenario 1: Form Data Validation
package main
import (
"fmt"
"regexp"
)
// Validator form validator
type Validator struct {
rules map[string]*regexp.Regexp
}
// NewValidator creates a validator and pre-compiles all regexes
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}
}
// Validate validates a single field
func (v *Validator) Validate(field, value string) bool {
re, ok := v.rules[field]
if !ok {
return true // No rule means pass
}
return re.MatchString(value)
}
// ValidateAll validates multiple fields, returns all errors
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("%s format incorrect: %s", field, value))
}
}
return errors
}
func main() {
v := NewValidator()
// Test data
data := map[string]string{
"phone": "13812345678",
"email": "test@example.com",
"id_card": "110101199001011234",
"username": "go_dev",
"date": "2025-06-27",
}
errors := v.ValidateAll(data)
if len(errors) == 0 {
fmt.Println("✓ All fields validated successfully")
} else {
fmt.Println("Validation failed:")
for _, err := range errors {
fmt.Printf(" ✗ %s\n", err)
}
}
// Test invalid data
invalidData := map[string]string{
"phone": "12345678901", // Not a valid phone number prefix
"email": "not-an-email", // Missing @
}
fmt.Println("\nInvalid data test:")
for field, value := range invalidData {
result := "✓"
if !v.Validate(field, value) {
result = "✗"
}
fmt.Printf(" %s %s: %s\n", result, field, value)
}
}
Scenario 2: Time Range Query — Activity Status Management
package main
import (
"fmt"
"strings"
"time"
)
// Activity struct
type Activity struct {
Name string
StartTime time.Time
EndTime time.Time
}
// Status gets the current activity status
func (a Activity) Status(now time.Time) string {
switch {
case now.Before(a.StartTime):
return "Not started"
case now.After(a.EndTime):
return "Ended"
default:
return "In progress"
}
}
// Remaining gets remaining time or countdown
func (a Activity) Remaining(now time.Time) string {
if now.Before(a.StartTime) {
d := a.StartTime.Sub(now)
return fmt.Sprintf("Starts in: %s", formatDur(d))
}
if now.Before(a.EndTime) {
d := a.EndTime.Sub(now)
return fmt.Sprintf("Remaining: %s", formatDur(d))
}
return "Expired"
}
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()
// Create activity list
activities := []Activity{
{
Name: "Summer Sale",
StartTime: mustParse("2025-06-01 00:00:00"),
EndTime: mustParse("2025-06-18 23:59:59"),
},
{
Name: "Back to School Special",
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("Current time: %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("Activity: %s\n", act.Name)
fmt.Printf(" Time: %s ~ %s\n",
act.StartTime.Format("2006-01-02"),
act.EndTime.Format("2006-01-02"))
fmt.Printf(" Status: %s | %s\n\n", status, remaining)
}
// Filter by status
fmt.Println("Activities in progress:")
found := false
for _, act := range activities {
if act.Status(now) == "In progress" {
fmt.Printf(" - %s\n", act.Name)
found = true
}
}
if !found {
fmt.Println(" No activities currently in progress")
}
}
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
}
❓ FAQ
Q1: Why can't my regex match Chinese characters?
Go's regexp package handles UTF-8 encoding by default, so Chinese characters themselves can be matched normally. The common issue is not using the correct character class:
// Wrong: \w doesn't match CJK
re := regexp.MustCompile(`^\w+$`)
re.MatchString("Salut") // false
// Correct: use Unicode character class or match CJK range directly
re2 := regexp.MustCompile(`^[\p{Han}]+$`)
re2.MatchString("Salut") // true
// Or mixed matching
re3 := regexp.MustCompile(`^[\w\p{Han}]+$`)
re3.MatchString("helloSalut123") // true
Q2: What's the difference between time.Parse and time.ParseInLocation?
// Parse uses UTC when no timezone info is present
t1, _ := time.Parse("2006-01-02", "2025-06-27")
fmt.Println(t1.Location()) // UTC
// ParseInLocation specifies a default timezone
t2, _ := time.ParseInLocation("2006-01-02", "2025-06-27", time.Local)
fmt.Println(t2.Location()) // Local (e.g., Asia/Shanghai)
// If the format string contains timezone info (e.g., -0700), both behave the same
t3, _ := time.Parse("2006-01-02 15:04:05 -0700", "2025-06-27 08:00:00 +0800")
fmt.Println(t3) // Correctly parsed as +0800 timezone
ParseInLocation and explicitly specify the timezone.
Q3: What if regex performance is poor?
// Wrong: recompile on every call
func validatePhone(phone string) bool {
re := regexp.MustCompile(`^1[3-9]\d{9}$`) // Compiles every time
return re.MatchString(phone)
}
// Correct: pre-compile as a package-level variable
var phoneRe = regexp.MustCompile(`^1[3-9]\d{9}$`)
func validatePhone2(phone string) bool {
return phoneRe.MatchString(phone)
}
// For high-frequency matching, consider using Regexp.Copy() to avoid lock contention
var globalRe = regexp.MustCompile(`\d+`)
func processConcurrently(text string) string {
re := globalRe.Copy() // Get a copy, avoid concurrent locks
return re.ReplaceAllString(text, "#")
}
Q4: How to handle timezone conversion?
package main
import (
"fmt"
"time"
)
func main() {
// Load specific timezones
shanghai, _ := time.LoadLocation("Asia/Shanghai")
tokyo, _ := time.LoadLocation("Asia/Tokyo")
newyork, _ := time.LoadLocation("America/New_York")
// Create time with timezone
t := time.Date(2025, 6, 27, 14, 0, 0, 0, shanghai)
fmt.Println("Shanghai:", t.Format("15:04 MST"))
// Convert timezone
fmt.Println("Tokyo:", t.In(tokyo).Format("15:04 MST"))
fmt.Println("New York:", t.In(newyork).Format("15:04 MST"))
// Parse from string and convert
parsed, _ := time.ParseInLocation("2006-01-02 15:04",
"2025-06-27 14:00", shanghai)
fmt.Println("\nParsed then converted to UTC:", parsed.UTC().Format("2006-01-02 15:04 MST"))
}
time.LoadLocation may require installing tzdata on some systems. Go 1.15+ can embed timezone data by importing _ "time/tzdata".
📖 Summary
This lesson covered two core topics:
| Topic | Package | Core Types | Key Operations |
|---|---|---|---|
| Regular Expressions | regexp |
Regexp |
Compile, Match, Find, Replace |
| Date and Time | time |
Time, Duration |
Format, Parse, Calculate, Timer |
Regex Key Points:
- Use
MustCompileto pre-compile constant regexes - Named capture groups
(?P<name>...)improve readability \p{Han}matches Chinese,\p{L}matches all Unicode letters- RE2 engine doesn't support backreferences, but guarantees linear time complexity
Time Key Points:
- Format template is the reference time
2006-01-02 15:04:05 Parsedefaults to UTC,ParseInLocationspecifies timezoneDurationrepresents time intervals, supports rich unit methods- Timers must be
Stop()ed after use to avoid goroutine leaks
📝 Exercises
Exercise 1: Markdown Link Extractor
Write a program that extracts all links from Markdown text, outputting in the format Title -> URL.
// Hint: match [Title](URL) format
// Input: `Visit [GoSite](https://golang.org) or [GitHub](https://github.com)`
// Output:
// GoSite -> https://golang.org
// GitHub -> https://github.com
Exercise 2: Business Day Calculator
Write a function AddBusinessDays(t time.Time, n int) time.Time that calculates the date after n business days from a given date (skipping Saturday and Sunday).
// Tests:
// AddBusinessDays(2025-06-27 Friday, 1) → 2025-06-30 Monday
// AddBusinessDays(2025-06-27 Friday, 5) → 2025-07-04 Friday
Exercise 3: Sensitive Word Filter System
Implement a sensitive word filter:
- Load sensitive word list from configuration, compile to regex
- Support
*replacement and complete removal modes - Support sensitive word variant detection (e.g., spaces inserted in middle: "gam bling")
// Input text: "This is a gambling website, offering gam bling services"
// Replacement mode output: "This is a * website, offering * services"
// Removal mode output: "This is a website, offering services"
Next Lesson
In the next lesson, we will learn about Command-Line Program Development, understanding how to use Go to build powerful CLI tools, including argument parsing, subcommands, interactive input, and other practical skills.



