如何在 PyObjC 中使用 NSISO8601DateFormatOptions

问题描述

调用 NSISO8601DateFormatter 以从 NSDate 格式化时间戳时,我想设置格式选项。 像 the documentation for NSISO8601DateFormatOptions

中描述的 NSISO8601DateFormatWithSpaceBetweenDateAndTime

我似乎不知道如何在 Python 中指定必要的导入,以及如何应用这些选项。我在 the library repo 中找不到 NSISO8601DateFormatter,例如NSTimeZone 但 NSISO8601DateFormatter 导入有效。

我已经尝试使用带注释的行进行导入,但这给出了 ImportError: cannot import name...

我正在运行 macOS 11.2.3 和认 python 2.7.16 的 Mac 上进行测试

示例输出

2021-04-01T12:55:41+02:00 [py_objc_timestamp] timestamp from PyObjC

所需输出的示例:

2021-04-01 12:55:41+02:00 [py_objc_timestamp] timestamp from PyObjC

这是我的测试代码,除了注释掉的行之外,它还能工作。

#!/usr/bin/python

# coding=utf-8

import CoreFoundation
from Foundation import (NSDate,NSISO8601DateFormatter,NSTimeZone)
#from Foundation import kcfISO8601DateFormatWithSpaceBetweenDateAndTime
#from Foundation import NSISO8601DateFormatOptions


def timestamp(dt=None):
    if not dt:
        dt = NSDate.date() # use Now
    the_format = NSISO8601DateFormatter.alloc().init()
    the_format.setTimeZone_(NSTimeZone.localTimeZone())
    #the_format.formatOptions = { kcfISO8601DateFormatWithSpaceBetweenDateAndTime }
    timestamp = the_format.stringFromDate_(dt)
    return timestamp

if __name__ == '__main__':
    print (timestamp() + " [py_objc_timestamp] timestamp from PyObjC")

解决方法

免责声明:手头没有我的 mac,无法测试任何这些

然而,这是您要查找的标题: https://github.com/apple/swift-corelibs-foundation/blob/main/CoreFoundation/Locale.subproj/CFDateFormatter.h#L53

这些是常量。

这是我们如何加载这些:

from Foundation import NSBundle

# Load the framework bundle by its identifier
Foundation_bundle = NSBundle.bundleWithIdentifier_("com.apple.Foundation")
objc.loadBundleVariables(Foundation_bundle,globals(),[('kCFISO8601DateFormatWithSpaceBetweenDateAndTime','@')])

这会将常量作为您现在可以使用的 python 变量加载。

这里有更多信息(ctrl + f loadBundleVariables):
https://pyobjc.readthedocs.io/en/latest/metadata/manual.html

,

看起来那些枚举常量不是在 PyObjC 的 bridgesupport 文件中定义的,但它们的值仍然可以通过在 Foundation 文档中查找来使用。请注意,在创建格式掩码时,all 需要包括各个部分:

kCFISO8601DateFormatWithFullDate = 275
kCFISO8601DateFormatWithFullTime = 1632
kCFISO8601DateFormatWithSpaceBetweenDateAndTime = 128

通常使用按位运算来获取掩码,但将它们相加得到的值为 2035。

另一个问题是设置格式选项。目前这是一个属性,但在这种情况下需要使用 setter 方法(它可能因特定框架而异)。

将所有这些放在一起,不需要注释的导入语句,格式化语句(在 Catalina 和 Big Sur 中测试)变为:

the_format.setFormatOptions_(2035)
,

在其他两个答案中得到 kritred_menace 的帮助后回答我自己的问题,并做了一些我自己的研究。

关于无法从 Foundation 导入 NSISO8601DateFormatOptions 常量,我能找到的最好线索是我正在寻找的常量 kCFISO8601DateFormatWithFractionalSeconds 只出现在 a PyObjC Foundation metadata filealiases 中。相比之下,可以从 CoreFoundation 导入的 kCFPreferencesCurrentHost 出现在该存储库中类似元数据文件的 constants 中。

也许 PyObjC 的未来版本将支持导入这些常量。

这是我更新的测试代码,它演示了我们如何手动定义不同的常量,我添加了所有常量,以防有类似需求的人来这里查看。

#!/usr/bin/python
# coding=utf-8

# File: py_objc_timestamp.py
# Demonstrates how to get ISO8601 / RFC3339 Internet timestamps
# using PyObjC bridge and Foundation framework

import CoreFoundation
from Foundation import (NSDate,NSISO8601DateFormatter,NSTimeZone)

'''
It doesn’t look like NSISO8601DateFormatOptions constants are covered by PyObjC’s
    bridgesupport file - see https://stackoverflow.com/a/66933245/4326287
They're listed not as constants or enums but as aliases in
    https://github.com/ronaldoussoren/pyobjc/blob/master/pyobjc-framework-Cocoa/Lib/CoreFoundation/_metadata.py
'''
try:
    from Foundation import (kCFISO8601DateFormatWithInternetDateTime,\
        kCFISO8601DateFormatWithFullTime)
except:
    '''
    manually defining constants for option flag bits,gleaning constants and values from
    https://github.com/apple/swift-corelibs-foundation/blob/main/CoreFoundation/Locale.subproj/CFDateFormatter.h#L53
    '''
    print("[py_objc_timestamp] import of NSISO8601DateFormatOptions constants from Foundation failed,defining manually")
    kCFISO8601DateFormatWithYear = 1 << 0
    kCFISO8601DateFormatWithMonth = 1 << 1
    kCFISO8601DateFormatWithWeekOfYear = 1 << 2
    kCFISO8601DateFormatWithDay = 1 << 4
    kCFISO8601DateFormatWithTime = 1 << 5
    kCFISO8601DateFormatWithTimeZone = 1 << 6
    kCFISO8601DateFormatWithSpaceBetweenDateAndTime = 1 << 7
    kCFISO8601DateFormatWithDashSeparatorInDate = 1 << 8
    kCFISO8601DateFormatWithColonSeparatorInTime = 1 << 9
    kCFISO8601DateFormatWithColonSeparatorInTimeZone = 1 << 10
    kCFISO8601DateFormatWithFractionalSeconds = 1 << 11
    #kCFISO8601DateFormatWithFullDate = 275
    kCFISO8601DateFormatWithFullDate = kCFISO8601DateFormatWithYear | \
        kCFISO8601DateFormatWithMonth | kCFISO8601DateFormatWithDay | \
        kCFISO8601DateFormatWithDashSeparatorInDate
    #kCFISO8601DateFormatWithFullTime = 1632
    kCFISO8601DateFormatWithFullTime = kCFISO8601DateFormatWithTime | \
        kCFISO8601DateFormatWithColonSeparatorInTime | \
        kCFISO8601DateFormatWithTimeZone | \
        kCFISO8601DateFormatWithColonSeparatorInTimeZone
    # Default - int(1907)
    kCFISO8601DateFormatWithInternetDateTime = kCFISO8601DateFormatWithFullDate | \
        kCFISO8601DateFormatWithFullTime

# define as global,need to setup only once
timestamp_format = NSISO8601DateFormatter.alloc().init()

# I'm a fan of local timezone timestamps rather than UTC,easier in testing software
timestamp_format.setTimeZone_(NSTimeZone.localTimeZone())

# Give us a Internet style,timezone-aware format,space instead of T for readability
timestamp_format_format_options = kCFISO8601DateFormatWithInternetDateTime | \
    kCFISO8601DateFormatWithSpaceBetweenDateAndTime
print("[py_objc_timestamp] NSISO8601DateFormatOptions add up to: %i" % timestamp_format_format_options)
timestamp_format.setFormatOptions_(timestamp_format_format_options)
# If you absolutely must have speed rather than readability,you can strip the
# formatting constants and hardcode the combined value instead
# timestamp_format.setFormatOptions_(2035)

def timestamp(dt=None):
    if not dt:
        dt = NSDate.date() # use now
    timestamp = timestamp_format.stringFromDate_(dt)
    return timestamp

if __name__ == '__main__':
    print (timestamp() + " [py_objc_timestamp] timestamp from PyObjC")

输出示例:

[py_objc_timestamp] import of NSISO8601DateFormatOptions constants from Foundation failed,defining manually
[py_objc_timestamp] NSISO8601DateFormatOptions add up to: 2035
2021-04-04 20:09:43+02:00 [py_objc_timestamp] timestamp from PyObjC