Go: Go Strings ومعالجة التاريخ والوقت
آخر تحديث: 2026-08-26
السلاسل والتواريخ هما نوعا البيانات الأساسيان في جميع البرامج — توفر المكتبة القياسية للغة Go مجموعة شاملة من الأدوات لكليهما، دون الحاجة إلى الاعتماد على مكتبات خارجية.
تحتوي حزمة strings على أكثر من 40 دالة؛ وتتعامل حزمة strconv مع جميع عمليات تحويل الأنواع؛ بينما تغطي حزمة time جميع العمليات المتعلقة بالزمن. في هذا الدرس، ستتقن المجموعة الكاملة من الأدوات اللازمة للتعامل مع السلاسل والزمن في لغة Go.
1. ستتعلم
- الوظائف الشائعة في حزمة
strings(القص، والتقسيم، والاستبدال، والبحث) - تحويل الأنواع باستخدام strconv (Atoi/Itoa/Parse)
- strings.Builder: ربط السلاسل بكفاءة
- time.Now / تحليل / تنسيق
- حساب المدة الزمنية باستخدام time.Duration
- مؤقتات time.Timer / time.Ticker
- مثال شامل: محلل الطابع الزمني للسجلات
2. قصة حقيقية لمهندس عمليات
(1) المشكلة: يؤدي تحليل الطوابع الزمنية يدويًّا إلى حدوث أخطاء في كل سطر من السجل
بوب مهندس عمليات. وهو بحاجة إلى استخراج الطوابع الزمنية من سجلات الخادم لحساب عدد العمليات في الثانية (QPS):
"500,000 سطر سجل يوميًا، مع طوابع زمنية بتنسيقين:
2026-07-08T10:00:00Zو07/08/2026 10:00:00 AM. كتبت 20 سطرًا من الكود باستخدام وحدةdatetimeفي لغة Python، لكن زميلي لم يستطع فهمها. فانتقلت إلى لغة Go، واضطررت في النهاية إلى الرجوع إلى الوثائق 10 مرات لمجرد فهم سلاسل تنسيق التاريخ والوقت."
النسخة الأولى التي كتبها هي:
// 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)
}
غالبًا ما يشعر الزملاء بالحيرة إزاء تنسيق الطابع الزمني في لغة Go ("2006-01-02 15:04:05").
(2) Go Solution: تغطية كاملة للمكتبة القياسية
// 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))
}
}
الناتج:
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) المزايا: اكتمال المكتبة القياسية
| الحزمة | القدرات الأساسية | تحل محل مكتبات الجهات الخارجية |
|---|---|---|
| سلاسل | أكثر من 40 دالة لمعالجة السلاسل | لا حاجة إلى lodash/underscore |
| strconv | تحويل الأنواع | لا يتطلب تحليلاً يدويًّا |
| strings.Builder | التسلسل الفعال | bytes.Buffer |
| الوقت | تحليل الوقت/تنسيقه/المؤقتات | moment.js / date-fns |
Mon Jan 2 15:04:05 MST 2006 (حيث تشير الأرقام 01/02/03/04/05/06 إلى الشهر/اليوم/الساعة/الدقيقة/الثانية/السنة)، لذا ما عليك سوى تذكر 2006-01-02 15:04:05.
3. الوظائف الأساسية لحزمة strings
▶ مثال: 12 دالة شائعة للسلاسل
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)
}
الناتج:
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) مرجع سريع لوظيفة strings
| الفئة | الوظيفة | الغرض |
|---|---|---|
| التنقيح | TrimSpace / Trim / TrimPrefix / TrimSuffix |
إزالة المسافات أو الأحرف المحددة |
| تقسيم | Split / SplitN / Fields |
تقسيم إلى شرائح |
| انضم | Join |
دمج الشرائح في سلسلة |
| بحث | Contains / Index / LastIndex / Count |
التحقق من السلسلة الفرعية |
| استبدال | Replace / ReplaceAll |
استبدال جزء من السلسلة |
| الحالة | ToUpper / ToLower / Title |
تحويل الحالة |
| البرمجة | Builder / Repeat |
التسلسل الفعال للسلاسل / تكرار السلاسل |
4. تحويل الأنواع باستخدام strconv
(1) سلسلة ↔ رقم
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))
}
▶ مثال: الفرق بين strconv وتحويل الأنواع
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
}
| السيناريو | باستخدام تحويل الأنواع | باستخدام strconv |
|---|---|---|
| int ↔ float64 | ✅ float64(n) |
❌ |
| عدد صحيح ↔ سلسلة أحرف | ❌ | ✅ strconv.Itoa / Atoi |
| سلسلة ↔ float64 | ❌ | ✅ ParseFloat / FormatFloat |
| تحليل قاعدة الأرقام | ❌ | ✅ ParseInt("FF", 16, 64) |
| عدد صحيح ↔ uint/int32 | ✅ | ❌ |
5. ربط السلاسل بكفاءة باستخدام strings.Builder
(1) لماذا نستخدم أداة «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)
}
▶ مثال: الاستخدام الكامل لأداة «Builder»
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))
}
الناتج:
Name,Age,City
Alice,28,Shanghai
Bob,32,Beijing
Length: 52
+ لتسلسل السلاسل إلى إنشاء سلسلة جديدة في كل مرة، مما ينتج عنه أداء من الدرجة O(n²). أما strings.Builder فيحتفظ داخليًّا بمخزن قابل للتعديل، مما ينتج عنه أداء من الدرجة O(n). وعند إجراء عدد كبير من عمليات التسلسل، قد يصل الفرق في الأداء إلى 1,000 ضعف.
6. حزمة time: التعامل مع الوقت
(1) الوقت. الوقت الحالي وتنسيقات الوقت
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"))
}
▶ مثال: تحليل الوقت
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)
}
الناتج:
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) حساب المدة الزمنية
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) جدول وحدات المدة
| ثابت | المعنى |
|---|---|
time.Nanosecond |
1 نانو ثانية |
time.Microsecond |
1 ميكروثانية = 1000 نانوثانية |
time.Millisecond |
1 مللي ثانية = 1000 ميكروثانية |
time.Second |
1 ثانية = 1000 مللي ثانية |
time.Minute |
60 ثانية |
time.Hour |
60 دقيقة |
7. المؤقت / شريط التحديثات
(1) 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!")
}
▶ مثال: time.Ticker: مؤقت دوري
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
}
}
}
الناتج:
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.Stop() |
ticker.Stop() |
| قناة الإشارة | .C |
.C |
| إعادة الضبط | timer.Reset(d) |
❌ |
| السيناريوهات الشائعة | التحكم في مهلة الانتظار / التنفيذ المؤجل | إشارات التحقق / المهام المجدولة |
8. التعامل مع المناطق الزمنية
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))
}
| الطريقة | الوصف |
|---|---|
time.LoadLocation("Asia/Shanghai") |
اسم المنطقة الزمنية وفقًا لـ IANA |
time.FixedZone("CST", 8*3600) |
إزاحة ثابتة |
t.In(loc) |
التحويل إلى المنطقة الزمنية المستهدفة |
t.UTC() |
تحويل إلى توقيت UTC |
t.Local() |
التحويل إلى المنطقة الزمنية المحلية |
9. مثال كامل: محلل الطابع الزمني للسجلات
// 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)
}
النتيجة المتوقعة:
=== 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 = السنة، 01 = الشهر، 02 = اليوم، 15 = الساعة (نظام الـ 24 ساعة)، 03 = الساعة (نظام الـ 12 ساعة)، 04 = الدقيقة، 05 = الثانية. هذا التصميم فريد من نوعه، لكنه يبدو طبيعيًا بمجرد التعود عليه.
❓ أسئلة شائعة
strings؟strconv وتحويل الأنواع؟int إلى float64)، بينما يُستخدم strconv للتحويلات بين السلاسل والأرقام. لا يمكنك استخدام تحويل الأنواع بين string وint أو float — يجب عليك استخدام strconv.time.Parse؟2006-01-02 15:04:05 (الشهر/اليوم/الساعة/الدقيقة/الثانية/السنة بهذا الترتيب). 2006 = السنة، 01 = الشهر، 02 = اليوم، 15 = النظام الزمني ذو 24 ساعة، 03 = النظام الزمني ذو 12 ساعة، 04 = الدقائق، 05 = الثواني.Duration؟time.Duration هو قيمة int64 تمثل القيمة العددية بالنانو ثانية. الإنشاء: 5 * time.Second؛ العمليات: t.Add(d)، t.Sub(t2)؛ الاسترجاع: d.Hours()، d.Minutes()، d.Seconds()..C ويدعم طريقة .Stop() لإيقاف التنفيذ.datetime.timedelta في لغة Python؟time.Duration هي بالضبط تلك الميزة. الإنشاء: d := 2*time.Hour + 30*time.Minute؛ الجمع والطرح: t.Add(d)، t.Add(-d)؛ الفرق بين وقتين: t2.Sub(t1).time.LoadLocation("Asia/Shanghai")، ثم قم بتحويلها باستخدام t.In(loc). يتم تخزين الأوقات داخليًّا بتوقيت UTC ويتم تحويلها إلى المنطقة الزمنية المستهدفة عند الإخراج.strings.Builder مقارنةً بـ +؟+ بإنشاء سلسلة أحرف جديدة في كل مرة => O(n²)؛ أما المخزن المؤقت الداخلي لـ Builder => O(n). عند إجراء عملية التسلسل 10,000 مرة، يكون Builder أسرع بأكثر من 1,000 مرة.📖 ملخص
- تتضمن حزمة
stringsأكثر من 40 دالة تغطي جميع عمليات معالجة السلاسل - تقوم دالة strconv بالتحويلات بين السلاسل ↔ الأعداد/القيم المنطقية (وهو ما لا تستطيع عملية تحويل الأنواع القيام به)
- يوفر
strings.Builderعملية ربط سلاسل نصية فعالة ويتجنب التعقيد الحسابي O(n²) الخاص بمُشغِّل+ time.Nowيسترد الوقت الحالي؛Format/Parseيقومان بتنسيقه/تحليله- يعتمد تنسيق تاريخ «Go» على الوقت المرجعي
2006-01-02 15:04:05 time.Durationهو قيمة عددية تُقاس بالنانو ثانية؛ وهو يدعم العمليات الحسابية والمقارناتTimerللتوقيت لمرة واحدة،Tickerللتوقيت الدوري- معالجة المناطق الزمنية: طريقة
LoadLocation+In()
📝 تمارين
-
المشكلة الأساسية (الصعوبة ⭐): قم بتنفيذ دالة
wordCount(s string) map[string]intباستخدام حزمةstringsلحساب عدد المرات التي تظهر فيها كل كلمة في سلسلة أحرف. يجب عليك استخدامFields، وحلقة تكرار، وخريطة. -
مشكلة متقدمة (درجة الصعوبة ⭐⭐): قم بتنفيذ دالة
timeAgo(t time.Time) stringتُرجع وصفًا يمكن قراءته بصيغة بشرية ("قبل 3 دقائق" / "قبل ساعتين" / "أمس" / "قبل 3 أيام"). يجب عليك استخدامtime.Since()مع شرطDuration. -
مشكلة التحدي (الصعوبة ⭐⭐⭐): قم بتنفيذ أداة تجميع السجلات: عند تلقي مدخلات على شكل سلسلة نصية (طابع زمني واحد + مستوى + رسالة في كل سطر)، استخدم
strings.Splitوtime.Parseوstrings.Builderوsort.Sliceللقيام بما يلي: (1) تحليل السجلات وتصفية السجلات ذات مستوى ERROR؛ (2) فرزها حسب الوقت؛ (3) استخدامBuilderلإنشاء تقرير يحتوي على إحصائيات مجمعة. يجب أن تدعم الأداة 3 تنسيقات زمنية على الأقل.