Groovy 日期和时间速查表

作者: Paul King
发布: 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()      
println Instant.now()
2022-10-24T12:40:02.218130200
2022-10-24T02:40:02.223131Z
println new Date()               
println Calendar.instance.time
Mon Oct 24 12:40:02 AEST 2022
Mon Oct 24 12:40:02 AEST 2022
当前年份的日期 &
当前月份的日期
println LocalDateTime.now().dayOfYear
println LocalDateTime.now().dayOfMonth
297
24
println Calendar.instance[DAY_OF_YEAR]
println Calendar.instance[DAY_OF_MONTH]
297
24
提取今天的
年份、月份 & 日
var now = LocalDate.now()     // or LocalDateTime

println SV(now.year, now.monthValue, now.dayOfMonth)

(Y, M, D) = now[YEAR, MONTH_OF_YEAR, DAY_OF_MONTH]
println "Today is $Y $M $D"
now.year=2022, now.monthValue=10, now.dayOfMonth=24
Today is 2022 10 24
var now = Calendar.instance
(_E, Y, M, _WY, _WM, D) = now
println "Today is $Y ${M+1} $D"

(Y, M, D) = now[YEAR, MONTH, DAY_OF_MONTH]
println "Today is $Y ${M+1} $D"
Today is 2022 10 24
Today is 2022 10 24
打印今天的替代方案
println now.format("'Today is 'YYYY-MM-dd")
printf 'Today is %1$tY-%1$tm-%1$te%n', now
Today is 2022-10-24
Today is 2022-10-24
println now.format("'Today is 'YYYY-MM-dd")
printf 'Today is %1$tY-%1$tm-%1$te%n', now
Today is 2022-10-24
Today is 2022-10-24

提取当前时间的部分

now = LocalTime.now() // or LocalDateTime
println SV(now.hour, now.minute, now.second)
(H, M, S) = now[HOUR_OF_DAY, MINUTE_OF_HOUR,
SECOND_OF_MINUTE]
printf 'The time is %02d:%02d:%02d\n', H, M, S
now.hour=12, now.minute=40, now.second=2
The time is 12:40:02
(H, M, S) = now[HOUR_OF_DAY, MINUTE, SECOND]

println SV(H, M, S)
printf 'The time is %02d:%02d:%02d%n', H, M, S
H=12, M=40, S=2
The time is 12:40:02
打印时间的替代方案
println now.format("'The time is 'HH:mm:ss")
printf 'The time is %1$tH:%1$tM:%1$tS%n', now
The time is 12:40:02
The time is 12:40:02
println now.format("'The time is 'HH:mm:ss")
printf 'The time is %1$tH:%1$tM:%1$tS%n', now
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 {
println format('HH:mm')
println format('hh:mm a')
println format('K:mm a')
}
00:01
12:01 am
0:01 am
Calendar.instance.with {
clear()
set(MINUTE, 1)
println format('HH:mm')
println format('hh:mm a')
println format('K:mm a')
}
00:01
12:01 am
0:01 am
午夜前一分钟
LocalTime.of(23, 59).with {
println format('HH:mm')
println format('hh:mm a')
println format('K:mm a')
}
23:59
11:59 pm
11:59 pm
Calendar.instance.with {
clear()
set(hourOfDay: 23, minute: 59)
println format('HH:mm')
println format('hh:mm a')
println format('K:mm a')
}
23:59
11:59 pm
11:59 pm
用餐时间
var breakfast = LocalTime.of(7, 30)
var lunch = LocalTime.parse('12:30')
assert lunch == LocalTime.parse('12:30.00 pm', 'hh:mm.ss a')
lunch.with { assert hour == 12 && minute == 30 }
var dinner = lunch.plusHours(7)
assert dinner == lunch.plus(7, ChronoUnit.HOURS)
assert Duration.between(lunch, dinner).toHours() == 7
assert breakfast.isBefore(lunch) // Java API
assert lunch < dinner // Groovy shorthand
assert lunch in breakfast..dinner
assert dinner.format('hh:mm a') == '07:30 pm'
assert dinner.format('k:mm') == '19:30'
assert dinner.format(FormatStyle.MEDIUM) == '7:30:00 pm'
assert dinner.timeString == '19:30:00'
var breakfast = Date.parse('hh:mm', '07:30')
var lunch = Calendar.instance.tap {
clear()
set(hourOfDay: 12, minute: 30)
}
assert lunch[HOUR_OF_DAY, MINUTE] == [12, 30]
var dinner = lunch.clone().tap { it[HOUR_OF_DAY] += 7 }
assert dinner == lunch.copyWith(hourOfDay: 19)
assert dinner.format('hh:mm a') == '07:30 pm'
assert dinner.format('k:mm') == '19:30'
assert dinner.time.timeString == '7:30:00 pm'
assert breakfast.before(lunch.time) // Java API
assert lunch < dinner // Groovy shorthand

处理日期

要使用遗留日期类表示仅日期信息,您可以将时间方面设置为零,或者干脆忽略它们。 或者,您可以考虑不太常用的 java.sql.Date 类。 新的日期类为此目的专门提供了 LocalDate 类,我们强烈推荐它。

这些示例为万圣节和墨尔本杯赛日(澳大利亚维多利亚州的公共假期)创建日期。 我们查看了这两个日期的各种属性。

任务java.time遗留
假期
var halloween22 = LocalDate.of(2022, 10, 31)
var halloween23 = LocalDate.parse('2023-Oct-31', 'yyyy-LLL-dd')
assert halloween22 == halloween23 - 365
assert halloween23 == halloween22.plusYears(1)
var melbourneCup22 = LocalDate.of(2022, 11, 1)
assert halloween22 < melbourneCup22
assert melbourneCup22 - halloween22 == 1
assert Period.between(halloween22, melbourneCup22).days == 1
assert ChronoUnit.DAYS.between(halloween22, melbourneCup22) == 1L
var days = []
halloween22.upto(melbourneCup22) {days << "$it.dayOfWeek" }
assert days == ['MONDAY', 'TUESDAY']
var hols = halloween22..melbourneCup22
assert hols.size() == 2
var halloween21 = Date.parse('dd/MM/yyyy', '31/10/2021')
var halloween22 = Date.parse('yyyy-MMM-dd', '2022-Oct-31')
assert halloween21 + 365 == halloween22
var melbourneCup22 = new GregorianCalendar(2022, 10, 1).time
assert melbourneCup22.dateString == '1/11/22' // AU Locale
assert halloween22 < melbourneCup22
assert melbourneCup22 - halloween22 == 1
assert melbourneCup22 == halloween22.copyWith(month: 10, date: 1)
var days = []
halloween22.upto(melbourneCup22) { days << it.format('EEEEE') }
assert days == ['Monday', 'Tuesday']
var hols = halloween22..melbourneCup22
assert hols.size() == 2

处理日期和时间组合

新的日期类使用 LocalDateTime 来表示包含日期和时间方面的量。 早些时候看到的大多数方法在这里也适用。

这些示例展示了如何创建和打印墨尔本杯赛日午餐的表示形式。

任务java.time遗留
假期
var melbourneCupLunch = LocalDateTime.of(2022, 11, 1, 12, 30)
assert melbourneCupLunch.timeString == '12:30:00'
assert melbourneCupLunch.dateString == '2022-11-01'
assert melbourneCupLunch.dateTimeString == '2022-11-01T12:30:00'
assert melbourneCupLunch.toLocalDate() == melbourneCup22
assert melbourneCupLunch.toLocalTime() == lunch
assert melbourneCupLunch == melbourneCup22 << lunch
var melbourneCupLunch = new GregorianCalendar(2022, 10, 1, 12, 30).time
assert melbourneCupLunch.timeString == '12:30:00 pm' // Locale specific
assert melbourneCupLunch.dateString == '1/11/22' // Locale specific
assert melbourneCupLunch.dateTimeString == '1/11/22, 12:30:00 pm' // Locale specific
assert melbourneCupLunch.clearTime() == melbourneCup22

处理带时区的日期和时间

遗留日期类具有 TimeZone 的概念,主要由 Calendar 类使用。 新的日期类具有类似的概念,但使用 ZoneIdZoneOffsetZonedDateTime 类(以及其他类)。

这些示例展示了时区的各种属性,并展示了在墨尔本杯早餐期间,洛杉矶仍然是前一天晚上(万圣节)。 它们还显示,这两个时区在一年中的那个时间相差 18 个小时。

任务java.time遗留
假期
var aet = ZoneId.of('Australia/Sydney')
assert aet.fullName == 'Australian Eastern Time' && aet.shortName == 'AET'
assert aet.offset == ZoneOffset.of('+11:00')
var melbCupBreakfastInAU = ZonedDateTime.of(melbourneCup22, breakfast, aet)
var melbCupBreakfast = LocalDateTime.of(melbourneCup22, breakfast)
assert melbCupBreakfastInAU == melbCupBreakfast << aet
var pst = ZoneId.of('America/Los_Angeles')
assert pst.fullName == 'Pacific Time' && pst.shortName == 'GMT-08:00'
var meanwhileInLA = melbCupBreakfastInAU.withZoneSameInstant(pst)
assert halloween22 == meanwhileInLA.toLocalDate()
assert aet.offset.hours - pst.offset.hours == 18
var aet = TimeZone.getTimeZone('Australia/Sydney')
assert aet.displayName == 'Australian Eastern Standard Time'
assert aet.observesDaylightTime()
var melbourneCupBreakfast = new GregorianCalendar(aet).tap {
set(year: 2022, month: 10, date: 1, hourOfDay: 7, minute: 30)
}
var pst = TimeZone.getTimeZone('America/Los_Angeles')
assert pst.displayName == 'Pacific Standard Time'
var meanwhileInLA = new GregorianCalendar(pst).tap {
setTimeInMillis(melbourneCupBreakfast.timeInMillis)
}
assert meanwhileInLA.time.format('MMM dd', pst) == halloween22.format('MMM dd')
assert aet.rawOffset / 3600000 - pst.rawOffset / 3600000 == 18

其他有用的类

新的日期类提供了一些更有用的类。 以下是一些常见的类

  • 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 模式

我们在上面的示例中看到了几个使用 formatparse 方法的示例。 对于遗留日期类,许多 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 的 formatparse 对新日期类的增强功能委托给 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.formatparseprintf 或 Groovy 的 sprintf 使用。 在上面的示例中,我们看到了几个使用 printfparse 格式化的示例。

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'特定于区域设置的上午或下午标记,小写,ampm
(转换前缀 '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

更多信息