扩展 openpyxl 对 Excel 中自定义单元格格式的处理

背景

在用 openpyxl 读取 Excel 中的数据时,发现某些单元格读取到的是数字 44712,但打开 Excel 文件却显示的是时间:2022年6月2日

查看单元格格式会发现这个单元格属于自定义格式中的: yyyy"年"m"月"d"日"。这个可以理解,但为啥 openpyxl 读到的不是时间呢?

原因分析

通过查看 openpyxl 的源码,发现其在 openpyxl.styles.numbers.py 中定义了一组格式,如下

BUILTIN_FORMATS = {
    0: 'General',
    1: '0',
    2: '0.00',
    3: '#,##0',
    4: '#,##0.00',
    5: '"$"#,##0_);("$"#,##0)',
    6: '"$"#,##0_);[Red]("$"#,##0)',
    7: '"$"#,##0.00_);("$"#,##0.00)',
    8: '"$"#,##0.00_);[Red]("$"#,##0.00)',
    9: '0%',
    10: '0.00%',
    11: '0.00E+00',
    12: '# ?/?',
    13: '# ??/??',
    14: 'mm-dd-yy',
    15: 'd-mmm-yy',
    16: 'd-mmm',
    17: 'mmm-yy',
    18: 'h:mm AM/PM',
    19: 'h:mm:ss AM/PM',
    20: 'h:mm',
    21: 'h:mm:ss',
    22: 'm/d/yy h:mm',

    37: '#,##0_);(#,##0)',
    38: '#,##0_);[Red](#,##0)',
    39: '#,##0.00_);(#,##0.00)',
    40: '#,##0.00_);[Red](#,##0.00)',

    41: r'_(* #,##0_);_(* \(#,##0\);_(* "-"_);_(@_)',
    42: r'_("$"* #,##0_);_("$"* \(#,##0\);_("$"* "-"_);_(@_)',
    43: r'_(* #,##0.00_);_(* \(#,##0.00\);_(* "-"??_);_(@_)',

    44: r'_("$"* #,##0.00_)_("$"* \(#,##0.00\)_("$"* "-"??_)_(@_)',
    45: 'mm:ss',
    46: '[h]:mm:ss',
    47: 'mmss.0',
    48: '##0.0E+0',
    49: '@', }

其中并没有我们想要的 yyyy"年"m"月"d"日"。

然后通过查阅 Excel 的官方文档中关于 NumberingFormat Class 的解释发现 openpyxl 中定义的这些格式属于通用格式,是不区分语种的。

而我们想要找的这种格式,属于汉语中的特殊格式。对于这种特殊格式, Excel 中仅保存一个格式 ID ,但不保存具体格式的定义,格式的定义会随着所在国家发生变化。

解决方案

解决方案很简单,从文档中找到中文对应的格式 ID 和格式字符串的对应关系,然后采用 hook 的方式将其注入 openpyxl 模块中即可。

代码如下:(注意这些代码需要在导入 openpyxl 模块之前执行)

# 扩展openpyxl的数字格式
# 此处扩展的是中文格式
extra_formats = {
    27: 'yyyy"年"m"月"',
    28: 'm"月"d"日"',
    29: 'm"月"d"日"',
    30: "m-d-yy",
    31: 'yyyy"年"m"月"d"日"',
    32: 'h"时"mm"分"',
    33: 'h"时"mm"分"ss"秒"',
    34: '上午/下午h"时"mm"分"',
    35: '上午/下午h"时"mm"分"ss"秒"',
    36: 'yyyy"年"m"月"',
    #
    50: 'yyyy"年"m"月"',
    51: 'm"月"d"日"',
    52: 'yyyy"年"m"月"',
    53: 'm"月"d"日"',
    54: 'm"月"d"日"',
    55: '上午/下午h"时"mm"分"',
    56: '上午/下午h"时"mm"分"ss"秒"',
    57: 'yyyy"年"m"月"',
    58: 'm"月"d"日"',
}
from openpyxl.styles.numbers import BUILTIN_FORMATS

BUILTIN_FORMATS.update(extra_formats)

参考文档:

https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.numberingformat?view=openxml-2.8.1

posted @ 2022-06-03 00:01  Mr..D  阅读(468)  评论(0编辑  收藏  举报