LocalDateTime

参考廖雪峰博客

从Java 8开始,java.time包提供了新的日期和时间API,主要涉及的类型有:
本地日期和时间:LocalDateTime,LocalDate,LocalTime;
带时区的日期和时间:ZonedDateTime;
时刻:Instant;
时区:ZoneId,ZoneOffset;
时间间隔:Duration。
以及一套新的用于取代SimpleDateFormat的格式化类型DateTimeFormatter。
和旧的API相比,新API严格区分了时刻、本地日期、本地时间和带时区的日期时间,并且,对日期和时间进行运算更加方便。
此外,新API修正了旧API不合理的常量设计:
Month的范围用1-12表示1月到12月;
Week的范围用1-7表示周一到周日。
最后,新API的类型几乎全部是不变类型(和String类似),可以放心使用不必担心被修改。

我们首先来看最常用的LocalDateTime,它表示一个本地日期和时间:

1
2
3
4
5
6
LocalDate d = LocalDate.now(); // 当前日期
LocalTime t = LocalTime.now(); // 当前时间
LocalDateTime dt = LocalDateTime.now(); // 当前日期和时间
System.out.println(d); // 严格按照ISO 8601格式打印
System.out.println(t); // 严格按照ISO 8601格式打印
System.out.println(dt); // 严格按照ISO 8601格式打印

本地日期和时间通过now()获取到的总是以当前默认时区返回的,和旧API不同,LocalDateTime、
LocalDate和LocalTime默认严格按照ISO 8601规定的日期和时间格式进行打印。
上述代码其实有一个小问题,在获取3个类型的时候,由于执行一行代码总会消耗一点时间,因此,3个类型的日期和时间很可能对不上(时间的毫秒数基本上不同)。为了保证获取到同一时刻的日期和时间,可以改写如下:

1
2
3
LocalDateTime dt = LocalDateTime.now(); // 当前日期和时间
LocalDate d = dt.toLocalDate(); // 转换到当前日期
LocalTime t = dt.toLocalTime(); // 转换到当前时间

获取当前此刻的时间

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
LocalDateTime rightNow = LocalDateTime.now();
System.out.println( "当前时刻:" + rightNow );
System.out.println( "当前年份:" + rightNow.getYear() );
System.out.println( "当前月份:" + rightNow.getMonth() );
System.out.println( "当前日份:" + rightNow.getDayOfMonth() );
System.out.println( "当前时:" + rightNow.getHour() );
System.out.println( "当前分:" + rightNow.getMinute() );
System.out.println( "当前秒:" + rightNow.getSecond() );

// 输出结果:
当前时刻2019-12-13T22:05:26.779
当前年份2019
当前月份DECEMBER
当前日份13
当前时22
当前分5
当前秒26

反过来,通过指定的日期和时间创建 LocalDateTime 可以通过of()方法:

1
2
3
4
5
// 指定日期和时间:
LocalDate d2 = LocalDate.of(2019, 11, 30); // 2019-11-30, 注意11=11月
LocalTime t2 = LocalTime.of(15, 16, 17); // 15:16:17
LocalDateTime dt2 = LocalDateTime.of(2019, 11, 30, 15, 16, 17);
LocalDateTime dt3 = LocalDateTime.of(d2, t2);

按照ISO 8601的格式传入字符串,就可以转为LocalDateTime

1
2
3
LocalDateTime dt = LocalDateTime.parse("2019-11-19T15:16:17");
LocalDate d = LocalDate.parse("2019-11-19");
LocalTime t = LocalTime.parse("15:16:17");

注意ISO 8601规定的日期和时间分隔符是T。标准格式如下:

1
2
3
4
5
日期: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

如果要自定义输出的格式,或者要把一个非ISO 8601格式的字符串解析成LocalDateTime,可以使用新的DateTimeFormatter:

1
2
3
4
5
6
7
// 自定义格式化:
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
System.out.println(dtf.format(LocalDateTime.now()));

// 将字符串反解析成 LocalDateTime
LocalDateTime dt2 = LocalDateTime.parse("2019/11/30 15:16:17", dtf);
System.out.println(dt2);

LocalDateTime提供了对日期和时间进行加减的非常简单的链式调用:

1
2
3
4
5
6
7
8
LocalDateTime dt = LocalDateTime.of(2019, 10, 26, 20, 30, 59);
System.out.println(dt);
// 加5天减3小时:
LocalDateTime dt2 = dt.plusDays(5).minusHours(3);
System.out.println(dt2); // 2019-10-31T17:30:59
// 减1月:
LocalDateTime dt3 = dt2.minusMonths(1);
System.out.println(dt3); // 2019-09-30T17:30:59

注意到月份加减会自动调整日期,例如从2019-10-31减去1个月得到的结果是2019-09-30,因为9月没有31日。
对日期和时间进行调整则使用withXxx()方法,例如:withHour(15)会把10:11:12变为15:11:12
调整年:withYear()
调整月:withMonth()
调整日:withDayOfMonth()
调整时:withHour()
调整分:withMinute()
调整秒:withSecond()

1
2
3
4
5
6
7
8
LocalDateTime dt = LocalDateTime.of(2019, 10, 26, 20, 30, 59);
System.out.println(dt);
// 日期变为31日:
LocalDateTime dt2 = dt.withDayOfMonth(31);
System.out.println(dt2); // 2019-10-31T20:30:59
// 月份变为9:
LocalDateTime dt3 = dt2.withMonth(9);
System.out.println(dt3); // 2019-09-30T20:30:59

同样注意到调整月份时,会相应地调整日期,即把2019-10-31的月份调整为9时,日期也自动变为30。
实际上,LocalDateTime还有一个通用的with()方法允许我们做更复杂的运算。例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 本月第一天0:00时刻:
LocalDateTime firstDay = LocalDate.now().withDayOfMonth(1).atStartOfDay();
System.out.println(firstDay);

// 本月最后1天:
LocalDate lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());
System.out.println(lastDay);

// 下月第1天:
LocalDate nextMonthFirstDay = LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth());
System.out.println(nextMonthFirstDay);

// 本月第1个周一:
LocalDate firstWeekday = LocalDate.now().with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
System.out.println(firstWeekday);

要判断两个LocalDateTime的先后,可以使用isBefore()、isAfter()方法,对于LocalDate和LocalTime类似:

1
2
3
4
5
LocalDateTime now = LocalDateTime.now();
LocalDateTime target = LocalDateTime.of(2019, 11, 19, 8, 15, 0);
System.out.println(now.isBefore(target));
System.out.println(LocalDate.now().isBefore(LocalDate.of(2019, 11, 19)));
System.out.println(LocalTime.now().isAfter(LocalTime.parse("08:15:00")));

注意到LocalDateTime无法与时间戳进行转换,因为LocalDateTime没有时区,无法确定某一时刻。后面我们要介绍的ZonedDateTime相当于LocalDateTime加时区的组合,它具有时区,可以与long表示的时间戳进行转换。

Duration和Period
Duration表示两个时刻之间的时间间隔。另一个类似的Period表示两个日期之间的天数:
注意到两个LocalDateTime之间的差值使用Duration表示,类似PT1235H10M30S,表示1235小时10分钟30秒。而两个LocalDate之间的差值用Period表示,类似P1M21D,表示1个月21天。

Duration和Period的表示方法也符合ISO 8601的格式,它以P…T…的形式表示,P…T之间表示日期间隔,T后面表示时间间隔。如果是PT…的格式表示仅有时间间隔。利用ofXxx()或者parse()方法也可以直接创建Duration:

1
2
Duration d1 = Duration.ofHours(10); // 10 hours
Duration d2 = Duration.parse("P1DT2H3M"); // 1 day, 2 hours, 3 minutes

ZonedDateTime

LocalDateTime总是表示本地日期和时间,要表示一个带时区的日期和时间,我们就需要ZonedDateTime。
可以简单地把ZonedDateTime理解成LocalDateTime加ZoneId。ZoneId是java.time引入的新的时区类,注意和旧的java.util.TimeZone区别。
要创建一个ZonedDateTime对象,有以下几种方法,一种是通过now()方法返回当前时间:

1
2
3
4
ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
ZonedDateTime zny = ZonedDateTime.now(ZoneId.of("America/New_York")); // 用指定时区获取当前时间
System.out.println(zbj);
System.out.println(zny);

观察打印的两个ZonedDateTime,发现它们时区不同,但表示的时间都是同一时刻(毫秒数不同是执行语句时的时间差):
2021-03-31T14:47:40.550+08:00[Asia/Shanghai]
2021-03-31T02:47:40.552-04:00[America/New_York]

另一种方式是通过给一个LocalDateTime附加一个ZoneId,就可以变成ZonedDateTime:

1
2
3
4
5
LocalDateTime ldt = LocalDateTime.of(2019, 9, 15, 15, 16, 17);
ZonedDateTime zbj = ldt.atZone(ZoneId.systemDefault());
ZonedDateTime zny = ldt.atZone(ZoneId.of("America/New_York"));
System.out.println(zbj);
System.out.println(zny);

以这种方式创建的ZonedDateTime,它的日期和时间与LocalDateTime相同,但附加的时区不同,因此是两个不同的时刻:

1
2
2019-09-15T15:16:17+08:00[Asia/Shanghai]
2019-09-15T15:16:17-04:00[America/New_York]

时区转换
要转换时区,首先我们需要有一个ZonedDateTime对象,然后,通过withZoneSameInstant()将关联时区转换到另一个时区,转换后日期和时间都会相应调整。

1
2
3
4
5
6
// 以中国时区获取当前时间:
ZonedDateTime zbj = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
// 转换为纽约时间:
ZonedDateTime zny = zbj.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println(zbj);
System.out.println(zny);

要特别注意,时区转换的时候,由于夏令时的存在,不同的日期转换的结果很可能是不同的。这是北京时间9月15日的转换结果:
2019-09-15T21:05:50.187697+08:00[Asia/Shanghai]
2019-09-15T09:05:50.187697-04:00[America/New_York]

这是北京时间11月15日的转换结果:
2019-11-15T21:05:50.187697+08:00[Asia/Shanghai]
2019-11-15T08:05:50.187697-05:00[America/New_York]
两次转换后的纽约时间有1小时的夏令时时差。

涉及到时区时,千万不要自己计算时差,否则难以正确处理夏令时。
有了ZonedDateTime,将其转换为本地时间就非常简单:
ZonedDateTime zdt = …
LocalDateTime ldt = zdt.toLocalDateTime();
转换为LocalDateTime时,直接丢弃了时区信息。

某航线从北京飞到纽约需要13小时20分钟,请根据北京起飞日期和时间计算到达纽约的当地日期和时间。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
    LocalDateTime departureAtBeijing = LocalDateTime.of(2019, 9, 15, 13, 0, 0);
    int hours = 13;
    int minutes = 20;
    LocalDateTime arrivalAtNewYork = calculateArrivalAtNY(departureAtBeijing, hours, minutes);
    System.out.println(departureAtBeijing + " -> " + arrivalAtNewYork);
    // test:
    if (!LocalDateTime.of(2019, 10, 15, 14, 20, 0)
        .equals(calculateArrivalAtNY(LocalDateTime.of(2019, 10, 15, 13, 0, 0), 13, 20))) {
        System.err.println("测试失败!");
    } else if (!LocalDateTime.of(2019, 11, 15, 13, 20, 0)
               .equals(calculateArrivalAtNY(LocalDateTime.of(2019, 11, 15, 13, 0, 0), 13, 20))) {
        System.err.println("测试失败!");
    }
}

static LocalDateTime calculateArrivalAtNY(LocalDateTime bj, int h, int m) {
    return bj;
}

DateTimeFormatter

旧的Date对象,用SimpleDateFormat进行格式化。
LocalDateTime或 ZonedLocalDateTime 用 DateTimeFormatter 格式化。
和SimpleDateFormat不同的是,DateTimeFormatter不但是不变对象,它还是线程安全的。线程的概念我们会在后面涉及到。现在我们只需要记住:因为SimpleDateFormat不是线程安全的,使用的时候,只能在方法内部创建新的局部变量。而DateTimeFormatter可以只创建一个实例,到处引用。

创建DateTimeFormatter时,我们仍然通过传入格式化字符串实现:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
格式化字符串的使用方式与SimpleDateFormat完全一致。

另一种创建DateTimeFormatter的方法是,传入格式化字符串时,同时指定Locale:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("E, yyyy-MMMM-dd HH:mm", Locale.US);
这种方式可以按照Locale默认习惯格式化。我们来看实际效果:

1
2
3
4
5
6
7
8
9
ZonedDateTime zdt = ZonedDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm ZZZZ");
System.out.println(formatter.format(zdt));

DateTimeFormatter zhFormatter = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm", Locale.CHINA);
System.out.println(zhFormatter.format(zdt));

DateTimeFormatter usFormatter = DateTimeFormatter.ofPattern("E, MMMM/dd/yyyy HH:mm", Locale.US);
System.out.println(usFormatter.format(zdt));

在格式化字符串中,如果需要输出固定字符,可以用’xxx’表示。
运行上述代码,分别以默认方式、中国地区和美国地区对当前时间进行显示,结果如下:
2019-09-15T23:16 GMT+08:00
2019 9月 15 周日 23:16
Sun, September/15/2019 23:16

当我们直接调用System.out.println()对一个ZonedDateTime或者LocalDateTime实例进行打印的时候,实际上,调用的是它们的toString()方法,默认的toString()方法显示的字符串就是按照ISO 8601格式显示的,我们可以通过DateTimeFormatter预定义的几个静态变量来引用:

1
2
3
var ldt = LocalDateTime.now();
System.out.println(DateTimeFormatter.ISO_DATE.format(ldt));
System.out.println(DateTimeFormatter.ISO_DATE_TIME.format(ldt));

得到的输出和toString()类似:
2019-09-15
2019-09-15T23:16:51.56217

Instant

我们已经讲过,计算机存储的当前时间,本质上只是一个不断递增的整数。Java提供的
System.currentTimeMillis()返回的就是以毫秒表示的当前时间戳。这个当前时间戳在java.time中以Instant类型表示,我们用Instant.now()获取当前时间戳,效果和System.currentTimeMillis()类似:

1
2
3
Instant now = Instant.now();
System.out.println(now.getEpochSecond()); // 秒
System.out.println(now.toEpochMilli()); // 毫秒

实际上,Instant内部只有两个核心字段:

1
2
3
4
public final class Instant implements ... {
    private final long seconds;
    private final int nanos;
}

一个是以秒为单位的时间戳,一个是更精确的纳秒精度。它和System.currentTimeMillis()返回的long相比,只是多了更高精度的纳秒。既然Instant就是时间戳,那么,给它附加上一个时区,就可以创建出ZonedDateTime:

1
2
3
4
// 以指定时间戳创建Instant:
Instant ins = Instant.ofEpochSecond(1568568760);
ZonedDateTime zdt = ins.atZone(ZoneId.systemDefault());
System.out.println(zdt); // 2019-09-16T01:32:40+08:00[Asia/Shanghai]

可见,对于某一个时间戳,给它关联上指定的ZoneId,就得到了ZonedDateTime,继而可以获得了对应时区的LocalDateTime。

所以,LocalDateTime,ZoneId,Instant,ZonedDateTime和long都可以互相转换:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
┌─────────────┐
│LocalDateTime│────┐
└─────────────┘    │    ┌─────────────┐
                   ├───>│ZonedDateTime│
┌─────────────┐    │    └─────────────┘
│   ZoneId    │────┘           ▲
└─────────────┘      ┌─────────┴─────────┐
                     │                   │
                     ▼                   ▼
              ┌─────────────┐     ┌─────────────┐
              │   Instant   │<───>│    long     │
              └─────────────┘     └─────────────┘

转换的时候,只需要留意long类型以毫秒还是秒为单位即可。

最佳实践

由于Java提供了新旧两套日期和时间的API,除非涉及到遗留代码,否则我们应该坚持使用新的API。
如果需要与遗留代码打交道,如何在新旧API之间互相转换呢?

旧API转新API

如果要把旧式的Date或Calendar转换为新API对象,可以通过toInstant()方法转换为Instant对象,再继续转换为ZonedDateTime:

1
2
3
4
5
6
7
8
9
// Date -> Instant:
Instant ins1 = new Date().toInstant();
// Instant -> localDateTime
LocalDateTime localDateTime = instant.atZone(ZoneId.systemDefault()).toLocalDateTime();

// Calendar -> Instant -> ZonedDateTime:
Calendar calendar = Calendar.getInstance();
Instant ins2 = calendar.toInstant();
ZonedDateTime zdt = ins2.atZone(calendar.getTimeZone().toZoneId());

从上面的代码还可以看到,旧的TimeZone提供了一个toZoneId(),可以把自己变成新的ZoneId。

新API转旧API

如果要把新的ZonedDateTime转换为旧的API对象,只能借助long型时间戳做一个“中转”:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// ZonedDateTime -> long:
ZonedDateTime zdt = ZonedDateTime.now();
long ts = zdt.toEpochSecond() * 1000;
    
// long -> Date:
Date date = new Date(ts);

// 或者这样
Date.from(zonedDateTime.toInstant())

// long -> Calendar:
Calendar calendar = Calendar.getInstance();
calendar.clear();
calendar.setTimeZone(TimeZone.getTimeZone(zdt.getZone().getId()));
calendar.setTimeInMillis(zdt.toEpochSecond() * 1000);

从上面的代码还可以看到,新的ZoneId转换为旧的TimeZone,需要借助ZoneId.getId()返回的String完成。

在数据库中存储日期和时间

除了旧式的java.util.Date,我们还可以找到另一个java.sql.Date,它继承自java.util.Date,但会自动忽略所有时间相关信息。这个奇葩的设计原因要追溯到数据库的日期与时间类型。

在数据库中,也存在几种日期和时间类型:
DATETIME:表示日期和时间;
DATE:仅表示日期;
TIME:仅表示时间;
TIMESTAMP:和DATETIME类似,但是数据库会在创建或者更新记录的时候同时修改TIMESTAMP。

在使用Java程序操作数据库时,我们需要把数据库类型与Java类型映射起来。下表是数据库类型与Java新旧API的映射关系:

数据库 对应Java类(旧) 对应Java类(新)
DATETIME java.util.Date LocalDateTime
DATE java.sql.Date LocalDate
TIME java.sql.Time LocalTime
TIMESTAMP java.sql.Timestamp LocalDateTime

实际上,在数据库中,我们需要存储的最常用的是时刻(Instant),因为有了时刻信息,就可以根据用户自己选择的时区,显示出正确的本地时间。所以,最好的方法是直接用长整数long表示,在数据库中存储为BIGINT类型。

通过存储一个long型时间戳,我们可以编写一个timestampToString()的方法,非常简单地为不同用户以不同的偏好来显示不同的本地时间:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public static void main(String[] args) {
        long ts = 1574208900000L;
        System.out.println(timestampToString(ts, Locale.CHINA, "Asia/Shanghai"));
        System.out.println(timestampToString(ts, Locale.US, "America/New_York"));
    }

    static String timestampToString(long epochMilli, Locale lo, String zoneId) {
        Instant ins = Instant.ofEpochMilli(epochMilli);
        DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT);
        return f.withLocale(lo).format(ZonedDateTime.ofInstant(ins, ZoneId.of(zoneId)));
    }

小结
处理日期和时间时,尽量使用新的java.time包;
在数据库中存储时间戳时,尽量使用long型时间戳,它具有省空间,效率高,不依赖数据库的优点。

Date介绍(废弃)

Date 定义
public class Date implements java.io.Serializable, Cloneable, Comparable<Date> {}

说明:

  • milliseconds 表示毫秒。
    milliseconds = “实际时间” - “1970-01-01 00:00:00”。Calendar 和 Date依赖于 milliseconds,从而表示时间。

  • Calendar表示日期/时间。
    它是一个抽象类,依赖于milliseconds。GregorianCalendar是Calendar的子类,通常我们通过Calendar.getInstance() 获取Calendar实例时,实际上返回的是 GregorianCalendar 对象。
    Calendar和Locale关联,而Locale代表区域;Locale的值不同时,Calendar的日期/时间也不同。
    Calendar和TimeZone关联,而TimeZone代表时区;不同的时区,Calendar的日期/时间也不同。

  • Date 表示日期/时间。
    它也依赖于 milliseconds实现。
    在 JDK 1.1 之前,通常是通过Data操作“年月日时分秒”。不过,由于Date的相关 API 不易于实现国际化。
    从 JDK 1.1 开始,应该使用 Calendar 类来操作“年月日时分秒”,同时可以通过 DateFormat 类来格式化和解析日期字符串。Date 中的相应方法已废弃。

  • DateFormat是格式化/解析“日期/时间”的工具类。
    它是Date的格式化工具,它能帮助我们格式化Date,进而将Date转换成我们想要的String字符串供我们使用。它是一个抽象类。通常,我们通过getInstance(), getDateInstance()和getDateTimeInstance() 等获取DateFormat实例时;实际上是返回的SimpleDateFormat对象。

Date 是表示时间的类。
一个Date对象表示一个特定的瞬间,能精确到毫秒。我们可以通过这个特定的瞬间,来获取到Date对应的“年、月、日、时、分、秒”。反之亦然,我们也可以通过设置Date的“年、月、日、时、分、秒”等信息,来改变Date所指定的特定瞬间。除了“年月日时分秒”等信息之外,Data也允许格式化和解析日期字符串。即,我们可以定义一个字符串,这个字符串包含时间信息,然后将字符串通过Date来解析,从而得到相应的Date对象。在 JDK 1.1 之前,通常是通过Data操作“年月日时分秒”。不过,由于Date的相关 API 不易于实现国际化。从 JDK 1.1 开始,应该使用 Calendar 类来操作“年月日时分秒”,同时可以通过 DateFormat 类来格式化和解析日期字符串。Date 中的相应方法已废弃。jdk1.8之后,不建议再使用date对象,而使用LocalDateTime

Date和Calendar相互转换

  • Date转换为Calendar
1
2
3
4
5
// 新建date,且日期/时间为2013-09-19 14:22:30
Date date = new Date(113, 8, 19, 14, 22, 30);
// 新建Calendar对象,并设置日期为date
Calendar cal = Calendar.getInstance();
cal.setTime(date);
  • Calendar换为Date
1
2
3
4
// 新建Calendar对象
Calendar cal = Calendar.getInstance();
// 获取Calendar对应的Date
Date date = cal.getTime();

Date 函数列表

Date 共包含下面5个构造函数

 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
// Date构造函数一:传入“年、月、日”
// (01) 年 -- 减 1900 的年份。若要设为1988,则“年”应该是88。
// (02) 月 -- 0-11 的月份。0是一月,1是二月,依次类推。
// (03) 日 -- 1-31 之间的某一天。
Date(int year, int month, int day)

// Date构造函数二:传入“年、月、日、时、分”
// (01) 年 -- 减 1900 的年份。若要设为1988,则“年”应该是88。
// (02) 月 -- 0-11 的月份。0是一月,1是二月,依次类推。
// (03) 日 -- 1-31 之间的某一天。
// (04) 时 -- 0-23 之间的小时数。
// (05) 分 -- 0-59 之间的分钟数。
Date(int year, int month, int day, int hour, int minute)

// Date构造函数三:传入“年、月、日、时、分、秒”
// (01) 年 -- 减 1900 的年份。若要设为1988,则“年”应该是88。
// (02) 月 -- 0-11 的月份。0是一月,1是二月,依次类推。
// (03) 日 -- 1-31 之间的某一天。
// (04) 时 -- 0-23 之间的小时数。
// (05) 分 -- 0-59 之间的分钟数。
// (06) 秒 -- 0-59 之间的秒钟数。
Date(int year, int month, int day, int hour, int minute, int second)

// Date构造函数四:传入“毫秒”。 毫秒 = “目标时间” - “1970-01-01 00:00:00 GMT”
Date(long milliseconds)

// Date构造函数五:传入“字符串”。
Date(String string)

Date的操作API

 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// 根据参数确定日期和时间。这些参数被解释为年份、月份、月中某一天、一天中的某一小时、小时中某一分钟和分钟中的某一秒。
static long UTC(int year, int month, int day, int hour, int minute, int second)

// 此日期是否在指定日期之后。
boolean after(Date date)

// 此日期是否在指定日期之前
boolean before(Date date)

// 返回此对象的副本。
Object clone()

// 比较两个日期的顺序。
int compareTo(Date date)

// 比较两个日期是否相等。
boolean equals(Object object)

// 返回此 Date 对象表示的月份中的某一天。返回的值在 1 和 31 之间,表示包含或开始于此 Date 对象表示的时间的月份中的某一天(用本地时区进行解释)。
int getDate()

// 返回此日期表示的周中的某一天。返回值 (0 = Sunday, 1 = Monday, 2 = Tuesday, 3 = Wednesday, 4 = Thursday, 5 = Friday, 6 = Saturday) 表示一周中的某一天
int getDay()

// 返回此 Date 对象表示的小时。返回值是一个数字(0 至 23)
int getHours()

// 返回此日期所表示的小时已经过去的分钟数(用本地时区进行解释)。返回值在 0 和 59 之间。
int getMinutes()

// 返回表示月份的数字,该月份包含或开始于此 Date 对象所表示的瞬间。返回的值在 0 和 11 之间。0为一月,1为二月,依次类推。
int getMonth()

// 返回此日期所表示的分钟已经过去的秒数。返回的值在 0 和 61 之间。值 60 和 61 只可能发生在考虑了闰秒的 Java 虚拟机上。
int getSeconds()

// 返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
long getTime()

// 返回相对于 UTC(相应于此 Date 对象表示的时间)的本地时区的偏移量(以分钟为单位)。
int getTimezoneOffset()

// 返回一个值,此值是从包含或开始于此 Date 对象表示的瞬间的年份减去 1900 的结果(用本地时区进行解释)。
int getYear()

// 哈希值
int hashCode()

// 把字符串 s 解释为日期和时间的表示形式。
static long parse(String string)

// 把此 Date 对象的月份中的某一天设置为指定值。
void setDate(int day)

// 把此 Date 对象的小时设置为指定值。
void setHours(int hour)

// 把此 Date 对象的分钟数设置为指定值。
void setMinutes(int minute)

// 把此日期的月份设置为指定值。0为一月,1为二月,依次类推。
void setMonth(int month)

// 把此 Date 的秒数设置为指定值。
void setSeconds(int second)

// 设置此 Date 对象,以表示 1970 年 1 月 1 日 00:00:00 GMT 以后 time 毫秒的时间点。
void setTime(long milliseconds)

// 把此 Date 对象的年份设置为指定的值加 1900。
void setYear(int year)

// 返回GMT字符串
String toGMTString()

// 返回本地字符串
String toLocaleString()

// 返回字符串
String toString()

Date操作示例

下面我们通过示例学习使用Date的API。
源码如下(DateTest.java):

  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
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import java.util.Date;
import java.util.Calendar;

/**
 * java Date类的测试程序
 * 
 * 注意几点:
 * (01) Date中的“年”      -- 读取/设置 到的年份值=“‘时间年份’ - 1900年”
 * (02) Date中的“月”      -- 0是一月,1是二月,2是三月,依次类推。
 * (03) Date中的“星期几”  -- 1是周日,2是周一,3是周二,依次类推。
 *
 * @author skywang
 */
public class DateTest {
    
    public static void main(String[] args) {

        // 测试Date的构造函数:Date共有5类构造函数
        testDateConstructor();

        // 测试Date类的“设置”、“读取”函数
        testDateSet();

        // 测试Date类的before(), after(), compareTo(), equals(), clone(), parse()等接口
        testOtherDateAPIs();
    }

    /**
     * 测试Date的构造函数:Date共有5类构造函数
     */
    private static void testDateConstructor() {

        Date date;

        // Date构造函数一:传入“年、月、日”。
        // 参数说明
        // (01) 年 -- 减 1900 的年份。若要设为1988,则“年”应该是88。
        // (02) 月 -- 0-11 的月份。0是一月,1是二月,依次类推。
        // (03) 日 -- 1-31 之间的某一天。
        // 设置时间为“1988-08-08”
        date = new Date(88,7,8);
        System.out.printf("Constructor-1  : %s\n", tostring(date));

        // Date构造函数二:传入“年、月、日、时、分”
        // (01) 年 -- 减 1900 的年份。若要设为1988,则“年”应该是88。
        // (02) 月 -- 0-11 的月份。0是一月,1是二月,依次类推。
        // (03) 日 -- 1-31 之间的某一天。
        // (04) 时 -- 0-23 之间的小时数。
        // (05) 分 -- 0-59 之间的分钟数。
        // 设置时间为“1999-09-09 19:19”
        date = new Date(99,8,9,19,19);
        System.out.printf("Constructor-2  : %s\n", tostring(date));

        // Date构造函数三:传入“年、月、日、时、分、秒”
        // (01) 年 -- 减 1900 的年份。若要设为1988,则“年”应该是88。
        // (02) 月 -- 0-11 的月份。0是一月,1是二月,依次类推。
        // (03) 日 -- 1-31 之间的某一天。
        // (04) 时 -- 0-23 之间的小时数。
        // (05) 分 -- 0-59 之间的分钟数。
        // (06) 秒 -- 0-59 之间的秒钟数。
        date = new Date(100,10,10,20,10,10);
        System.out.printf("Constructor-3  : %s\n", tostring(date));

        // Date构造函数四:传入“毫秒”。 毫秒 = “目标时间” - “1970-01-01 00:00:00 GMT”
        // 973858210000(ms) 对应时间 2000-10-10 8:10:10
        date = new Date(973858210000l);
        System.out.printf("Constructor-4  : %s\n", tostring(date));

        // Date构造函数五:传入“字符串”。可以为以下几种格式:
        // (注意,year值 = “实际年份-1900”)
        // 1955-08-12 13:30:00
        date = new Date("Sat, 12 Aug 95 13:30:00 GMT");
        System.out.printf("Constructor-5.1: %s\n", tostring(date));
        // 1955-08-12 13:30:00
        date = new Date("12 Aug 95 13:30:00");
        System.out.printf("Constructor-5.2: %s\n", tostring(date));
        // 1955-08-12
        date = new Date("12 Aug 95");
        System.out.printf("Constructor-5.3: %s\n", tostring(date));
    }

    /**
     * 测试Date类的“读取”函数
     */
    private static void testGet(Date date) {
        // “年”。减 1900 的年份。若为1988,则“年”是88。
        int year = date.getYear();
        // “月”。 0-11 的月份。0是一月,1是二月,依次类推。
        int month = date.getMonth();
        // “日”
        int day = date.getDate();
        // “时”
        int hour = date.getHours();
        // “分”
        int minute = date.getMinutes();
        // “秒”
        int second = date.getSeconds();
        // “星期(几)”。 周日是1,周一是2,周二是3,依次类推。
        int weekday = date.getDay();
        // “毫秒”。毫秒 = “目标时间” - “1970-01-01 00:00:00 GMT”
        long millis = date.getTime();
        // “时区偏移”。相对于 UTC 的本地时区的偏移量(以分钟为单位)。
        int timezoneoffset = date.getTimezoneOffset();

        //System.out.printf("\t!!!date get is: %s\n", tostring(date));
        System.out.println("\t!!!get date: "+year+"-"+month+"-"+day+" "+hour+":"+minute+":"+second+"\t"+millis+"(ms)");
    }


    /**
     * 测试Date类的“设置”函数
     */
    private static void testDateSet() {
        // 新建date
        Date date = new Date(99,8,9);

        // 设置Date为“2013-09-19 15:28:30”
        // 设为“2013年”。传入值是“‘目标年份’ - ‘1900 的年份’”
        date.setYear(113);
        // 设为“8月”,传入的参数应该是8。因为,一月是0,二月是1,依次类推。
        date.setMonth(8);
        // 设为“19日”
        date.setDate(19);
        // 设为“15”(上午)。采用的24时制;因此,若要设为上午3点,应该传入参数3。
        date.setHours(11);
        // 设为“28分”
        date.setMinutes(28);
        // 设为“30秒”
        date.setSeconds(30);
        System.out.printf("new date-01 is: %s\n", tostring(date));

        // 测试Date的获取函数
        testGet(date);

        // 设为“毫秒”,1379561310000(ms) 对应的时间是“2013-09-19 15:28:30”
        date.setTime(1379561310000l);
        System.out.printf("new date-02 is: %s\n", tostring(date));
    }

    /**
     * 测试Date类的before(), after(), compareTo(), equals(), clone(), parse()等接口
     */
    private static void testOtherDateAPIs() {
        // 初始化d1=2008-05-12, d2=2009-03-15。
        Date d1 = new Date(108,4,12);
        System.out.printf("\nd1 is: %s\n", tostring(d1));
        Date d2 = new Date(109,2,15);
        System.out.printf("d2 is: %s\n", tostring(d2));

        // 克隆
        Date d3 = (Date) d1.clone();
        System.out.printf("clone of d1 is: %s\n", tostring(d3));

        // d1 是否早于 d2
        boolean isBefore = d1.before(d2);
        // d1 是否晚于 d2
        boolean isAfter = d1.after(d2);
        // d1 是否等于 d2
        boolean isEquals = d1.after(d2);
        // d1 和 d2 比较。
        // 若d1 早于 d2,返回 -1
        // 若d1 晚于 d2,返回 1
        // 若d1 等于 d2,返回 0
        int comp = d1.compareTo(d2);
        System.out.printf("isBefore=%s, isAfter=%s, isEquals=%s, comp=%s\n", 
                isBefore, isAfter, isEquals, comp);

        // parse接口
        long millis = Date.parse("13 Mar 2009");
        // (注意,通过这种方式设置Date,获取的Year值是“实际年份-1900”)
        Date d4 = new Date(millis);
        System.out.printf("millis=%s, d4=%s\n", millis, tostring(d4));
        System.out.printf("d1.toGMTString()%s\n", d1.toGMTString());
        System.out.printf("d1.toLocaleString()%s\n", d1.toLocaleString());
        System.out.printf("d1.toString()%s\n", d1.toString());
    }

    /**
     * 将date转换Calendar对象,并返回实际的年月日。
     */
    private static String tostring(Date date) {
        // 获取Date对应的Calendar
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        int year = cal.get(Calendar.YEAR);
        int month = cal.get(Calendar.MONTH)+1;
        int day = cal.get(Calendar.DATE);
        int hour = cal.get(Calendar.HOUR);
        int minute = cal.get(Calendar.MINUTE);
        int second = cal.get(Calendar.SECOND);
        long millis = cal.getTimeInMillis();

        return year+"-"+month+"-"+day+" "+hour+":"+minute+":"+second+"\t"+millis+"(ms)";
    }
}

Calendar介绍(废弃)

public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {}
Calendar 是一个抽象类。
它的实现,采用了设计模式中的工厂方法。表现在:当我们获取Calendar实例时,Calendar会根据传入的参数来返回相应的Calendar对象。获取Calendar实例,有以下两种方式:

  • 当我们通过 Calendar.getInstance() 获取日历时,默认的是返回的一个GregorianCalendar对象。
    GregorianCalendar是Calendar的一个实现类,它提供了世界上大多数国家/地区使用的标准日历系统。
  • 当我们通过
    Calendar.getInstance(TimeZone timezone, Locale locale)或
    Calendar.getInstance(TimeZone timezone)或
    Calendar.getInstance(Locale locale)获取日历时,是返回“对应时区(zone) 或 地区(local)等所使用的日历”。例如,若是日本,则返回JapaneseImperialCalendar对象。

源码如下:

 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
33
34
35
36
37
38
39
40
41
42
43
public static Calendar getInstance(){
    // 调用createCalendar()创建日历
    Calendar cal = createCalendar(TimeZone.getDefaultRef(), Locale.getDefault());
    cal.sharedZone = true;
    return cal;
}


public static Calendar getInstance(TimeZone zone){
    // 调用createCalendar()创建日历
    return createCalendar(zone, Locale.getDefault());
}


public static Calendar getInstance(Locale aLocale) {
    // 调用createCalendar()创建日历
    Calendar cal = createCalendar(TimeZone.getDefaultRef(), aLocale);
    cal.sharedZone = true;
    return cal;
}

public static Calendar getInstance(TimeZone zone,
                   Locale aLocale){
    // 调用createCalendar()创建日历
    return createCalendar(zone, aLocale);
}

private static Calendar createCalendar(TimeZone zone,
                   Locale aLocale){
    // (01) 若地区是“th”,则返回BuddhistCalendar对象
    // (02) 若地区是“JP”,则返回JapaneseImperialCalendar对象
    if ("th".equals(aLocale.getLanguage())
        && ("TH".equals(aLocale.getCountry()))) {
        return new sun.util.BuddhistCalendar(zone, aLocale);
    } else if ("JP".equals(aLocale.getVariant())
       && "JP".equals(aLocale.getCountry())
       && "ja".equals(aLocale.getLanguage())) {
        return new JapaneseImperialCalendar(zone, aLocale);
    }        

    // (03) 否则,返回GregorianCalendar对象
    return new GregorianCalendar(zone, aLocale);    
}

当我们获取Calendar实例之后,就可以通过Calendar提供的一些列方法来操作日历。

Calendar 原理和思想

我们使用Calendar,无非是操作Calendar的“年、月、日、星期、时、分、秒”这些字段。

Calendar 各个字段值的来源

本质上,Calendar就是保存了一个时间。如下定义:

1
2
3
// time 是当前时间,单位是毫秒。
// 它是当前时间距离“January 1, 1970, 0:00:00 GMT”的差值。
protected long time;

Calendar就是根据 time 计算出 “Calendar的年、月、日、星期、时、分、秒”等等信息。

Calendar 各个字段的定义和初始化

Calendar 的“年、月、日、星期、时、分、秒”这些信息,一共是17个字段。
我们使用Calendar,无非是就是使用这17个字段。它们的定义如下:
(字段0) public final static int ERA = 0;
说明:纪元。
取值:只能为0 或 1。0表示BC(“before Christ”,即公元前),1表示AD(拉丁语“Anno Domini”,即公元)。

(字段1) public final static int YEAR = 1;
说明:年。

(字段2) public final static int MONTH = 2;
说明:月
取值:可以为,JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER, UNDECIMBER。
其中第一个月是 JANUARY,它为 0。

(字段3) public final static int WEEK_OF_YEAR = 3;
说明:当前日期在本年中对应第几个星期。一年中第一个星期的值为 1。

(字段4) public final static int WEEK_OF_MONTH = 4;
说明:当前日期在本月中对应第几个星期。一个月中第一个星期的值为 1。

(字段5) public final static int DATE = 5;
说明:日。一个月中第一天的值为 1。

(字段5) public final static int DAY_OF_MONTH = 5;
说明:同“DATE”,表示“日”。

(字段6) public final static int DAY_OF_YEAR = 6;
说明:当前日期在本年中对应第几天。一年中第一天的值为 1。

(字段7) public final static int DAY_OF_WEEK = 7;
说明:星期几。
取值:可以为,SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY 和 SATURDAY。
其中,SUNDAY为1,MONDAY为2,依次类推。

(字段8) public final static int DAY_OF_WEEK_IN_MONTH = 8;
说明:当前月中的第几个星期。
取值:DAY_OF_MONTH 1 到 7 总是对应于 DAY_OF_WEEK_IN_MONTH 1;8 到 14 总是对应于 DAY_OF_WEEK_IN_MONTH 2,依此类推。

(字段9) public final static int AM_PM = 9;
说明:上午 还是 下午
取值:可以是AM 或 PM。AM为0,表示上午;PM为1,表示下午。

(字段10) public final static int HOUR = 10;
说明:指示一天中的第几小时。
HOUR 用于 12 小时制时钟 (0 - 11)。中午和午夜用 0 表示,不用 12 表示。

(字段11) public final static int HOUR_OF_DAY = 11;
说明:指示一天中的第几小时。
HOUR_OF_DAY 用于 24 小时制时钟。例如,在 10:04:15.250 PM 这一时刻,HOUR_OF_DAY 为 22。

(字段12) public final static int MINUTE = 12;
说明:一小时中的第几分钟。
例如,在 10:04:15.250 PM这一时刻,MINUTE 为 4。

(字段13) public final static int SECOND = 13;
说明:一分钟中的第几秒。
例如,在 10:04:15.250 PM 这一时刻,SECOND 为 15。

(字段14) public final static int MILLISECOND = 14;
说明:一秒中的第几毫秒。
例如,在 10:04:15.250 PM 这一时刻,MILLISECOND 为 250。

(字段15) public final static int ZONE_OFFSET = 15;
说明:毫秒为单位指示距 GMT 的大致偏移量。

(字段16) public final static int DST_OFFSET = 16;
说明:毫秒为单位指示夏令时的偏移量。

public final static int FIELD_COUNT = 17;

这17个字段是保存在int数组中。定义如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 保存这17个字段的数组
protected int           fields[];
// 数组的定义函数
protected Calendar(TimeZone zone, Locale aLocale)
{
    // 初始化“fields数组”
    fields = new int[FIELD_COUNT];
    isSet = new boolean[FIELD_COUNT];
    stamp = new int[FIELD_COUNT];

    this.zone = zone;
    setWeekCountData(aLocale);
}

protected Calendar(TimeZone zone, Locale aLocale) 这是Calendar的构造函数。它会被它的子类的构造函数调用到,从而新建“保存Calendar的17个字段数据”的数组。

Calendar各个字段值的计算

下面以get(int field)为例,简要的说明Calendar的17个字段的计算和操作。
get(int field)是获取“field”字段的值。它的定义如下:

1
2
3
4
5
6
public int get(int field) {
    // 计算各个字段的值
    complete();
    // 返回field字段的值
    return internalGet(field);
}

说明:
get(int field)的代码很简单。先通过 complete() 计算各个字段的值,然后在通过 internalGet(field) 返回“field字段的值”。

complete() 的作用就是计算Calendar各个字段的值。它定义在Calendar.java中,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
protected void complete()
{
    if (!isTimeSet)
    updateTime();
    if (!areFieldsSet || !areAllFieldsSet) {
        computeFields(); // fills in unset fields
        areAllFieldsSet = areFieldsSet = true;
    }
}
private void updateTime() {
    computeTime();
    isTimeSet = true;
}

updateTime() 调用到的 computeTime() 定义在 Calendar.java的实现类中。下面,我列出GregorianCalendar.java中computeTime()的实现
略…自己参考源码

下面,我们看看internalGet(field)的定义。如下:

1
2
3
protected final int internalGet(int field) {
    return fields[field];
}

从中,我们就看出,get(int field) 最终是通过 internalGet(int field)来返回值的。
而 internalGet(int field) ,实际上返回的是field数组中的第field个元素。这就正好和Calendar的17个元素所对应了!

总之,我们需要了解的就是:Calendar就是以一个time(毫秒)为基数,而计算出“年月日时分秒”等,从而方便我们对“年月日时分秒”等进行操作。下面,介绍以下Calendar提供的相关操作函数。

Calendar函数接口

Calendar的17个字段的公共接口
Calendar的这17个字段,都支持下面的公共函数接口。
这些公共接口的使用示例,请参考下方测试类CalendarTest.java 示例中的 testAllCalendarSections() 函数。

getMaximum(int field)

作用:获取“字段的最大值”。注意“对比它和 getActualMaximum() 的区别”。
示例:以“MONTH”字段来说。使用方法为:

1
2
3
4
// 获取Calendar实例
Calendar cal = Calendar.getInstance();
// 获取MONTH的最大值
int max = cal.getMaximum(Calendar.MONTH);

若要获取其它字段的最大值,只需要将示例中的MONTH相应的替换成其它字段名即可。

getActualMaximum(int field)

作用:获取“当前日期下,该字段的最大值”。
示例:以“MONTH”字段来说。使用方法为:

1
2
3
4
// 获取Calendar实例
Calendar cal = Calendar.getInstance();
// 获取当前MONTH的最大值
int max = cal.getActualMaximum(Calendar.MONTH);

若要获取其它字段的最大值,只需要将示例中的MONTH相应的替换成其它字段名即可。

注意:对比getActualMaximum() 和 getMaximum() 的区别。参考下面的对比示例,

  • getMaximum() 获取的“字段最大值”,是指在综合所有的日期,在所有这些日期中得出的“字段最大值”。
    例如,getMaximum(Calendar.DATE)的目的是“获取‘日的最大值’”。综合所有的日期,得出一个月最多有31天。因此,getMaximum(Calendar.DATE)的返回值是“31”!
  • getActualMaximum() 获取的“当前日期时,该字段的最大值”。
    例如,当日期为2013-09-01时,getActualMaximum(Calendar.DATE)是获取“日的最大值”是“30”。当前日期是9月份,而9月只有30天。因此,getActualMaximum(Calendar.DATE)的返回值是“30”!

getMinimum(int field)

作用:获取“字段的最小值”。注意“对比它和 getActualMinimum() 的区别”。
示例:以“MONTH”字段来说。使用方法为:

1
2
3
4
// 获取Calendar实例
Calendar cal = Calendar.getInstance();
// 获取MONTH的最小值
int min = cal.getMinimum(Calendar.MONTH);

若要获取其它字段的最小值,只需要将示例中的MONTH相应的替换成其它字段名即可。

getActualMinimum(int field)

作用:获取“当前日期下,该字段的最小值”。
示例:以“MONTH”字段来说。使用方法为:

1
2
3
4
// 获取Calendar实例
Calendar cal = Calendar.getInstance();
// 获取MONTH的最小值
int min = cal.getMinimum(Calendar.MONTH);

若要获取其它字段的最小值,只需要将示例中的MONTH相应的替换成其它字段名即可。
注意:在Java默认的Calendar中,虽然 getMinimum() 和 getActualMinimum() 的含义不同;但是,它们的返回值是一样的。因为Calendar的默认是返回GregorianCalendar对象,而在GregorianCalendar.java中,getMinimum() 和 getActualMinimum() 返回值一样。

get(int field)

作用:获取“字段的当前值”。获取field字段的当前值。
示例:以“MONTH”字段来说。“获取MONTH的当前值”的方法为:

1
2
3
4
// 获取Calendar实例
Calendar cal = Calendar.getInstance();
// 获取“cal日历”的当前MONTH值
int MONTH = cal.get(Calendar.MONTH);

若要获取其它字段的当前值,只需要将示例中的MONTH相应的替换成其它字段名即可。

set(int field, int value)

作用:设置“字段的当前值”。设置field字段的当前值为value
示例:以“MONTH”字段来说。“设置MONTH的当前值”的方法为:

1
2
3
4
// 获取Calendar实例
Calendar cal = Calendar.getInstance();
// 设置“cal日历”的当前MONTH值为 1988年
cal.set(Calendar.MONTH, 1988);

说明:

  • 1988是想要设置的MONTH的当前值。这个设置值必须是整数。
  • 若要设置其它字段的当前值,只需要将示例中的MONTH相应的替换成其它字段名即可。

add(int field, int value)

作用:给“字段的当前值”添加值。给field字段的当前值添加value。
示例:以“MONTH”字段来说。方法如下:

1
2
3
4
5
6
7
// 获取Calendar实例,并设置日期为“2013-09-01”
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 2013);
cal.set(Calendar.MONTH, 8);
cal.set(Calendar.DATE, 1);
// 给“cal日历”的当前MONTH值 “添加-10”
cal.add(Calendar.MONTH, -10);

说明:

  • -10是添加值。
    添加值可以为正数,也可以是负数。
    正数表示将日期增加,负数表示将日期减少。

    假设:现在cal的值是“2013-09-01”,现在我们将MONTH字段值增加-10。得到的结果是:“2012-10-01”。
    为什么会这样呢?“2013-09-01”增加-10,也就是将日期向前减少10个月;得到的结果就是“2012-10-01”。

  • Calendar的17个字段中:除了回滚Calendar.ZONE_OFFSET时,会抛出IllegalArgumentException异常;其它的字段都支持该操作。

  • 若要设置其它字段的当前值,只需要将示例中的MONTH相应的替换成其它字段名即可。

roll(int field, int value)

作用:回滚“字段的当前值”
示例:以“MONTH”字段来说。“回滚MONTH的当前值”的方法为:

1
2
3
4
5
6
7
// 获取Calendar实例,并设置日期为“2013-09-01”
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 2013);
cal.set(Calendar.MONTH, 8);
cal.set(Calendar.DATE, 1);
// 将“cal日历”的当前MONTH值 “向前滚动10”
cal.roll(Calendar.MONTH, -10);

说明:

  • -10是回滚值。
    当回滚值是负数时,表示将当前字段向前滚;
    当回滚值是正数时,表示将当前字段向后滚。

回滚Calendar中某一字段时,不更改更大的字段!
这是roll()与add()的根据区别!add()可能会更改更大字段,比如“使用add()修改‘MONTH’字段,可能会引起‘YEAR’字段的改变”;但是roll()不会更改更大的字段,例如“使用roll()修改‘MONTH’字段,不回引起‘YEAR’字段的改变。”

假设:现在cal的值是“2013-09-01”,现在我们将MONTH字段值增加-10。得到的结果是:“2013-10-01”。
为什么会这样呢?这就是因为“回滚”就是“在最小值和最大值之间来回滚动”。本例中,MONTH是9月,前回滚10,得到的值是10月,但是roll()不会改变“比MONTH”更大的字段,所以YEAR字段不会改变。所以结果是“2013-10-01”。

  • Calendar的17个字段中:除了回滚Calendar.ZONE_OFFSET时,会抛出IllegalArgumentException异常;其它的字段都支持该操作。
  • 若要设置其它字段的当前值,只需要将示例中的MONTH相应的替换成其它字段名即可。

clear(int field)

作用:清空“字段的当前值”。所谓清空,实际上是将“field”的值设置为0;若field最小值为1,则设置为1。示例:以“MONTH”字段来说。“清空MONTH”的方法为:

1
2
3
4
5
// 获取Calendar实例,并设置日期为“9月”
Calendar cal = Calendar.getInstance();
cal.set(Calendar.MONTH, 9);
// 清空MONTH
cal.clear(Calendar.MONTH);

若要清空其它字段,只需要将示例中的MONTH相应的替换成其它字段名即可。

isSet(int field)

作用:判断“字段field”是否被设置。若调用clear()清空之后,则field变为“没有设置状态”。
示例:以“MONTH”字段来说。“判断MONTH是否被设置”的方法为:

1
2
3
4
// 获取Calendar实例
Calendar cal = Calendar.getInstance();
// 判断MONTH是否被设置
boolean bset = cal.isSet(Calendar.MONTH);

若要判断其它字段,只需要将示例中的MONTH相应的替换成其它字段名即可。

Calendar的其它函数

日期比较函数

Calendar的比较函数,主要有以下几个:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 比较“当前Calendar对象”和“calendar” 的日期、时区等内容是否相等。
boolean equals(Object object)
// 当前Calendar对象 是否 早于calendar
boolean before(Object calendar)
// 当前Calendar对象 是否 晚于calendar
boolean after(Object calendar)
// 比较“当前Calendar对象”和“calendar”。
// 若 早于 “calendar” 则,返回-1
// 若 相等, 则,返回0
// 若 晚于 “calendar” 则,返回1
int compareTo(Calendar anotherCalendar)

这些函数的使用示例,请参考CalendarTest.java示例中的 testComparatorAPIs()函数。

示例:假设cal1 和 cal2 都是Calendar的两个对象。

1
2
3
4
5
// 它们的使用方法如下
boolean isEqual = cal1.equals(cal2);
boolean isBefore = cal1.before(cal2);
boolean isAfter = cal1.after(cal2);
int icompare = cal1.compareTo(cal2);

“宽容”函数

1
2
3
4
// 设置“Calendar的宽容度”
void setLenient(boolean value)
// 获取“Calendar的宽容度”
boolean isLenient()

这些函数的使用示例,请参考CalendarTest.java示例中的 testLenientAPIs() 函数。
说明:
Calendar 有两种解释日历字段的模式,即 lenient 和 non-lenient。

  • 当 Calendar 处于 lenient 模式时,它可接受比它所生成的日历字段范围更大范围内的值。当 Calendar 重新计算日历字段值,以便由 get() 返回这些值时,所有日历字段都被标准化。
    例如,lenient 模式下的 GregorianCalendar 将 MONTH == JANUARY、DAY_OF_MONTH == 32 解释为 February 1。
  • 当 Calendar 处于 non-lenient 模式时,如果其日历字段中存在任何不一致性,它都会抛出一个异常。
    例如,GregorianCalendar 总是在 1 与月份的长度之间生成 DAY_OF_MONTH 值。如果已经设置了任何超出范围的字段值,那么在计算时间或日历字段值时,处于 non-lenient 模式下的 GregorianCalendar 会抛出一个异常。
    注意:在(02)步骤中的异常,在使用set()时不会抛出,而需要在使用get()、getTimeInMillis()、getTime()、add() 和 roll() 等函数中才抛出。因为set()只是设置了一个修改标志,而get()等方法才会引起时间的重新计算,此时才会抛出异常!

“年月日(时分秒)"、Date、TimeZone、MilliSecond函数

 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
// 设置“年月日”
final void set(int year, int month, int day)

// 设置“年月日时分”
final void set(int year, int month, int day, int hourOfDay, int minute, int second)

// 设置“年月日时分秒”
final void set(int year, int month, int day, int hourOfDay, int minute)

// 获取Calendar对应的日期
final Date getTime()

// 设置Calendar为date
final void setTime(Date date)

// 获取Calendar对应的时区
TimeZone getTimeZone()

// 设置Calendar对应的时区
void setTimeZone(TimeZone timezone)

// 获取Calendar对应的milliscondes值,就是“Calendar当前日期”距离“1970-01-01 0:00:00 GMT”的毫秒数
long getTimeInMillis()

// 设置Calendar对应的milliscondes值
void setTimeInMillis(long milliseconds)

这些函数的使用示例,请参考CalendarTest.java示例中的 testTimeAPIs() 函数。

其它操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 克隆Calendar
Object clone()
// 获取“每周的第一天是星期几”。例如,在美国,这一天是 SUNDAY,而在法国,这一天是 MONDAY。
int getFirstDayOfWeek()
// 设置“每周的第一天是星期几”。例如,在美国,这一天是 SUNDAY,而在法国,这一天是 MONDAY。
void setFirstDayOfWeek(int value)
// 获取一年中第一个星期所需的最少天数,例如,如果定义第一个星期包含一年第一个月的第一天,则此方法将返回 1。如果最少天数必须是一整个星期,则此方法将返回 7。
int getMinimalDaysInFirstWeek()
// 设置一年中第一个星期所需的最少天数,例如,如果定义第一个星期包含一年第一个月的第一天,则使用值 1 调用此方法。如果最少天数必须是一整个星期,则使用值 7 调用此方法。
void setMinimalDaysInFirstWeek(int value)

这些函数的使用示例,请参考CalendarTest.java示例中的 testOtherAPIs() 函数。

Calendar操作示例

下面,我们通过示例学习使用Calendar的API。CalendarTest.java的源码如下:

  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
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
import java.util.Date;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.Random;

/**
 * Calendar的API测试程序
 *
 * @author skywang 
 * @email kuiwu-wang@163.com
 */
public class CalendarTest {

    public static void main(String[] args) {

        // 测试Calendar的“17个字段的公共函数接口”
        testAllCalendarSections() ;

        // 测试Calendar的“比较接口”
        testComparatorAPIs() ;

        // 测试Calendar的“比较接口”
        testLenientAPIs() ;

        // 测试Calendar的Date、TimeZone、MilliSecond等相关函数
        testTimeAPIs() ;

        // 测试Calendar的clone(),getFirstDayOfWeek()等接口
        testOtherAPIs() ;

    }


    /**
     * 测试“Calendar的字段”
     *
     * @param cal   -- Calendar对象
     * @param field -- 要测试的“Calendar字段”。可以为以下值:
     *   Calendar.YEAR, Calendar.MONTH, Calendar.DATE, ... 等等
     * @param title -- 标题
     * @author skywang (kuiwu-wang@163.com)
     */
    private static void testSection(Calendar cal, int field, String title) {
        final Random random = new Random();
        final Date date = cal.getTime();

        final int min = cal.getMinimum(field);    // 获取"字段最小值"
        final int max = cal.getMaximum(field);    // 获取“字段最大值”

        final int actualMin = cal.getActualMinimum(field);    // 获取"当前日期下,该字段最小值"
        final int actualMax = cal.getActualMaximum(field);    // 获取“当前日期下,该字段的最大值”

        // 获取“字段的当前值”
        final int ori = cal.get(field);            

        // 设置“字段的当前值”, 并获取“设置之后的值”
        final int r1 = random.nextInt(max);
        cal.set(field, r1);                
        final int set = cal.get(field);            
        try {
            // 回滚“字段的当前值”:在“字段最小值”和“字段最大值”之间回滚。
            // “回滚值”可以为正,也可以为负。
            cal.roll(field, -max);            
        } catch (IllegalArgumentException e) {
            // 当field == Calendar.ZONE_OFFSET时,会抛出该异常!
            e.printStackTrace();
        }
        final int roll = cal.get(field);            

        // 获取一个随机值
        final int sign = ( random.nextInt(2) == 1) ? 1 : -1;
        final int r2 = sign * random.nextInt(max);
        try {
            // 增加“字段的当前值” ,并获取“新的当前字段值”
            // add的“参数值”可以为正,也可以为负。
            cal.add(field, r2);            
        } catch (IllegalArgumentException e) {
            // 当field == Calendar.ZONE_OFFSET时,会抛出该异常!
            e.printStackTrace();
        }
        final int add = cal.get(field);

        // 打印字段信息
        System.out.printf("%s:\n\trange is [%d - %d] actualRange is [%d - %d].  original=%d, set(%d)=%d, roll(%d)=%d, add(%d)=%d\n",
               title, min, max, actualMin, actualMax, ori, r1, set, -max, roll, r2, add);
    }

    /**
     * 测试Calendar的“17个字段的公共函数接口”
     *
     * @author skywang (kuiwu-wang@163.com)
     */
    private static void testAllCalendarSections() {
        // 00. ERA 字段
        testSection(Calendar.getInstance(), Calendar.ERA, "Calendar.ERA");
        // 01. YEAR 字段
        testSection(Calendar.getInstance(), Calendar.YEAR, "Calendar.YEAR");
        // 02. MONTH 字段
        testSection(Calendar.getInstance(), Calendar.MONTH, "Calendar.MONTH");
        // 03. WEEK_OF_YEAR 字段
        testSection(Calendar.getInstance(), Calendar.WEEK_OF_YEAR, "Calendar.WEEK_OF_YEAR");
        // 04. WEEK_OF_MONTH 字段
        testSection(Calendar.getInstance(), Calendar.WEEK_OF_MONTH, "Calendar.WEEK_OF_MONTH");
        // 05. DATE 字段
        testSection(Calendar.getInstance(), Calendar.DATE, "Calendar.DATE");
        // 06. DAY_OF_MONTH 字段
        testSection(Calendar.getInstance(), Calendar.DAY_OF_MONTH, "Calendar.DAY_OF_MONTH");
        // 07. DAY_OF_YEAR 字段
        testSection(Calendar.getInstance(), Calendar.DAY_OF_YEAR, "Calendar.DAY_OF_YEAR");
        // 08. DAY_OF_WEEK 字段
        testSection(Calendar.getInstance(), Calendar.DAY_OF_WEEK, "Calendar.DAY_OF_WEEK");
        // 09. DAY_OF_WEEK_IN_MONTH 字段
        testSection(Calendar.getInstance(), Calendar.DAY_OF_WEEK_IN_MONTH, "Calendar.DAY_OF_WEEK_IN_MONTH");
        // 10. AM_PM 字段
        testSection(Calendar.getInstance(), Calendar.AM_PM, "Calendar.AM_PM");
        // 11. HOUR 字段
        testSection(Calendar.getInstance(), Calendar.HOUR, "Calendar.HOUR");
        // 12. HOUR_OF_DAY 字段
        testSection(Calendar.getInstance(), Calendar.HOUR_OF_DAY, "Calendar.HOUR_OF_DAY");
        // 13. MINUTE 字段
        testSection(Calendar.getInstance(), Calendar.MINUTE, "Calendar.MINUTE");
        // 14. SECOND 字段
        testSection(Calendar.getInstance(), Calendar.SECOND, "Calendar.SECOND");
        // 15. MILLISECOND 字段
        testSection(Calendar.getInstance(), Calendar.MILLISECOND, "Calendar.MILLISECOND");
        // 16. ZONE_OFFSET 字段
        testSection(Calendar.getInstance(), Calendar.ZONE_OFFSET, "Calendar.ZONE_OFFSET");
    }

    /**
     * 测试Calendar的“比较接口”
     *
     * @author skywang (kuiwu-wang@163.com)
     */
    private static void testComparatorAPIs() {
        // 新建cal1 ,且时间为1988年
        Calendar cal1 = Calendar.getInstance();
        cal1.set(Calendar.YEAR, 1988);
        // 新建cal2 ,且时间为2000年
        Calendar cal2 = Calendar.getInstance();
        cal2.set(Calendar.YEAR, 2000);
        // 新建cal3, 为cal1的克隆对象
        Calendar cal3 = (Calendar)cal1.clone();

        // equals 判断 cal1和cal2的“时间、时区等”内容是否相等
        boolean isEqual12 = cal1.equals(cal2);
        // equals 判断 cal1和cal3的“时间、时区等”内容是否相等
        boolean isEqual13 = cal1.equals(cal3);
        // cal1是否比cal2早
        boolean isBefore = cal1.before(cal2);
        // cal1是否比cal2晚
        boolean isAfter = cal1.after(cal2);
        // 比较cal1和cal2
        // (01) 若cal1 早于 cal2,返回-1
        // (02) 若cal1 等于 cal2,返回0
        // (03) 若cal1 晚于 cal2,返回1
        int icompare = cal1.compareTo(cal2);
        
        System.out.printf("\ntestComparatorAPIs: isEuqal12=%s, isEqual13=%s, isBefore=%s, isAfter=%s, icompare=%s\n",
               isEqual12, isEqual13, isBefore, isAfter, icompare);
    }

    /**
     * 测试Calendar的“比较接口”
     *
     * @author skywang (kuiwu-wang@163.com)
     */
    private static void testLenientAPIs() {
            Calendar cal = Calendar.getInstance();

            // 获取默认的“宽容度”。返回true
            boolean oriLenient = cal.isLenient();
            // MONTH值只能是“0-11”,这里越界。但是由于当前cal是宽容的,所以不会抛出异常
            cal.set(Calendar.MONTH, 50);    

            // 设置“宽容度”为false。
            cal.setLenient(false);
            // 获取设置后的“宽容度”
            boolean curLenient = cal.isLenient();
            try {
            // MONTH值只能是“0-11”,这里越界。而且当前cal是不宽容的,所以会产生异常。
            // 但是,异常到下次计算日期时才会抛出。即,set()中不回抛出异常,而要等到get()中才会抛出异常
            cal.set(Calendar.MONTH, 50);
            // 此时,对cal进行读取。读取会导致重新计算cal的值,所以此时抛出异常!
            int m2 = cal.get(Calendar.MONTH);    
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        }

        System.out.printf("\ntestLenientAPIs: oriLenient=%s, curLenient=%s\n",
               oriLenient, curLenient);
    }

    /**
     * 测试Calendar的Date、TimeZone、MilliSecond等相关函数
     *
     * @author skywang (kuiwu-wang@163.com)
     */
    private static void testTimeAPIs() {
        Calendar cal = Calendar.getInstance();

        // 设置cal的时区为“GMT+8”
        cal.setTimeZone(TimeZone.getTimeZone("GMT+8"));
        // 获取当前的cal时区
        TimeZone timezone = cal.getTimeZone();

        // 设置 milliseconds
        cal.setTimeInMillis(1279419645742l);
        // 获取 milliseconds
        long millis = cal.getTimeInMillis();
        // 设置 milliseconds之后,时间也改变了。
        // 获取cal对应的日期
        Date date = cal.getTime();

        // 设置时间为“1988-08-08”
        cal.set(1988, 08, 08);
        // 设置时间为“1999-09-09 09:09”
        cal.set(1999, 09, 09, 9, 9);
        // 设置时间为“2000-10-10 10:10:10”
        cal.set(2000, 10, 10, 10, 10, 10);

        System.out.printf("\ntestTimeAPIs: date=%s, timezone=%s, millis=%s\n",
               date, timezone, millis);
    }

    /**
     * 测试Calendar的clone(),getFirstDayOfWeek()等接口
     * @author skywang (kuiwu-wang@163.com)
     */
    private static void testOtherAPIs() {
        Calendar cal = Calendar.getInstance();
        // 克隆cal
        Calendar clone = (Calendar)cal.clone();

        // 设置 为 2013-01-10。 
        // 注:2013-01-01 为“星期二”,2013-01-06为“星期天”,
        clone.set(Calendar.YEAR, 2013);
        clone.set(Calendar.MONTH, 0);
        clone.set(Calendar.DATE, 10);
        // 设置“本年的第一个星期最少包含1天”。
        // 则2013-01-10属于第2个星期
        clone.setMinimalDaysInFirstWeek(1);
        int m1 = clone.getMinimalDaysInFirstWeek();
        int index1 = clone.get(Calendar.WEEK_OF_YEAR);

        // 设置“本年的第一个星期最少包含7天”。
        // 则2013-01-10属于第1个星期
        clone.setMinimalDaysInFirstWeek(7);
        int m2 = clone.getMinimalDaysInFirstWeek();
        int index2 = clone.get(Calendar.WEEK_OF_YEAR);

        // 设置“每周的第一天是星期几”。
        clone.setFirstDayOfWeek(Calendar.WEDNESDAY);
        // 获取“每周的第一天是星期几”。
        int firstdayOfWeek = clone.getFirstDayOfWeek();

        System.out.printf("\ntestOtherAPIs: firstdayOfWeek=%s, [minimalDay, WeekOfYear]={(%s, %s), (%s, %s)} %s\n",
               firstdayOfWeek, m1, index1, m2, index2, clone.getTime());
    }
}

Locale 介绍(废弃)

Locale 表示地区。每一个Locale对象都代表了一个特定的地理、政治和文化地区。
在操作 Date, Calendar等表示日期/时间的对象时,经常会用到;因为不同的区域,时间表示方式都不同。

下面说说Locale对象的3种常用创建方式。

获取默认的Locale

使用方法:
Locale locale = Locale.getDefault()

直接使用Locale的静态对象

Locale.java中提供了以下静态对象

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static final Locale CANADA
public static final Locale CANADA_FRENCH
public static final Locale CHINA
public static final Locale CHINESE
public static final Locale ENGLISH
public static final Locale FRANCE
public static final Locale FRENCH
public static final Locale GERMAN
public static final Locale GERMANY
public static final Locale ITALIAN
public static final Locale ITALY
public static final Locale JAPAN
public static final Locale JAPANESE
public static final Locale KOREA
public static final Locale KOREAN
public static final Locale PRC
public static final Locale ROOT
public static final Locale SIMPLIFIED_CHINESE
public static final Locale TAIWAN
public static final Locale TRADITIONAL_CHINESE
public static final Locale UK
public static final Locale US

使用方法:下面的Locale对象是对应 “中国(大陆)”的
Locale locale = Locale.SIMPLIFIED_CHINESE

通过Locale的构造函数创建Locale对象

Locale的构造函数共有3个。如下:

1
2
3
Locale(String language)
Locale(String language, String country)
Locale(String language, String country, String variant)

使用方法:
Locale local = new Locale("zh", "CN");

Locale类支持非常多的国家和地区。我们可以通过以下方法,查看Locale支持的全部区域:

1
2
3
4
5
Locale[] ls = Locale.getAvailableLocales();

for (Locale locale:ls) {
    System.out.println("locale :"+locale);
}

输入结果如下:
All Locales: ja_JP, es_PE, en, ja_JP_JP, es_PA, sr_BA, mk, es_GT, ar_AE, no_NO, sq_AL, bg, ar_IQ, ar_YE, hu, pt_PT, el_CY, ar_QA, mk_MK, sv, de_CH, en_US, fi_FI, is, cs, en_MT, sl_SI, sk_SK, it, tr_TR, zh, th, ar_SA, no, en_GB, sr_CS, lt, ro, en_NZ, no_NO_NY, lt_LT, es_NI, nl, ga_IE, fr_BE, es_ES, ar_LB, ko, fr_CA, et_EE, ar_KW, sr_RS, es_US, es_MX, ar_SD, in_ID, ru, lv, es_UY, lv_LV, iw, pt_BR, ar_SY, hr, et, es_DO, fr_CH, hi_IN, es_VE, ar_BH, en_PH, ar_TN, fi, de_AT, es, nl_NL, es_EC, zh_TW, ar_JO, be, is_IS, es_CO, es_CR, es_CL, ar_EG, en_ZA, th_TH, el_GR, it_IT, ca, hu_HU, fr, en_IE, uk_UA, pl_PL, fr_LU, nl_BE, en_IN, ca_ES, ar_MA, es_BO, en_AU, sr, zh_SG, pt, uk, es_SV, ru_RU, ko_KR, vi, ar_DZ, vi_VN, sr_ME, sq, ar_LY, ar, zh_CN, be_BY, zh_HK, ja, iw_IL, bg_BG, in, mt_MT, es_PY, sl, fr_FR, cs_CZ, it_CH, ro_RO, es_PR, en_CA, de_DE, ga, de_LU, de, es_AR, sk, ms_MY, hr_HR, en_SG, da, mt, pl, ar_OM, tr, th_TH_TH, el, ms, sv_SE, da_DK, es_HN

下面选择其中的两个进行说明,如何利用它们来创建Locale对象:
例如,第一个输出是“ja_JP”。

其中,ja代表“语言”,这里指日语;“JP”代表国家,这里指日本。
我们可以通过如下方法,创建“语言是日语,国家是日本的Locale对象”。
Locale locale = new Locale("ja", "JP");

例如,第三个输出是“en”。

其中,en代表“语言”,这里指英语。
我们可以通过如下方法,创建“语言是英文的Locale对象”。
Locale locale = new Locale("en");

Locale 函数列表

 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
// Locale的构造函数
Locale(String language)
Locale(String language, String country)
Locale(String language, String country, String variant)

Object                         clone()
boolean                     equals(Object object)
static Locale[]             getAvailableLocales()
String                         getCountry()
static Locale                 getDefault()
String                         getDisplayCountry(Locale locale)
final String                 getDisplayCountry()
final String                 getDisplayLanguage()
String                         getDisplayLanguage(Locale locale)
String                         getDisplayName(Locale locale)
final String                 getDisplayName()
final String                 getDisplayVariant()
String                         getDisplayVariant(Locale locale)
String                         getISO3Country()
String                         getISO3Language()
static String[]             getISOCountries()
static String[]             getISOLanguages()
String                         getLanguage()
String                         getVariant()
synchronized int             hashCode()
synchronized static void     setDefault(Locale locale)
final String                 toString()

Locale示例

下面通过示例演示在Date中使用Locale的。
参考代码如下(LocaleTest.java):

 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import java.util.Locale;
import java.util.Date;
import java.util.Calendar;
import java.text.SimpleDateFormat;
import java.text.DateFormat;

/**
 * Locale 的测试程序
 *
 * @author skywang
 * @email kuiwu-wang@163.com
 */
public class LocaleTest {

    public static void main(String[] args) {
        // 2种不同的Locale的创建方法
        testDiffDateLocales();

        // 显示所有的Locales
        testAllLocales();
    }


    /**
     *  2种不同的Locale的创建方法
     */
    private static void testDiffDateLocales() {
        // date为2013-09-19 14:22:30
        Date date = new Date(113, 8, 19, 14, 22, 30);

        // 创建“简体中文”的Locale
        Locale localeCN = Locale.SIMPLIFIED_CHINESE;
        // 创建“英文/美国”的Locale
        Locale localeUS = new Locale("en", "US");

        // 获取“简体中文”对应的date字符串
        String cn = DateFormat.getDateInstance(DateFormat.MEDIUM, localeCN).format(date);
        // 获取“英文/美国”对应的date字符串
        String us = DateFormat.getDateInstance(DateFormat.MEDIUM, localeUS).format(date);

        System.out.printf("cn=%s\nus=%s\n", cn, us);
    }

    /**
     *  显示所有的Locales
     */
    private static void testAllLocales() {
        Locale[] ls = Locale.getAvailableLocales();

        System.out.print("All Locales: ");
        for (Locale locale:ls) {
            System.out.printf(locale+", ");
        }
        System.out.println();
    }
}

TimeZone简介

TimeZone 表示时区偏移量,也可以计算夏令时。
在操作 Date, Calendar等表示日期/时间的对象时,经常会用到TimeZone;因为不同的时区,时间不同。

下面说说TimeZone对象的 2种常用创建方式。

  • 获取默认的TimeZone对象
    使用方法:
    TimeZone tz = TimeZone.getDefault()

  • 使用 getTimeZone(String id) 方法获取TimeZone对象
    使用方法:

1
2
3
4
// 获取 “GMT+08:00”对应的时区
TimeZone china = TimeZone.getTimeZone("GMT+:08:00");
// 获取 “中国/重庆”对应的时区
TimeZone chongqing = TimeZone.getTimeZone("Asia/Chongqing");

关于 getTimeZone(String id) 这种方式支持的全部id参数的取值,可以通过以下方式查找:

1
2
3
String[] ids = TimeZone.getAvailableIDs();
for (String id:ids) 
    System.out.printf(id+", ");

输出结果略…

例如,创建上面第2个打印值“Etc/GMT+11”对应的TimeZone。方法如下:
TimeZone tz = TimeZone.getTimeZone("Etc/GMT+11");

TimeZone的函数接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 构造函数
TimeZone()

Object                           clone()
synchronized static String[]     getAvailableIDs()
synchronized static String[]     getAvailableIDs(int offsetMillis)
int                              getDSTSavings()
synchronized static TimeZone     getDefault()
final String                     getDisplayName(Locale locale)
String                           getDisplayName(boolean daylightTime, int style, Locale locale)
final String                     getDisplayName()
final String                     getDisplayName(boolean daylightTime, int style)
String                           getID()
abstract int                     getOffset(int era, int year, int month, int day, int dayOfWeek, int timeOfDayMillis)
int                              getOffset(long time)
abstract int                     getRawOffset()
synchronized static TimeZone     getTimeZone(String id)
boolean                          hasSameRules(TimeZone timeZone)
abstract boolean                 inDaylightTime(Date time)
synchronized static void         setDefault(TimeZone timeZone)
void                             setID(String id)
abstract void                    setRawOffset(int offsetMillis)
abstract boolean                 useDaylightTime()

TimeZone示例

下面通过示例演示在Date中使用TimeZone。
参考代码如下(TimeZoneTest.java):

 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import java.text.DateFormat;
import java.util.Date;
import java.util.TimeZone;

/**
 * TimeZone的测试程序
 *
 * @author skywang
 * @email kuiwu-wang@163.com
 */
public class TimeZoneTest {

    public static void main(String[] args) {

        // 测试创建TimeZone对象的3种方法
        showUsageOfTimeZones() ;

        // 测试TimeZone的其它API
        testOtherAPIs() ;

        // 打印getTimeZone(String id)支持的所有id
        //printAllTimeZones() ;
    }


    /**
     * 测试创建TimeZone对象的3种方法
     */
    public static void showUsageOfTimeZones() {
        TimeZone tz;

        // (01) 默认时区
        tz = TimeZone.getDefault();
        printDateIn(tz) ;

        // (02) 设置时区为"GMT+08:00"
        tz = TimeZone.getTimeZone("GMT+08:00");
        printDateIn(tz) ;

        // (03) 设置时区为""
        tz = TimeZone.getTimeZone("Asia/Chongqing");
        printDateIn(tz) ;
    }

    /**
     * 打印 tz对应的日期/时间
     */
    private static void printDateIn(TimeZone tz) {
        // date为2013-09-19 14:22:30
        Date date = new Date(113, 8, 19, 14, 22, 30);
        // 获取默认的DateFormat,用于格式化Date
        DateFormat df = DateFormat.getInstance();
        // 设置时区为tz
        df.setTimeZone(tz);
        // 获取格式化后的字符串
        String str = df.format(date);

        System.out.println(tz.getID()+" :"+str);
    }

    /**
     * 测试TimeZone的其它API
     */
    public static void testOtherAPIs() {
        // 默认时区
        TimeZone tz = TimeZone.getDefault();

        // 获取“id”
        String id = tz.getID();

        // 获取“显示名称”
        String name = tz.getDisplayName();

        // 获取“时间偏移”。相对于“本初子午线”的偏移,单位是ms。
        int offset = tz.getRawOffset();
        // 获取“时间偏移” 对应的小时
        int gmt = offset/(3600*1000);

        System.out.printf("id=%s, name=%s, offset=%s(ms), gmt=%s\n",
                id, name, offset, gmt);
    }

    /**
     * 打印getTimeZone(String id)支持的所有id
     */
    public static void printAllTimeZones() {

        String[] ids = TimeZone.getAvailableIDs();
        for (String id:ids) {
            //int offset = TimeZone.getTimeZone(avaIds[i]).getRawOffset();
            //System.out.println(i+"  "+avaIds[i]+" "+offset / (3600 * 1000) + "\t");
            System.out.printf(id+", ");
        }
        System.out.println();
    }
}

DateFormat 介绍(废弃)

DateFormat 的作用是 格式化并解析“日期/时间”。实际上,它是Date的格式化工具,它能帮助我们格式化Date,进而将Date转换成我们想要的String字符串供我们使用
不过DateFormat的格式化Date的功能有限,没有SimpleDateFormat强大;但DateFormat是SimpleDateFormat的父类。所以,我们先对DateFormat有个整体了解,然后再学习SimpleDateFormat。
DateFormat 的作用是格式化Date。它支持格式化风格包括 FULL、LONG、MEDIUM 和 SHORT 共4种:

  • DateFormat.SHORT
    完全为数字,如 12.13.52 或 3:30pm
  • DateFormat.MEDIUM
    较长,如 Jan 12, 1952
  • DateFormat.LONG
    更长,如 January 12, 1952 或 3:30:32pm
  • DateFormat.FULL
    是完全指定,如 Tuesday、April 12、1952 AD 或 3:30:42pm PST。

DateFormat 的定义如下

public abstract class NumberFormat extends Format {}

DateFormat 的函数接口

 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
// 默认构造函数
DateFormat()

// 非构造函数
Object                   clone()
boolean                  equals(Object object)
abstract StringBuffer    format(Date date, StringBuffer buffer, FieldPosition field)
final StringBuffer       format(Object object, StringBuffer buffer, FieldPosition field)
final String             format(Date date)
static Locale[]          getAvailableLocales()
Calendar                 getCalendar()
final static DateFormat     getInstance()
final static DateFormat     getDateInstance()
final static DateFormat     getDateInstance(int style)
final static DateFormat     getDateInstance(int style, Locale locale)
final static DateFormat     getTimeInstance()
final static DateFormat     getTimeInstance(int style)
final static DateFormat     getTimeInstance(int style, Locale locale)
final static DateFormat     getDateTimeInstance()
final static DateFormat     getDateTimeInstance(int dateStyle, int timeStyle)
final static DateFormat     getDateTimeInstance(int dateStyle, int timeStyle, Locale locale)
NumberFormat     getNumberFormat()
TimeZone         getTimeZone()
int              hashCode()
boolean          isLenient()
Date             parse(String string)
abstract Date    parse(String string, ParsePosition position)
Object           parseObject(String string, ParsePosition position)
void             setCalendar(Calendar cal)
void             setLenient(boolean value)
void             setNumberFormat(NumberFormat format)
void             setTimeZone(TimeZone timezone)

注意:DateFormat是一个抽象类。

当我们通过DateFormat的 getInstance(), getDateInstance()和getDateTimeInstance() 获取DateFormat实例时;实际上是返回的SimpleDateFormat对象。
下面的函数实际上都是返回的SimpleDateFormat对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
final static DateFormat getInstance()
final static DateFormat getTimeInstance()
final static DateFormat getTimeInstance(int style)
final static DateFormat getTimeInstance(int style, Locale locale)
final static DateFormat getDateInstance()
final static DateFormat getDateInstance(int style)
final static DateFormat getDateInstance(int style, Locale locale)
final static DateFormat getDateTimeInstance()
final static DateFormat getDateTimeInstance(int dateStyle, int timeStyle)
final static DateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale locale)

这些函数在SimpleDateFormat.java中的定义如下:

 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
public static final int FULL = 0;
public static final int LONG = 1;
public static final int MEDIUM = 2;
public static final int SHORT = 3;
public static final int DEFAULT = MEDIUM;

public final static DateFormat getInstance() {
    return getDateTimeInstance(SHORT, SHORT);
}

public final static DateFormat getTimeInstance(){
    return get(DEFAULT, 0, 1, Locale.getDefault());
}

public final static DateFormat getTimeInstance(int style){
    return get(style, 0, 1, Locale.getDefault());
}

public final static DateFormat getTimeInstance(int style,
                                             Locale aLocale){
    return get(style, 0, 1, aLocale);
}

public final static DateFormat getDateInstance(){
    return get(0, DEFAULT, 2, Locale.getDefault());
}

public final static DateFormat getDateInstance(int style){
    return get(0, style, 2, Locale.getDefault());
}

public final static DateFormat getDateInstance(int style,
                                             Locale aLocale){
    return get(0, style, 2, aLocale);
}

public final static DateFormat getDateTimeInstance(){
    return get(DEFAULT, DEFAULT, 3, Locale.getDefault());
}

public final static DateFormat getDateTimeInstance(int dateStyle,
                                                   int timeStyle){
    return get(timeStyle, dateStyle, 3, Locale.getDefault());
}

public final static DateFormat
    getDateTimeInstance(int dateStyle, int timeStyle, Locale aLocale){
    return get(timeStyle, dateStyle, 3, aLocale);
}

/**
 * 获取DateFormat实例,实际上是返回SimpleDateFormat对象。
 * 
 * timeStyle -- 值可以为“FULL”或“LONG”或“MEDIUM”或“SHORT”
 * dateStyle -- 值可以为“FULL”或“LONG”或“MEDIUM”或“SHORT”
 * flags     -- 值可以为“1”或“2”或“3”。
 *       1 表示获取“时间样式”
 *       2 表示获取“日期样式”
 *       3 表示获取“时间和日期样式”
 * loc       -- locale对象,表示“区域”
 */
private static DateFormat get(int timeStyle, int dateStyle,
                              int flags, Locale loc) {
    if ((flags & 1) != 0) {
        if (timeStyle < 0 || timeStyle > 3) {
            throw new IllegalArgumentException("Illegal time style " + timeStyle);
        }
    } else {
        timeStyle = -1;
    }
    if ((flags & 2) != 0) {
        if (dateStyle < 0 || dateStyle > 3) {
            throw new IllegalArgumentException("Illegal date style " + dateStyle);
        }
    } else {
        dateStyle = -1;
    }
    try {
        // Check whether a provider can provide an implementation that's closer 
        // to the requested locale than what the Java runtime itself can provide.
        LocaleServiceProviderPool pool =
            LocaleServiceProviderPool.getPool(DateFormatProvider.class);
        if (pool.hasProviders()) {
            DateFormat providersInstance = pool.getLocalizedObject(
                                                DateFormatGetter.INSTANCE,
                                                loc, 
                                                timeStyle,
                                                dateStyle,
                                                flags);
            if (providersInstance != null) {
                return providersInstance;
            }
        }

        return new SimpleDateFormat(timeStyle, dateStyle, loc);
    } catch (MissingResourceException e) {
        return new SimpleDateFormat("M/d/yy h:mm a");
    }
}

通过上面的代码,我们能够进一步的认识到:DateFormat的作用是格式化Date;帮助我们将Date转换成我们需要的String字符串。DateFormat提供的功能非常有限,它只能支持FULL、LONG、MEDIUM 和 SHORT 这4种格式。而且,我们获取DateFormat实例时,实际上是返回的SimpleDateFormat对象。

DateFormat 示例

下面,我们通过示例学习使用DateFormat的常用API。
源码如下(DateFormatTest.java):

  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
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import java.util.Date;
import java.util.Locale;
import java.text.DateFormat;
import java.text.FieldPosition;

/**
 * DateFormat 的API测试程序
 *
 * @author skywang
 * @email kuiwu-wang@163.com
 */
public class DateFormatTest {
    
    public static void main(String[] args) {

        // 只显示“时间”:调用getTimeInstance()函数
        testGetTimeInstance() ;

        // 只显示“日期”:调用getDateInstance()函数
        testGetDateInstance() ;

        // 显示“日期”+“时间”:调用getDateTimeInstance()函数
        testGetDateTimeInstance() ;
        
        // 测试format()函数
        testFormat();
    }

    /**
     * 测试DateFormat的getTimeInstance()函数
     * 它共有3种重载形式:
     * (01) getTimeInstance()
     * (02) getTimeInstance(int style)
     * (03) getTimeInstance(int style, Locale locale)
     *
     * @author skywang
     */
    private static void testGetTimeInstance() {
        Date date = new Date(); 

        //Locale locale = new Locale("fr", "FR");
        Locale locale = new Locale("zh", "CN"); 

        // 等价于 DateFormat.getTimeInstance( DateFormat.MEDIUM); 
        DateFormat short0  = DateFormat.getTimeInstance( ); 

        // 参数是:“时间的显示样式”
        DateFormat short1  = DateFormat.getTimeInstance( DateFormat.SHORT); 
        DateFormat medium1 = DateFormat.getTimeInstance( DateFormat.MEDIUM); 
        DateFormat long1   = DateFormat.getTimeInstance( DateFormat.LONG); 
        DateFormat full1   = DateFormat.getTimeInstance( DateFormat.FULL); 

        // 参数是:“时间的显示样式” 和 “地区”
        DateFormat short2  = DateFormat.getTimeInstance( DateFormat.SHORT, locale); 
        DateFormat medium2 = DateFormat.getTimeInstance( DateFormat.MEDIUM, locale); 
        DateFormat long2   = DateFormat.getTimeInstance( DateFormat.LONG, locale); 
        DateFormat full2   = DateFormat.getTimeInstance( DateFormat.FULL, locale); 

        System.out.println("\n----getTimeInstance ----\n"
                + "(1.0) Empty Param   : " + short0.format(date) +"\n"
                + "(2.1) One Param(s)  : " + short1.format(date) +"\n"
                + "(2.2) One Param(m)  : " + medium1.format(date) +"\n"
                + "(2.3) One Param(l)  : " + long1.format(date) +"\n"
                + "(2.4) One Param(f)  : " + full1.format(date) +"\n"
                + "(3.1) One Param(s,l): " + short2.format(date) +"\n"
                + "(3.2) One Param(m,l): " + medium2.format(date) +"\n"
                + "(3.3) One Param(l,l): " + long2.format(date) +"\n"
                + "(3.4) One Param(f,l): " + full2.format(date) +"\n"
                ); 
    }

    /**
     * 测试DateFormat的getDateTimeInstance()函数
     * 它共有3种重载形式:
     * (01) getDateInstance()
     * (02) getDateInstance(int style)
     * (03) getDateInstance(int style, Locale locale)
     */
    public static void testGetDateTimeInstance() {
        Date date = new Date(); 

        Locale locale = new Locale("zh", "CN"); 

        // 等价于 DateFormat.getDateTimeInstance( DateFormat.MEDIUM); 
        DateFormat short0  = DateFormat.getDateTimeInstance( ); 

        DateFormat short1  = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT); 
        DateFormat medium1 = DateFormat.getDateTimeInstance( DateFormat.MEDIUM, DateFormat.MEDIUM); 
        DateFormat long1   = DateFormat.getDateTimeInstance( DateFormat.LONG, DateFormat.LONG); 
        DateFormat full1   = DateFormat.getDateTimeInstance( DateFormat.FULL, DateFormat.FULL); 

        DateFormat short2  = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT, locale); 
        DateFormat medium2 = DateFormat.getDateTimeInstance( DateFormat.MEDIUM, DateFormat.MEDIUM, locale); 
        DateFormat long2   = DateFormat.getDateTimeInstance( DateFormat.LONG, DateFormat.LONG, locale); 
        DateFormat full2   = DateFormat.getDateTimeInstance( DateFormat.FULL, DateFormat.FULL, locale); 

        System.out.println("\n----getDateTimeInstance ----\n"
                + "(1.0) Empty Param   : " + short0.format(date) +"\n"
                + "(2.1) One Param(s)  : " + short1.format(date) +"\n"
                + "(2.2) One Param(m)  : " + medium1.format(date) +"\n"
                + "(2.3) One Param(l)  : " + long1.format(date) +"\n"
                + "(2.4) One Param(f)  : " + full1.format(date) +"\n"
                + "(3.1) One Param(s,l): " + short2.format(date) +"\n"
                + "(3.2) One Param(m,l): " + medium2.format(date) +"\n"
                + "(3.3) One Param(l,l): " + long2.format(date) +"\n"
                + "(3.4) One Param(f,l): " + full2.format(date) +"\n"
                ); 
    }

    /**
     * 测试DateFormat的getDateInstance()函数
     * 它共有3种重载形式:
     * (01) getDateTimeInstance()
     * (02) getDateTimeInstance(int dateStyle, int timeStyle)
     * (03) getDateTimeInstance(int dateStyle, int timeStyle, Locale locale)
     */
    public static void testGetDateInstance() {
        Date date = new Date(); 

        //Locale locale = new Locale("en", "US"); 
        Locale locale = new Locale("zh", "CN"); 

        // 等价于 DateFormat.getDateInstance( DateFormat.MEDIUM); 
        DateFormat short0  = DateFormat.getDateInstance( ); 

        DateFormat short1  = DateFormat.getDateInstance( DateFormat.SHORT); 
        DateFormat medium1 = DateFormat.getDateInstance( DateFormat.MEDIUM); 
        DateFormat long1   = DateFormat.getDateInstance( DateFormat.LONG); 
        DateFormat full1   = DateFormat.getDateInstance( DateFormat.FULL); 

        DateFormat short2  = DateFormat.getDateInstance( DateFormat.SHORT, locale); 
        DateFormat medium2 = DateFormat.getDateInstance( DateFormat.MEDIUM, locale); 
        DateFormat long2   = DateFormat.getDateInstance( DateFormat.LONG, locale); 
        DateFormat full2   = DateFormat.getDateInstance( DateFormat.FULL, locale); 

        System.out.println("\n----getDateInstance ----\n"
                + "(1.0) Empty Param   : " + short0.format(date) +"\n"
                + "(2.1) One Param(s)  : " + short1.format(date) +"\n"
                + "(2.2) One Param(m)  : " + medium1.format(date) +"\n"
                + "(2.3) One Param(l)  : " + long1.format(date) +"\n"
                + "(2.4) One Param(f)  : " + full1.format(date) +"\n"
                + "(3.1) One Param(s,l): " + short2.format(date) +"\n"
                + "(3.2) One Param(m,l): " + medium2.format(date) +"\n"
                + "(3.3) One Param(l,l): " + long2.format(date) +"\n"
                + "(3.4) One Param(f,l): " + full2.format(date) +"\n"
                ); 

    }


    /**
     * 测试DateFormat的format()函数
     */
    public static void testFormat() {
        Date date = new Date(); 
        StringBuffer sb = new StringBuffer();
        FieldPosition field = new FieldPosition(DateFormat.YEAR_FIELD);
        DateFormat format = DateFormat.getDateTimeInstance();

        sb =  format.format(date, sb, field);
        System.out.println("\ntestFormat"); 
        System.out.printf("sb=%s\n", sb);
    }
}

运行结果:

 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
33
34
35
36
37
38
----getTimeInstance ----
(1.0) Empty Param   : 4:54:22 PM
(2.1) One Param(s)  : 4:54 PM
(2.2) One Param(m)  : 4:54:22 PM
(2.3) One Param(l)  : 4:54:22 PM CST
(2.4) One Param(f)  : 4:54:22 PM CST
(3.1) One Param(s,l): 下午4:54
(3.2) One Param(m,l): 16:54:22
(3.3) One Param(l,l): 下午04时54分22秒
(3.4) One Param(f,l): 下午04时54分22秒 CST


----getDateInstance ----
(1.0) Empty Param   : Jan 23, 2014
(2.1) One Param(s)  : 1/23/14
(2.2) One Param(m)  : Jan 23, 2014
(2.3) One Param(l)  : January 23, 2014
(2.4) One Param(f)  : Thursday, January 23, 2014
(3.1) One Param(s,l): 14-1-23
(3.2) One Param(m,l): 2014-1-23
(3.3) One Param(l,l): 2014年1月23日
(3.4) One Param(f,l): 2014年1月23日 星期四


----getDateTimeInstance ----
(1.0) Empty Param   : Jan 23, 2014 4:54:23 PM
(2.1) One Param(s)  : 1/23/14 4:54 PM
(2.2) One Param(m)  : Jan 23, 2014 4:54:23 PM
(2.3) One Param(l)  : January 23, 2014 4:54:23 PM CST
(2.4) One Param(f)  : Thursday, January 23, 2014 4:54:23 PM CST
(3.1) One Param(s,l): 14-1-23 下午4:54
(3.2) One Param(m,l): 2014-1-23 16:54:23
(3.3) One Param(l,l): 2014年1月23日 下午04时54分23秒
(3.4) One Param(f,l): 2014年1月23日 星期四 下午04时54分23秒 CST


testFormat
sb=Jan 23, 2014 4:54:23 PM

SimpleDateFormat 介绍(废弃)

SimpleDateFormat 是一个格式化Date 以及 解析日期字符串 的工具。它的最常用途是,能够按照指定的格式来对Date进行格式化,然后我们使用可以格式化Date后得到的字符串。
更严格的说,SimpleDateFormat 是一个以与语言环境有关的方式来格式化和解析日期的具体类。它允许进行格式化(日期 -> 文本)、解析(文本 -> 日期)和规范化。

SimpleDateFormat的构造函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 构造函数
SimpleDateFormat()
SimpleDateFormat(String pattern)
SimpleDateFormat(String template, DateFormatSymbols value)
SimpleDateFormat(String template, Locale locale)

// 非构造函数
void                             applyLocalizedPattern(String template)
void                             applyPattern(String template)
Object                           clone()
boolean                          equals(Object object)
StringBuffer                     format(Date date, StringBuffer buffer, FieldPosition fieldPos)
AttributedCharacterIterator      formatToCharacterIterator(Object object)
Date                             get2DigitYearStart()
DateFormatSymbols                getDateFormatSymbols()
int                              hashCode()
Date                             parse(String string, ParsePosition position)
void                             set2DigitYearStart(Date date)
void                             setDateFormatSymbols(DateFormatSymbols value)
String                           toLocalizedPattern()
String                           toPattern()

SimpleDateFormat 简单示范:

1
2
3
4
5
6
// 新建date对象,时间是2013-09-19
Date date = new Date(113,8,19); 
// 新建“SimpleDateFormat对象”,并设置 sdf 的“格式化模式”
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 用 sdf 格式化 date,并返回字符串。
String str = sdf.format(date); 

SimpleDateFormat 相关格式说明

日期和时间模式
日期和时间格式由日期和时间模式 字符串指定。在日期和时间模式字符串中,未加引号的字母 ‘A’ 到 ‘Z’ 和 ‘a’ 到 ‘z’ 被解释为模式字母,用来表示日期或时间字符串元素。文本可以使用单引号 (’) 引起来,以免进行解释。
“’’” 表示单引号。所有其他字符均不解释;只是在格式化时将它们简单复制到输出字符串,或者在解析时与输入字符串进行匹配。
定义了以下模式字母(所有其他字符 ‘A’ 到 ‘Z’ 和 ‘a’ 到 ‘z’ 都被保留):

字母 日期或时间元素 表示 示例
G Era 标志符 Text AD
y Year 1996;96
M 年中的月份 Month July; Jul; 07
w 年中的周数 Number 27
W 月份中的周数 Number 2
D 年中的天数 Number 189
d 月份中的天数 Number 10
F 月份中的星期 Number 2
E 星期中的天数 Text Tuesday; Tue
a Am/pm 标记 Text PM
H 一天中的小时数(0-23) Number 0
k 一天中的小时数(1-24) Number 24
K am/pm 中的小时数(0-11) Number 0
h am/pm 中的小时数(1-12) Number 12
m 小时中的分钟数 Number 30
s 分钟中的秒数 Number 55
S 毫秒数 Number 978
z 时区 General time zone Pacific Standard Time; PST; GMT-08:00
Z 时区 RFC 822 time zone -0800

模式字母通常是重复的,其数量确定其精确表示:

Text: 对于格式化来说,如果模式字母的数量大于等于 4,则使用完全形式;否则,在可用的情况下使用短形式或缩写形式。对于解析来说,两种形式都是可接受的,与模式字母的数量无关。
Number: 对于格式化来说,模式字母的数量是最小的数位,如果数位不够,则用 0 填充以达到此数量。对于解析来说,模式字母的数量被忽略,除非必须分开两个相邻字段。
Year: 如果格式器的 Calendar 是格里高利历,则应用以下规则。
Month: 如果模式字母的数量为 3 或大于 3,则将月份解释为 text;否则解释为 number。
对于格式化来说,如果模式字母的数量为 2,则年份截取为 2 位数,否则将年份解释为 number。
对于解析来说,如果模式字母的数量大于 2,则年份照字面意义进行解释,而不管数位是多少。因此使用模式 “MM/dd/yyyy”,将 “01/11/12” 解析为公元 12 年 1 月 11 日。
在解析缩写年份模式(“y” 或 “yy”)时,SimpleDateFormat 必须相对于某个世纪来解释缩写的年份。这通过将日期调整为 SimpleDateFormat 实例创建之前的 80 年和之后 20 年范围内来完成。例如,在 “MM/dd/yy” 模式下,如果 SimpleDateFormat 实例是在 1997 年 1 月 1 日创建的,则字符串 “01/11/12” 将被解释为 2012 年 1 月 11 日,而字符串 “05/04/64” 将被解释为 1964 年 5 月 4 日。在解析时,只有恰好由两位数字组成的字符串(如 Character#isDigit(char) 所定义的)被解析为默认的世纪。其他任何数字字符串将照字面意义进行解释,例如单数字字符串,3 个或更多数字组成的字符串,或者不都是数字的两位数字字符串(例如”-1")。因此,在相同的模式下, “01/02/3” 或 “01/02/003” 解释为公元 3 年 1 月 2 日。同样,“01/02/-3” 解析为公元前 4 年 1 月 2 日。
否则,则应用日历系统特定的形式。对于格式化和解析,如果模式字母的数量为 4 或大于 4,则使用日历特定的 long form。否则,则使用日历特定的 short or abbreviated form。
SimpleDateFormat 还支持本地化日期和时间模式 字符串。在这些字符串中,以上所述的模式字母可以用其他与语言环境有关的模式字母来替换。SimpleDateFormat 不处理除模式字母之外的文本本地化;而由类的客户端来处理。
示例

以下示例显示了如何在美国语言环境中解释日期和时间模式。给定的日期和时间为美国太平洋时区的本地时间 2001-07-04 12:08:56。

日期和时间模式 结果
“yyyy.MM.dd G ‘at’ HH:mm:ss z” 2001.07.04 AD at 12:08:56 PDT
“EEE, MMM d, ‘‘yy” Wed, Jul 4, ‘01
“h:mm a” 12:08 PM
“hh ‘o’‘clock’ a, zzzz” 12 o’clock PM, Pacific Daylight Time
“K:mm a, z” 0:08 PM, PDT
“yyyyy.MMMMM.dd GGG hh:mm aaa” 02001.July.04 AD 12:08 PM
“EEE, d MMM yyyy HH:mm:ss Z” Wed, 4 Jul 2001 12:08:56 -0700
“yyMMddHHmmssZ” 010704120856-0700
“yyyy-MM-dd’T’HH:mm:ss.SSSZ” 2001-07-04T12:08:56.235-0700

日期格式是不同步的。建议为每个线程创建独立的格式实例。如果多个线程同时访问一个格式,则它必须是外部同步的。

SimpleDateFormat 示例

下面,我们通过示例学习如何使用SimpleDateFormat。
源码如下(SimpleDateFormatTest.java):

 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import java.util.Date;
import java.util.Locale;
import java.util.Calendar;
import java.text.DateFormat;
import java.text.SimpleDateFormat;

/**
 * SimpleDateFormat 的API测试程序
 *
 * @author skywang
 * @email kuiwu-wang@163.com
 */
public class SimpleDateFormatTest {
    
    public static void main(String[] args) {

        // 通过SimpleDateFormat 获取日期/时间:有多种格式
        testSimpleDateFormats() ;

        // 通过DateFormat 获取日期/时间
        superTest() ;
    }

    /**
     * 通过SimpleDateFormat 获取日期/时间。有多种格式可以选择
     */
    private static void testSimpleDateFormats() {
        String[] formats = new String[] {
            "HH:mm",                                // 14:22
            "h:mm a",                               // 2:22 下午
            "HH:mm z",                              // 14:22 CST
            "HH:mm Z",                              // 14:22 +0800
            "HH:mm zzzz",                           // 14:22 中国标准时间
            "HH:mm:ss",                             // 14:22:30
            "yyyy-MM-dd",                           // 2013-09-19
            "yyyy-MM-dd HH:mm",                     // 2013-09-19 14:22
            "yyyy-MM-dd HH:mm:ss",                  // 2013-09-19 14:22:30
            "yyyy-MM-dd HH:mm:ss zzzz",             // 2013-09-19 14:22:30 中国标准时间
            "EEEE yyyy-MM-dd HH:mm:ss zzzz",        // 星期四 2013-09-19 14:22:30 中国标准时间
            "yyyy-MM-dd HH:mm:ss.SSSZ",             // 2013-09-19 14:22:30.000+0800
            "yyyy-MM-dd'T'HH:mm:ss.SSSZ",           // 2013-09-19T14:22:30.000+0800
            "yyyy.MM.dd G 'at' HH:mm:ss z",         // 2013.09.19 公元 at 14:22:30 CST
            "K:mm a",                               // 2:22 下午, CST
            "EEE, MMM d, ''yy",                     // 星期四, 九月 19, '13
            "hh 'o''clock' a, zzzz",                // 02 o'clock 下午, 中国标准时间
            "yyyyy.MMMMM.dd GGG hh:mm aaa",         // 02013.九月.19 公元 02:22 下午
            "EEE, d MMM yyyy HH:mm:ss Z",           // 星期四, 19 九月 2013 14:22:30 +0800
            "yyMMddHHmmssZ",                        // 130919142230+0800
            "yyyy-MM-dd'T'HH:mm:ss.SSSZ",           // 2013-09-19T14:22:30.000+0800
            "EEEE 'DATE('yyyy-MM-dd')' 'TIME('HH:mm:ss')' zzzz",        // 星期四 2013-09-19 14:22:30 中国标准时间
        };

        //Date date = (new Date(0));                    // date为1970-01-01 07:00:00
        //Date date = Calendar.getInstance().getTime(); // date为当前时间
        Date date = new Date(113, 8, 19, 14, 22, 30);   // date为2013-09-19 14:22:30
        for (String format : formats) {
            SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.SIMPLIFIED_CHINESE);
            //SimpleDateFormat sdf = new SimpleDateFormat(format);
            System.out.format("%30s    %s\n", format, sdf.format(date));
         }
    }

    /**
     * 通过DateFormat 获取日期/时间
     */
    private static void superTest() {
        // 新建date对象,时间是2013-09-19 14:22:30
        // (01) 年=“‘目标年’ - 1900”,
        // (02) 月。 0是一月,1是二月,依次类推。
        // (03) 日。 1-31之间的数
        Date mDate = new Date(113, 8, 19, 14, 22, 30);
        Locale locale = new Locale("zh", "CN"); 

        // 14:22:30
        String time = DateFormat.getTimeInstance( DateFormat.MEDIUM, Locale.SIMPLIFIED_CHINESE).format(mDate);
        // 2013-09-19
        String date = DateFormat.getDateInstance( DateFormat.MEDIUM, Locale.SIMPLIFIED_CHINESE).format(mDate);
        // 2013-09-19 14:22:30
        String datetime = DateFormat.getDateTimeInstance( DateFormat.MEDIUM, DateFormat.MEDIUM, Locale.SIMPLIFIED_CHINESE).format(mDate);

        System.out.printf("\ntime=%s\ndate=%s\ndatetime=%s\n",time,date,datetime); 
    }
}

运行结果:

 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
HH:mm    14:22
                        h:mm a    2:22 下午
                       HH:mm z    14:22 CST
                       HH:mm Z    14:22 +0800
                    HH:mm zzzz    14:22 中国标准时间
                      HH:mm:ss    14:22:30
                    yyyy-MM-dd    2013-09-19
              yyyy-MM-dd HH:mm    2013-09-19 14:22
           yyyy-MM-dd HH:mm:ss    2013-09-19 14:22:30
      yyyy-MM-dd HH:mm:ss zzzz    2013-09-19 14:22:30 中国标准时间
 EEEE yyyy-MM-dd HH:mm:ss zzzz    星期四 2013-09-19 14:22:30 中国标准时间
      yyyy-MM-dd HH:mm:ss.SSSZ    2013-09-19 14:22:30.000+0800
    yyyy-MM-dd'T'HH:mm:ss.SSSZ    2013-09-19T14:22:30.000+0800
  yyyy.MM.dd G 'at' HH:mm:ss z    2013.09.19 公元 at 14:22:30 CST
                        K:mm a    2:22 下午
              EEE, MMM d, ''yy    星期四, 九月 19, '13
         hh 'o''clock' a, zzzz    02 o'clock 下午, 中国标准时间
  yyyyy.MMMMM.dd GGG hh:mm aaa    02013.九月.19 公元 02:22 下午
    EEE, d MMM yyyy HH:mm:ss Z    星期四, 19 九月 2013 14:22:30 +0800
                 yyMMddHHmmssZ    130919142230+0800
    yyyy-MM-dd'T'HH:mm:ss.SSSZ    2013-09-19T14:22:30.000+0800
EEEE 'DATE('yyyy-MM-dd')' 'TIME('HH:mm:ss')' zzzz    星期四 DATE(2013-09-19) TIME(14:22:30) 中国标准时间

time=14:22:30
date=2013-9-19
datetime=2013-9-19 14:22:30

GMT、UTC、时区的关系

许多人都知道两地时间表简称为GMT或UTC,而世界时区表则通称为World Time,那么GMT与UTC的实质原意又是为何?世界时区又是怎么区分的?面盘上密密麻麻的英文单字代表着什么意义与作用呢?这些都是新手在接触两地时间表或世界时区表时,脑海中所不断浮现的种种疑问,以下将带您一探时区奥妙的究竟。

全球24个时区的划分

相较于两地时间表,可以显示世界各时区时间和地名的世界时区表(World Time),就显得精密与复杂多了,通常世界时区表的表盘上会标示着全球24个时区的城市名称,但究竟这24个时区是如何产生的?过去世界各地原本各自订定当地时间,但随着交通和电讯的发达,各地交流日益频繁,不同的地方时间,造成许多困扰,于是在西元1884年的国际会议上制定了全球性的标准时,明定以英国伦敦格林威治这个地方为零度经线的起点(亦称为本初子午线),并以地球由西向东每24小时自转一周360°,订定每隔经度15°,时差1小时。而每15°的经线则称为该时区的中央经线,将全球划分为24个时区,其中包含23个整时区及180°经线左右两侧的2个半时区。就全球的时间来看,东经的时间比西经要早,也就是如果格林威治时间是中午12时,则中央经线15°E的时区为下午1时,中央经线30°E时区的时间为下午2时;反之,中央经线15°W的时区时间为上午11时,中央经线30°W时区的时间为上午10时。以台湾为例,台湾位于东经121°,换算后与格林威治就有8小时的时差。如果两人同时从格林威治的0°各往东、西方前进,当他们在经线180°时,就会相差24小时,所以经线180°被定为国际换日线,由西向东通过此线时日期要减去一日,反之,若由东向西则要增加一日。

格林威治标准时间GMT

十七世纪,格林威治皇家天文台为了海上霸权的扩张计画而进行天体观测。
1675年旧皇家观测所(Old Royal Observatory) 正式成立,到了1884年决定以通过格林威治的子午线作为划分地球东西两半球的经度零度。观测所门口墙上有一个标志24小时的时钟,显示当下的时间,对全球而言,这里所设定的时间是世界时间参考点,全球都以格林威治的时间作为标准来设定时间,这就是我们耳熟能详的「格林威治标准时间」(Greenwich Mean Time,简称G.M.T.)的由来,标示在手表上,则代表此表具有两地时间功能,也就是同时可以显示原居地和另一个国度的时间。

世界协调时间UTC

多数的两地时间表都以GMT来表示,但也有些两地时间表上看不到GMT字样,出现的反而是UTC这3个英文字母,究竟何谓UTC?事实上,UTC指的是Coordinated Universal Time- 世界协调时间(又称世界标准时间、世界统一时间),是经过平均太阳时(以格林威治时间GMT为准)、地轴运动修正后的新时标以及以「秒」为单位的国际原子时所综合精算而成的时间,计算过程相当严谨精密,因此若以「世界标准时间」的角度来说,UTC比GMT来得更加精准。其误差值必须保持在0.9秒以内,若大于0.9秒则由位于巴黎的国际地球自转事务中央局发布闰秒,使UTC与地球自转周期一致。所以基本上UTC的本质强调的是比GMT更为精确的世界时间标准,不过对于现行表款来说,GMT与UTC的功能与精确度是没有差别的。

夏日节约时间DST

所谓「夏日节约时间」Daylight Saving Time(简称D.S.T.),是指在夏天太阳升起的比较早时,将时钟拨快一小时,以提早日光的使用,在英国则称为夏令时间(Summer Time)。这个构想于1784年由美国班杰明·富兰克林提出来,1915年德国成为第一个正式实施夏令日光节约时间的国家,以削减灯光照明和耗电开支。自此以后,全球以欧洲和北美为主的约70个国家都引用这个做法。目前被划分成两个时区的印度也正在商讨是否全国该统一实行夏令日光节约时间。欧洲手机上也有很多GSM系统的基地台,除了会传送当地时间外也包括夏令日光节约时间,做为手机的时间标准,使用者可以自行决定要开启或关闭。值得注意的是,某些国家有实施「夏日节约时间」的制度,出国时别忘了跟随当地习惯在表上调整一下,这可是机械表没有的功能设计哦!