0

0

如何在 Go 中正确解析多种日期格式的正则匹配结果

花韻仙語

花韻仙語

发布时间:2026-01-25 13:10:11

|

489人浏览过

|

来源于php中文网

原创

如何在 Go 中正确解析多种日期格式的正则匹配结果

本文详解 go 中使用命名捕获组(`(?p...)`)匹配多格式日期时,如何避免子表达式名称冲突、安全提取字段并统一标准化输出。重点解决 `findallstringsubmatch` 返回的嵌套字节切片遍历难题。

在 Go 的 regexp 包中,命名捕获组不支持跨分支复用同一名称(如 (?P...) 在 A|B 的两个分支中重复定义),这是导致原始代码失败的根本原因。当正则表达式包含 |(或)操作符且各分支均含同名组时,SubexpNames() 会返回所有分支中所有组的名称(含重复),而 FindAllStringSubmatch 返回的每个匹配项字节切片([][]byte)按全局子表达式索引顺序排列——即第 0 个元素是完整匹配,第 1~n 个对应 SubexpNames()[1:] 的每个组,但未匹配的分支组将为 nil

因此,原始代码中 match[i][j] 直接索引会导致越界或空值混入,且无法区分哪一分支真正命中。正确的做法是:对每种日期格式单独编译正则,分别匹配、独立解析——这不仅语义清晰,还能规避命名冲突,并便于针对不同格式定制归一化逻辑(如月份转数字、年份补全等)。

以下是一个健壮、可扩展的实现方案:

Originality AI
Originality AI

专门为网络出版商设计的抄袭和AI检测工具

下载
package main

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

// monthNumFromName 将英文月份缩写转为两位数字
func monthNumFromName(m string) string {
    m = strings.ToLower(strings.TrimSpace(m))
    switch {
    case strings.HasPrefix(m, "jan"): return "01"
    case strings.HasPrefix(m, "feb"): return "02"
    case strings.HasPrefix(m, "mar"): return "03"
    case strings.HasPrefix(m, "apr"): return "04"
    case strings.HasPrefix(m, "may"): return "05"
    case strings.HasPrefix(m, "jun"): return "06"
    case strings.HasPrefix(m, "jul"): return "07"
    case strings.HasPrefix(m, "aug"): return "08"
    case strings.HasPrefix(m, "sep"): return "09"
    case strings.HasPrefix(m, "oct"): return "10"
    case strings.HasPrefix(m, "nov"): return "11"
    case strings.HasPrefix(m, "dec"): return "12"
    default:
        // 尝试解析为数字(支持 1-12 或 01-12)
        if i, err := strconv.Atoi(m); err == nil && i >= 1 && i <= 12 {
            return fmt.Sprintf("%02d", i)
        }
        return ""
    }
}

// normalizeYear 补全年份(2位→4位,默认 1950+ 归 20xx,否则 19xx)
func normalizeYear(y string) string {
    if len(y) == 4 {
        return y
    }
    if len(y) != 2 {
        return y // 无法处理,原样返回
    }
    if i, err := strconv.Atoi(y); err == nil {
        if i > 50 {
            return "19" + y
        }
        return "20" + y
    }
    return y
}

// padZero 将单数字字符串补零为两位
func padZero(s string) string {
    s = strings.TrimSpace(s)
    if len(s) == 1 {
        return "0" + s
    }
    return s
}

func main() {
    text := "February 6 2004 Jan 12th 56 1/12/2000 2013/12/1 1/12/1999"

    // 定义多种格式的正则(每种独立编译,无命名冲突)
    patterns := []struct {
        re    *regexp.Regexp
        parse func(map[string]string) string // 解析函数:输入命名组映射,输出标准日期字符串
    }{
        // MM/DD/YYYY 或 M/D/YYYY
        {
            regexp.MustCompile(`(?i)(?P\d{1,2})[/.-](?P\d{1,2})[/.-](?P\d{4})`),
            func(m map[string]string) string {
                return padZero(m["month"]) + "/" + padZero(m["day"]) + "/" + m["year"]
            },
        },
        // YYYY/MM/DD
        {
            regexp.MustCompile(`(?i)(?P\d{4})[/.-](?P\d{1,2})[/.-](?P\d{1,2})`),
            func(m map[string]string) string {
                return padZero(m["month"]) + "/" + padZero(m["day"]) + "/" + m["year"]
            },
        },
        // DD/MM/YYYY
        {
            regexp.MustCompile(`(?i)(?P\d{1,2})[/.-](?P\d{1,2})[/.-](?P\d{4})`),
            func(m map[string]string) string {
                return padZero(m["month"]) + "/" + padZero(m["day"]) + "/" + m["year"]
            },
        },
        // Month DD YYYY(如 January 12 2023)
        {
            regexp.MustCompile(`(?i)(?P[a-z]+)\s+(?P\d{1,2})\w*\s+(?P\d{4})`),
            func(m map[string]string) string {
                mm := monthNumFromName(m["month"])
                if mm == "" {
                    return ""
                }
                return mm + "/" + padZero(m["day"]) + "/" + m["year"]
            },
        },
        // DD Month YYYY(如 12 January 2023)
        {
            regexp.MustCompile(`(?i)(?P\d{1,2})\w*\s+(?P[a-z]+)\s+(?P\d{4})`),
            func(m map[string]string) string {
                mm := monthNumFromName(m["month"])
                if mm == "" {
                    return ""
                }
                return mm + "/" + padZero(m["day"]) + "/" + m["year"]
            },
        },
        // 支持两位年份(需补全)
        {
            regexp.MustCompile(`(?i)(?P\d{1,2})[/.-](?P\d{1,2})[/.-](?P\d{2})`),
            func(m map[string]string) string {
                y := normalizeYear(m["year"])
                return padZero(m["month"]) + "/" + padZero(m["day"]) + "/" + y
            },
        },
    }

    // 对每个模式执行匹配与解析
    for _, p := range patterns {
        matches := p.re.FindAllStringSubmatchIndex([]byte(text), -1)
        for _, match := range matches {
            // 提取命名组内容
            groups := make(map[string]string)
            for i, name := range p.re.SubexpNames() {
                if i == 0 || name == "" {
                    continue // 跳过完整匹配组和空名
                }
                start, end := match[2*i], match[2*i+1]
                if start >= 0 && end >= start {
                    groups[name] = string(text[start:end])
                } else {
                    groups[name] = ""
                }
            }
            // 解析并输出标准化日期
            if date := p.parse(groups); date != "" {
                fmt.Println("✅ Parsed:", date)
            }
        }
    }
}

关键要点总结:

  • 禁止在 | 分支中复用命名组:Go 正则引擎不支持,会导致 SubexpNames() 返回冗余/错位名称;
  • 分治策略更可靠:为每种格式单独编译正则,逻辑隔离、调试简单、易于扩展;
  • 安全提取子匹配:使用 FindAllStringSubmatchIndex + 显式索引,避免 nil panic;
  • 标准化不可少:月份补零、英文月转数字、两位年份智能补全(如 56 → 1956, 23 → 2023);
  • 防御性编程:检查空值、长度、解析错误,避免崩溃(生产环境务必添加错误处理)。

此方案兼顾可读性、健壮性与可维护性,是处理多格式日期解析的 Go 最佳实践。

相关专题

更多
js正则表达式
js正则表达式

php中文网为大家提供各种js正则表达式语法大全以及各种js正则表达式使用的方法,还有更多js正则表达式的相关文章、相关下载、相关课程,供大家免费下载体验。

510

2023.06.20

正则表达式不包含
正则表达式不包含

正则表达式,又称规则表达式,,是一种文本模式,包括普通字符和特殊字符,是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串,通常被用来检索、替换那些符合某个模式的文本。php中文网给大家带来了有关正则表达式的相关教程以及文章,希望对大家能有所帮助。

251

2023.07.05

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

745

2023.07.05

java正则表达式匹配字符串
java正则表达式匹配字符串

在Java中,我们可以使用正则表达式来匹配字符串。本专题为大家带来java正则表达式匹配字符串的相关内容,帮助大家解决问题。

213

2023.08.11

正则表达式空格
正则表达式空格

正则表达式空格可以用“s”来表示,它是一个特殊的元字符,用于匹配任意空白字符,包括空格、制表符、换行符等。本专题为大家提供正则表达式相关的文章、下载、课程内容,供大家免费下载体验。

351

2023.08.31

Python爬虫获取数据的方法
Python爬虫获取数据的方法

Python爬虫可以通过请求库发送HTTP请求、解析库解析HTML、正则表达式提取数据,或使用数据抓取框架来获取数据。更多关于Python爬虫相关知识。详情阅读本专题下面的文章。php中文网欢迎大家前来学习。

293

2023.11.13

正则表达式空格如何表示
正则表达式空格如何表示

正则表达式空格可以用“s”来表示,它是一个特殊的元字符,用于匹配任意空白字符,包括空格、制表符、换行符等。想了解更多正则表达式空格怎么表示的内容,可以访问下面的文章。

234

2023.11.17

正则表达式中如何匹配数字
正则表达式中如何匹配数字

正则表达式中可以通过匹配单个数字、匹配多个数字、匹配固定长度的数字、匹配整数和小数、匹配负数和匹配科学计数法表示的数字的方法匹配数字。更多关于正则表达式的相关知识详情请看本专题下面的文章。php中文网欢迎大家前来学习。

528

2023.12.06

c++ 根号
c++ 根号

本专题整合了c++根号相关教程,阅读专题下面的文章了解更多详细内容。

41

2026.01.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Go 教程
Go 教程

共32课时 | 4.2万人学习

Go语言实战之 GraphQL
Go语言实战之 GraphQL

共10课时 | 0.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号