التعبيرات النمطية والتاريخ

الدرس 24: التعبيرات النمطية والتاريخ

تشبيه من الحياة

تخيل أنك عامل فرز الطرود:

في البرمجة، التعبيرات النمطية تساعدك على تحديد واستخراج المعلومات بدقة من النصوص، بينما حزمة time تساعدك على قياس وحساب وعرض الوقت.


المفاهيم الأساسية

التعبيرات النمطية (حزمة regexp)

حزمة regexp في Go مبنية على محرك صياغة RE2، تدعم معظم صيغ regexp الشائعة، لكنها لا تدعم التراجع والمراجع الخلفية (خيار تصميمي يُعطي الأولوية للأداء).

الواجهات الأساسية:
- Compile: regexp.Compile(pattern) → (*Regexp, error)
- Match: MatchString(s) → bool
- Find: FindString / FindAllString / FindStringSubmatch
- Replace: ReplaceAllString / ReplaceAllStringFunc

التاريخ والوقت (حزمة time)

التعامل مع الوقت في Go يتركز على هيكل Time، باستخدام وقت مرجعي ثابت كقالب تنسيق:

الوقت المرجعي: Mon Jan 2 15:04:05 MST 2006
الاختصاص العددي: 01/02 03:04:05PM 2006 -0700
💡 لماذا هذه الأرقام الغريبة؟ اخترت Go التسلسل 1 2 3 4 5 6 7 (الشهر 1، اليوم 2، الساعة 3، الدقيقة 4، الثانية 5، السنة 6، المنطقة الزمنية 7) لسهولة الحفظ.


الصياغة الأساسية والاستخدام

1. أساسيات التعبيرات النمطية

GO
package main

import (
    "fmt"
    "regexp"
)

func main() {
    // تجميع تعبير نمطي
    re, err := regexp.Compile(`\d{3}-\d{4}-\d{4}`)
    if err != nil {
        fmt.Println("فشل تجميع regexp:", err)
        return
    }

    // التحقق من المطابقة
    phone := "138-1234-5678"
    fmt.Println("يطابق:", re.MatchString(phone)) // true

    // إيجاد أول مطابقة
    text := "جهة الاتصال: 138-1234-5678 أو 139-8765-4321"
    fmt.Println("الأولى:", re.FindString(text)) // 138-1234-5678

    // إيجاد جميع المطابقات
    all := re.FindAllString(text, -1)
    fmt.Println("الكل:", all) // [138-1234-5678 139-8765-4321]
}
💡 MustCompile مقابل Compile: MustCompile يُسبب panic مباشرة عند فشل التجميع، مناسب لـ regexp ثابتة كثوابت عالمية؛ Compile تُرجع خطأ، مناسبة لـ regexp المبنية ديناميكياً وقت التشغيل.

2. مجموعات التقاط مسماة

GO
package main

import (
    "fmt"
    "regexp"
)

func main() {
    // استخدام مجموعات التقاط مسماة لاستخراج أجزاء البريد الإلكتروني
    re := regexp.MustCompile(`(?P<user>\w+)@(?P<domain>\w+\.\w+)`)

    email := "zhangsan@example.com"

    // الحصول على نتائج المطابقة
    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. عمليات الاستبدال

GO
package main

import (
    "fmt"
    "regexp"
    "strings"
)

func main() {
    re := regexp.MustCompile(`\s+`)

    // استبدال جميع المسافات البيضاء بمسافة واحدة
    text := "  hello   world   go  "
    result := re.ReplaceAllString(text, " ")
    fmt.Printf("[%s]\n", result) // [hello world go]

    // استخدام دالة للاستبدال الديناميكي
    re2 := regexp.MustCompile(`\b\w`)
    title := re2.ReplaceAllStringFunc("hello world go", func(s string) string {
        // تحويل الحرف الأول إلى كبير
        return strings.ToUpper(s)
    })
    fmt.Println(title) // Hello World Go
}

4. عمليات الوقت الأساسية

GO
package main

import (
    "fmt"
    "time"
)

func main() {
    // الحصول على الوقت الحالي
    now := time.Now()
    fmt.Println("الوقت الحالي:", now)

    // استخراج المكونات الفردية
    fmt.Println("السنة:", now.Year())
    fmt.Println("الشهر:", now.Month())
    fmt.Println("اليوم:", now.Day())
    fmt.Println("الساعة:", now.Hour())
    fmt.Println("الدقيقة:", now.Minute())
    fmt.Println("الثانية:", now.Second())
    fmt.Println("اليوم:", now.Weekday())
}
💡 time.Now() تُرجع وقت المنطقة الزمنية المحلية. لوقت UTC، استخدم time.Now().UTC().

5. تنسيق وتحليل الوقت

GO
package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()

    // التنسيق: استخدام الوقت المرجعي كقالب
    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

    // صيغ مسبقة التعريف شائعة
    fmt.Println(now.Format(time.RFC3339))  // 2025-06-27T14:30:00+08:00

    // تحليل نص إلى وقت
    t, err := time.Parse("2006-01-02", "2025-12-25")
    if err != nil {
        fmt.Println("فشل التحليل:", err)
        return
    }
    fmt.Println("نتيجة التحليل:", t)

    // التحليل مع المنطقة الزمنية
    t2, _ := time.ParseInLocation("2006-01-02 15:04:05",
        "2025-12-25 08:00:00", time.Local)
    fmt.Println("مع المنطقة الزمنية:", t2)
}
⚠️ التنسيق والتحليل يستخدمان نفس سلسلة الوقت المرجعي — هذا تصميم فريد لـ Go. فقط تذكر 2006-01-02 15:04:05.

6. المدة وحسابات الوقت

GO
package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()

    // إضافة وطرح الوقت
    tomorrow := now.Add(24 * time.Hour)
    fmt.Println("غداً:", tomorrow.Format("2006-01-02"))

    twoHoursLater := now.Add(2 * time.Hour)
    fmt.Println("بعد ساعتين:", twoHoursLater.Format("15:04:05"))

    // حساب الفرق الزمني
    diff := tomorrow.Sub(now)
    fmt.Println("الفرق الزمني:", diff)           // 24h0m0s
    fmt.Println("الساعات:", diff.Hours())         // 24
    fmt.Println("الدقائق:", diff.Minutes())       // 1440

    // ترتيب المقارنة
    fmt.Println("غداً بعد:", tomorrow.After(now))  // true
    fmt.Println("اليوم قبل:", now.Before(tomorrow)) // true

    // اقتطاع إلى دقة محددة
    floored := now.Truncate(time.Hour)
    fmt.Println("مقتطع إلى الساعة:", floored.Format("15:04:05"))
}

7. المؤقتات

GO
package main

import (
    "fmt"
    "time"
)

func main() {
    // مؤقت لمرة واحدة
    timer := time.NewTimer(2 * time.Second)
    fmt.Println("انتظار 2 ثانية...")
    <-timer.C
    fmt.Println("انتهى الوقت!")

    // مؤقت دوري (Ticker)
    ticker := time.NewTicker(500 * time.Millisecond)
    defer ticker.Stop() // تذكر الإيقاف لتجنب التسريبات

    count := 0
    for t := range ticker.C {
        count++
        fmt.Println(".Tick:", t.Format("15:04:05.000"))
        if count >= 3 {
            break
        }
    }

    // نسخة time.After المبسطة (انتظار واحد)
    <-time.After(1 * time.Second)
    fmt.Println("تم التنفيذ بعد ثانية واحدة")
}
💡 احذر من استخدام time.After في الحلقات — كل تكرار ينشئ قناة جديدة، والمؤقتات القديمة لن يتم جمعها، مما قد يسبب تسرب ذاكرة. في الحلقات، استخدم time.NewTimer وقم بإعادة التعيين يدوياً.


أمثلة الكود

مثال: التحقق والاستخراج — تحليل السجلات (الصعوبة ⭐)

GO
package main

import (
    "fmt"
    "regexp"
)

func main() {
    // أسطر سجلات محاكاة
    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",
    }

    // مطابقة صيغة السجل: وقت + مستوى + رسالة
    re := regexp.MustCompile(`\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] (\w+)\s+(.+)`)

    // العد حسب المستوى
    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("الوقت: %s | المستوى: %-5s | الرسالة: %s\n",
            timestamp, level, message)

        levelCount[level]++
    }

    fmt.Println("\nإحصائيات المستوى:")
    for level, count := range levelCount {
        fmt.Printf("  %s: %d إدخال\n", level, count)
    }
}
▶ جرّب الكود

المخرجات:

TEXT
الوقت: 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

إحصائيات المستوى:
  ERROR: 2 إدخال
  INFO: 1 إدخال
  WARN: 1 إدخال

مثال: محرك القوالب — نظام استبدال نصوص بسيط (الصعوبة ⭐⭐)

GO
package main

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

func main() {
    template := `عزيزي {{name}}:
    طلبك {{order}} تم شحنه في {{date}}.
    التسليم المتوقع خلال {{days}} أيام.
    الوقت الحالي: {{now}}`

    // تعريف خريطة المتغيرات
    vars := map[string]string{
        "name":  "Alice",
        "order": "ORD-20250627-001",
        "date":  "2025-06-27",
        "days":  "3",
    }

    // مطابقة نمط {{اسم_المتغير}}
    re := regexp.MustCompile(`\{\{(\w+)\}\}`)

    // استبدال المتغيرات
    result := re.ReplaceAllStringFunc(template, func(match string) string {
        // استخراج اسم المتغير (إزالة {{ و }})
        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 // المتغير غير موجود، الإبقاء كما هو
    })

    fmt.Println(result)

    // عدد المتغيرات في القالب
    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("\nمتغيرات القالب: %s\n", strings.Join(varNames, ", "))
}
▶ جرّب الكود

المخرجات:

TEXT
عزيزي Alice:
    طلبك ORD-20250627-001 تم شحنه في 2025-06-27.
    التسليم المتوقع خلال 3 أيام.
    الوقت الحالي: 2025-06-27 14:30:00

متغيرات القالب: name, order, date, days, now

مثال: مؤقت عد تنازلي — عرض فوري مع تنسيق (الصعوبة ⭐⭐⭐)

GO
package main

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

// formatDuration تنسق Duration كـ "Xd Xh Xm Xs"
func formatDuration(d time.Duration) string {
    if d <= 0 {
        return "منتهي"
    }

    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("%dي", days))
    }
    if hours > 0 {
        parts = append(parts, fmt.Sprintf("%دس", hours))
    }
    if minutes > 0 {
        parts = append(parts, fmt.Sprintf("%دد", minutes))
    }
    parts = append(parts, fmt.Sprintf("%02dث", seconds))

    return strings.Join(parts, " ")
}

// parseDeadline تحلل صيغ تاريخ متعددة
func parseDeadline(s string) (time.Time, error) {
    // محاولة صيغ متعددة
    formats := []string{
        "2006-01-02 15:04:05",
        "2006-01-02",
        "2006/01/02 15:04",
        "01-02 15:04",
    }

    // التحقق مما إذا كان وقتاً نسبياً (مثل "+2h30m")
    re := regexp.MustCompile(`^\+(\d+)([hms])`)
    if match := re.FindStringSubmatch(s); match != nil {
        // تحليل وقت نسبي (مبسط، وحدة واحدة فقط)
        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("غير قادر على تحليل الوقت: %s", s)
}

func main() {
    // تحليل الموعد النهائي
    deadline, err := parseDeadline("2025-12-31 23:59:59")
    if err != nil {
        fmt.Println("خطأ:", err)
        return
    }

    fmt.Printf("الموعد النهائي: %s\n", deadline.Format("2006-01-02 15:04:05"))
    fmt.Println(strings.Repeat("─", 40))

    // محاكاة العد التنازلي (يتحدث كل ثانية، 5 مرات إجمالاً)
    ticker := time.NewTicker(1 * time.Second)
    defer ticker.Stop()

    count := 0
    for now := range ticker.C {
        remaining := deadline.Sub(now)

        // تأثير مسح الشاشة: الكتابة فوق السطر الحالي بإرجاع عربة
        fmt.Printf("\rالمتبقي: %-30s", formatDuration(remaining))

        count++
        if count >= 5 || remaining <= 0 {
            break
        }
    }

    fmt.Println("\n\nاكتمل عرض العد التنازلي")

    // التحقق من صيغة الوقت
    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("التحقق من التنسيق: %v\n", re.MatchString(formatted)) // true
}
▶ جرّب الكود

سيناريوهات التطبيق العملي

السيناريو 1: التحقق من بيانات النموذج

GO
package main

import (
    "fmt"
    "regexp"
)

// Validator مُتحقق من النموذج
type Validator struct {
    rules map[string]*regexp.Regexp
}

// NewValidator ينشئ مُتحققاً ويجمع جميع regexp مسبقاً
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 يتحقق من حقل واحد
func (v *Validator) Validate(field, value string) bool {
    re, ok := v.rules[field]
    if !ok {
        return true // لا قاعدة يعني اجتاز
    }
    return re.MatchString(value)
}

// ValidateAll يتحقق من حقول متعددة، يُرجع جميع الأخطاء
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 غير صحيح: %s", field, value))
        }
    }

    return errors
}

func main() {
    v := NewValidator()

    // بيانات اختبار
    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("✓ تم التحقق من جميع الحقول بنجاح")
    } else {
        fmt.Println("فشل التحقق:")
        for _, err := range errors {
            fmt.Printf("  ✗ %s\n", err)
        }
    }

    // اختبار بيانات غير صالحة
    invalidData := map[string]string{
        "phone": "12345678901",  // بادئة هاتف غير صالحة
        "email": "not-an-email", // مفقود @
    }

    fmt.Println("\nاختبار بيانات غير صالحة:")
    for field, value := range invalidData {
        result := "✓"
        if !v.Validate(field, value) {
            result = "✗"
        }
        fmt.Printf("  %s %s: %s\n", result, field, value)
    }
}

السيناريو 2: استعلام النطاق الزمني — إدارة حالة النشاط

GO
package main

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

// Activity هيكل النشاط
type Activity struct {
    Name      string
    StartTime time.Time
    EndTime   time.Time
}

// Status يحصل على حالة النشاط الحالية
func (a Activity) Status(now time.Time) string {
    switch {
    case now.Before(a.StartTime):
        return "لم يبدأ"
    case now.After(a.EndTime):
        return "انتهى"
    default:
        return "جارٍ"
    }
}

// Remaining يحصل على الوقت المتبقي أو العد التنازلي
func (a Activity) Remaining(now time.Time) string {
    if now.Before(a.StartTime) {
        d := a.StartTime.Sub(now)
        return fmt.Sprintf("يبدأ خلال: %s", formatDur(d))
    }
    if now.Before(a.EndTime) {
        d := a.EndTime.Sub(now)
        return fmt.Sprintf("متبقي: %s", formatDur(d))
    }
    return "منتهي"
}

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("%دد %دس %دد", days, hours, minutes)
    }
    return fmt.Sprintf("%دس %دد", hours, minutes)
}

func main() {
    now := time.Now()

    // إنشاء قائمة الأنشطة
    activities := []Activity{
        {
            Name:      "تخفيضات الصيف",
            StartTime: mustParse("2025-06-01 00:00:00"),
            EndTime:   mustParse("2025-06-18 23:59:59"),
        },
        {
            Name:      "عرض العودة للمدارس",
            StartTime: mustParse("2025-07-01 00:00:00"),
            EndTime:   mustParse("2025-08-31 23:59:59"),
        },
        {
            Name:      "معاينة الجمعة السوداء",
            StartTime: mustParse("2025-11-01 00:00:00"),
            EndTime:   mustParse("2025-11-11 23:59:59"),
        },
    }

    fmt.Printf("الوقت الحالي: %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("النشاط: %s\n", act.Name)
        fmt.Printf("  الوقت: %s ~ %s\n",
            act.StartTime.Format("2006-01-02"),
            act.EndTime.Format("2006-01-02"))
        fmt.Printf("  الحالة: %s | %s\n\n", status, remaining)
    }

    // التصفية حسب الحالة
    fmt.Println("الأنشطة الجارية:")
    found := false
    for _, act := range activities {
        if act.Status(now) == "جارٍ" {
            fmt.Printf("  - %s\n", act.Name)
            found = true
        }
    }
    if !found {
        fmt.Println("  لا توجد أنشطة جارية حالياً")
    }
}

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
}

❓ أسئلة شائعة

س1: لماذا لا يستطيع regexp تطابق الأحرف الصينية؟

حزمة regexp في Go تتعامل مع ترميز UTF-8 افتراضياً، لذا يمكن مطابقة الأحرف الصينية نفسها بشكل طبيعي. المشكلة الشائعة هي عدم استخدام فئة الأحرف الصحيحة:

GO
// خطأ: \w لا يطابق الصينية
re := regexp.MustCompile(`^\w+$`)
re.MatchString("مرحبا") // false

// صحيح: استخدم فئة Unicode أو نطاق الصينية مباشرة
re2 := regexp.MustCompile(`^[\p{Han}]+$`)
re2.MatchString("مرحبا") // true

// أو مطابقة مختلطة
re3 := regexp.MustCompile(`^[\w\p{Han}]+$`)
re3.MatchString("helloمرحبا123") // true

س2: ما الفرق بين time.Parse و time.ParseInLocation؟

GO
// Parse تستخدم UTC عندما لا توجد معلومات منطقة زمنية
t1, _ := time.Parse("2006-01-02", "2025-06-27")
fmt.Println(t1.Location()) // UTC

// ParseInLocation تحدد منطقة زمنية افتراضية
t2, _ := time.ParseInLocation("2006-01-02", "2025-06-27", time.Local)
fmt.Println(t2.Location()) // Local (مثل Asia/Shanghai)

// إذا كانت سلسلة التنسيق تحتوي على معلومات منطقة زمنية (مثل -0700)، تتصرفان بنفس الطريقة
t3, _ := time.Parse("2006-01-02 15:04:05 -0700", "2025-06-27 08:00:00 +0800")
fmt.Println(t3) // تم تحليلها بشكل صحيح كمنطقة +0800
💡 توصية: عند تحليل سلاسل التاريخ بدون منطقة زمنية، استخدم دائماً ParseInLocation وحدد المنطقة الزمنية صراحةً.

س3: ماذا لو كان أداء regexp ضعيفاً؟

GO
// خطأ: إعادة التجميع في كل استدعاء
func validatePhone(phone string) bool {
    re := regexp.MustCompile(`^1[3-9]\d{9}$`) // يُجمع كل مرة
    return re.MatchString(phone)
}

// صحيح: تجميع مسبق كمتغير على مستوى الحزمة
var phoneRe = regexp.MustCompile(`^1[3-9]\d{9}$`)

func validatePhone2(phone string) bool {
    return phoneRe.MatchString(phone)
}

// للمطابقات عالية التكرار،فكّر في استخدام Regexp.Copy() لتجنب قفل التنافس
var globalRe = regexp.MustCompile(`\d+`)

func processConcurrently(text string) string {
    re := globalRe.Copy() // الحصول على نسخة، تجنب الأقفال المتزامنة
    return re.ReplaceAllString(text, "#")
}

س4: كيف أتعامل مع تحويل المنطقة الزمنية؟

GO
package main

import (
    "fmt"
    "time"
)

func main() {
    // تحميل مناطق زمنية محددة
    shanghai, _ := time.LoadLocation("Asia/Shanghai")
    tokyo, _ := time.LoadLocation("Asia/Tokyo")
    newyork, _ := time.LoadLocation("America/New_York")

    // إنشاء وقت بمنطقة زمنية
    t := time.Date(2025, 6, 27, 14, 0, 0, 0, shanghai)
    fmt.Println("Shanghai:", t.Format("15:04 MST"))

    // تحويل المنطقة الزمنية
    fmt.Println("Tokyo:", t.In(tokyo).Format("15:04 MST"))
    fmt.Println("New York:", t.In(newyork).Format("15:04 MST"))

    // التحليل من نص والتحويل
    parsed, _ := time.ParseInLocation("2006-01-02 15:04",
        "2025-06-27 14:00", shanghai)
    fmt.Println("\nتم التحليل ثم التحويل إلى UTC:", parsed.UTC().Format("2006-01-02 15:04 MST"))
}
⚠️ time.LoadLocation قد تتطلب تثبيت tzdata على بعض الأنظمة. يمكن لـ Go 1.15+ تضمين بيانات المنطقة الزمنية باستيراد _ "time/tzdata".


📖 ملخص

غطى هذا الدرس موضوعين أساسيين:

الموضوع الحزمة الأنواع الأساسية العمليات الرئيسية
التعبيرات النمطية regexp Regexp Compile, Match, Find, Replace
التاريخ والوقت time Time, Duration Format, Parse, Calculate, Timer

نقاط أساسية لـ regexp:

نقاط أساسية للوقت:


📝 تمارين

التمرين 1: مستخرج روابط Markdown

اكتب برنامجاً يستخرج جميع الروابط من نص Markdown، ويُخرج بصيغة العنوان -> الرابط.

GO
// تلميح: مطابقة صيغة [العنوان](الرابط)
// المدخلات: `قم بزيارة [موقع Go](https://golang.org) أو [GitHub](https://github.com)`
// المخرجات:
//   موقع Go -> https://golang.org
//   GitHub -> https://github.com

التمرين 2: حاسب أيام العمل

اكتب دالة AddBusinessDays(t time.Time, n int) time.Time تحسب التاريخ بعد n يوم عمل من تاريخ معين (تخطي السبت والأحد).

GO
// الاختبارات:
// AddBusinessDays(2025-06-27 الجمعة, 1) → 2025-06-30 الاثنين
// AddBusinessDays(2025-06-27 الجمعة, 5) → 2025-07-04 الجمعة

التمرين 3: نظام فلترة الكلمات الحساسة

نفذ فلتر كلمات حساسة:

  1. تحميل قائمة الكلمات الحساسة من التكوين، وتجميعها إلى regexp
  2. دعم وضع الاستبدال بـ * والإزالة الكاملة
  3. دعم اكتشاف متغيرات الكلمات الحساسة (مثل مسافات مدرجة في المنتصف: "gam bling")
GO
// نص المدخلات: "هذا موقع قمار، يخدم gam bling"
// مخرجات وضع الاستبدال: "هذا موقع *، يخدم *"
// مخرجات وضع الإزالة: "هذا موقع، يخدم"

الدرس التالي

في الدرس التالي، سنتعلم تطوير برامج سطر الأوامر، ونفهم كيفية استخدام Go لبناء أدوات CLI قوية، بما في ذلك تحليل الوسائط، والأوامر الفرعية، والدخل التفاعلي، وغيرها من المهارات العملية.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%