JAVA学习(十)| 日期与时间、正则表达式
日期与时间⁍
计算机通过Locale
来针对当地用户习惯格式化日期、时间、数字、货币等。
Locale
由语言_国家
的字母缩写构成,例如,zh_CN
表示中文+中国,en_US
表示英文+美国。语言使用小写,国家使用大写。
对于日期来说,不同的Locale,例如,中国和美国的表示方式如下:
- zh_CN:2016-11-30
- en_US:11/30/2016
Java有两套日期和时间的API:
- 旧的Date、Calendar和TimeZone;
- 新的LocalDateTime、ZonedDateTime、ZoneId等。
分别位于java.util
和java.time
包中。
⭐ 从Java 8开始,java.time
包提供了新的日期和时间API,主要涉及的类型有:
- 本地日期和时间:
LocalDateTime
,LocalDate
,LocalTime
; - 带时区的日期和时间:
ZonedDateTime
; - 时刻:
Instant
; - 时区:
ZoneId
,ZoneOffset
; - 时间间隔:
Duration
。
以及一套新的用于取代SimpleDateFormat
的格式化类型DateTimeFormatter
。
Date和Calendar(旧API)⁍
计算机表示的时间是以整数表示的时间戳存储的,即Epoch Time
,Java使用long
型来表示以毫秒为单位的时间戳,通过System.currentTimeMillis()
获取当前时间戳。
Epoch Time
是计算从1970年1月1日零点(格林威治时区/GMT+00:00)到现在所经历的秒数
Date⁍
1 |
|
自定义格式输出:
1 |
|
假设当前月份是9月:
M
:输出9
MM
:输出09
MMM
:输出Sep
MMMM
:输出September
Calendar⁍
和Date
比,主要多了一个可以做简单的日期和时间运算的功能。
Calendar
的基本用法:
1 |
|
TimeZone⁍
Calendar
和Date
相比,它提供了时区转换的功能。时区用TimeZone
对象表示
利用Calendar
进行时区转换的步骤是:
- 清除所有字段;
- 设定指定时区;
- 设定日期和时间;
- 创建
SimpleDateFormat
并设定目标时区; - 格式化获取的
Date
对象(注意Date
对象无时区信息,时区信息存储在SimpleDateFormat
中)。
LocalDateTime⁍
import java.time.*;
获取日期和时间⁍
- 当前日期:
LocalDate d = LocalDate.now();
//2022-02-15 - 当前时间:
LocalTime t = LocalTime.now();
//08:51:53.807637953 - 当前日期和时间:
LocalDateTime dt = LocalDateTime.now();
//2022-02-15T08:51:53.807670493 - 用标准输出会严格按照ISO 8601格式打印
如果要在同一个时刻分别输出当前日期和时间,可以采用以下方法:(如果按上述分开写,每一行程序的执行时间可能会带来影响)
1 |
|
创建指定日期和时间⁍
1 |
|
ISO 8601规定的日期和时间分隔符是T
。标准格式如下:
- 日期:yyyy-MM-dd
- 时间:HH:mm:ss
- 带毫秒的时间:HH:mm:ss.SSS
- 日期和时间:yyyy-MM-dd’T’HH:mm:ss
- 带毫秒的日期和时间:yyyy-MM-dd’T’HH:mm:ss.SSS
DateTimeFormatter⁍
import java.time.format.*;
1 |
|
时间加减⁍
加5天减3小时:
1 |
|
减一个月
1 |
|
时间调整⁍
对日期和时间进行调整则使用withXxx()
方法,例如:withHour(15)
会把10:11:12
改为15:11:12
:
- 调整年:withYear()
- 调整月:withMonth()
- 调整日:withDayOfMonth()
- 调整时:withHour()
- 调整分:withMinute()
- 调整秒:withSecond()
其他,例如
1 |
|
时间比较⁍
可以使用isBefore()
、isAfter()
方法
1 |
|
Duration和Period⁍
-
Duration
表示两个时刻之间的时间间隔 -
Period
表示两个日期之间的天数1
2Duration d = Duration.between(start_dt, end_dt);// PT1235H10M30S
Period p = LocalDate.of(2019, 11, 19).until(LocalDate.of(2020, 1, 9)); //P1M21D -
利用
ofXxx()
或者parse()
方法也可以直接创建Duration
:1
2Duration d1 = Duration.ofHours(10); // 10 hours
Duration d2 = Duration.parse("P1DT2H3M"); // 1 day, 2 hours, 3 minutes
ZonedDateTime⁍
获得不同时区的当前时间⁍
1 |
|
表示同一时间
给当前时间附加不同时区⁍
1 |
|
表示不同时间
时区转换⁍
将一个ZonedDateTime
对象,通过withZoneSameInstant()
将关联时区转换到另一个时区
涉及到时区时,千万不要自己计算时差,否则难以正确处理夏令时。
1 |
|
ZonedDateTime
转换为本地时间:
1 |
|
DateTimeFormatter⁍
和SimpleDateFormat
不同的是,DateTimeFormatter
不但是不变对象,它还是线程安全的。
1 |
|
Instant⁍
用Instant.now()
获取当前时间戳,效果和System.currentTimeMillis()
类似,精度更高
可以和ZonedDateTime
以及long
互相转换。
1 |
|
最佳实践——新旧API处理⁍
处理日期和时间时,尽量使用新的java.time
包;
在数据库中存储时间戳时,尽量使用long
型时间戳,它具有省空间,效率高,不依赖数据库的优点。
数据库 | 对应Java类(旧) | 对应Java类(新) |
---|---|---|
DATETIME | java.util.Date | LocalDateTime |
DATE | java.sql.Date | LocalDate |
TIME | java.sql.Time | LocalTime |
TIMESTAMP | java.sql.Timestamp | LocalDateTime |
正则表达式⁍
匹配规则⁍
单个字符的匹配规则如下:
正则表达式 | 规则 | 可以匹配 |
---|---|---|
A |
指定字符 | A |
\u548c |
指定Unicode字符 | 和 |
. |
任意字符 | a ,b ,& ,0 |
\d |
数字0~9 | 0 ~9 |
\w |
大小写字母,数字和下划线 | a ~z , A ~Z , 0 ~9 ,_ |
\s |
空格、Tab键 | 空格,Tab |
\D |
非数字 | a ,A ,& ,_ ,…… |
\W |
非\w | & ,@ ,中 ,…… |
\S |
非\s | a ,A ,& ,_ ,…… |
多个字符的匹配规则如下:
正则表达式 | 规则 | 可以匹配 |
---|---|---|
A* |
任意个数字符 | 空,A ,AA ,AAA ,…… |
A+ |
至少1个字符 | A ,AA ,AAA ,…… |
A? |
0个或1个字符 | 空,A |
A{3} |
指定个数字符 | AAA |
A{2,3} |
指定范围个数字符 | AA ,AAA |
A{2,} |
至少n个字符 | AA ,AAA ,AAAA ,…… |
A{0,3} |
最多n个字符 | 空,A ,AA ,AAA |
复杂匹配规则⁍
正则表达式 | 规则 | 可以匹配 |
---|---|---|
^ |
开头 | 字符串开头 |
$ |
结尾 | 字符串结束 |
[ABC] |
[…]内任意字符 | A,B,C |
[A-F0-9xy] |
指定范围的字符 | A ,……,F ,0 ,……,9 ,x ,y |
[^A-F] |
指定范围外的任意字符 | 非A ~F |
AB|CD|EF |
AB或CD或EF | AB ,CD ,EF |
分组匹配⁍
引入java.util.regex
包,用Pattern
对象匹配,匹配后获得一个Matcher
对象,如果匹配成功,就可以直接从Matcher.group(index)
返回子串
正则表达式用(...)
分组可以通过Matcher
对象快速提取子串:
group(0)
表示匹配的整个字符串;group(1)
表示第1个子串,group(2)
表示第2个子串,以此类推
1 |
|
非贪婪匹配⁍
正则表达式匹配默认使用贪婪匹配,可以使用?
表示对某一规则进行非贪婪匹配。
例:给定一个字符串表示的数字,判断该数字末尾0
的个数。例如:
"123000"
:3个0
"10100"
:2个0
"1001"
:0个0
正则表达式(\d+?)(0*)
,后面?
表示非贪婪匹配,让\d+
尽量少匹配,让0*
尽量多匹配
搜索和替换⁍
使用正则表达式可以:
-
分割字符串:
String.split()
-
搜索子串:
Matcher.find()
-
替换字符串:
String.replaceAll()
-
反向引用 可以使用
$1
、$2
来反向引用匹配到的子串1
2
3
4
5
6
7
8
9
10
11String s = "the quick brown fox jumps over the lazy dog.";
String r = s.replaceAll("\\s([a-z]{4})\\s", " <b>$1</b> ");
//the quick brown fox jumps <b>over</b> the <b>lazy</b> dog.
[Matcher (Java SE 11 & JDK 11 ) (runoob.com)](https://www.runoob.com/manual/jdk11api/java.base/java/util/regex/Matcher.html)
> #### 模板引擎
>
> 模板引擎是指,定义一个字符串作为模板:
>
-
Hello, ${name}! You are learning ${lang}!
1
2
3
4
5
6
7
8
9
10
11
12
其中,以`${key}`表示的是变量,也就是将要被替换的内容
当传入一个`Map<String, String>`给模板后,需要把对应的key替换为Map的value。
例如,传入`Map`为:
```json
{
"name": "Bob",
"lang": "Java"
}然后,
${name}
被替换为Map
对应的值"Bob”,${lang}
被替换为Map
对应的值"Java",最终输出的结果为:
1Hello, Bob! You are learning Java!
用正则表达式实现:
Matcher.appendReplacement (Java SE 11 & JDK 11 ) (runoob.com)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.itranswarp.learnjava;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Learn Java from https://www.liaoxuefeng.com/
*
* @author liaoxuefeng
*/
public class Template {
final String template;
final Pattern pattern = Pattern.compile("\\$\\{(\\w+)\\}");
public Template(String template) {
this.template = template;
}
public String render(Map<String, Object> data) {
Matcher m = pattern.matcher(template);
// TODO:
StringBuilder sb = new StringBuilder();
while (m.find()) {
m.appendReplacement(sb,data.get(m.group(1)).toString());
}
m.appendTail(sb);
return sb.toString();
}
}