Python实战之数字、日期和时间的高级处理

写在前面

  • 博文为《Python Cookbook》读书后笔记整理
  • 涉及内容包括:
    • 浮点数执行指定精度的舍入运算。
    • 执行精确的浮点数运算
    • 数字的格式化输出
    • 对数值进行取整
    • 二进制、八进制和十六进制整数转化输出
    • 从字节串中打包和解包大整数
    • 复数的数学运算
    • 处理无穷大和NaN
    • 处理大型数组的计算
    • 矩阵和线性代数的计算
    • 计算当前日期做后一个星期几的日期
    • 找出当月的日期范围
    • 将字符串转换为日期
    • 处理涉及到时区的日期问题
  • 理解不足小伙伴帮忙指正

傍晚时分,你坐在屋檐下,看着天慢慢地黑下去,心里寂寞而凄凉,感到自己的生命被剥夺了。当时我是个年轻人,但我害怕这样生活下去,衰老下去。在我看来,这是比死亡更可怕的事。——–王小波


数字、日期和时间的高级处理

嗯,学习发现有些方法函数即使大版本相同,小版本也是有些差距的,这是我学习的版本

┌──[root@liruilongs.Github.io]-[~]
└─$python3
Python 3.6.8 (default, Nov 16 2020, 16:55:22)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] on Linux
Type "help""copyright""credits" or "license" for more information.

浮点数执行指定精度的舍入运算。

「对浮点数执行指定精度的舍入运算。」

对于简单的舍入运算,使用内置的round(value, ndigits)函数即可。

┌──[root@liruilongs.github.io]-[~]
└─$python3
Python 3.6.8 (default, Nov 16 202016:55:22)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] on linux
Type "help""copyright""credits" or "license" for more information.
>>> round(1.231)
1.2
>>> round(1.25361,3)
1.254
>>>

当一个值刚好在两个边界的中间的时候, round 函数返回离它最近的偶数。对 1.5 或者 2.5 的舍入运算都会得到 2。

>>> round(1.5,0)
2.0
>>> round(2.5,0)
2.0
>>>

传给 round() 函数的 ndigits 参数可以是负数,舍入运算会作用在十位、百位、千位等上面

>>> a = 1627731
>>> round(a, -1)
1627730
>>> round(a, -2)
1627700
>>>

不要将舍入和格式化输出搞混淆了。如果你的目的只是简单的输出一定宽度的数,你不需要使用round()函数。而仅仅只需要在格式化的时候指定精度即可,

>>> x = 1.23456
>>> format(x, '0.2f')
'1.23'
>>> format(x, '0.4f')
'1.2346'
>>> 'value is {:0.3f}'.format(x)
'value is 1.235'
>>>

可以看到,输出的数据并不相同,一个是字符串,一个是int等数字类型

>>> type(format(1.5'0.0f'))
<class 'str'>
>>> type(round(a, -1))
<class 'int'>
>>> type(round(1.25361,3))
<class 'float'>
>>>

不要试着去舍入浮点值来” 修正” 表面上看起来正确的问题。大多数使用到浮点的程序,没有必要也不推荐这样做.

>>> 2.1 + 4.2
6.300000000000001
>>> round(2.1 + 4.2,2)
6.3
>>>

执行精确的浮点数运算

「要对浮点数执行精确的计算操作,并且不希望有任何小误差的出现。」

浮点数的一个普遍问题是它们并不能精确的表示十进制数。并且,即使是最简单的数学运算也会产生小的误差

就那上面的Demo来讲

>>> 2.1 + 4.2
6.300000000000001
>>> round(2.1 + 4.2,2)
6.3
>>> (2.1 + 4.2) == 6.3
False
>>>

错误是由底层 CPU 和 IEEE 754 标准通过自己的浮点单位去执行算术时的特征。由于 Python 的浮点数据类型使用底层表示存储数据,因此你没办法去避免这样的误差。在Java里也出现同样的问题

public static void main(String[] args) {
       System.out.println(2.1+4.2);
   }
// 6.300000000000001   

如果你想更加精确 (并能容忍一定的性能损耗),你可以使用 decimal 模块

>>> from decimal import Decimal
>>> a = Decimal('4.2')
>>> b = Decimal('2.1')
>>> a + b
Decimal('6.3')
>>> print(a + b)
6.3
>>> (a + b) == Decimal('6.3')
True
>>>

decimal 模块的一个主要特征是允许你控制计算的每一方面,包括数字位数和四舍五入运算。为了这样做,你先得创建一个本地上下文并更改它的设置

>>> from decimal import localcontext
>>> a = Decimal('1.3')
>>> b = Decimal('1.7')
>>> print(a / b)
0.7647058823529411764705882353
>>> with localcontext() as ctx:
... ctx.prec = 3
... print(a / b)
...
0.765
>>> with localcontext() as ctx:
... ctx.prec = 50
... print(a / b)
...
0.76470588235294117647058823529411764705882352941176
>>>

decimal 模块实现了 IBM 的” 通用小数运算规范”,Python 新手会倾向于使用 decimal 模块来处理浮点数的精确运算。但是要根据业务需求来处理,decimal 模块主要用在涉及到金融的领域。

  • 原生的浮点数计算要快的多
  • 在真实世界中很少会要求精确到普通浮点数能提供的 17 位精度

其他的一些误差,大数和小数的加法运算(在Java里也出现同样的问题)

>>> nums = [1.23e+181-1.23e+18]
>>> sum(nums)
0.0
>>> 1.23e+18 + 1 + -1.23e+18
0.0
>>> 1.23e+18 +  -1.23e+18 + 1
1.0
>>>
   public static void main(String[] args) {
       System.out.println(1.23e+18 + 1 + -1.23e+18);
   } // 0.0

py的解决办法

>>> import math
>>> math.fsum(nums)
1.0
>>>

数字的格式化输出

「你需要将数字格式化后输出,并控制数字的位数、对齐、千位分隔符和其他的细节。」

格式化输出单个数字的时候,可以使用内置的format()函数,这个前面也有好的Demo,不多说

>>> x = 1234.56789
>>> format(x, '0.2f')
'1234.57'
>>> format(x, '>10.1f')
'    1234.6'
>>> format(x, '<10.1f')
'1234.6    '
>>> format(x, '0>10.1f')
'00001234.6'
>>> format(x, '^10.1f')
'  1234.6  '
>>> format(x, ',')
'1,234.56789'
>>> format(x, '0,.1f')
'1,234.6'
>>>

使用指数记法,将 f 改成 e 或者 E(取决于指数输出的大小写形式)

>>> format(x, 'e')
'1.234568e+03'
>>> format(x, '0.2E')
'1.23E+03'
>>>

同时指定宽度和精度的一般形式是 '[<>ˆ]?width[,]?(.digits)?' ,其中 width 和 digits 为整数,?代表可选部分。同样的格式也被用在字符串format()方法中。

>>> 'The value is {:0,.2f}'.format(x)
'The value is 1,234.57'
>>>

format() 函数 同时适用于浮点数和 decimal模块中的 Decimal 数字对象。

>>> x
1234.56789
>>> format(x, '0.1f')
'1234.6'
>>> format(-x, '0.1f')
'-1234.6'
>>>

包含千位符的格式化跟本地化没有关系。如果你需要根据地区来显示千位符,你需要自己去调查下 locale 模块中的函数了。你同样也

可以使用字符串的 translate()方法来交换千位符。

>>> swap_separators = { ord('.'):',', ord(','):'.' }
>>> format(x, ',').translate(swap_separators)
'1.234,56789'
>>> x
1234.56789
>>>

也可以直接使用% 来格式化数字的

>>> x
1234.56789
>>> '%0.2f' % x
'1234.57'
>>> '%10.1f' % x
'    1234.6'
>>> '%-10.1f' % x
'1234.6    '

二进制、八进制和十六进制整数转化输出

「转换或者输出使用二进制,八进制或十六进制表示的整数。」

为了将整数转换为二进制、八进制或十六进制文本串,可以分别使用bin() ,oct() 或 hex()函数:

>>> x = 1234
>>> bin(x)
'0b10011010010'
>>> oct(x)
'0o2322'
>>> hex(x)
'0x4d2'
>>>

不想输出 0b , 0o 或者 0x 的前缀的话,可以使用format()函数

>>> format(x, 'b')
'10011010010'
>>> format(x, 'o')
'2322'
>>> format(x, 'x')
'4d2'
>>>

整数是有符号的,所以如果你在处理负数的话,输出结果会包含一个负号

>>> x = -1234
>>> format(x, 'b')
'-10011010010'
>>> format(x, 'x')
'-4d2'
>>>

如果你想产生一个无符号值,你需要增加一个指示最大位长度的值。比如为了显示32 位的值,

>>> x = -1234
>>> format(2**32 + x, 'b')
'11111111111111111111101100101110'
>>> format(2**32 + x, 'x')
'fffffb2e'
>>>

为了以不同的进制转换整数字符串,简单的使用带有进制的int()函数即可:

>>> int('4d2'16)
1234
>>> int('10011010010'2)
1234
>>>

Python 指定八进制数的语法跟其他语言稍有不同

>>> import os
>>> os.chmod('script.py'0755)
  File "<stdin>", line 1
    os.chmod('script.py'0755)
                             ^
SyntaxError: invalid token
>>> os.chmod('app.py'0o755)
>>

字节字符串到大整数的相互转化

「你有一个字节字符串并想将它解压成一个整数。或者,你需要将一个大整数转换为一个字节字符串。」

大整数和字节字符串之间的转换操作并不常见,一些场景也会用到,IPv6 网络地址使用一个 128 位的整数表示。如果你要从一个数据记录中提取这样的值的时候

处理一个拥有 128 位长的 16 个元素的字节字符串

>>> data = b'x00x124Vx00xx90xabx00xcdxefx01x00#x004'
>>> len(data)
16
>>> int.from_bytes(data, 'little')
69120565665751139577663547927094891008
>>> int.from_bytes(data, 'big')
94522842520747284487117727783387188
>>>

为了将一个大整数转换为一个字节字符串,使用int.to_bytes()方法,并像下面这样指定字节数和字节顺序:

>>> x = 94522842520747284487117727783387188
>>> x.to_bytes(16'big')
b'x00x124Vx00xx90xabx00xcdxefx01x00#x004'
>>> x.to_bytes(16'little')
b'4x00#x00x01xefxcdx00xabx90xx00V4x12x00'
>>>

这里字节顺序规则 (little 或 big) 仅仅指定了构建整数时的字节的低位高位排列方式

>>> x = 0x01020304
>>> x.to_bytes(4'big')
b'x01x02x03x04'
>>> x.to_bytes(4'little')
b'x04x03x02x01'
>>>

如果你试着将一个整数打包为字节字符串,那么它就不合适了,你会得到一个错误。如果需要的话,你可以使用 int.bit_length() 方法来决定需要多少字节位来存储这个值。(** 表示乘方,divmod() 函数把除数和余数运算结果结合起来,返回一个包含商和余数的元组(a // b, a % b)。)

>>> x = 523 ** 23
>>> x
335381300113661875107536852714019056160355655333978849017944067
>>> x.to_bytes(16'little')
Traceback (most recent call last):
File "<stdin>", line 1in <module>
OverflowError: int too big to convert
>>> x.bit_length()
208
>>> nbytes, rem = divmod(x.bit_length(), 8)
>>> if rem:
... nbytes += 1
...
>>>
>>> x.to_bytes(nbytes, 'little')
b'x03Xxf1x82iTx96xacxc7cx16xf3xb9xcfx18xeexecx91xd1x98xa2xc8xd9Rxb5xd0'
>>>

复数的数学运算

「使用复数来执行一些计算操作。」

复数可以用使用函数complex(real, imag)或者是带有后缀j的浮点数来指定。

>>> a = complex(24)
>>> b = 3 - 5j
>>> a
(2+4j)
>>> b
(3-5j)
>>>

对应的实部、虚部和共轭复数可以很容易的获取。

>>> a.real
2.0
>>> a.imag
4.0
>>> a.conjugate()
(2-4j)
>>>

常见的数学四则运算

>>> a + b
(5-1j)
>>> a * b
(26+2j)
>>> a / b
(-0.4117647058823529+0.6470588235294118j)
>>> abs(a)
4.47213595499958
>>>

如果要执行其他的复数函数比如正弦、余弦或平方根,使用 cmath 模块

>>> import cmath
>>> cmath.sin(a)
(24.83130584894638-11.356612711218174j)
>>> cmath.cos(a)
(-11.36423470640106-24.814651485634187j)
>>> cmath.exp(a)
(-4.829809383269385-5.5920560936409816j)
>>>

Python 中大部分与数学相关的模块都能处理复数。使用 numpy 很容易的构造一个复数数组并在这个数组上执行各种操作

>>> import numpy as np
>>> a = np.array([2+3j4+5j6-7j8+9j])
>>> a
array([ 2.+3.j4.+5.j6.-7.j8.+9.j])
>>> a + 2
array([ 4.+3.j6.+5.j8.-7.j10.+9.j])
>>> np.sin(a)
array([ 9.15449915 -4.16890696j-56.16227422 -48.50245524j,
-153.20827755-526.47684926j4008.42651446-589.49948373j])
>>>

Python 的标准数学函数确实情况下并不能产生复数值,因此你的代码中不可能会出现复数返回值

>>> import math
>>> math.sqrt(-1)
Traceback (most recent call last):
File "<stdin>", line 1in <module>
ValueError: math domain error
>>>

如果你想生成一个复数返回结果,你必须显示的使用 cmath 模块

>>> import cmath
>>> cmath.sqrt(-1)
1j
>>>

处理无穷大和NaN

「你想创建或测试正无穷、负无穷或 NaN(非数字) 的浮点数。」

Python 并没有特殊的语法来表示这些特殊的浮点值,但是可以使用float()来创建它们。比如:

>>> a = float('inf')
>>> b = float('-inf')
>>> c = float('nan')
>>> a
inf
>>> b
-inf
>>> c
nan
>>>

使用 math.isinf() 和 math.isnan() 函数来测试

>>> math.isinf(a)
True
>>> math.isnan(c)
True
>>>

无穷大数在执行数学计算的时候会传播

>>> a = float('inf')
>>> a + 45
inf
>>> a * 10
inf
>>> 10 / a
0.0
>>>

有些操作时未定义的并会返回一个 NaN 结果

>>> a = float('inf')
>>> a/a
nan
>>> b = float('-inf')
>>> a + b
nan
>>>

NaN 值会在所有操作中传播,而不会产生异常

>>> c = float('nan')
>>> c + 23
nan
>>> c / 2
nan
>>> c * 2
nan
>>> math.sqrt(c)
nan
>>>

NaN 之间的比较操作总是返回 False

>>> c = float('nan')
>>> d = float('nan')
>>> c == d
False
>>> is d
False
>>>

有时候程序员想改变 Python 默认行为,在返回无穷大或 NaN 结果的操作中抛出异常。fpectl 模块可以用来改变这种行为,但是它在标准的 Python 构建中并没有被启用,它是平台相关的,并且针对的是专家级程序员。

分数运算

「在一个允许接受分数形式的测试单位并以分数形式执行运算的程序中,直接使用分数可以减少手动转换为小数或浮点数的工作」

fractions 模块可以被用来执行包含分数的数学运算。

>>> from fractions import Fraction
>>> a = Fraction(54)
>>> b = Fraction(716)
>>> print(a + b)
27/16
>>> print(a * b)
35/64
>>> c = a * b
>>> c.numerator
35
>>> c.denominator
64
>>> float(c)
0.546875
>>>

limit_denominator(max_denominator) 方法可以找到并返回一个 Fraction 使得其值最接近 self 并且分母不大于 max_denominator。

as_integer_ratio()用于将浮点数转化为分数

>>> print(c.limit_denominator(8))
4/7
>>> x = 3.75
>>> y = Fraction(*x.as_integer_ratio())
>>> y
Fraction(154)
>>>

处理大型数组的计算

「在大数据集 (比如数组或网格) 上面执行计算。」

到数组的重量级运算操作,可以使用 NumPy 库。NumPy 的一个主要特征是它会给 Python 提供一个数组对象,相比标准的 Python 列表而已更适合用来做数学运算。

额,数学忘光了….这里先记录下,list运算并没有遵守矩阵运算规则,肯定不能直接用

>>> # Python lists
>>> x = [1234]
>>> y = [5678]
>>> x * 2
[12341234]
>>> x + 10
Traceback (most recent call last):
File "<stdin>", line 1in <module>
TypeError: can only concatenate list (not "int") to list
>>> x + y
[12345678]
>>> # Numpy arrays
>>> import numpy as np
>>> ax = np.array([1234])
>>> ay = np.array([5678])
>>> ax * 2

NumPy 还为数组操作提供了大量的通用函数,这些函数可以作为 math 模块中类似函数的替代

>>> np.sqrt(ax)
array([ 1. , 1.414213561.732050812. ])
>>> np.cos(ax)
array([ 0.54030231-0.41614684-0.9899925 , -0.65364362])
>>>

底层实现中, NumPy 数组使用了 C 或者 Fortran 语言的机制分配内存。也就是说,它们是一个非常大的连续的并由同类型数据组成的内存区域。所以,你可以构造一个比普通 Python 列表大的多的数组。比如,如果你想构造一个 10,000*10,000浮点数二维网格,很轻松

>>> import numpy as np
>>> grid = np.zeros(shape=(10000,10000), dtype=float)
>>> grid
array([[0.0.0., ..., 0.0.0.],
       [0.0.0., ..., 0.0.0.],
       [0.0.0., ..., 0.0.0.],
       ...,
       [0.0.0., ..., 0.0.0.],
       [0.0.0., ..., 0.0.0.],
       [0.0.0., ..., 0.0.0.]])
>>>

所有的普通操作是遵循矩阵运算法则的:

>>> grid += 10
>>> grid
array([[10.10.10., ..., 10.10.10.],
       [10.10.10., ..., 10.10.10.],
       [10.10.10., ..., 10.10.10.],
       ...,
       [10.10.10., ..., 10.10.10.],
       [10.10.10., ..., 10.10.10.],
       [10.10.10., ..., 10.10.10.]])

关于 NumPy,它扩展 Python 列表的索引功能 – 特别是对于多维数组,这里和matlab的数组语法有些类似

>>> a = np.array([[1234], [5678], [9101112]])
>>> a
array([[ 1234],
       [ 5678],
       [ 9101112]])
>>> # Select row 1
>>> a[1]
array([5678])
>>> # Select column 1
>>> a[:,1]
array([ 2610])
>>> # 选择子区域并进行修改
>>> a[1:31:3]
array([[ 67],
[1011]])
>>> a[1:31:3] += 10
>>> a
array([[ 1234],
      [ 516178],
      [ 9202112]])
>>> # 在所有行的操作上广播行向量  
>>> a + [100101102103]
array([[101103105107],
      [105117119111],
      [109121123115]])
>>> a
array([[ 1234],
516178],
9202112]])
>>> # 数组的条件赋值
>>> np.where(a < 10, a, 10)
array([[ 1234],
      [ 510108],
      [ 9101010]])
>>>

矩阵和线性代数的计算

「执行矩阵和线性代数运算,比如矩阵乘法、寻找行列式、求解线性方程组等。」

这些一般做数据分析会用到,但是数据分析需要有一定的数学基础,时间关系这里简单了解下,好后悔之前没好好学,都忘光了…

>>> import numpy as np
>>> m = np.matrix([[1,-2,3],[0,4,5],[7,8,-9]])
>>> m
matrix([[ 1-23],
045],
78-9]])
>>> # Return transpose
>>> m.T
matrix([[ 107],
[-248],
35-9]])
>>> # Return inverse
>>> m.I
matrix([[ 0.33043478-0.026086960.09565217],
[-0.152173910.130434780.02173913],
0.121739130.09565217-0.0173913 ]])
>>> # Create a vector and multiply
>>> v = np.matrix([[2],[3],[4]])
>>> v
matrix([[2],
[3],
[4]])
>>> m * v
matrix([[ 8],
[32],
2]])
>>>

numpy.linalg子包中找到更多的操作函数

>>> import numpy.linalg
>>> # Determinant
>>> numpy.linalg.det(m)
-229.99999999999983
>>> # Eigenvalues
>>> numpy.linalg.eigvals(m)
array([-13.114743122.759561546.35518158])
>>> # Solve for x in mx = v
>>> x = numpy.linalg.solve(m, v)
>>> x
matrix([[ 0.96521739],
0.17391304],
0.46086957]])
>>> m * x
matrix([[ 2.],
3.],
4.]])
>>> v
matrix([[2],
[3],
[4]])
>>>

随机选择

「从一个序列中随机抽取若干元素,或者想生成几个随机数。」

random 模块有大量的函数用来产生随机数和随机选择元素。比如,要想从一个序列中随机的抽取一个元素,可以使用 random.choice()

>>> import random
>>> values = [123456]
>>> random.choice(values)
2
>>> random.choice(values)
3
>>> random.choice(values)
1
>>> random.choice(values)
4
>>> random.choice(values)
6
>>>

为了提取出 N 个不同元素的样本用来做进一步的操作,可以使用 random.sample()

>>> random.sample(values, 2)
[62]
>>> random.sample(values, 2)
[43]
>>> random.sample(values, 3)
[431]
>>> random.sample(values, 3)
[541]
>>>

只是想打乱序列中元素的顺序,可以使用random.shuffle()

>>> random.shuffle(values)
>>> values
[246531]
>>> random.shuffle(values)
>>> values
[352164]
>>>

生成随机整数,请使用 random.randint()

>>> random.randint(0,10)
2
>>> random.randint(0,1000)
452

为了生成 0 到 1 范围内均匀分布的浮点数,使用random.random()

>>> random.random()
0.9406677561675867
>>> random.random()
0.133129581343897
>>> random.random()
0.4144991136919316
>>>

要获取 N 位随机位 (二进制) 的整数,使用 random.getrandbits() :

>>> random.getrandbits(200)
335837000776573622800628485064121869519521710558559406913275
>>>

random 模块使用 Mersenne Twister 算法来计算生成随机数。这是一个确定性算法,但是你可以通过random.seed()函数修改初始化种子

random.seed() # Seed based on system time or os.urandom()
random.seed(12345# Seed based on integer given
random.seed(b'bytedata'# Seed based on byte data

除了上述介绍的功能,random 模块还包含基于均匀分布高斯分布和其他分布的随机数生成函数。比如,random.uniform()计算均匀分布随机数, random.gauss()计算正态分布随机数。

。。。。概率论的知识,唉,没好好听课

random 模块中的函数不应该用在和密码学相关的程序中。,可以使用 ssl 模块中相应的函数。比如, ssl.RAND_bytes() 可以用来生成一个安全的随机字节序列。

基本的日期与时间转换

「你需要执行简单的时间转换,比如天到秒,小时到分钟等的转换。」

为了执行不同时间单位的转换和计算,请使用 datetime 模块。比如,为了表示一个时间段,可以创建一个 timedelta 实例,可以直接转化天到秒,但是小时的话需要运算

>>> a = timedelta(days=2, hours=6)
>>> b = timedelta(hours=4.5)
>>> c = a + b
>>> c
datetime.timedelta(237800)
>>> c.days
2
>>> c.hours
Traceback (most recent call last):
  File "<stdin>", line 1in <module>
AttributeError: 'datetime.timedelta' object has no attribute 'hours'
>>>  c.seconds
  File "<stdin>", line 1
    c.seconds
    ^
IndentationError: unexpected indent
>>> c.seconds
37800
>>> c.seconds / 3600
10.5
>>> c.total_seconds() / 3600
58.5
>>>

表示指定的日期和时间,先创建一个datetime实例然后使用标准的数学运算来操作它们

>>> from datetime import datetime, date, timedelta
>>> from datetime import datetime
>>> a  = datetime(2022,5,4)
>>> a
datetime.datetime(20225400)
>>> a + timedelta(days=20)
datetime.datetime(202252400)
>>> print(a + timedelta(days=20))
2022-05-24 00:00:00
>>> a - datetime(2021,1,1)
datetime.timedelta(488)
>>> (a - datetime(2021,1,1)).days
488
>>> datetime.today()
datetime.datetime(202254122129867955)
>>> print(datetime.today())
2022-05-04 12:21:40.399207
>>> print(datetime.today() + timedelta(minutes=10))
2022-05-04 12:32:05.150013
>>>

需要注意的是 datetime 会自动处理闰年,基本的日期和时间处理问题, datetime 模块以及足够了,需要更加复杂的日期操作,可以考虑使用 dateutil 模块,许多类似的时间计算可以使用 dateutil.relativedelta() 函数代替

┌──[root@liruilongs.github.io]-[~]
└─$ python3 -m  pip install python-dateutil
>>> a = datetime(2012923)
>>> a + timedelta(months=1)
Traceback (most recent call last):
File "<stdin>", line 1in <module>
TypeError: 'months' is an invalid keyword argument for this function
>>>
>>> from dateutil.relativedelta import relativedelta
>>> a + relativedelta(months=+1)
datetime.datetime(2012102300)
>>> a + relativedelta(months=+4)
datetime.datetime(201312300)
>>>
>>> # Time between two dates
>>> b = datetime(20121221)
>>> d = b - a
>>> d
datetime.timedelta(89)
>>> d = relativedelta(b, a)
>>> d
relativedelta(months=+2, days=+28)
>>> d.months
2
>>> d.days
28
>>>

计算上周5的日期

「你需要查找星期中某一天最后出现的日期,比如星期五。」

Python 的 datetime 模块中有工具函数和类可以帮助你执行这样的计算

先将开始日期目标日期映射到星期数组的位置上 (星期一索引为 0),然后通过模运算计算出目标日期要经过多少天才能到达开始日期。然后用开始日期减去那个时间差即得到结果日期

from datetime import datetime, timedelta
weekdays = ['Monday''Tuesday''Wednesday''Thursday',
            'Friday''Saturday''Sunday']


def get_previous_byday(dayname, start_date=None):
    if start_date is None:
        start_date = datetime.today()
    day_num = start_date.weekday()
    day_num_target = weekdays.index(dayname)
    days_ago = (7 + day_num - day_num_target) % 7  #这里不是特别懂
    if days_ago == 0:
        days_ago = 7
    target_date = start_date - timedelta(days=days_ago)
    return target_date

可选的 start_date 参数可以由另外一个 datetime 实例来提供

datetime.today()

print(get_previous_byday('Monday'))
get_previous_byday('Sunday', datetime(20221221))

找出当月的日期范围

「需要在当前月份中循环每一天,想找到一个计算这个日期范围的高效方法。」

需要事先构造一个包含所有日期的列表。你可以先计算出开始日期和结束日期,然后在你步进的时候使用 datetime.timedelta 对象递增这个日期变量即可。


from datetime import datetime, date, timedelta
import calendar


def get_month_range(start_date=None):
    if start_date is None:
        start_date = date.today().replace(day=1)
    _, days_in_month = calendar.monthrange(start_date.year, start_date.month)
    end_date = start_date + timedelta(days=days_in_month)
    return (start_date, end_date)

a_day = timedelta(days=1)
first_day, last_day = get_month_range()
while first_day < last_day:
    print(first_day)
    first_day += a_day

对应月份第一天的日期,使用 date 或 datetime 对象的 replace() 方法简单的将days属性设置成1即可。replace() 方法一个好处就是它会创建和你开始传入对象类型相同的对象

使用 calendar.monthrange() 函数来找出该月的总天数

将字符串转换为日期

「应用程序接受字符串格式的输入,但是你想将它们转换为 datetime 对象以便在上面执行非字符串操作」

>>> from datetime import datetime
>>> text = '2012-09-20'
>>> y = datetime.strptime(text, '%Y-%m-%d')
>>> z = datetime.now()
>>> print( datetime.now() -  datetime.strptime(text, '%Y-%m-%d'))
19:37:02.113440
>>>

datetime.strptime() 方法支持很多的格式化代码,比如 %Y 代表 4 位数年份, %m 代表两位数月份,strptime() 的性能要比你想象中的差很多,如果你已经知道所以日期格式是YYYY-MM-DD,你可以像下面这样 实现一个解析函数.

from datetime import datetime
def parse_ymd(s):
  year_s, mon_s, day_s = s.split('-')
  return datetime(int(year_s), int(mon_s), int(day_s))

处理涉及到时区的日期问题

「你有一个安排在 2012 年 12 月 21 日早上 9:30 的电话会议,地点在芝加哥。而你的朋友在印度的班加罗尔,那么他应该在当地时间几点参加这个会议呢?」

几乎所有涉及到时区的问题,你都应该使用 pytz 模块。这个包提供了 Olson 时区数据库,它是时区信息的事实上的标准,在很多语言和操作系统里面都可以找到。

pytz 模块一个主要用途是将 datetime 库创建的简单日期对象本地化

>>> from datetime import datetime
>>> from pytz import timezone
>>> datetime.today()
datetime.datetime(202254185856843102)
>>> print(datetime.today())
2022-05-04 18:59:12.433695
>>> print(central.localize(datetime.today()).astimezone(timezone('US/Central')))
2022-05-04 06:04:33.518289-05:00

本地化日期上执行计算,你需要特别注意夏令时转换和其他细节。这个我们不涉及,先不看

处理本地化日期的通常的策略先将所有日期转换为 UTC 时间

>>> import pytz
>>> utc_d = datetime.today().astimezone(pytz.utc)
>>> utc_d
datetime.datetime(202254111135701740, tzinfo=<UTC>)
>>> print(utc_d)
2022-05-04 11:11:35.701740+00:00
>>> from datetime import datetime, date, timedelta
>>> later_utc = utc_d + timedelta(hours=8)
>>> print(later_utc)
2022-05-04 19:11:35.701740+00:00
from datetime import datetime
from datetime import timedelta
from datetime import timezone
 
SHA_TZ = timezone(
    timedelta(hours=8),
    name='Asia/Shanghai',
)
 
# 协调世界时
utc_now = datetime.utcnow().replace(tzinfo=timezone.utc)
print("UTC:")
print(utc_now, utc_now.time())
print(utc_now.date(), utc_now.tzname())
 
# 北京时间
beijing_now = utc_now.astimezone(SHA_TZ)
print("Beijing:")
print(beijing_now.strftime('%H_%M_%S'))
print(beijing_now, beijing_now.time())
print(beijing_now.date(), beijing_now.tzname())
 
# 系统默认时区
local_now = utc_now.astimezone()
print("Default:")
print(local_now, local_now.time())
print(local_now.date(), local_now.tzname())


原文始发于微信公众号(山河已无恙):Python实战之数字、日期和时间的高级处理

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/60011.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!