Groovy™ 日期和时间备忘单
发布时间:2022-10-24 07:27AM
Java 从一开始就有 Date
类,Groovy 支持使用它和一些相关的类,如 Calendar
。在本博客文章中,我们将这些类称为旧版日期类。Groovy 通过更简单的机制来格式化、解析和从相关类中提取字段,从而增强了使用旧版日期类的体验。
自 Java 8 以来,JDK 中包含了 JSR-310 日期时间 API。我们将这些类称为新版日期类。新版日期类消除了旧版日期类的许多限制,并带来了许多备受赞赏的额外一致性。Groovy 也为新版日期类提供了类似的增强功能。
Groovy 对旧版日期类的增强功能位于 groovy-dateutil
模块中(在 Groovy 2.5 之前,此功能内置在核心模块中)。groovy-datetime
模块包含对新版日期类的增强功能。您可以在构建文件中包含对此模块的依赖项,或引用 groovy-all
pom 依赖项。这两个模块都是标准 Groovy 安装的一部分。
接下来的几节将演示常见的日期和时间任务,以及使用新旧类和 Groovy 增强功能执行这些任务的代码。
请注意:某些格式化命令是与区域设置相关的,如果您自己运行这些示例,输出可能会略有不同。
表示当前日期/时间
旧版日期类具有包含日期和时间的抽象。如果您只对其中一个方面感兴趣,您只需忽略另一个方面。新版日期类允许您拥有仅日期、仅时间和日期时间表示。
示例创建表示当前日期和/或时间的实例。从实例中提取各种信息,并以各种方式打印它们。某些示例使用 SV
宏,该宏打印一个或多个变量的名称和字符串值。
任务 | java.time | 旧版 |
---|---|---|
当前日期和时间 |
println LocalDateTime.now() 2022-10-24T12:40:02.218130200 2022-10-24T02:40:02.223131Z |
println new Date() Mon Oct 24 12:40:02 AEST 2022 Mon Oct 24 12:40:02 AEST 2022 |
当前年份中的天数 & 当前月份中的天数 |
println LocalDateTime.now().dayOfYear 297 24 |
println Calendar.instance[DAY_OF_YEAR] 297 24 |
提取今天日期中的 年、月和日 |
var now = LocalDate.now() // or LocalDateTime now.year=2022, now.monthValue=10, now.dayOfMonth=24 Today is 2022 10 24 |
var now = Calendar.instance Today is 2022 10 24 Today is 2022 10 24 |
打印今天的日期的替代方法 | println now.format("'Today is 'YYYY-MM-dd") Today is 2022-10-24 Today is 2022-10-24 |
println now.format("'Today is 'YYYY-MM-dd") Today is 2022-10-24 Today is 2022-10-24 |
提取当前时间的部分 |
now = LocalTime.now() // or LocalDateTime now.hour=12, now.minute=40, now.second=2 The time is 12:40:02 |
(H, M, S) = now[HOUR_OF_DAY, MINUTE, SECOND] H=12, M=40, S=2 The time is 12:40:02 |
打印时间的替代方法 | println now.format("'The time is 'HH:mm:ss") The time is 12:40:02 The time is 12:40:02 |
println now.format("'The time is 'HH:mm:ss") The time is 12:40:02 The time is 12:40:02 |
处理时间
新版日期类有一个 LocalTime
类,专门用于表示仅时间量。旧版日期类没有这种专用抽象;您基本上只是忽略日期的日、月和年部分。java.sql.Time
类可以作为替代,但很少使用。Java 将新版日期类与旧版等效类进行比较的文档,讨论了使用 GregorianCalendar
并将日期设置为纪元值 1970-01-01
作为 LocalTime
类的近似值。我们将在此处遵循该方法以提供比较,但如果您需要表示仅时间值或在 JDK 8 之前的版本上使用 Joda-Time 库,我们强烈建议升级到新类。
这些示例着眼于表示午夜前后一分钟的时间,以及您可能用餐的一些时间。对于用餐,除了打印各种值外,我们可能还对根据现有时间计算新时间感兴趣,例如午餐和晚餐相隔 7 小时。
任务 | java.time | 旧版 |
---|---|---|
午夜后一分钟 | LocalTime.of(0, 1).with { 00:01 12:01 am 0:01 am |
Calendar.instance.with { 00:01 12:01 am 0:01 am |
午夜前一分钟 | LocalTime.of(23, 59).with { 23:59 11:59 pm 11:59 pm |
Calendar.instance.with { 23:59 11:59 pm 11:59 pm |
用餐时间 | var breakfast = LocalTime.of(7, 30) |
var breakfast = Date.parse('hh:mm', '07:30') |
处理日期
要使用旧版日期类表示仅日期信息,您可以将时间方面设置为零,或者简单地忽略它们。或者,您可以考虑使用不那么常用的 java.sql.Date
类。新版日期类为此目的提供了特殊的 LocalDate
类,我们强烈推荐使用。
这些示例创建了万圣节和墨尔本杯日(澳大利亚维多利亚州的公共假日)的日期。我们查看了这两个日期的各种属性。
任务 | java.time | 旧版 |
---|---|---|
假期 | var halloween22 = LocalDate.of(2022, 10, 31) |
var halloween21 = Date.parse('dd/MM/yyyy', '31/10/2021') |
处理日期和时间组合
新版日期类使用 LocalDateTime
来表示具有日期和时间两方面的量。前面看到的许多方法也适用于此处。
这些示例展示了如何在墨尔本杯日创建和打印午餐的表示形式。
任务 | java.time | 旧版 |
---|---|---|
假期 | var melbourneCupLunch = LocalDateTime.of(2022, 11, 1, 12, 30) |
var melbourneCupLunch = new GregorianCalendar(2022, 10, 1, 12, 30).time |
处理带时区的日期和时间
旧版日期类具有 TimeZone
的概念,主要由 Calendar 类使用。新版日期类具有类似的概念,但使用 ZoneId
、ZoneOffset
和 ZonedDateTime
类(以及其他类)。
这些示例展示了时区的各种属性,并表明在墨尔本杯早餐期间,洛杉矶仍然是前一天晚上(万圣节)。它们还表明,在一年中的那个时候,这两个时区相隔 18 小时。
任务 | java.time | 旧版 |
---|---|---|
假期 | var aet = ZoneId.of('Australia/Sydney') |
var aet = TimeZone.getTimeZone('Australia/Sydney') |
其他有用的类
新版日期类提供了更多有用的类。以下是一些常见的类:
-
OffsetDateTime
- 类似于ZonedDateTime
,但只包含与 UTC 的偏移量,而不是完整的时区 -
Instant
- 类似于OffsetDateTime
,但与 UTC 绑定 -
YearMonth
- 类似于LocalDate
,但没有日期组件 -
MonthDay
- 类似于LocalDate
,但没有年份组件 -
Period
- 用于表示时间段,例如Period.ofDays(14)
、Period.ofYears(2)
;另请参阅上面的LocalDate
示例。 -
Duration
- 基于时间的时间量,例如Duration.ofSeconds(30)
、Duration.ofHours(7)
;另请参阅上面的LocalTime
示例。
转换
在新旧类之间进行转换非常有用。下面显示了一些有用的转换方法,Groovy 增强功能以蓝色显示。
来源 | 转换方法/属性 |
---|---|
GregorianCalendar | toInstant() toZonedDateTime() from(ZonedDateTime) |
Calendar | toInstant()
toZonedDateTime()
toOffsetDateTime()
toLocalDateTime()
toLocalDate()
toLocalTime()
toOffsetTime()
toDayOfWeek()
toYear()
toYearMonth()
toMonth()
toMonthDay()
zoneOffset
zoneId
|
Date | toInstant()
from(Instant)
toZonedDateTime()
toOffsetDateTime()
toLocalDateTime()
toLocalDate()
toLocalTime()
toOffsetTime()
toDayOfWeek()
toYear()
toYearMonth()
toMonth()
toMonthDay()
zoneOffset
zoneId
|
ZonedDateTime OffsetDateTime LocalDateTime LocalDate LocalTime |
toDate()
toCalendar()
|
SimpleDateFormat 模式
我们上面看到了几个使用 format
和 parse
方法的例子。对于旧版日期类,许多 Groovy 增强功能委托给 SimpleDateFormat
。这个类使用模式字符串表示日期/时间格式。这些是特殊字母,用于表示时间或日期组件,并与转义的字面字符串混合。特殊字母通常重复以表示数字组件的最小字段大小,以及其他组件是使用完整形式还是缩写形式。
例如,对于美国区域设置和美国太平洋时间时区,以下模式
yyyy.MM.dd G 'at' HH:mm:ss z
将适用于以下文本
2001.07.04 AD at 12:08:56 PDT
字母 | 描述 |
---|---|
G | 纪元指定符 AD |
y | 年份 1996; 96 |
Y | 周年份(与年份相似,但按周分配;一年的前几天/最后几天可能分配给结束/开始上周/前一周) |
M | 年份中的月份(上下文敏感) July; Jul; 07 |
L | 年份中的月份(独立形式) July; Jul; 07 |
w | 年份中的周 27 |
W | 月份中的周 2 |
D | 年份中的天 189 |
d | 月份中的天 10 |
F | 月份中的星期几 2 |
E | 星期几的名称 Tuesday; Tue |
u | 星期几的数字(1 = 星期一,...,7 = 星期日) |
a | 上午/下午标记 PM |
H | 天中的小时 (0-23) 0 |
k | 天中的小时 (1-24) 24 |
K | 上午/下午中的小时 (0-11) 0 |
h | 上午/下午中的小时 (1-12) 12 |
m | 小时中的分钟 30 |
s | 分钟中的秒 55 |
S | 毫秒 978 |
z | 时区 Pacific Standard Time; PST; GMT-08:00 |
Z | 时区 (RFC 822) -0800 |
X | 时区 (ISO 8601) -08; -0800; -08:00 |
' | 要转义文本,请在两边加单引号 |
'' | 两个单引号表示一个字面单引号 ' |
DateTimeFormatter 模式
Groovy 对新日期类的 format
和 parse
增强功能委托给 DateTimeFormatter
类。其行为与我们看到的 SimpleDateFormat
类似,但转换字母略有不同:
转换后缀 | 描述 |
---|---|
G | 纪元 AD |
u | 年份 2004; 04 |
y | 纪元年 2004; 04 |
D | 年份中的天 189 |
M/L | 年份中的月份 7; 07; Jul; July; J |
d | 月份中的天 10 |
Q/q | 年份中的季度 3; 03; Q3; 3rd quarter |
Y | 基于周的年份 1996; 96 |
w | 基于周的年份中的周 27 |
W | 月份中的周 4 |
E | 星期几 Tue; Tuesday; T |
e/c | 本地化星期几 2; 02; Tue; Tuesday; T |
F | 月份中的周 3 |
a | 日中的上午-下午 PM |
h | 上午-下午中的时钟小时 (1-12) 12 |
K | 上午-下午中的小时 (0-11) 0 |
k | 上午-下午中的时钟小时 (1-24) 0 |
H | 日中的小时 (0-23) 0 |
m | 小时中的分钟 30 |
s | 分钟中的秒 55 |
S | 秒中的小数 978 |
A | 日中的毫秒 1234 |
n | 秒中的纳秒 987654321 |
N | 日中的纳秒 1234000000 |
V | 时区 ID America/Los_Angeles; Z; -08:30 |
z | 时区名称 Pacific Standard Time; PST |
O | 本地化时区偏移 GMT+8; GMT+08:00; UTC-08:00; |
X | 零时区偏移 'Z' Z; -08; -0830; -08:30; -083015; -08:30:15; |
x | 时区偏移 +0000; -08; -0830; -08:30; -083015; -08:30:15; |
Z | 时区偏移 +0000; -0800; -08:00; |
p | 填充下一个 |
' | 要转义文本,请在两边加单引号 |
'' | 两个单引号表示一个字面单引号 ' |
本地化模式
JDK19 添加了 ofLocalizedPattern(String requestedTemplate)
方法。请求的模板是一个或多个正则表达式模式符号,按从最大到最小的单位排序,由以下模式组成:
"G{0,5}" + // Era "y*" + // Year "Q{0,5}" + // Quarter "M{0,5}" + // Month "w*" + // Week of Week Based Year "E{0,5}" + // Day of Week "d{0,2}" + // Day of Month "B{0,5}" + // Period/AmPm of Day "[hHjC]{0,2}" + // Hour of Day/AmPm (refer to LDML for 'j' and 'C') "m{0,2}" + // Minute of Hour "s{0,2}" + // Second of Minute "[vz]{0,4}" // Zone
请求的模板映射到 Unicode LDML 规范中定义的可用本地化格式中最接近的格式。以下是一个用法示例:
var now = ZonedDateTime.now()
var columns = '%7s | %10s | %10s | %10s | %14s%n'
printf columns, 'locale', 'GDK', 'custom', 'local', 'both'
[locale('en', 'US'),
locale('ro', 'RO'),
locale('vi', 'VN')].each { locale ->
Locale.default = locale
var gdk = now.format('y-MM-dd')
var custom = now.format(ofPattern('y-MM-dd'))
var local = now.format(ofLocalizedDate(SHORT))
var both = now.format(ofLocalizedPattern('yMM'))
printf columns, locale, gdk, custom, local, both
}
其输出如下
locale | GDK | custom | local | both en_US | 2022-12-18 | 2022-12-18 | 12/18/22 | 12/2022 ro_RO | 2022-12-18 | 2022-12-18 | 18.12.2022 | 12.2022 vi_VN | 2022-12-18 | 2022-12-18 | 18/12/2022 | tháng 12, 2022
示例来源:此示例来自 Nicolai Parlog。
格式化器格式
java.util.Formatter
类是 Java 中各种格式化的基类。它可以直接使用,也可以通过 String.format
、parse
、printf
或 Groovy 的 sprintf
使用。我们在上面的示例中看到了几个使用 printf
和 parse
格式化的示例。
Formatter
类的方法将其第一个参数作为格式字符串,并接受零个或多个附加参数。格式字符串通常包含一个或多个格式说明符(以百分号开头),这些说明符表示一个格式化版本的附加参数之一应放置在字符串中的该点。格式说明符的一般形式是:
%[argument_index$][flag][width][.precision]conversion
大部分部分是可选的。argument_index
部分仅在多次(或乱序)引用附加参数之一时使用。precision
部分仅用于浮点数。flag
部分用于指示始终包含符号(+)、零填充(0)、区域设置特定的逗号分隔符(,)和左对齐(-)。width
指示输出的最小字符数。conversion
指示参数应如何处理,例如作为数字字段、日期、特殊字符或其他特殊处理。大多数转换都有大写和小写变体,对于大写变体,在转换完成后将调用 toUpperCase
。
转换 | 描述 |
---|---|
'b', 'B' | 视为布尔值,如果为 null 则为 false |
'h', 'H' | 将参数的哈希码输出为十六进制字符串 |
's', 'S' | 视为字符串 |
'c', 'C' | 视为 Unicode 字符 |
'd' | 视为十进制整数 |
'o' | 视为八进制整数 |
'x', 'X' | 视为十六进制整数 |
'e', 'E' | 视为科学记数法中的十进制数 |
'f' | 视为浮点数 |
'g', 'G' | 视为十进制或科学记数法中的浮点数 |
'a', 'A' | 视为十六进制浮点数 |
't', 'T' | 视为日期/时间转换的前缀 |
'%' | 一个字面百分号 |
'n' | 行分隔符 |
使用日期/时间前缀时,适用附加后缀。
用于格式化时间
转换后缀 | 描述 |
---|---|
'H' | 24小时制的小时数,两位数字 00 - 23 |
'I' | 12小时制的小时数,两位数字 01 - 12 |
'k' | 24小时制的小时数 0 - 23 |
'l' | 12小时制的小时数 1 - 12 |
'M' | 小时内的分钟数,两位数字 00 - 59 |
'S' | 分钟内的秒数,两位数字 00 - 60 (“60”用于闰秒) |
'L' | 秒内的毫秒数,三位数字 000 - 999 |
'N' | 秒内的纳秒数,九位数字 000000000 - 999999999 |
'p' | 区域设置特定的上午或下午标记,小写,am 或 pm (转换前缀 'T' 强制此输出为大写) |
'z' | RFC 822 样式数字时区偏移,例如 GMT -0800 (根据夏令时需要调整) |
'Z' | 缩写时区 |
's' | 自 1970 年 1 月 1 日 00:00:00 UTC 纪元开始以来的秒数 |
'Q' | 自 1970 年 1 月 1 日 00:00:00 UTC 纪元开始以来的毫秒数 |
用于格式化日期
转换后缀 | 描述 |
---|---|
'B' | 区域设置特定完整月份名称 January |
'b', 'h' | 区域设置特定缩写月份名称 Jan |
'A' | 区域设置特定星期几完整名称 Sunday |
'a' | 区域设置特定星期几缩写名称 Sun |
'C' | 四位年份的前两位数字 00 - 99 |
'Y' | 四位年份 0092 |
'y' | 年份的最后两位数字 00 - 99 |
'j' | 年份中的天数,三位数字 001 - 366 |
'm' | 月份,两位数字 01 - 13 |
'd' | 月份中的天数,两位数字 01 - 31 |
'e' | 月份中的天数 1 - 31 |
用于格式化日期/时间组合
转换后缀 | 描述 |
---|---|
'R' | 时间格式化为 24 小时制,如 "%tH:%tM" |
'T' | 时间格式化为 24 小时制,如 "%tH:%tM:%tS" |
'r' | 时间格式化为 12 小时制,如 "%tI:%tM:%tS %Tp" 上午或下午标记('%Tp')的位置可能取决于区域设置。 |
'D' | 日期格式化为 "%tm/%td/%ty" |
'F' | ISO 8601 日期格式化为 "%tY-%tm-%td" |
'c' | 日期和时间格式化为 "%ta %tb %td %tT %tZ %tY" Sun Jul 21 15:17:00 EDT 1973 |