日期时间工具 - DateUtil / DateTime¶
由来¶
Java 中的日期操作一直为人诟病,Hutool 的 DateUtil 极大简化了日期操作。Python 版基于 pendulum 库实现,保留了 Hutool 的 API 风格。
DateTime¶
DateTime 是对 pendulum.DateTime 的封装,支持所有 pendulum 的日期操作:
from hutool import DateUtil, DateTime
dt = DateUtil.date() # 获取当前 DateTime
工厂方法¶
from hutool import DateTime
# 从年月日创建
dt = DateTime.of_date(2024, 6, 15)
# 从年月日时分秒创建
dt = DateTime.of_datetime(2024, 6, 15, 14, 30, 45)
# 从格式字符串解析
dt = DateTime.of_pattern("2024-06-15 14:30:45", "yyyy-MM-dd HH:mm:ss")
# UTC 当前时间
dt = DateTime.now_utc()
# 从时间戳创建
dt = DateTime.of_epoch(1700000000000) # 毫秒
dt = DateTime.of_epoch(1700000000, False) # 秒
查询方法¶
dt = DateTime.of_date(2024, 1, 13)
dt.is_weekend() # True(周六)
dt.is_am() # True(hour < 12)
dt.is_pm() # False
dt.is_past() # True(已过去)
dt.is_future() # False
dt.is_leap_year() # True(2024 闰年)
dt.is_last_day_of_month() # False
# 比较
dt1 = DateTime.of_date(2024, 1, 1)
dt2 = DateTime.of_date(2024, 6, 1)
dt1.is_before(dt2) # True
dt2.is_after(dt1) # True
dt1.is_between(dt1, dt2) # True
# 属性
dt = DateTime.of_date(2024, 2, 1)
dt.length_of_month() # 29
dt.length_of_year() # 366
偏移方法¶
dt = DateTime.of_date(2024, 1, 15)
dt.offset_day(1) # 2024-01-16
dt.offset_week(2) # 2024-01-29
dt.offset_month(-1) # 2023-12-15
dt.offset_year(1) # 2025-01-15
开始/结束时间(实例方法)¶
dt = DateTime.of_datetime(2024, 6, 15, 14, 30, 45)
dt.begin_of_hour() # 14:00:00
dt.end_of_hour() # 14:59:59
dt.begin_of_minute() # 14:30:00
dt.end_of_minute() # 14:30:59
dt.begin_of_second() # 14:30:45
dt.end_of_second() # 14:30:45.999999
格式化与时区¶
dt = DateTime.of_datetime(2024, 6, 15, 14, 30, 45)
dt.format("YYYY-MM-DD") # "2024-06-15"
dt.to_local_datetime_str() # "2024-06-15 14:30:45"
# 时区转换
dt_utc = DateTime.now_utc()
dt_sh = dt_utc.with_timezone("Asia/Shanghai")
dt_sh.timezone_name() # "Asia/Shanghai"
DateUtil¶
获取当前时间¶
DateUtil.now() # "2024-01-01 12:00:00"
DateUtil.today() # "2024-01-01"
DateUtil.current() # 毫秒时间戳
DateUtil.current_seconds() # 秒时间戳
解析¶
dt = DateUtil.parse("2024-01-01")
dt = DateUtil.parse("2024-01-01 12:00:00")
dt = DateUtil.parse("20240101", "yyyyMMdd")
dt = DateUtil.parse_iso8601("2024-01-01T12:00:00Z")
格式化¶
dt = DateUtil.parse("2024-01-01 12:30:45")
DateUtil.format(dt, "yyyy/MM/dd") # "2024/01/01"
DateUtil.format_date(dt) # "2024-01-01"
DateUtil.format_time(dt) # "12:30:45"
DateUtil.format_date_time(dt) # "2024-01-01 12:30:45"
日期部分¶
dt = DateUtil.parse("2024-03-15 14:30:00")
DateUtil.year(dt) # 2024
DateUtil.month(dt) # 3
DateUtil.day_of_month(dt) # 15
DateUtil.hour(dt) # 14
DateUtil.minute(dt) # 30
DateUtil.second(dt) # 0
DateUtil.day_of_week(dt) # 5(周五)
DateUtil.quarter(dt) # 1(第一季度)
DateUtil.is_weekend(dt) # False
日期偏移¶
dt = DateUtil.parse("2024-01-15")
DateUtil.offset_day(dt, 7) # 2024-01-22
DateUtil.offset_month(dt, 2) # 2024-03-15
DateUtil.offset_year(dt, 1) # 2025-01-15
DateUtil.offset_hour(dt, 3) # 加3小时
DateUtil.tomorrow() # 明天
DateUtil.yesterday() # 昨天
DateUtil.next_week() # 下周
DateUtil.last_month() # 上月
开始/结束时间¶
dt = DateUtil.parse("2024-03-15 14:30:00")
DateUtil.begin_of_day(dt) # 2024-03-15 00:00:00
DateUtil.end_of_day(dt) # 2024-03-15 23:59:59
DateUtil.begin_of_month(dt) # 2024-03-01 00:00:00
DateUtil.end_of_month(dt) # 2024-03-31 23:59:59
DateUtil.begin_of_year(dt) # 2024-01-01 00:00:00
DateUtil.end_of_year(dt) # 2024-12-31 23:59:59
时间差¶
start = DateUtil.parse("2024-01-01")
end = DateUtil.parse("2024-03-15")
DateUtil.between_day(start, end) # 74
DateUtil.between_month(start, end) # 2
DateUtil.between_year(start, end) # 0
DateUtil.format_between(start, end) # "2个月14天"
截断¶
from datetime import date, datetime
# 截断到天/月/年/季度/周/小时
DateUtil.date_trunc("day", datetime(2024, 3, 15, 14, 30))
# datetime(2024, 3, 15, 0, 0)
DateUtil.date_trunc("month", date(2024, 3, 15))
# date(2024, 3, 1)
DateUtil.date_trunc("year", date(2024, 6, 15))
# date(2024, 1, 1)
DateUtil.date_trunc("quarter", date(2024, 5, 15))
# date(2024, 4, 1)
DateUtil.date_trunc("week", date(2024, 3, 15))
# date(2024, 3, 11)(周一)
时间跨度¶
# 获取周号
DateUtil.get_week(date(2024, 1, 1)) # (1, 2024)
# 获取月首末日
first, last = DateUtil.get_monthspan(date(2024, 3, 15))
# first=date(2024, 3, 1), last=date(2024, 3, 31)
# 获取周首末日
monday, sunday = DateUtil.get_weekspan(date(2024, 3, 15))
# monday=date(2024, 3, 11), sunday=date(2024, 3, 17)
# 获取季度首末日
first, last = DateUtil.get_quarterspan(date(2024, 5, 15))
# first=date(2024, 4, 1), last=date(2024, 6, 30)
# 获取年首末日
first, last = DateUtil.get_yearspan(date(2024, 6, 15))
# first=date(2024, 1, 1), last=date(2024, 12, 31)
# getDaySpan — 日跨度
DateUtil.get_day_span(date(2024, 1, 1), date(2024, 1, 31)) # 30
# getMonthSpan — 月跨度
DateUtil.get_month_span(date(2024, 1, 1), date(2024, 6, 1)) # 5
月份加减¶
DateUtil.month_add(date(2024, 1, 15), 1) # date(2024, 2, 15)
DateUtil.month_add(date(2024, 1, 31), 1) # date(2024, 2, 29)(月末溢出)
DateUtil.month_add(date(2024, 3, 15), -2) # date(2024, 1, 15)
DateUtil.month_add(date(2024, 11, 15), 3) # date(2025, 2, 15)(跨年)
RFC 格式¶
from datetime import datetime
# RFC 3339
DateUtil.rfc3339_date(datetime(2024, 3, 15, 14, 30)) # "2024-03-15T14:30:00+08:00"
DateUtil.rfc3339_date_parse("2024-03-15T14:30:00") # datetime 对象
# RFC 2616(HTTP 头日期格式)
DateUtil.rfc2616_date(datetime(2024, 3, 15, 14, 30)) # "Fri, 15 Mar 2024 14:30:00 GMT"
DateUtil.rfc2616_date_parse("Fri, 15 Mar 2024 14:30:00 GMT") # datetime 对象
通用转换¶
# 字符串 → date
DateUtil.convert_to_date("2024-03-15") # date(2024, 3, 15)
DateUtil.convert_to_date(datetime(2024, 3, 15)) # date(2024, 3, 15)
# 字符串 → datetime
DateUtil.convert_to_datetime("2024-03-15 14:30:00") # datetime 对象
DateUtil.convert_to_datetime("20240315") # datetime 对象
DateUtil.convert_to_datetime(date(2024, 3, 15)) # datetime 对象
实用方法¶
import time
# 根据生日计算年龄
DateUtil.age_by_birthday("1990-06-15") # 34
DateUtil.age_by_birthday(date(1990, 6, 15)) # 34
# 判断同月/同周
DateUtil.is_same_month(date(2024, 3, 1), date(2024, 3, 31)) # True
DateUtil.is_same_week(date(2024, 1, 15), date(2024, 1, 17)) # True
# 相对时间
DateUtil.time_ago(time.time()) # "刚刚"
DateUtil.time_ago(time.time() - 300) # "5分钟前"
DateUtil.time_ago(time.time() - 7200) # "2小时前"
DateUtil.time_ago(time.time() - 86400*3) # "3天前"
# ISO 时间戳
DateUtil.iso_timestamp() # "2024-01-01T12:00:00.000Z"
判断¶
from datetime import date
# 是否月末最后一天
DateUtil.is_last_day_of_month(date(2024, 2, 29)) # True(闰年)
DateUtil.is_last_day_of_month(date(2024, 3, 15)) # False
# 是否在有效期外
DateUtil.is_expired(date(2025, 1, 1), date(2024, 1, 1), date(2024, 12, 31)) # True
# 两个时间段是否重叠
DateUtil.is_overlap(date(2024, 1, 1), date(2024, 6, 30),
date(2024, 3, 1), date(2024, 12, 31)) # True
# 日期是否在范围内
DateUtil.is_between(date(2024, 3, 15), date(2024, 1, 1), date(2024, 12, 31)) # True
日期属性¶
from datetime import date
# 年内第几天
DateUtil.day_of_year(date(2024, 3, 1)) # 61
# 当月天数
DateUtil.length_of_month(date(2024, 2, 1)) # 29(闰年)
DateUtil.length_of_month(date(2023, 2, 1)) # 28
# 当年天数
DateUtil.length_of_year(date(2024, 1, 1)) # 366(闰年)
# 毫秒部分
DateUtil.millisecond(DateUtil.parse("2024-01-01 12:00:00.123")) # 123
# 星座
DateUtil.get_zodiac(3, 21) # "白羊座"
# 生肖
DateUtil.get_chinese_zodiac(2024) # "龙"
日期比较与时区¶
from datetime import date
# null-safe 日期比较
DateUtil.compare(date(2024, 1, 1), date(2024, 1, 2)) # -1
DateUtil.compare(date(2024, 1, 2), date(2024, 1, 1)) # 1
DateUtil.compare(None, date(2024, 1, 1)) # -1
# 时区转换
dt = DateUtil.parse("2024-03-15 12:00:00")
DateUtil.convert_timezone(dt, "Asia/Shanghai", "America/New_York")
# 中文日期格式
DateUtil.format_chinese_date(date(2024, 3, 15)) # "2024年03月15日"
当前时间快捷方法¶
DateUtil.this_year() # 2024
DateUtil.this_month() # 3
DateUtil.this_week_of_year() # 12
DateUtil.this_week_of_month() # 3
DateUtil.this_day_of_month() # 15
DateUtil.this_day_of_week() # 5(周五)
DateUtil.this_hour() # 14
DateUtil.this_minute() # 30
DateUtil.this_second() # 45
DateUtil.this_millisecond() # 123
时间转换¶
# "HH:mm:ss" → 秒数
DateUtil.time_to_second("01:00:00") # 3600
DateUtil.time_to_second("00:01:30") # 90
# 秒数 → "HH:mm:ss"
DateUtil.second_to_time(3600) # "01:00:00"
DateUtil.second_to_time(90) # "00:01:30"
年龄计算¶
# 计算到当前时间的年龄
DateUtil.age_of_now("1990-01-01") # 34
# 计算两个日期间的年龄
from datetime import date
DateUtil.age(date(1990, 6, 15), date(2024, 6, 14)) # 33
DateUtil.age(date(1990, 6, 15), date(2024, 6, 15)) # 34
截断与舍入¶
from datetime import datetime
dt = datetime(2024, 6, 15, 14, 30, 45)
# 截断(向下取整)
DateUtil.truncate(dt, "year") # 2024-01-01 00:00:00
DateUtil.truncate(dt, "month") # 2024-06-01 00:00:00
DateUtil.truncate(dt, "day") # 2024-06-15 00:00:00
DateUtil.truncate(dt, "hour") # 2024-06-15 14:00:00
DateUtil.truncate(dt, "minute") # 2024-06-15 14:30:00
DateUtil.truncate(dt, "second") # 2024-06-15 14:30:45
# 四舍五入
DateUtil.round(datetime(2024, 6, 15, 12, 0, 0), "day") # 2024-06-16(>= 12:00 进位)
DateUtil.round(datetime(2024, 6, 15, 11, 59, 0), "day") # 2024-06-15(< 12:00 截断)
# 天花板舍入(向上取整)
DateUtil.ceiling(dt, "day") # 2024-06-16 00:00:00
DateUtil.ceiling(dt, "hour") # 2024-06-15 15:00:00
小时/分钟/秒 开始与结束¶
dt = DateUtil.parse("2024-06-15 14:30:45")
DateUtil.begin_of_hour(dt) # 2024-06-15 14:00:00
DateUtil.end_of_hour(dt) # 2024-06-15 14:59:59
DateUtil.begin_of_minute(dt) # 2024-06-15 14:30:00
DateUtil.end_of_minute(dt) # 2024-06-15 14:30:59
DateUtil.begin_of_second(dt) # 2024-06-15 14:30:45
DateUtil.end_of_second(dt) # 2024-06-15 14:30:45.999999
通用偏移¶
from datetime import datetime
dt = datetime(2024, 1, 31)
DateUtil.offset(dt, "day", 5) # 2024-02-05
DateUtil.offset(dt, "month", 1) # 2024-02-29(月末溢出自动处理)
DateUtil.offset(dt, "year", -1) # 2023-01-31
DateUtil.offset(dt, "hour", 3) # 加3小时
DateUtil.offset(dt, "week", 2) # 加2周
日期范围¶
from datetime import datetime
# 生成日期范围(不含结束日期)
DateUtil.range(datetime(2024, 1, 1), datetime(2024, 1, 4))
# [datetime(2024,1,1), datetime(2024,1,2), datetime(2024,1,3)]
# 生成日期范围(含结束日期)
DateUtil.range_to_list(datetime(2024, 1, 1), datetime(2024, 1, 3))
# [datetime(2024,1,1), datetime(2024,1,2), datetime(2024,1,3)]
# 按周步进
DateUtil.range(datetime(2024, 1, 1), datetime(2024, 1, 22), "week")
格式化间隔¶
DateUtil.format_between_ms(3661001)
# "1小时1分1秒1毫秒"
DateUtil.format_between_ms(90000000, level="day")
# "1天"
DateUtil.format_between_ms(7200000, level="hour")
# "2小时"
DateUtil.format_between_ms(5000, level="second")
# "5秒"
本地格式化与解析¶
from datetime import datetime
# 格式化为 "YYYY-MM-DD HH:mm:ss"
DateUtil.format_local_datetime(datetime(2024, 6, 15, 14, 30, 45))
# "2024-06-15 14:30:45"
# 解析 "YYYY-MM-DD HH:mm:ss"
DateUtil.parse_local_datetime("2024-06-15 14:30:45")
# 解析 UTC 时间
DateUtil.parse_utc("2024-01-15T10:30:00Z")
# 解析 RFC 2822 日期
DateUtil.parse_rfc2822("Mon, 15 Jan 2024 10:30:00 +0000")
纳秒转换¶
DateUtil.nanos_to_millis(1_000_000) # 1.0
DateUtil.nanos_to_seconds(1_000_000_000) # 1.0
秒表计时器¶
sw = DateUtil.create_stop_watch("性能测试")
sw.start("步骤1")
# ... 执行操作 ...
sw.stop()
sw.start("步骤2")
# ... 执行操作 ...
sw.stop()
print(sw.pretty_print())
# StopWatch '性能测试': running time = 1234.56 ms
# ---------------------------------------------
# ms % Task name
# ---------------------------------------------
# 1000.00 81.0% 步骤1
# 234.56 19.0% 步骤2
范围判断¶
from datetime import datetime
# 判断日期是否在范围内(含边界)
DateUtil.range_contains(
datetime(2024, 1, 1), datetime(2024, 12, 31),
datetime(2024, 6, 15)
) # True
# 年份+季度
DateUtil.year_and_quarter(datetime(2024, 3, 15)) # "20241"
DateUtil.year_and_quarter(datetime(2024, 9, 15)) # "20243"
AM/PM 判断¶
from datetime import datetime
DateUtil.is_am(datetime(2024, 6, 15, 10, 0)) # True
DateUtil.is_pm(datetime(2024, 6, 15, 14, 0)) # True
月内周次与月末日¶
from datetime import date
# weekOfMonth — 月内第几周
DateUtil.week_of_month(date(2024, 6, 15)) # 3
# getLastDayOfMonth — 月末日
DateUtil.get_last_day_of_month(date(2024, 2, 1)) # 29(闰年)
DateUtil.get_last_day_of_month(date(2023, 2, 1)) # 28
DateUtil.get_last_day_of_month(date(2024, 1, 15)) # 31
毫秒偏移¶
from datetime import datetime
dt = datetime(2024, 6, 15, 12, 0, 0)
DateUtil.offset_millisecond(dt, 1500) # 12:00:01.500
解析扩展¶
# 解析 CST 格式
DateUtil.parse_cst("Mon Jun 16 12:00:00 CST 2026")
# 解析 UTC 格式
DateUtil.parse_utc("2024-01-15T10:30:00Z")
# 解析 RFC 2822 格式
DateUtil.parse_rfc2822("Mon, 15 Jan 2024 10:30:00 +0000")
# 解析今日时间字符串
DateUtil.parse_time_today("12:30:00") # 今天 12:30:00
日期范围遍历¶
from datetime import date
# 日期范围遍历(生成器)
for d in DateUtil.range_func(date(2024, 1, 1), date(2024, 1, 4)):
print(d)
# 2024-01-01, 2024-01-02, 2024-01-03
# 日期范围消费(回调)
DateUtil.range_consume(date(2024, 1, 1), date(2024, 1, 4), lambda d: print(d))
计时与转换¶
import time
# 计时(返回毫秒)
start = time.time()
# ... 执行操作 ...
ms = DateUtil.spend_nt(start) # 消耗毫秒数
# 转为秒级时间戳
DateUtil.to_int_second(date(2024, 1, 1)) # 1704067200
HTTP 日期格式¶
from datetime import datetime
DateUtil.format_http_date(datetime(2024, 3, 15, 14, 30, 0))
# "Fri, 15 Mar 2024 14:30:00 GMT"
分组聚合¶
from datetime import date
from hutool import DateUtil
# 按年分组
dates = [date(2023, 1, 1), date(2023, 6, 15), date(2024, 3, 1)]
DateUtil.group_by_year(dates)
# {2023: [date(2023, 1, 1), date(2023, 6, 15)], 2024: [date(2024, 3, 1)]}
# 按月分组
DateUtil.group_by_month(dates)
# 按季度分组
DateUtil.group_by_quarter(dates)
# 按周分组
DateUtil.group_by_week(dates)
# 按日分组
DateUtil.group_by_day(dates)
# 按小时分组
DateUtil.group_by_hour([datetime(2024, 1, 1, 10, 0), datetime(2024, 1, 1, 10, 30)])
季度操作¶
from datetime import date
# getTertial — 获取半年(上/下半年)
DateUtil.get_tertial(date(2024, 3, 15)) # 1(上半年)
DateUtil.get_tertial(date(2024, 9, 15)) # 2(下半年)
# tertialAdd — 半年加减
DateUtil.tertial_add(date(2024, 3, 15), 1) # 2024-09-15
便捷方法¶
from datetime import date
# 今天/昨天/明天
DateUtil.today_str() # "2024-06-15"
DateUtil.yesterday_str() # "2024-06-14"
DateUtil.tomorrow_str() # "2024-06-16"
# 周首末日
DateUtil.week_start(date(2024, 6, 15)) # date(2024, 6, 10)(周一)
DateUtil.week_end(date(2024, 6, 15)) # date(2024, 6, 16)(周日)
# 日首末时间
DateUtil.day_start(date(2024, 6, 15)) # datetime(2024, 6, 15, 0, 0, 0)
DateUtil.day_end(date(2024, 6, 15)) # datetime(2024, 6, 15, 23, 59, 59)
# 月首末日
DateUtil.month_start(date(2024, 6, 15)) # date(2024, 6, 1)
DateUtil.month_end(date(2024, 6, 15)) # date(2024, 6, 30)
格式化便捷方法¶
from datetime import datetime, date
# format_date — 仅日期
DateUtil.format_date(date(2024, 6, 15)) # "2024-06-15"
# format_datetime — 日期时间
DateUtil.format_datetime(datetime(2024, 6, 15, 14, 30, 0))
# "2024-06-15 14:30:00"
# format_time — 仅时间
DateUtil.format_time(datetime(2024, 6, 15, 14, 30, 45))
# "14:30:45"
SimpleFormat 工厂¶
# newSimpleFormat — 创建格式化函数
fmt = DateUtil.new_simple_format("yyyy/MM/dd")
from datetime import date
fmt(date(2024, 6, 15)) # "2024/06/15"