最近在 Java 中遇到这样一个需求:对字符串日期加减数天,比如
“20220815” 加 1 天变为 “20220816”
“20220901” 减 1 天变为 “20220831”
“20240228” 加 2 天变为 “20240301”
等等。不熟悉 API 的我愣了,于是经学习整理出本文,希望能帮到读者。本文会梳理 String Date Calendar 的基本用法及相互转换。复习的话直接看总结部分就可以了。
基本思路:
先说实现对字符串加减数日的需求,需要这样的方法:
String addDay(String str, int num)
但 Java 中没有方法能直接把字符串看作日期并对其加减若干天,所以先把格式五花八门的 String 解析成唯一的、能够代表一个时刻的 Date, 然后对 Date 一番操作,加数天也好减数月也好,再按希望的格式转回 String, 这就是基本思路。
而在这个过程中,涉及到 String Date Calendar 三种类型的对象的相互转换。如下:
String →simple.parse(str)→
Date →calendar.setTime(date)→
Calendar →calendar.getTime()→
Date →simple.format(date)→
String
接下来是面向 0 基础的逐步讲解:
String ← Date
Java 中一个 Date 类型对象可以表示一个瞬时时刻,最高精确到毫秒。
public static void main(String[] args) {
Date date = new Date();
System.out.println(date.toString()); // Mon Aug 15 18:19:55 CST 2022
}
new Date()
能够获得一个表示当前时刻的 Date 对象,直接输出之即可。(本文会在非 String 类型对象输出时加上 toString()
方法,便于理解)
如果不想输出默认字符串,可以借助:SimpleDateFormat 类自定义输出字符串的格式:
public static void main(String[] args) {
Date date = new Date();
System.out.println(date.toString()); // Mon Aug 15 18:19:55 CST 2022
SimpleDateFormat f = new SimpleDateFormat("yyyy/MM/dd");
System.out.println(f.format(date)); // 2022/08/15
}
public final String format(Date date)
方法能按照调用对象指定的格式返回一个 date
存储的时刻对应的字符串。这里定义格式 “年/月/日” 并存储在 f
对象中。
指定格式的方法如下表所示:
格式 | 解释 | 举例 |
---|---|---|
yyyy | 年 | 2022 |
MM | 月 | 08 |
dd | 日 | 15 |
E | 周 | Mon |
HH | 小时 | 18 |
mm | 分钟 | 19 |
ss | 秒 | 55 |
格式还有很多,比如说 M MM MMM 甚至分别表示 8 08 Aug, 所以很灵活。你甚至可以模仿默认格式……
public static void main(String[] args) {
Date date = new Date();
System.out.println(date.toString()); // Mon Aug 15 18:19:55 CST 2022
SimpleDateFormat f2 = new SimpleDateFormat("E MMM d HH:mm:ss zz yyyy", Locale.ENGLISH);
System.out.println(f2.format(date)); // Mon Aug 15 18:19:55 CST 2022
}
不过常用的就是上表的那些。
String → Date
若要解析 String → Date, 同样可以借助 SimpleDateFormat, 用另一个方法:parse()
.
public static void main(String[] args) {
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd");
System.out.println(f.parse("2022-08-15").toString());
}
public Date parse(String source)
方法按照调用者指定的格式返回一个与字符串参数对应时刻相同的 Date 类型的对象。
但要注意上面这样是会报错的,因为假设你将来没有按照 2022-08-15 输入,Java 应抛出异常,用 try/catch 包围代码吧。
public static void main(String[] args) {
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd");
try {
System.out.println(f.parse("2022-08-15").toString()); // Mon Aug 15 00:00:00 CST 2022
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
因为没有指定时分秒,就按全为 0 输出了。这不重要,重要的是我们确实成功得到了一个 Date 类型对象。
如果格式不匹配会怎样呢?如图,抛出了 java.text.ParseException
异常。
public static void main(String[] args) {
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd");
try {
System.out.println(f.parse("20220815").toString());
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
![图1 异常](https://i-blog.csdnimg.cn/blog_migrate/2ce5d5bef7cf34ae8da8d77fd1d987c4.png)
到此为止就实现了任意格式字符串与 Date 的互转。
Date ↔ Calendar
得到一个 Date 对象后,如何对其进行加减?这需要抽象类 Calendar 的帮助。其实 Calendar 能在许多场景替换 Date, 不过似乎却不能不借助 Date 和 SimpleDateFormat 来与 String 转换,所以先梳理了上文。
总之,Calendar 也可以表示一个瞬时时刻,不过不能像 Date 那样直接输出,需要先借助 getTime() 转换为 Date.
public static void main(String[] args) {
Date date = new Date();
System.out.println(date.toString()); // Mon Aug 15 18:19:55 CST 2022
Calendar badExample = Calendar.getInstance();
System.out.println(badExample.toString()); // java.util.GregorianCalendar[time=....非常长,后面省略
Calendar calendar = Calendar.getInstance();
System.out.println(calendar.getTime().toString()); // Mon Aug 15 18:19:55 CST 2022
}
因为是抽象类,所以借助 public static Calendar getInstance()
方法得到一个表示当前时刻的 Calendar 对象。
public final Date getTime()
方法可以直接返回一个表示此调用者存储的时间值的 Date 对象。相似的,public final void setTime(Date date)
同理。没错,Date 和 Calendar 类互转非常简单。
并且 Calendar 类是比较强大的,借助 public int get(int field)
方法可以返回指定时间字段的值,public void set(int field, int value)
方法可以设置指定时间字段的值。时间字段代表是年,还是月,还是日,使用 Calendar 的静态成员变量即可。举例:
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
int year = calendar.get(Calendar.YEAR); // 2022
int month = calendar.get(Calendar.MONTH); // 7
int day = calendar.get(Calendar.DAY_OF_MONTH); // 15
calendar.set(Calendar.YEAR, 2333); // 年份从 2022 变为 2333
}
注意:month
∈ [0, 11], 比人类习惯的 [1, 12] 少 1.
加减日期
截止目前已经完成了 String Date Calendar 互转了。(Calendar 到 String 恐怕只能通过 Date 中转一下,我没有发现任何无需借助 Date 的方法)
接下来该实现按格式输入字符串日期,任意加减天数的需求了,用到 Calendar 的成员方法:public abstract void add(int field, int amount)
.
它能在 field
代表的时间字段上加 amount
个单位(可以为负数)。字段同样使用 Calendar 的静态成员变量。结合例子很容易理解:
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
System.out.println(calendar.getTime().toString()); // Mon Aug 15 18:19:55 CST 2022
calendar.add(Calendar.YEAR, 2); // Mon Aug 15 18:19:55 CST 2024
calendar.add(Calendar.MONTH, -6); // Thu Feb 15 18:19:55 CST 2024
calendar.add(Calendar.DAY_OF_MONTH, 14); // Thu Feb 29 18:19:55 CST 2024
System.out.println(calendar.getTime().toString()); // Thu Feb 29 18:19:55 CST 2024
}
最后,把上面的内容连起来,笔者终于完成了:按指定格式 “yyyyMMdd” 传入字符串与加减天数,返回同样格式字符串的方法:String addDay(String str, int num)
. 如下:
public class Main {
/**
* 对日期加减数天
*
* @param yyyyMMdd 形如 "20240228"
* @param day 形如 2(支持负数)
* @return 形如 "20240301"
*/
public static String addDay(String yyyyMMdd, int day) {
// String to Date
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
Date date;
try {
date = format.parse(yyyyMMdd);
} catch (ParseException e) {
throw new RuntimeException(e);
}
// Change Date
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(Calendar.DAY_OF_MONTH, day);
date = calendar.getTime();
// Date to String
yyyyMMdd = format.format(date);
return yyyyMMdd;
}
public static void main(String[] args) {
System.out.println(addDay("20240228", 2)); // 20240301
}
}
即使天数很多也没问题:
System.out.println(addDay("20240228", 2333)); // 20300719
也可以完全同理实现加减年/月/时/分/秒,等等,只需更改 field
为 Calendar.XXX
即可,不一而足。
总结
1、三者互转:
![图2 总结](https://i-blog.csdnimg.cn/blog_migrate/3cea86af4269a191ac46be57f332f87e.png)
(SimpleDateFormat 对象和 Calendar 对象用 s
c
简写)
2、实现日期字符串的加减:String → Date → Calendar → Date → String.
核心是对 Calendar 对象使用 add()
方法。
那么本文就结束了,作者非职业自学 Java,有写得不好的地方欢迎斧正。