引言
时间,无疑是宇宙最神秘的艺术之一。在软件开发的世界中,时间是一种珍贵的资源,它如同沙漏中的细沙,不停地从我们手中溜走。当我们构建应用程序时,时间扮演着重要的角色,它贯穿于事件的发生、数据的记录, 甚至用户界面的呈现。
为了与时间打交道,Java 提供了Date
和DateFormat
这两个关键工具。Date
类是我们的时间捕手,它能够获取当前日期和时间,甚至允许我们进行日期比较和格式化。而DateFormat
则是时间的翻译官,它负责将日期时间转换为人类可读的字符串,并允许我们解析用户输入的日期。
然而,尽管它们是强大的工具,但它们也带来了许多陷阱和挑战。如何处理时区问题?如何在多线程环境中安全使用它们?如何实现国际化的日期时间格式?这就是我们将要探索的内容。
在这个时间之旅中,我们将不仅仅了解这两个类的使用方法,还将深入探讨它们的局限性,以及如何使用 Java 8 引入的java.time
包来优雅地处理日期和时间。同时,我们还会揭示时间背后的一些最佳实践,包括如何处理日期范围和区间,以及时区的重要性。
让我们一起踏上这段有趣而富有挑战的时间之旅,探索时间的奥秘,解锁时间的力量,为我们的应用程序注入更多时间的价值。
一、Date 类
1. Date 类概述
Date 类用于在Java中表示日期和时间。它是Java标准库中用于处理日期和时间的基本类之一。
1.1 日期和时间的表示
日期和时间的表示通常涉及以下几个方面:
- 日期:表示年、月、日,如 "2023-10-29" 表示2023年10月29日。
- 时间:表示时、分、秒,如 "15:30:00" 表示下午3点30分。
- 日期时间:同时包括日期和时间信息,如 "2023-10-29 15:30:00" 表示2023年10月29日下午3点30分。
- 时区:由于不同地区使用不同的时区,同一时刻在不同时区可能对应不同的实际时间。
1.2 Java 中的 Date 类
Date 类是Java标准库中用于表示日期和时间的类,它包含了一个长整型数,表示自1970年1月07:30:00(以SGT即新加坡标准时区为准)以来的毫秒数。这个长整型数被称为"Unix时间"或"时间戳"。
1.3 Date 对象的创建
可以通过以下方式创建 Date 对象:
示例:
java
// 获取当前时间的时间戳
Date now = new Date();
System.out.println(now); // 输出当前日期和时间
// 创建特定日期时间的 Date 对象
long timestamp = 1603960800000L; // 2020-10-29 16:40:00的时间戳
Date specificDate = new Date(timestamp);
System.out.println(specificDate); // 输出指定日期和时间
运行结果如下
bash
Sat Nov 04 09:57:48 SGT 2023
Thu Jan 01 07:30:00 SGT 1970
Sat Nov 04 09:57:48 SGT 2023
即"星期六 11月 4号 9点57分48秒 新加坡标准时间 2023年" 中国话叫:2023年11月4号上午9点57分48秒,星期六。
1.4 日期和时间的时区问题
日期和时间的时区问题在全球化的背景下变得尤为重要。不同国家和地区使用不同的时区,而且一些地区还可能会在夏季采用夏令时。这使得处理日期和时间变得相当复杂。
在 Java 中,Date
类包含日期和时间信息以及默认的时区信息。这意味着 Date
对象会随着默认时区的更改而调整其内部时间表示,Date
类只会输出默认时区的时间。但也可以结合TimeZone
类进行更精确的时区控制。
下面我们来看一个示例,展示了如何在不同的时区中处理日期和时间:
java
import java.util.Date;
import java.util.TimeZone;
public class TimeZoneExample {
public static void main(String[] args) {
// 创建一个日期对象,表示特定的日期和时间
Date date = new Date();
// 保存当前系统的默认时区
TimeZone defaultTimeZone = TimeZone.getDefault();
// 指定目标时区
TimeZone timeZone = TimeZone.getTimeZone("GMT");
TimeZone.setDefault(timeZone);
// 输出 GMT 时间
System.out.println("GMT 时间: " + date);
// 指定目标时区
timeZone=TimeZone.getTimeZone("America/New_York");
TimeZone.setDefault(timeZone);
// 输出 纽约 时间
System.out.println("在纽约的时间: " + date);
// 恢复原来的默认时区
TimeZone.setDefault(defaultTimeZone);
}
}
运行结果如下:
bash
GMT 时间: Sat Nov 04 02:15:59 GMT 2023
在纽约的时间: Fri Nov 03 22:15:59 EDT 2023
2. Date 对象操作
2.1 获取当前日期和时间
要获取当前日期和时间,可以使用 Date 类的无参构造函数,它会创建一个表示当前日期和时间的 Date 对象。示例如下:
java
// 获取当前日期和时间
Date currentDate = new Date();
System.out.println(currentDate);
2.2 获取毫秒数
Date 对象内部保存了自1970年1月07:30:00 (以SGT即新加坡标准时区为准) 以来的毫秒数。
我们可以使用 getTime() 方法来获取这个毫秒数。
java
// 获取 Date 对象的毫秒数
Date date = new Date();
long timestamp = date.getTime();
System.out.println("Unix时间戳: " + timestamp);
运行时间及结果: 2023/11/04 10:40 Unix时间戳 : 1699065624934
2.3 比较日期
可以使用 Date 对象的方法来比较日期,如比较两个日期是否相等、日期的先后顺序等。
示例:
java
// 创建两个 Date 对象
Date date1 = new Date();// 即当前时间: 2023-11-04 10:45:00
Date date2 = new Date(1603960800000L);//2020-10-29 16:40:00
// 比较两个日期是否相等
boolean isEqual = date1.equals(date2);
System.out.println("日期1和日期2是否相等: " + isEqual);
// 比较两个日期的先后顺序
int comparison = date1.compareTo(date2);
if (comparison < 0) {
System.out.println("日期1在日期2之前");
} else if (comparison > 0) {
System.out.println("日期1在日期2之后");
} else {
System.out.println("日期1和日期2相等");
}
运行结果如下:
arduino
日期1和日期2是否相等: false
日期1在日期2之后
2.4 日期的格式化和解析
为了将 Date 对象以人类可读的方式显示,或者将人类输入的日期字符串解析为 Date 对象,通常需要进行日期的格式化和解析。
java
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.Date;
// 创建一个 SimpleDateFormat 对象用于日期格式化和解析
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 日期格式化为字符串
Date date = new Date();
String formattedDate = dateFormat.format(date);
System.out.println("格式化后的日期: " + formattedDate);
// 字符串解析为日期
String dateString = "2023-10-29 15:30:00";
try {
Date parsedDate = dateFormat.parse(dateString);
System.out.println("解析后的日期: " + parsedDate);
} catch (ParseException e) {
System.out.println("日期解析失败:" + e.getMessage());
}
运行结果如下:
bash
格式化后的日期: 2023-11-04 10:48:31
解析后的日期: Sun Oct 29 15:30:00 SGT 2023
2.5 Date 的可变性和不可变性
Date 类是可变的,这意味着可以通过 set 方法修改 Date 对象的值。这可能会导致线程安全性问题,因此在多线程环境中使用时需要格外小心。
java
// Date 对象的可变性示例
Date mutableDate = new Date();
System.out.println("可变日期:" + mutableDate);
// 修改 Date 对象的值
mutableDate.setTime(1603960800000L);
System.out.println("修改后的日期:" + mutableDate);
运行结果如下:
bash
可变日期:Sat Nov 04 11:07:58 SGT 2023
修改后的日期:Thu Oct 29 16:40:00 SGT 2020
在实际应用中,为了避免线程安全问题,推荐使用不可变的日期和时间处理类,如 Java 8 中的 LocalDate 、LocalTime 和 LocalDateTime 等。
3. Date 类的问题
3.1 Date 类存在的问题
传统的 Date 类存在一些问题,这些问题主要包括:
-
可变性 : Date 类是可变的,这意味着一旦创建,其值可以随时修改。这会导致潜在的线程安全问题和不可预测的行为。
-
精度问题 : Date 类只能精确到毫秒级,不能表示更小的时间单位,例如纳秒。
-
时区问题 : Date 类在处理时区时不够灵活,通常需要通过手动设置时区来进行处理,容易出错。
-
命名问题 : Date 类中的一些方法的命名不够清晰,容易引起歧义。
3.2 时区问题的处理
Date 类在处理时区问题时存在困难。为了解决这个问题,可以使用 java.time 包中的新类,如 ZonedDateTime 和 ZoneId。这些类提供了更灵活的时区支持,能够轻松处理不同时区的日期和时间。
示例:
java
// 使用 ZonedDateTime 处理时区问题
ZonedDateTime dateTimeInNewYork = ZonedDateTime.now(ZoneId.of("America/New_York"));
ZonedDateTime dateTimeInTokyo = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
// 比较两个不同时区的日期和时间
if (dateTimeInNewYork.isEqual(dateTimeInTokyo)) {
System.out.println("纽约和东京的时间相同。");
}else{
System.out.println("纽约和东京的时间不同。");
}
System.out.println(dateTimeInNewYork);
System.out.println(dateTimeInTokyo);
运行结果如下:
bash
纽约和东京的时间不同。
2023-11-03T23:08:53.777344300-04:00[America/New_York]
2023-11-04T12:08:53.783692300+09:00[Asia/Tokyo]
3.3 Date 类和多线程问题
由于 Date 类的可变性,它在多线程环境下容易引发线程安全问题。多个线程同时访问和修改同一个 Date 对象可能导致不一致的结果。为了解决这个问题,可以使用 java.time 包中的不可变类,如 LocalDateTime 、LocalDate 和 LocalTime,它们在多线程环境下更安全。
示例:
java
// 使用 LocalDateTime 类,它是不可变的
LocalDateTime currentDateTime = LocalDateTime.now();
// 在多线程环境下更安全
4. 替代方案 - java.time 包
4.1 为什么要使用 java.time
在 Java 8 中引入了 java.time 包,它提供了更加强大和灵活的日期和时间处理功能,相比于传统的 Date 类,它具有以下优势:
-
不可变性 : java.time 包中的大多数类都是不可变的,这意味着一旦创建,它们的值不能被修改,这有助于避免意外的修改和副作用。
-
丰富的方法 : java.time 包提供了丰富的方法来处理日期和时间,包括日期的创建、计算、格式化、解析等。这些方法使得日期和时间操作更加方便。
-
清晰的命名 : java.time 包中的类和方法都有清晰的命名,使得代码更易理解和维护。
-
处理时区 : java.time 包提供了更好的时区支持,可以轻松处理不同时区的日期和时间。
4.2 LocalDateTime、LocalDate 和其他类
java.time 包中引入了一系列新的日期和时间类,包括:
- LocalDateTime: 表示日期和时间,不包含时区信息。
- LocalDate: 表示日期,不包含时分秒。
- LocalTime: 表示时间,不包含日期。
- ZonedDateTime: 表示带时区的日期和时间。
- ZoneId: 表示时区。
示例:
java
// 创建 LocalDateTime 对象表示当前日期和时间
LocalDateTime currentDateTime = LocalDateTime.now();
// 创建 LocalDate 对象表示当前日期
LocalDate currentDate = LocalDate.now();
// 创建 LocalTime 对象表示当前时间
LocalTime currentTime = LocalTime.now();
4.3 与 Date 类的比较
与 Date 类相比,java.time 包中的类更容易使用和理解。例如,要比较两个日期是否相等,使用 LocalDate 类的 isEqual 方法更加清晰和安全:
java
LocalDate date1 = LocalDate.of(2023, 10, 1);
LocalDate date2 = LocalDate.of(2023, 10, 1);
if (date1.isEqual(date2)) {
System.out.println("日期相等");
}
4.4 处理时区的新方法
java.time 包中的 ZonedDateTime 类允许处理带时区的日期和时间。它可以表示不同时区的日期和时间,并提供了时区的支持。通过 ZoneId 类,可以获取系统支持的时区列表。
示例:
java
// 创建一个带有时区信息的 ZonedDateTime 对象,表示当前美国纽约的时间
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("America/New_York"));
System.out.println("当前纽约时间: " + zonedDateTime);
// 获取系统支持的所有时区标识符
Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
// 遍历所有时区标识符并打印出来
availableZoneIds.forEach(System.out::println);
运行结果如下:
bash
当前纽约时间: 2023-11-03T23:24:25.782088800-04:00[America/New_York]
Asia/Aden
America/Cuiaba
Etc/GMT+9
Etc/GMT+8
...(略)
java.time 包提供了更多的功能和方法,用于日期和时间的操作和计算。它是处理日期和时间的首选替代方案,特别适用于需要处理多时区、复杂日期计算和格式化的应用。
二、DateFormat 类
1. DateFormat 类概述
DateFormat 类是 Java 中用于日期和时间的格式化和解析的类。它允许将日期和时间格式化为字符串,也可以将字符串解析为日期和时间。以下是关于 DateFormat 类的一些概述:
1.1 日期和时间的格式化和解析
DateFormat 类主要用于以下两个任务:
-
格式化:将日期和时间对象格式化为字符串,以便进行显示或存储。这将日期和时间表示为可读高的字符串。
-
解析 :即格式化的逆过程。将日期和时间的字符串表示解析为日期和时间对象。这用于从用户输入或外部数据源中还原日期和时间。
1.2 Java 中的 DateFormat 类
在 Java 中,DateFormat 类是一个抽象类,它有多个实现类,如 SimpleDateFormat。这些实现类提供了不同的日期和时间格式化和解析功能,以满足各种需求。
1.3 不同国家和地区的日期格式
DateFormat 类支持不同国家和地区的日期和时间格式。它可以根据特定的 Locale(语言和地区)来格式化和解析日期。这意味着相同的日期和时间可以以不同的格式显示,根据不同的地区和语言习惯。
示例:
java
// 创建一个 DateFormat 对象,使用美国地区的日期格式
DateFormat dfUS = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US);
Date date = new Date();
String formattedDate = dfUS.format(date);
System.out.println("美国日期格式:" + formattedDate);
// 创建一个 DateFormat 对象,使用法国地区的日期格式
DateFormat dfFR = DateFormat.getDateInstance(DateFormat.SHORT, Locale.FRANCE);
String formattedDateFR = dfFR.format(date);
System.out.println("法国日期格式:" + formattedDateFR);
运行结果如下:
bash
美国日期格式:11/4/23
法国日期格式:04/11/2023
1.4 使用不同日期样式
DateFormat 类提供了不同的日期和时间样式,包括 SHORT 、MEDIUM 、LONG 和 FULL。每种样式代表了不同的日期和时间格式。通过选择不同的样式,可以获得不同粒度的日期和时间信息。
示例:
java
// 使用 MEDIUM 样式格式化日期和时间
DateFormat dfMedium = DateFormat.getDateTimeInstance(DateFormat.MEDIUM,DateFormat.MEDIUM);
System.out.println("MEDIUM 样式:" + dfMedium.format(date));
// 使用 FULL 样式格式化日期和时间
DateFormat dfFull = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL);
System.out.println("FULL 样式:" + dfFull.format(date));
// 使用 Long 样式格式化日期和时间
DateFormat dfLong = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
System.out.println("Long 样式:" + dfLong.format(date));
运行结果如下:
bash
MEDIUM 样式:2023年11月4日 下午2:42:43
FULL 样式:2023年11月4日星期六 新加坡标准时间 下午2:42:43
Long 样式:2023年11月4日 SGT 下午2:51:30
2. SimpleDateFormat 类
SimpleDateFormat 类是 Java 中用于日期和时间的格式化和解析的主要类。它是 DateFormat 类的一个具体实现,提供了更灵活的日期和时间格式化和解析功能。以下是关于 SimpleDateFormat 类的一些重要内容:
2.1 格式化日期和时间
SimpleDateFormat 主要用于将日期和时间对象格式化为字符串。它允许我们根据模式字符串定义日期和时间的显示格式。以下是一个简单的示例:
java
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date now = new Date();
String formattedDate = sdf.format(now);
System.out.println("格式化后的日期:" + formattedDate);
运行结果如下:
bash
格式化后的日期:2023-11-04 14:54:18
上述代码中,我们创建了一个 SimpleDateFormat 对象,使用模式字符串 "yyyy-MM-dd HH:mm:ss" 来指定日期和时间的格式。然后,我们将当前日期和时间对象格式化为指定格式的字符串。
2.2 自定义日期格式
SimpleDateFormat 允许我们根据需要自定义日期格式。模式字符串中包含不同的字符,它们代表日期和时间的不同部分。以下是一些常用的模式字符:
- "yyyy":四位年份
- "MM":月份
- "dd":日期
- "HH":小时(24 小时制)
- "mm":分钟
- "ss":秒
通过组合这些模式字符,可以创建自定义日期格式。例如:
java
SimpleDateFormat customSdf = new SimpleDateFormat("dd/MM/yyyy HH:mm");
String customFormattedDate = customSdf.format(now);
System.out.println("自定义格式的日期:" + customFormattedDate);
运行结果如下:
bash
自定义格式的日期:04/11/2023 14:57
2.3 解析日期和时间字符串
SimpleDateFormat 也可以用于将日期和时间的字符串表示解析为日期和时间对象。我们需要使用相同格式的模式字符串来告诉 SimpleDateFormat 如何解析字符串。以下是一个解析的示例:
java
String dateString = "2023-10-30 15:30:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
Date parsedDate = sdf.parse(dateString);
System.out.println("解析后的日期:" + parsedDate);
} catch (ParseException e) {
e.printStackTrace();
}
运行结果如下:
bash
解析后的日期:Mon Oct 30 15:30:00 SGT 2023
上述代码中,我们创建了一个 SimpleDateFormat 对象,使用相同的模式字符串 "yyyy-MM-dd HH:mm:ss" 来指导解析。然后,我们尝试将日期和时间的字符串表示解析为日期对象。
2.4 SimpleDateFormat 的线程安全问题和解决方案
SimpleDateFormat 不是线程安全的类。如果多个线程同时访问同一个 SimpleDateFormat 实例,可能会导致解析和格式化错误。为了解决这个问题,可以采取以下两种方法之一:
-
使用线程局部变量(ThreadLocal ):每个线程都有自己的 SimpleDateFormat 实例,避免多线程竞争。
-
使用 java.time.format.DateTimeFormatter :java.time 包提供的日期时间 API 是线程安全的,可以考虑迁移到新的 API。
java
ThreadLocal<SimpleDateFormat> threadLocalSdf = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
SimpleDateFormat sdf = threadLocalSdf.get();
或者,使用 java.time.format.DateTimeFormatter:
java
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime now = LocalDateTime.now();
String formattedDate = dtf.format(now);
3. DateFormat 类的线程安全问题
3.1 DateFormat 对象的线程安全性
DateFormat 类的对象在多线程环境中不是线程安全的。这是因为 DateFormat 类的实例包含了内部状态,用于格式化和解析日期和时间。多个线程同时访问同一个 DateFormat 对象可能导致竞争条件和不确定的结果。
3.2 SimpleDateFormat 的线程安全问题
SimpleDateFormat 是 DateFormat 类的一个具体实现,它继承了 DateFormat 的线程不安全性。具体来说,多个线程同时访问同一个 SimpleDateFormat 实例进行格式化或解析操作可能导致混乱或错误的结果。
以下是一个简单的示例,展示了 SimpleDateFormat 的线程安全问题:
java
// 3.2
import java.text.SimpleDateFormat;
import java.util.concurrent.atomic.AtomicBoolean;
public class DateFormatThreadSafetyExample {
private static final AtomicBoolean STOP = new AtomicBoolean(); // 创建一个原子布尔值,用于控制线程停止
private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("yyyy-M-d"); // 创建一个日期格式化对象,格式为"yyyy-M-d"
public static void main(String[] args) {
Runnable runnable = () -> { // 创建一个Runnable对象,用于执行线程任务
int count = 0; // 初始化计数器
while (!STOP.get()) { // 当STOP值为false时,循环执行以下操作
try {
FORMATTER.parse("2023-11-3"); // 尝试解析日期字符串"2023-11-3"
} catch (Exception e) { // 如果解析失败,捕获异常
e.printStackTrace(); // 打印异常堆栈信息
if (++count > 3) { // 如果计数器大于3,将STOP值设为true,停止线程
STOP.set(true);
}
}
}
};
new Thread(runnable).start(); // 创建并启动一个新线程,执行runnable任务
new Thread(runnable).start(); // 创建并启动另一个新线程,执行runnable任务
}
}
运行结果如下:
bash
java.lang.NumberFormatException: multiple points
at java.base/jdk.internal.math.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
at java.base/jdk.internal.math.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.base/java.lang.Double.parseDouble(Double.java:651)
at java.base/java.text.DigitList.getDouble(DigitList.java:169)
at java.base/java.text.DecimalFormat.parse(DecimalFormat.java:2202)
at java.base/java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2244)
at java.base/java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1545)
at java.base/java.text.DateFormat.parse(DateFormat.java:397)
at Test.DateFormatThreadSafetyExample.lambda$0(DateFormatThreadSafetyExample.java:16)
at java.base/java.lang.Thread.run(Thread.java:833)
java.lang.NumberFormatException: multiple points
at java.base/jdk.internal.math.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
at java.base/jdk.internal.math.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.base/java.lang.Double.parseDouble(Double.java:651)
at java.base/java.text.DigitList.getDouble(DigitList.java:169)
at java.base/java.text.DecimalFormat.parse(DecimalFormat.java:2202)
at java.base/java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1937)
at java.base/java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1545)
at java.base/java.text.DateFormat.parse(DateFormat.java:397)
at Test.DateFormatThreadSafetyExample.lambda$0(DateFormatThreadSafetyExample.java:16)
at java.base/java.lang.Thread.run(Thread.java:833)
java.lang.NumberFormatException: For input string: ""
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
at java.base/java.lang.Long.parseLong(Long.java:721)
at java.base/java.lang.Long.parseLong(Long.java:836)
at java.base/java.text.DigitList.getLong(DigitList.java:195)
at java.base/java.text.DecimalFormat.parse(DecimalFormat.java:2197)
at java.base/java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1937)
at java.base/java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1545)
at java.base/java.text.DateFormat.parse(DateFormat.java:397)
at Test.DateFormatThreadSafetyExample.lambda$0(DateFormatThreadSafetyExample.java:16)
at java.base/java.lang.Thread.run(Thread.java:833)
java.lang.NumberFormatException: For input string: "E71"
at java.base/jdk.internal.math.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2054)
at java.base/jdk.internal.math.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.base/java.lang.Double.parseDouble(Double.java:651)
at java.base/java.text.DigitList.getDouble(DigitList.java:169)
at java.base/java.text.DecimalFormat.parse(DecimalFormat.java:2202)
at java.base/java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1937)
at java.base/java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1545)
at java.base/java.text.DateFormat.parse(DateFormat.java:397)
at Test.DateFormatThreadSafetyExample.lambda$0(DateFormatThreadSafetyExample.java:16)
at java.base/java.lang.Thread.run(Thread.java:833)
java.lang.NumberFormatException: For input string: "E7"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
at java.base/java.lang.Long.parseLong(Long.java:711)
at java.base/java.lang.Long.parseLong(Long.java:836)
at java.base/java.text.DigitList.getLong(DigitList.java:195)
at java.base/java.text.DecimalFormat.parse(DecimalFormat.java:2197)
at java.base/java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1937)
at java.base/java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1545)
at java.base/java.text.DateFormat.parse(DateFormat.java:397)
at Test.DateFormatThreadSafetyExample.lambda$0(DateFormatThreadSafetyExample.java:16)
at java.base/java.lang.Thread.run(Thread.java:833)
java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 19
at java.base/java.text.DigitList.fitsIntoLong(DigitList.java:230)
at java.base/java.text.DecimalFormat.parse(DecimalFormat.java:2195)
at java.base/java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1937)
at java.base/java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1545)
at java.base/java.text.DateFormat.parse(DateFormat.java:397)
at Test.DateFormatThreadSafetyExample.lambda$0(DateFormatThreadSafetyExample.java:16)
at java.base/java.lang.Thread.run(Thread.java:833)
java.lang.NumberFormatException: For input string: ""
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
at java.base/java.lang.Long.parseLong(Long.java:721)
at java.base/java.lang.Long.parseLong(Long.java:836)
at java.base/java.text.DigitList.getLong(DigitList.java:195)
at java.base/java.text.DecimalFormat.parse(DecimalFormat.java:2197)
at java.base/java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1937)
at java.base/java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1545)
at java.base/java.text.DateFormat.parse(DateFormat.java:397)
at Test.DateFormatThreadSafetyExample.lambda$0(DateFormatThreadSafetyExample.java:16)
at java.base/java.lang.Thread.run(Thread.java:833)
报错啦!!!
代码证明 SimpleDateFormat 类:线------程------不------安------全!
3.3 使用 ThreadLocal 解决线程安全问题
一种常见的解决 SimpleDateFormat 线程安全问题的方法是使用 ThreadLocal 。每个线程都拥有自己的 SimpleDateFormat 实例,避免了多线程竞争。以下是如何使用 ThreadLocal 来确保线程安全:
java
public class ThreadLocalDateFormatExample {
// 创建一个静态的ThreadLocal变量threadLocalSdf,用于存储SimpleDateFormat对象。使用withInitial方法初始化为当前时间的格式化字符串"yyyy-MM-dd HH:mm:ss"
private static ThreadLocal<SimpleDateFormat> threadLocalSdf = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static void main(String[] args) {
Runnable task = () -> { // 定义一个Runnable任务
SimpleDateFormat sdf = threadLocalSdf.get(); // 从threadLocalSdf中获取SimpleDateFormat对象
Date now = new Date(); // 创建一个新的Date对象,表示当前时间
String formattedDate = sdf.format(now); // 使用SimpleDateFormat对象将当前时间格式化为字符串
System.out.println("格式化的日期: " + formattedDate); // 输出格式化后的日期字符串
}; // 结束Runnable任务的定义
Thread thread1 = new Thread(task); // 创建一个新的线程,传入Runnable任务
Thread thread2 = new Thread(task); // 创建另一个新的线程,传入Runnable任务
thread1.start(); // 启动第一个线程
thread2.start(); // 启动第二个线程
}
}
运行结果如下:
bash
格式化的日期: 2023-11-04 15:10:41
格式化的日期: 2023-11-04 15:10:41
在这个示例中,每个线程通过 ThreadLocal 获得自己的 SimpleDateFormat 实例,确保了线程之间的隔离,从而解决了线程安全问题。
使用 ThreadLocal 是一种常用的解决方案,但需要小心,以免导致内存泄漏。确保在不再需要时清理 ThreadLocal 变量。
4. 本地化和国际化
4.1 日期格式的本地化
本地化是根据特定地区或文化习惯来适应不同的语言和格式。在日期格式化中,本地化通常涉及到日期的显示方式、月份和星期的命名等。
4.2 DateFormat 对不同语言的支持
DateFormat 类和其子类提供了对不同语言的支持。它们可以根据不同的 Locale 对象来格式化和解析日期和时间,以适应不同的语言和文化。
例如,下面是如何在不同语言环境下使用 SimpleDateFormat:
java
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class DateFormatLocalizationExample {
public static void main(String[] args) {
Date now = new Date();
// 适应美国英语环境 格式化日期和时间
SimpleDateFormat sdfUS = new SimpleDateFormat("MMMM dd, yyyy", Locale.US);
// 适应法国环境 格式化日期和时间
SimpleDateFormat sdfFR = new SimpleDateFormat("dd MMMM yyyy", Locale.FRENCH);
System.out.println("Date in US format: " + sdfUS.format(now));
System.out.println("Date in French format: " + sdfFR.format(now));
}
}
运行结果如下:
bash
Date in US format: November 04, 2023
Date in French format: 04 novembre 2023
在上面的示例中,我们创建了两个 SimpleDateFormat 实例,一个使用美国英语环境,另一个使用法国环境。这导致了不同的日期格式,分别使用不同的日期和月份名称。
4.3 使用 ResourceBundle 进行国际化
除了 DateFormat ,在实现国际化时,还可以使用 ResourceBundle 类来管理应用程序的本地化资源。这包括日期和时间格式、消息文本、标签和其他文本内容。
ResourceBundle 允许将不同的本地化资源存储在属性文件中,根据当前 Locale 动态加载适当的资源。
以下是一个简单的示例,演示如何使用 ResourceBundle 进行国际化:
java
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.ResourceBundle;
public class ResourceBundleExample {
public static void main(String[] args) {
// 获取当前日期
Date currentDate = new Date();
// 使用DateFormat格式化日期
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String formattedDate = dateFormat.format(currentDate);
// 将格式化后的日期字符串按"-"分割成数组
String date[]=formattedDate.split("-");
// 使用ResourceBundle加载资源文件,设置为中文资料
ResourceBundle bundle = ResourceBundle.getBundle("messages_CH");
// 从资源文件中获取翻译
String translatedDate = bundle.getString("date").replace("%year", date[0]).replace("%month", date[1]).replace("%date", date[2]);
// 输出结果
System.out.println("咱们中国人这么说:\n" + bundle.getString("hello")+"\n" +translatedDate);
// 切换到美国英语资源文件
bundle = ResourceBundle.getBundle("messages_US");
// 从资源文件中获取翻译
translatedDate = bundle.getString("date").replace("%year", date[0]).replace("%month", date[1]).replace("%date", date[2]);
// 输出结果
System.out.println("人家老美得这么说:\n" + bundle.getString("hello")+"\n" +translatedDate);
}
}
需要注意的是我们需要创建两个资源包文件,即messages_CH.properties
和messages_US.properties
,它们和ResourceBundleExample.java
的相对位置如下:
bash
my_project:
│ messages_CH.properties
│ messages_US.properties
└─src
ResourceBundleExample.java
messages_CH.properties
和messages_US.properties
的文件内容分别如下:
bash
hello=你好呀!
date=今天是%year年%month月%date日.
bash
hello=Hello!
date=Today is %month,%date,%year.
最后,程序的运行结果如下:
bash
咱们中国人这么说:
你好呀!
今天是2023年11月04日.
人家老美得这么说:
Hello!
Today is 11,04,2023.
4.4 示例:日期格式的本地化
下面是一个示例,演示了如何在不同本地化环境下格式化日期。在这个示例中,我们将使用 Locale 设置不同的语言环境并对日期进行本地化:
java
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
public class DateLocalizationExample {
public static void main(String[] args) {
// 创建一个Date对象,表示当前日期和时间
Date now = new Date();
// 创建一个Locale对象,表示美国英语、法国法语
Locale usLocale = new Locale("en", "US");
Locale frLocale = new Locale("fr", "FR");
// 分别使用美国英语、法国法语的Locale对象创建一个DateFormat对象,用于格式化日期和时间
DateFormat usDateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, usLocale);
DateFormat frDateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, frLocale);
System.out.println("Date and time in US English: " + usDateFormat.format(now));
System.out.println("Date and time in French: " + frDateFormat.format(now));
}
}
运行结果如下:
bash
Date and time in US English: November 4, 2023 at 3:24:03 PM SGT
Date and time in French: 4 novembre 2023 à 15:24:03 SGT
在这个示例中,我们使用 DateFormat 类的不同本地化设置,根据美国英语和法国法语环境分别格式化日期和时间。这会导致不同的日期和时间显示格式,以适应不同的语言和文化要求。
三、最佳实践
1. 处理日期范围和区间
在处理日期和时间时,处理日期范围和区间是一个常见但重要的任务。以下是一些最佳实践来处理日期范围和区间:
使用合适的数据结构: 为了表示日期范围和区间,可以使用 Java 中的 Date
对象或更现代的 LocalDate
对象。 LocalDate
对象是 Java 8 引入的 java.time
包的一部分,它提供了更好的支持。例如,你可以使用 LocalDate
来表示一个日期范围的开始和结束。
java
LocalDate startDate = LocalDate.of(2023, 1, 1);
LocalDate endDate = LocalDate.of(2023, 12, 31);
处理时区差异: 当涉及到跨越不同时区的日期和时间时,要特别小心。最好使用 ZonedDateTime
来表示具有时区信息的日期和时间。这将确保你的日期范围考虑了时区的变化。
java
ZoneId newYorkZone = ZoneId.of("America/New_York");
ZonedDateTime startDateTime = ZonedDateTime.of(2023, 1, 1, 0, 0, 0, 0, newYorkZone);
ZonedDateTime endDateTime = ZonedDateTime.of(2023, 12, 31, 23, 59, 59, 999, newYorkZone);
避免使用字符串表示: 避免在应用程序中使用字符串来表示日期范围,因为这可能导致格式化和解析错误。优选使用日期对象来处理日期范围。
2. 理解时区的重要性
时区在处理日期和时间时至关重要。以下是一些理解时区重要性的最佳实践:
存储时间戳而非字符串: 存储日期和时间时,最好使用时间戳(例如 Instant
或 ZonedDateTime
)而不是字符串。时间戳是相对于时区的不变值,可以轻松地转换为不同的时区。
显示本地时间: 当向用户显示日期和时间时,通常应将其转换为用户所在地区的本地时间。这可以通过 ZoneId
和 ZonedDateTime
来实现。
java
ZoneId userZone = ZoneId.of("Asia/Tokyo");
ZonedDateTime userDateTime = startDateTime.withZoneSameInstant(userZone);
考虑夏令时: 夏令时是时区的一部分,它会导致时间的变化。在处理涉及夏令时的日期和时间时要特别小心。
使用现代日期时间库: 如果可能的话,尽量使用 Java 8 引入的 java.time
包,它提供了更强大和准确的日期和时间处理功能,尤其在处理时区和夏令时方面。
总之,日期和时间的处理需要小心考虑时区和夏令时的问题。使用合适的数据结构和现代日期时间库,可以更容易地处理日期范围和区间,同时避免出现常见的日期和时间错误。
2. 时间戳和时区信息
2.1 使用时间戳表示日期和时间
在Java中,你可以使用时间戳来表示日期和时间,同时使用Date
和DateFormat
类来进行操作和格式化。下面是一个完整的示例,演示如何使用时间戳表示日期和时间,以及如何使用Date
和DateFormat
类进行相关操作:
java
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TimestampExample {
public static void main(String[] args) {
// 获取当前时间的时间戳
long currentTimestamp = System.currentTimeMillis();
// 使用时间戳创建Date对象
Date date = new Date(currentTimestamp);
// 使用SimpleDateFormat格式化日期
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String formattedDate = dateFormat.format(date);
System.out.println("当前时间的时间戳: " + currentTimestamp);
System.out.println("时间戳转换为日期时间: " + formattedDate);
// 创建一个特定的日期时间的时间戳
String dateStr = "2023-10-15 14:30:00";
try {
DateFormat parseFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date parsedDate = parseFormat.parse(dateStr);
long customTimestamp = parsedDate.getTime();
System.out.println("自定义日期时间的时间戳: " + customTimestamp);
} catch (Exception e) {
System.err.println("日期解析出错: " + e.getMessage());
}
}
}
运行结果如下:
bash
当前时间的时间戳: 1699088456494
时间戳转换为日期时间: 2023-11-04 17:00:56
自定义日期时间的时间戳: 1697351400000
2.2 存储和检索时区信息
java
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TimeZoneInfoExample {
public static void main(String[] args) {
// 创建一个自定义日期格式
SimpleDateFormat customDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
// 设置时区信息
customDateFormat.setTimeZone(java.util.TimeZone.getTimeZone("America/New_York"));
// 获取当前时间
Date currentTime = new Date();
// 格式化并输出带时区信息的日期时间
String formattedDate = customDateFormat.format(currentTime);
System.out.println("当前时间(纽约时区): " + formattedDate);
// 更改时区到柏林
customDateFormat.setTimeZone(java.util.TimeZone.getTimeZone("Europe/Berlin"));
// 格式化并输出带时区信息的日期时间
formattedDate = customDateFormat.format(currentTime);
System.out.println("当前时间(柏林时区): " + formattedDate);
// 更改时区到东京
customDateFormat.setTimeZone(java.util.TimeZone.getTimeZone("Asia/Tokyo"));
// 格式化并输出带时区信息的日期时间
formattedDate = customDateFormat.format(currentTime);
System.out.println("当前时间(东京时区): " + formattedDate);
}
}
运行结果如下:
bash
当前时间(纽约时区): 2023-11-04 03:30:38 EDT
当前时间(柏林时区): 2023-11-04 08:30:38 CET
当前时间(东京时区): 2023-11-04 16:30:38 JST
在这个示例中,我们首先创建一个SimpleDateFormat
对象,用于自定义日期时间格式,包括时区信息。然后,我们使用setTimeZone
方法来设置所需的时区,例如"America/New_York"、"Europe/Berlin"和"Asia/Tokyo"。
接下来,我们获取当前时间(默认时区),然后使用SimpleDateFormat
对象将其格式化为包含所选时区信息的日期时间字符串。这样,你可以存储和检索不同时区的日期时间信息。
四、实际应用
示例应用 - 日程管理系统(概念版)
Java
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.Scanner;
public class ScheduleApp {
private static ScheduleManager scheduleManager = new ScheduleManager(); // 创建日程管理器
private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.CHINA); // 日期格式
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in); // 创建一个输入扫描器
int choice;
do {
// 显示菜单选项
System.out.println("日程管理应用菜单:");
System.out.println("1. 添加事件");
System.out.println("2. 查看事件");
System.out.println("3. 删除事件");
System.out.println("4. 编辑事件");
System.out.println("5. 查找和过滤");
System.out.println("6. 按日历查找");
System.out.println("7. 清空事件");
System.out.println("8. 随机测试");
System.out.println("9. 退出");
System.out.print("请输入您的选择:");
choice = scanner.nextInt(); // 获取用户选择
switch (choice) {
case 1:
addEvent(scanner); // 调用添加事件的方法
break;
case 2:
viewEvents(); // 调用查看事件的方法
break;
case 3:
deleteEvent(scanner); // 调用删除事件的方法
break;
case 4:
editEvent(scanner); // 调用编辑事件的方法
break;
case 5:
findAndFilter(scanner); // 调用查找和过滤的方法
break;
case 6:
calendarQuery(scanner); // 调用按日历查询的方法
break;
case 7:
scheduleManager.clearEvents(); // 清空所有事件
System.out.println("已清空所有事件。");
break;
case 8:
randomTest(5); // 进行随机测试,生成5个随机事件
break;
case 9:
System.out.println("正在退出...");
break;
default:
System.out.println("无效选项。请重试。");
}
} while (choice != 9);
scanner.close();
}
// 添加事件
private static void addEvent(Scanner scanner) {
System.out.print("请输入事件标题: ");
scanner.nextLine();
String title = scanner.nextLine();
System.out.print("请输入事件描述(以 /end 结束输入):\n");
StringBuilder descriptionBuilder = new StringBuilder();
String line;
while (true) {
line = scanner.nextLine();
if ("/end".equals(line)) {
break;
}
descriptionBuilder.append(line).append("\n");
}
String description = descriptionBuilder.toString();
System.out.print("请输入事件的开始时间 (yyyy-MM-dd HH:mm): ");
Date startTime = null;
try {
startTime = dateFormat.parse(scanner.nextLine());
} catch (ParseException e) {
System.out.println("无效的日期格式。");
return;
}
System.out.print("请输入事件的结束时间 (yyyy-MM-dd HH:mm): ");
Date endTime = null;
try {
endTime = dateFormat.parse(scanner.nextLine());
} catch (ParseException e) {
System.out.println("无效的日期格式。");
return;
}
Event event = new Event(title, description, startTime, endTime);
System.out.print("是否设置提醒时间 (Y/N)?: ");
String setReminder = scanner.nextLine();
if (setReminder.equalsIgnoreCase("Y")) {
System.out.print("请输入提前提醒的时间段(30分钟/1小时/1天): ");
String reminderPeriod = scanner.nextLine();
Date reminderTime = calculateReminderTime(startTime, reminderPeriod);
event.setReminderTime(reminderTime);
}
scheduleManager.addEvent(event);
System.out.println("事件添加成功.");
}
// 计算提醒时间
private static Date calculateReminderTime(Date startTime, String reminderPeriod) {
long timeDifferenceMillis = 0;
if (reminderPeriod.equals("30分钟")) {
timeDifferenceMillis = 30 * 60 * 1000;
} else if (reminderPeriod.equals("1小时")) {
timeDifferenceMillis = 60 * 60 * 1000;
} else if (reminderPeriod.equals("1天")) {
timeDifferenceMillis = 24 * 60 * 60 * 1000;
}
return new Date(startTime.getTime() - timeDifferenceMillis);
}
// 查看事件
private static void viewEvents() {
List<Event> events = scheduleManager.getEvents();
if (events.isEmpty()) {
System.out.println("没有安排的事件.");
} else {
System.out.println("已安排的事件:");
int i = 0;
for (Event event : events) {
System.out.println("索引值: " + i++);
System.out.println(event.toString());
}
}
}
// 日历查询事件
private static void calendarQuery(Scanner scanner) {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA); // 日期格式
System.out.println("按日历查询事件:");
System.out.print("请输入起始日期 (yyyy-MM-dd): ");
String startDateStr = scanner.next();
System.out.print("请输入结束日期 (yyyy-MM-dd): ");
String endDateStr = scanner.next();
try {
Date startDate = dateFormat.parse(startDateStr);
Date endDate = dateFormat.parse(endDateStr);
List<Event> eventsInRange = scheduleManager.getEventsInRange(startDate, endDate);
if (eventsInRange.isEmpty()) {
System.out.println("在该日期范围内没有事件.");
} else {
System.out.println("在该日期范围内的事件:");
for (Event event : eventsInRange) {
System.out.println(event.toString());
}
}
} catch (ParseException e) {
System.out.println("无效的日期格式.");
}
}
// 删除事件
private static void deleteEvent(Scanner scanner) {
System.out.print("请输入要删除的事件索引: ");
int index = scanner.nextInt();
if (index >= 0 && index < scheduleManager.getEvents().size()) {
scheduleManager.deleteEvent(index);
System.out.println("事件删除成功.");
} else {
System.out.println("无效的索引. 未删除任何事件.");
}
}
// 编辑事件
private static void editEvent(Scanner scanner) {
System.out.print("请输入要编辑的事件索引: ");
int index = scanner.nextInt();
if (index >= 0 && index < scheduleManager.getEvents().size()) {
Event event = scheduleManager.getEvents().get(index);
System.out.println("当前事件信息:");
System.out.println(event.toString());
System.out.print("请输入新的事件标题 (或按 Enter 保留不变): ");
scanner.nextLine(); // 消耗换行字符
String newTitle = scanner.nextLine();
if (!newTitle.isEmpty()) {
event.setTitle(newTitle);
}
System.out.print("请输入新的事件描述 (或按 Enter 保留不变): ");
String newDescription = scanner.nextLine();
if (!newDescription.isEmpty()) {
event.setDescription(newDescription);
}
System.out.print("请输入新的事件开始时间 (yyyy-MM-dd HH:mm) (或按 Enter 保留不变): ");
String newStartTime = scanner.nextLine();
if (!newStartTime.isEmpty()) {
try {
Date startTime = dateFormat.parse(newStartTime);
event.setStartTime(startTime);
} catch (ParseException e) {
System.out.println("无效的日期格式. 事件开始时间未修改.");
}
}
System.out.print("请输入新的事件结束时间 (yyyy-MM-dd HH:mm) (或按 Enter 保留不变): ");
String newEndTime = scanner.nextLine();
if (!newEndTime.isEmpty()) {
try {
Date endTime = dateFormat.parse(newEndTime);
event.setEndTime(endTime);
} catch (ParseException e) {
System.out.println("无效的日期格式. 事件结束时间未修改.");
}
}
System.out.print("请输入新的提醒时间 (30分钟/1小时/1天) (或按 Enter 保留不变): ");
String newReminder = scanner.nextLine();
if (!newReminder.isEmpty()) {
Date reminderTime = calculateReminderTime(event.getStartTime(), newReminder);
event.setReminderTime(reminderTime);
}
System.out.println("事件编辑成功.");
} else {
System.out.println("无效的索引. 事件未修改.");
}
}
// 查找和过滤事件
private static void findAndFilter(Scanner scanner) {
System.out.print("请输入要查找的关键字: ");
scanner.nextLine();
String keyword = scanner.nextLine();
List<Event> filteredEvents = scheduleManager.findEventsByKeyword(keyword);
if (filteredEvents.isEmpty()) {
System.out.println("未找到包含关键字的事件.");
} else {
System.out.println("包含关键字的事件:");
for (Event event : filteredEvents) {
System.out.println(event.toString());
}
}
}
// 随机测试
private static void randomTest(int nums) {
Random random = new Random();
int numRandomEvents = nums; // 生成 nums 个随机事件
for (int i = 0; i < numRandomEvents; i++) {
String title = "随机事件 " + (i + 1);
String description = "这是一个随机生成的事件";
// 生成随机的开始时间和结束时间
Calendar calendar = Calendar.getInstance();
long currentTimeMillis = System.currentTimeMillis();
calendar.setTimeInMillis(currentTimeMillis + random.nextInt(30) * 60 * 1000); // 开始时间在未来30分钟内
Date startTime = calendar.getTime();
calendar.setTimeInMillis(currentTimeMillis + random.nextInt(60) * 60 * 1000); // 结束时间在未来60分钟内
Date endTime = calendar.getTime();
Event event = new Event(title, description, startTime, endTime);
// 随机设置提醒时间
if (random.nextBoolean()) {
calendar.setTime(startTime);
calendar.add(Calendar.MINUTE, -random.nextInt(60)); // 提前0-60分钟提醒
Date reminderTime = calendar.getTime();
event.setReminderTime(reminderTime);
}
// 将生成的事件添加到日程管理器
scheduleManager.addEvent(event);
}
System.out.println("生成了 " + numRandomEvents + " 个随机事件。");
}
}
java
// Event.java
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class Event {
private String title;
private String description;
private Date startTime;
private Date endTime;
private Date reminderTime;
private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.CHINA);
public Event(String title, String description, Date startTime, Date endTime) {
this.title = title;
this.description = description;
this.startTime = startTime;
this.endTime = endTime;
this.reminderTime = null; // 默认提醒时间为空
}
public void setTitle(String title) {
this.title = title;
}
public void setDescription(String description) {
this.description = description;
}
public void setStartTime(Date startTime) {
this.startTime = startTime;
}
public void setEndTime(Date endTime) {
this.endTime = endTime;
}
public void setReminderTime(Date reminderTime) {
this.reminderTime = reminderTime;
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public Date getStartTime() {
return startTime;
}
public Date getEndTime() {
return endTime;
}
public Date getReminderTime() {
return reminderTime;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("标题: ").append(title).append("\n");
sb.append("描述: ").append(description).append("\n");
sb.append("开始时间: ").append(dateFormat.format(startTime)).append("\n");
sb.append("结束时间: ").append(dateFormat.format(endTime)).append("\n");
// 计算距离事件开始时间的时间差
long startTimeMillis = startTime.getTime();
long currentTimeMillis = System.currentTimeMillis();
long timeDifferenceMillis = startTimeMillis - currentTimeMillis;
if (timeDifferenceMillis < 0) {
// 事件已经开始,计算已开始多长时间
timeDifferenceMillis = currentTimeMillis - startTimeMillis;
sb.append("已开始: ");
} else {
sb.append("距离开始时间还有: ");
}
long days = timeDifferenceMillis / (24 * 60 * 60 * 1000);
long hours = (timeDifferenceMillis % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000);
long minutes = (timeDifferenceMillis % (60 * 60 * 1000)) / (60 * 1000);
if (days > 0) {
sb.append(days).append(" 天 ");
}
if (hours > 0) {
sb.append(hours).append(" 小时 ");
}
sb.append(minutes).append(" 分钟\n");
return sb.toString();
}
}
Java
// ScheduleManager.java
// 导入 ArrayList 和 List 类,以支持事件列表的管理
import java.util.Date;
import java.util.ArrayList;
import java.util.List;
// ScheduleManager 类用于管理事件列表
public class ScheduleManager {
// 存储事件的列表
private List<Event> events = new ArrayList<>();
// 添加事件到事件列表
public void addEvent(Event event) {
events.add(event);
}
// 获取事件列表
public List<Event> getEvents() {
return events;
}
// 删除指定索引位置的事件
public boolean deleteEvent(int index) {
if (index >= 0 && index < events.size()) {
events.remove(index);
return true; // 删除成功
}
return false; // 删除失败,索引无效
}
// 获取指定索引位置的事件
public Event getEvent(int index) {
if (index >= 0 && index < events.size()) {
return events.get(index);
}
return null; // 返回空值,索引无效
}
// 根据关键字查找事件
public List<Event> findEventsByKeyword(String keyword) {
List<Event> filteredEvents = new ArrayList<>();
for (Event event : events) {
// 如果事件的标题或描述包含关键字,则将其添加到过滤事件列表中
if (event.getTitle().contains(keyword) || event.getDescription().contains(keyword)) {
filteredEvents.add(event);
}
}
return filteredEvents; // 返回包含关键字的事件列表
}
// 清空方法
public void clearEvents() {
events.clear();
}
// 获取日期范围内的事件
public List<Event> getEventsInRange(Date startDate, Date endDate) {
List<Event> eventsInRange = new ArrayList<>();
for (Event event : events) {
if (event.getStartTime().after(startDate) && event.getEndTime().before(endDate)) {
eventsInRange.add(event);
}
}
return eventsInRange;
}
}
演示
最后的小牢骚
在 Java 编程中,处理日期和时间就像是在穿越时间迷宫。我们的首站是
Date
类,它曾是日期时间的王者,但后来被证明有点靠不住。Date 类让我们能够获取当前日期和时间,但它是可变的,这就像在双刃剑上跳舞,很容易割伤自己。而且,不同国家和地区的日期格式也不能被满足。试图在多线程中使用 Date 类?那简直是自找麻烦。
然后,Java 8 出现了救世主 -java.time
包。它引入了一堆新朋友,比如LocalDate
、LocalDateTime
,它们都是不可变的。这些新朋友让我们处理日期和时间变得更加简单和安全。所以,从现在开始,不要再用老掉牙的Date
类了,转而拥抱java.time
包的时代。
如果你想了解更多关于java.time
包的知识,关注我!不仅是关于java.time
包,C++
、Android
、Java
、数据结构等知识我都将持续输出!🫡🫡🫡
但别急,如果你还需要和老代码打交道,有DateFormat
类来帮忙。DateFormat 能够格式化和解析日期时间字符串,它非常聪明,可以适应不同国家和地区的日期格式,而且可以让日期时间在不同语言之间自由切换。
然而,要小心,DateFormat
也有它的问题,尤其是在多线程环境中,你可能会发现它有点暴躁。不过,聪明的 Java 程序员总会找到解决方法,比如使用ThreadLocal
来确保每个线程都有自己的DateFormat
实例。
最后,如果你还需要应对国际化的挑战,ResourceBundle
是你的好朋友。它可以让你的日期格式适应不同语言和文化,让你的应用程序更受欢迎。
在穿越 Java 的时间迷宫中,不仅要了解这些工具,还要牢记最佳实践,特别是在处理日期范围和区间时要懂得时区的重要性。所以,朋友们,准备好了吗?一起在时间的大舞台上留下自己的足迹吧!