跳到主要内容

Java 日期时间格式化

日期和时间的格式化是处理日期时间数据的重要部分。在实际应用中,我们经常需要把日期时间对象转换为特定格式的字符串(如"2023-11-28"或"28/11/2023 15:30:45"),或者反过来,从字符串解析出日期时间对象。Java提供了全面的API来处理这些操作。

为什么需要日期时间格式化?

日期时间格式化主要有以下用途:

  • 用户界面展示(如网页、应用程序)
  • 数据交换(JSON、XML等格式)
  • 日志记录
  • 国际化应用(不同国家/地区使用不同的日期格式)

Java 8之前:SimpleDateFormat

在Java 8之前,SimpleDateFormat是格式化日期的主要类。它属于java.text包。

基本用法

java
import java.text.SimpleDateFormat;
import java.util.Date;

public class SimpleDateFormatExample {
public static void main(String[] args) {
// 获取当前日期
Date currentDate = new Date();

// 创建格式化对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

// 格式化日期为字符串
String formattedDate = sdf.format(currentDate);
System.out.println("格式化后的日期: " + formattedDate);

// 从字符串解析日期
try {
String dateStr = "2023-11-28 15:30:45";
Date parsedDate = sdf.parse(dateStr);
System.out.println("解析后的日期: " + parsedDate);
} catch (Exception e) {
e.printStackTrace();
}
}
}

输出示例:

格式化后的日期: 2023-11-28 15:30:45
解析后的日期: Tue Nov 28 15:30:45 CST 2023

常用的格式化模式

符号说明示例
yyyyy: 2023
MMM: 01-12
ddd: 01-31
H24小时制HH: 00-23
h12小时制hh: 01-12
m分钟mm: 00-59
sss: 00-59
S毫秒SSS: 000-999
E星期E: 星期几
aAM/PMa: AM/PM

SimpleDateFormat的问题

警告

SimpleDateFormat不是线程安全的,在多线程环境下可能会导致不可预期的结果。每个线程应该创建自己的SimpleDateFormat实例。

Java 8及以后:DateTimeFormatter

Java 8引入的新日期时间API (java.time)提供了更强大、更安全的DateTimeFormatter类。

基本用法

java
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class DateTimeFormatterExample {
public static void main(String[] args) {
// 获取当前日期时间
LocalDateTime now = LocalDateTime.now();

// 使用预定义的格式化器
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE_TIME;
String formatted = now.format(formatter);
System.out.println("使用ISO_DATE_TIME格式: " + formatted);

// 使用自定义模式的格式化器
formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
formatted = now.format(formatter);
System.out.println("使用自定义格式: " + formatted);

// 从字符串解析日期时间
String dateStr = "2023年11月28日 15:30:45";
LocalDateTime parsedDate = LocalDateTime.parse(dateStr, formatter);
System.out.println("解析后的日期时间: " + parsedDate);
}
}

输出示例:

使用ISO_DATE_TIME格式: 2023-11-28T15:30:45.123
使用自定义格式: 2023年11月28日 15:30:45
解析后的日期时间: 2023-11-28T15:30:45

预定义的格式化器

Java 8提供了许多预定义的格式化器常量:

java
// 基本ISO日期格式 (2023-11-28)
DateTimeFormatter.ISO_DATE

// 基本ISO日期时间格式 (2023-11-28T15:30:45.123)
DateTimeFormatter.ISO_DATE_TIME

// 基本ISO时间格式 (15:30:45.123)
DateTimeFormatter.ISO_TIME

// 基本ISO本地日期时间 (2023-11-28T15:30:45.123)
DateTimeFormatter.ISO_LOCAL_DATE_TIME

为不同的日期时间类型格式化

Java 8中有多种日期时间类型,每种都有其特定用途:

java
import java.time.*;
import java.time.format.DateTimeFormatter;

public class DifferentDateTimeFormats {
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

// 本地日期
LocalDate date = LocalDate.now();
System.out.println("LocalDate: " + date.format(formatter));

// 本地时间
formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
LocalTime time = LocalTime.now();
System.out.println("LocalTime: " + time.format(formatter));

// 本地日期时间
formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime dateTime = LocalDateTime.now();
System.out.println("LocalDateTime: " + dateTime.format(formatter));

// 带时区的日期时间
formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println("ZonedDateTime: " + zonedDateTime.format(formatter));
}
}

输出示例:

LocalDate: 2023-11-28
LocalTime: 15:30:45
LocalDateTime: 2023-11-28 15:30:45
ZonedDateTime: 2023-11-28 15:30:45 CST

国际化日期时间格式

在开发国际化应用程序时,需要根据不同的区域显示不同格式的日期。

java
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Locale;

public class InternationalDateExample {
public static void main(String[] args) {
LocalDate today = LocalDate.now();

// 使用不同地区的日期格式
DateTimeFormatter usFormatter =
DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(Locale.US);
System.out.println("美国格式: " + today.format(usFormatter));

DateTimeFormatter frFormatter =
DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(Locale.FRANCE);
System.out.println("法国格式: " + today.format(frFormatter));

DateTimeFormatter cnFormatter =
DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(Locale.CHINA);
System.out.println("中国格式: " + today.format(cnFormatter));
}
}

输出示例:

美国格式: Tuesday, November 28, 2023
法国格式: mardi 28 novembre 2023
中国格式: 2023年11月28日 星期二

实际应用案例

案例1:日志时间戳

在日志系统中,常常需要记录精确的时间戳:

java
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class LoggerExample {
public static void log(String message) {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
String timestamp = now.format(formatter);
System.out.println("[" + timestamp + "] " + message);
}

public static void main(String[] args) {
log("应用程序已启动");
// 模拟一些操作
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
log("用户已登录");
}
}

输出示例:

[2023-11-28 15:30:45.123] 应用程序已启动
[2023-11-28 15:30:46.624] 用户已登录

案例2:生日倒计时应用

java
import java.time.LocalDate;
import java.time.Period;
import java.time.format.DateTimeFormatter;
import java.util.Scanner;

public class BirthdayCountdown {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

System.out.print("请输入您的生日 (格式 yyyy-MM-dd): ");
String birthdayString = scanner.nextLine();

try {
LocalDate birthday = LocalDate.parse(birthdayString, formatter);
LocalDate today = LocalDate.now();

// 计算今年的生日日期
LocalDate nextBirthday = birthday.withYear(today.getYear());

// 如果今年的生日已经过了,计算明年的生日
if (nextBirthday.isBefore(today) || nextBirthday.isEqual(today)) {
nextBirthday = nextBirthday.plusYears(1);
}

// 计算距离下一个生日的天数
Period period = Period.between(today, nextBirthday);
int days = period.getYears() * 365 + period.getMonths() * 30 + period.getDays();

System.out.println("您的下一个生日是: " + nextBirthday.format(formatter));
System.out.println("距离下一个生日还有 " + days + " 天");

} catch (Exception e) {
System.out.println("日期格式不正确,请使用 yyyy-MM-dd 格式!");
} finally {
scanner.close();
}
}
}

最佳实践

  1. 在Java 8及以上版本中,优先使用DateTimeFormatter而非SimpleDateFormat

    • DateTimeFormatter是不可变的且线程安全的
  2. 根据需求选择合适的日期时间类

    • 只需要日期:LocalDate
    • 只需要时间:LocalTime
    • 需要日期和时间:LocalDateTime
    • 需要考虑时区:ZonedDateTime
  3. 处理用户输入的日期时间字符串时增加异常处理

    java
    try {
    LocalDate date = LocalDate.parse(userInput, formatter);
    } catch (DateTimeParseException e) {
    // 处理解析错误
    }
  4. 使用适当的格式模式,避免歧义

    • 例如,使用"yyyy-MM-dd"而不是"yy-MM-dd"以避免年份的歧义
  5. 对于跨国际的应用,使用带有Locale的格式化

总结

Java提供了丰富的日期时间格式化API,从Java 8之前的SimpleDateFormat到Java 8及以后的DateTimeFormatter。现代的DateTimeFormatter更加强大、安全,并与新的日期时间API完美配合。通过选择合适的格式模式,我们可以按照需求将日期时间对象转换为字符串,或从字符串解析出日期时间对象。

在实际应用中,日期时间格式化是非常常见的需求,掌握这些API可以帮助你更有效地处理各种与日期时间相关的任务。

练习

  1. 编写一个程序,将当前日期时间格式化为"yyyy年MM月dd日 星期几 HH时mm分ss秒"的形式。
  2. 创建一个工具类,提供方法将ISO格式的日期字符串(如"2023-11-28T15:30:45")转换为更友好的本地格式。
  3. 编写一个程序,接受用户输入的两个日期字符串,计算它们之间的天数差。
  4. 实现一个国际化的日期显示功能,根据不同的Locale显示不同格式的日期。

扩展资源

通过掌握日期时间格式化,你将能够更好地处理各种与日期时间相关的编程任务,从简单的日志记录到复杂的国际化应用程序都会得心应手。