ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Java 日期类

2021-02-03 13:30:06  阅读:229  来源: 互联网

标签:Java format 创建 日期 时间 Date LocalDate


Java 的 API 提供了很多有用的组件,能帮我们构建复杂的应用。比如日期处理,Java 从 1.0,就提供了 java.util.Date 类用于支持日期和时间的处理,不过由于该 API 设计的缺陷,产生了糟糕的易用性。随着 1.0 退出舞台,Date 类中的很多方法都被废弃了,Java 1.1 使用 java.util.Calendar 类取而代之,很不幸,Calendar 类也有类似的问题和设计缺陷,导致使用这些方法写出的代码非常容易出错。

所有这些缺陷和不一致导致用户们转投第三方的日期和时间库,比如 Joda-Time。为了解决这些问题,Oracle 决定在原生的 Java API 中提供高质量的日期和时间支持。你会看到 Java 8 在 java.time 包中整合了很多 Joda-Time 的特性。

在开始介绍日期类之前,我们先陈述几个经常出现的名次:

UTC

协调世界时,是最主要的世界时间标准,基于原子钟。

在 UTC 中,大约每一年或两年会多出一秒,称为“闰秒”,添加到一天的最后一秒,并且总是在 12 月 31 日或 6 月 30 日。

GMT

格林尼治标准时间,代表时区,标准时间为 UTC+0,是之前的民间标准,根据地球的自转和公转来计算时间,在欧洲国家和非洲国家用做当地标准时间。

GMT 因为是根据地球的转动来计算时间的,而地球的自转目前正在缓速变慢,所以 UTC 比 GMT 更加精准。但是一些计算机的标准是根据格林尼治标准时间(GMT)定义的,所以大多数计算机时钟不够精确。

在民用领域可以认为两者相同。

CST

北美中部时区,代表时区,标准时间为 UTC-6。

这种缩写的时区代表一片区域,但是由于没有明确的标准,CST 概念来说在中国也代表中国标准时间。具体缩写的定义可以查看 ZoneId.SHORT_IDS 定义。

所以 java 提供了明确的时区表示方法,具体可用的时区字符串 Id 可以通过 ZoneId.getAvailableZoneIds() 方法查看。

ISO 8601

代表国际标准的日期和时间表示方法。

日期格式一般为:yyyy-MM-dd

带时区的日期一般格式为:yyyy-MM-dd’T’HH:mm:ss.SSSZ

在 Java 中定义了一些参见的格式化表示方法,在 DateTimeFormatter 有具体的静态格式化类表示。

UNIX 纪元(Epoch)

代表 1970 年 1 月 1 日 0 时 0 分 0 秒 GMT 午夜

Date

Date 类表示特定的时刻,精度为毫秒,该类反映的是协调世界时(UTC),不反映时区。

该类在使用时有几点需要注意:

  • 该时刻表示的当前时间与纪元的差(以毫秒为单位);
  • 年代表与 1900 的差
  • 一个月由 0 到 11 之间的整数表示;0 是一月,1 是二月,以此类推;
  • 日期(一个月中的一天)由 1 到 31 之间的整数表示;
  • 小时用 0 到 23 之间的的整数表示;
  • 分钟用 0 到 59 之间的整数表示;
  • 秒用 0 到 61 之间的整数表示;值 60 和 61 仅在闰秒出现;
  • 使用 SimpleDateFormat 格式化显示,该类非线程安全。
  • 该类是可变类,线程不安全。

使用

创建

创建当前时刻:new Date(),相当于:new Date(System.currentTimeMillis()),使用纪元时间与当前时间毫秒差创建日期。

使用 Java8 Instant 转换:Date.from(Instant instant),内部还是使用纪元时间差,其它日期对象转换思想相同。

日期

Date 实现了 Comparable 接口,可以使用 compareTo 直接比较两个对象;

使用 afterbefore 实例方法;

文本间转换

使用 DateFormat 抽象类静态方法转换简单的 ISO 格式:

该抽象类非线程安全,由于使用了成员变量 calendar 维护内部时间状态,导致 format 等方法依赖状态。

Date date = new Date();
DateFormat dateInstance = DateFormat.getDateInstance();
String format = dateInstance.format(date);
Date parse = dateInstance.parse(format);
System.out.println(parse + " DateInstance format:" + format);
// Sun May 31 00:00:00 CST 2020 DateInstance format:2020-5-31

DateFormat dateTimeInstance = DateFormat.getDateTimeInstance();
String format1 = dateTimeInstance.format(date);
Date parse1 = dateInstance.parse(format1);
System.out.println(parse1 + " DateTimeInstance format:" + format1);
// Sun May 31 00:00:00 CST 2020 DateTimeInstance format:2020-5-31 21:04:03

DateFormat instance = DateFormat.getInstance();
String format2 = instance.format(date);
Date parse2 = instance.parse(format2);
System.out.println(parse2 + " Instance format:" + format2);
// Sun May 31 21:04:00 CST 2020 Instance format:20-5-31 下午9:04

DateFormat timeInstance = DateFormat.getTimeInstance();
String format3 = timeInstance.format(date);
Date parse3 = timeInstance.parse(format3);
System.out.println(parse3 + " TimeInstance format:" + format3);
// Thu Jan 01 21:04:03 CST 1970 TimeInstance format:21:04:03

或使用 DateFormat 实现类 SimpleDateFormat 基于 Pattern 形式转换:

该类非线程安全,原因和 DateFormat 相同。

字符日期或时间元素PresentationExamples
GEra 年代TextAD
yYear1996; 96
YWeek yearYear2009; 09
M一年中的月份 (上下文敏感)MonthJuly; Jul; 07
L一年中的月份 (独立格式)MonthJuly; Jul; 07
w一年中的周Number27
W一月中的周Number2
D一年中的天Number189
d一月中的天Number10
F每月的星期几Number2
EDay name in weekTextTuesday; Tue
u星期几 (1 = 星期一, …, 7 = 星期天)Number1
aAm/pm 标记TextPM
H小时 (0-23)Number0
k小时 (1-24)Number24
K基于上午/下午表示的时间 am/pm (0-11)Number0
h基于上午/下午表示的时间 am/pm (1-12)Number12
m分钟Number30
sNumber55
S毫秒Number978
z时区一般时区Pacific Standard Time; PST; GMT-08:00
Z时区RFC 822 时区-0800
X时区ISO 8601 时区-08; -0800; -08:00
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
String format = simpleDateFormat.format(new Date());
Date parse = simpleDateFormat.parse(format);

Calendar

该类是一个抽象类,和 Date 一样,代表特定的时刻,该类提供了一些操作日历字段的方法,比如获得下一周的日期。

该类使用时需要注意一下几点:

  • 支持指定时区和区域构造日历。最常见的实现是 GregorianCalendar,代表公历;
  • 该时刻表示的当前时间与纪元的差(以毫秒为单位);
  • 年份不再从 1900 年开始,月份依旧从 0 开始计算;
  • 没有专门的文本格式转换,通过 Date 作为中转
  • 该类是可变类,线程不安全。

使用

创建

  • 使用静态工厂方法获取基于系统时区的当前时刻:
Calendar rightNow = Calendar.getInstance();
  • 使用静态内部类构建器构建时间日历:
new Calendar.Builder()
        .setCalendarType("iso8601")
        .setWeekDate(2013, 1, MONDAY)
        .build();
  • DateCalendar
final Calendar calendar = new Calendar.Builder().setInstant(d).build();

比较

该类同样实现了 Comparable 接口,可以使用 compareTo 直接比较两个对象;

使用 afterbefore 实例方法;

修改字段

通过 set(int field, int value) 修改当前日历对象字段,第一个参数为字段类型,定义在类的静态常量中:

ERA, YEAR, MONTH(JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER, UNDECIMBER), WEEK_OF_YEAR, WEEK_OF_MONTH, DATE, DAY_OF_MONTH, DAY_OF_YEAR, DAY_OF_WEEK(SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY), DAY_OF_WEEK_IN_MONTH, AM_PM, HOUR, HOUR_OF_DAY, MINUTE, SECOND, MILLISECOND, ZONE_OFFSET, DST_OFFSET

Calendar rightNow = Calendar.getInstance();
rightNow.set(Calendar.MONTH, Calendar.JANUARY);

或者 set(int year, int month, int date, int hourOfDay, int minute)set(int year, int month, int date) 等。

增加或降低字段值:add(int field, int amount)

滚动增加或减小某个单位值,不影响前一个字段值:roll(int field, boolean up)

// create a calendar
Calendar cal = Calendar.getInstance();

// displays the current calendar
System.out.println("Month is " + cal.get(Calendar.MONTH));
// Month is 4

// roll month
cal.roll(Calendar.MONTH, true);
System.out.println("Month is " + cal.get(Calendar.MONTH));
// Month is 5

// roll downwards
cal.roll(Calendar.MONTH, false);
System.out.println("Month is " + cal.get(Calendar.MONTH));
// Month is 4

new package java.time

该包中定义的类代表基本的日期时间概念,包括瞬间,持续时间,日期,时间,时区和时间段。它们基于 ISO 日历系统(公历)。所有的类都是不可变的并且是线程安全的

每个日期实例都是由方便获取的字段 API 组成。对于较低级别的字段访问,可以参考 java.time.temporal 包。每个类都支持打印和解析为各种日期和时间,具体参阅 java.time.format 包以获取自定义选项。

java.time.chrono 包包含日历中立 API ChronoLocalDateChronoLocalDateTimeChronoZonedDateTimeEra,这些类可以用来构建地区化的日历系统,比如农历等,系统内置提供了 4 中日历系统,分别是:

  • ThaiBuddhistDate:泰国佛教历
  • MinguoDate:中华民国历
  • JapaneseDate:日本历
  • HijrahDate:伊斯兰历

package java.time.temporal

我们可以使用字段、单位和日期时间调节器来访问日期和时间。

该程序包在基本程序包上扩展,以提供更多功能来满足更强大的用例。支持包括:

  • 日期时间单位,例如年,月,日和小时
  • 日期时间字段,例如一年中的某月,一周中的某日或一天中的某小时
  • 日期时间调整功能
  • 周的不同定义

字段和单位

日期和时间以字段和单位表示。单位用于测量时间量,例如年,天或分钟。所有单位都实现 TemporalUnit。众所周知的单位集在 ChronoUnit 中定义,例如 DAYS。该单位中也定义了一些常用的单位操作 API,例如:

字段用于表示较大日期时间的一部分,例如年,一年中的月或一分钟中的秒。所有字段都实现 TemporalField。在 ChronoField 中定义了一组众所周知的字段,例如 HOUR_OF_DAY。其他字段由 JulianFieldsWeekFieldsIsoFields 定义。该字段也有一些常用的 API,例如:

字段的一种用法是从时间对象中获取没有便利方法的字段。例如,获取月中的某天很常见,以至于 LocalDate 上有一个名为 getDayOfMonth() 的方法。但是,对于更特殊的字段,必须使用该字段。例如,获取月中的周数,date.get(ChronoField.ALIGNED_WEEK_OF_MONTH)。

Temporal 作为支持字段的日期时间类型的抽象。其方法支持获取字段的值、创建修改字段值后的新日期时间以及查询其他信息(通常用于提取偏移量或时区)。

调整和查询

日期时间问题中的关键部分是将日期调整为新的相关值,例如“每月的最后一天”或“下一个星期三”。这些被建模为调整基准日期时间的函数。该函数实现 TemporalAdjuster 并在 Temporal 上运行。TemporalAdjusters 中提供了一组常用功能。例如,要查找给定日期后一周中某天的第一次出现,请使用 TemporalAdjusters.next(DayOfWeek),例如 date.with(next(MONDAY))。

TemporalAdjuster 函数接口有一些常见的静态实现:

方法名描述
dayOfWeekInMonth创建一个新的日期,它的值为同一个月中每一周的第几天
firstDayOfMonth创建一个新的日期,它的值为当月的第一天
firstDayOfNextMonth创建一个新的日期,它的值为下月的第一天
firstDayOfNextYear创建一个新的日期,它的值为明年的第一天
firstDayOfYear创建一个新的日期,它的值为当年的第一天
firstInMonth创建一个新的日期,它的值为同一个月中,第一个符合星期几要求的值
lastDayOfMonth创建一个新的日期,它的值为当月的最后一天
lastDayOfNextMonth创建一个新的日期,它的值为下月的最后一天
lastDayOfNextYear创建一个新的日期,它的值为明年的最后一天
lastDayOfYear创建一个新的日期,它的值为今年的最后一天
lastInMonth创建一个新的日期,它的值为同一个月中,最后一个符合星期几要求的值
next/previous创建一个新的日期,并将其值设定为日期调整后或者调整前,第一个符合指定星期几要求的日期
nextOrSame/previousOrSame创建一个新的日期,并将其值设定为日期调整后或者调整前,第一个符合指定星期几要求的日期,如果该日期已经符合要求,直接返回该对象

TemporalAmount 接口模拟相对时间量,比如常用的实现类 DurationPeriod

除了调整日期时间外,还提供了一个接口以启用通过 TemporalQuery 进行查询。查询接口的最常见实现是方法引用。可以使用主要时间对象类上的 from(TemporalAccessor) 方法,例如 LocalDate::fromMonth::from。在 TemporalQueries 中作为静态方法提供了进一步的实现。

final ZonedDateTime now = ZonedDateTime.now();

// Now: 2020-06-11T00:00:39.096326+08:00[Asia/Shanghai]
System.out.format("Now: %s%n", now);

final LocalDate localDate = now.query(TemporalQueries.localDate());
final LocalDate toLocalDate = now.toLocalDate();
final LocalDate fromLocalDate = now.query(LocalDate::from);

// true
System.out.println(localDate.isEqual(toLocalDate) && localDate.isEqual(fromLocalDate));

final TemporalUnit unit = now.query(TemporalQueries.precision());
final Chronology chronology = now.query(TemporalQueries.chronology());
final ZoneId zone = now.query(TemporalQueries.zoneId());
final ZoneOffset offset = now.query(TemporalQueries.offset());

// TemporalUnit: Nanos, Chronology: ISO, ZoneId: Asia/Shanghai, ZoneOffset: +08:00
System.out.format("TemporalUnit: %s, Chronology: %s, ZoneId: %s, ZoneOffset: %s %n", unit, chronology, zone, offset);

final Month month = now.query(Month::from);
final MonthDay monthDay = now.query(MonthDay::from);
final YearMonth yearMonth = now.query(YearMonth::from);
final DayOfWeek dayOfWeek = now.query(DayOfWeek::from);
final ZoneId zoneId = now.query(ZoneId::from);
final ZoneOffset zoneOffset = now.query(ZoneOffset::from);

// Month: JUNE, MonthDay: --06-11, YearMonth: 2020-06, DayOfWeek: THURSDAY, zoneId: Asia/Shanghai, zoneOffset: +08:00
System.out.format("Month: %s, MonthDay: %s, YearMonth: %s, DayOfWeek: %s, zoneId: %s, zoneOffset: %s %n", month, monthDay, yearMonth, dayOfWeek, zoneId, zoneOffset);

不同的语言环境对星期有不同的定义。例如,在欧洲,一周通常从星期一开始,而在美国,则从星期日开始。 WeekFields 类为这种区别建模。IOS 定义星期一为一周开始,一周至少需要四天。

ISO 日历系统定义了一个额外的基于周的年划分。这定义了基于从一年整个星期一到星期一的一年,它是在 IsoFields 中建模的。

日期和时间

日期和时间类的接口都提供了一致的设计,所以再很多 API 上都做了通用的处理,比如一致的方法前缀:

  • of - 静态工厂方法
  • parse - 侧重于解析的静态工厂方法
  • get - 获取值或其它
  • is - 检查是否为真
  • with - 等同于 setter 的不可变修改,返回新的副本
  • plus - 增加一定数量的单位
  • minus - 减少一定数量的单位
  • to - 将一个对象转换为另一个对象
  • at - 将一个对象结合到该对象中,例如 date.atTime(time)

这里主要以 Insant 展开介绍。

Instant

Instant 实质上是一个数字时间戳。可以从时钟(Clock)中获取当前的瞬时信息。这对于时间点的记录和持久化很有用,存储的结果与 System.currentTimeMillis()` 相关联。

创建

Date 获取:new Date().toInstant()

使用静态工厂方法:

  • 从系统时钟(Clock)构建:Instant.now()Instant.now(Clock clock)
  • 根据纪元差毫秒构建构建:Instant.ofEpochMilli(long epochMilli)
  • 使用标准 ISO 格式字符串构建:Instant.parse(CharSequence text),格式例如:2007-12-03T10:15:30.00Z
  • TemporalAccessor 实现类构建,该类可以为 Instant 或支持 INSTANT_SECONDSNANO_OF_SECOND 字段的实现类,比如:ZonedDateTime

比较

使用前需明确该对象支持的单位或字段类型,API 文档中有说明支持的类型;

或者使用:isSupported(TemporalUnit unit) 和 isSupported(TemporalUnit unit) 判断

获取

获取时间的字段值前同样得确认是否支持,这些方法通常以 get* 单位的形式存在,或者直接通过:

获取字段的有效值范围:range(TemporalField field)

调整

所有的修改方法都返回新的副本:

LocalDate

LocalDate 存储没有时间的日期,也不附带时区信息。它会存储 yyyy-MM-dd 格式的日期,可以用来存储生日。

LocalTime

LocalTime 存储没有日期的时间。它将存储类似于 HH:mm:ss 格式的时间。

LocalDateTime

LocalDateTime 存储日期和时间。它将存储类似于 yyyy-MM-dd HH:mm:ss 格式的时间。

ZonedDateTime

ZonedDateTime 存储带有时区的日期和时间。如果要考虑到 ZoneId(例如“欧洲/巴黎”)对日期和时间进行准确的计算,这将很有用。如果可能,建议使用没有时区的简单类。时区的广泛使用往往会增加应用程序的复杂性。

该类存储类似于 yyyy-MM-dd HH:mm:ss.zzz 格式的时间。可通过上面所述时间类通过:atZone(ZoneId zone) 添加时区而来。

下图对 ZoneDateTime 的组成部分进行了说明。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z9iwQD2o-1612329693139)(…/…/images/java/zoneDateTime.jpg)]

持续时间和周期

Duration

持续时间是沿时间线以纳秒为单位的时间的简单度量。

此类以秒和纳秒为单位对时间量进行建模。也可以使用其他基于持续时间的单位(例如分钟和小时)来访问它。

创建

目前为止, 我们看到的所有类都实现了 Temporal 接口, Temporal 接口定义了如何读取和操纵为时间建模的对象的值。

Duration 类的静态工厂方法 between 就可以为两个 Temporal 对象创建持续时间。

Duration d1 = Duration.between(time1, time2); 
Duration d1 = Duration.between(dateTime1, dateTime2); 
Duration d2 = Duration.between(instant1, instant2);

也可以使用静态工厂从其它时间单位创建:

Duration threeMinutes = Duration.ofMinutes(3); 
Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES);  

Period

周期以对人类有意义的单位表示时间量。例如年、月或天。

创建

使用静态工厂方法创建:

Period tenDays = Period.between(LocalDate.of(2014, 3, 8), LocalDate.of(2014, 3, 18));

Period tenDays = Period.ofDays(10); 
Period threeWeeks = Period.ofWeeks(3); 
Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);

Duration 类和 Period 类共享了很多相似的方法,参见下表所示。

方 法 名是否是静态方法方法描述
between创建两个时间点之间的 interval
from由一个临时时间点创建 interval
of由它的组成部分创建 interval 的实例
parse由字符串创建 interval 的实例
addTo创建该 interval 的副本,并将其?加到某个指定的 temporal 对象
get读取该 interval 的状态
isNegative检查该 interval 是否为负值,不包含零
isZero检查该 interval 的时长是否为零
minus通过减去一定的时间创建该 interval 的副本
multipliedBy将 interval 的值乘以某个标量创建该 interval 的副本
negated以忽略某个时长的方式创建该 interval 的副本
plus以增加某个指定的时长的方式创建该 interval 的副本
subtractFrom从指定的 temporal 对象中减去该 interval

打印输出及解析日期

处理日期和时间对象时,格式化以及解析日期-时间对象是另一个非常重要的功能。新的 java.time.format 包就是特别为这个目的而设计的。

这个包中,最重要的类是 DateTimeFormatter。创建格式器最简单的方法是通过它的静态工厂方法以及常量。

它包含了 IOS 格式定义的常见格式,像 BASIC_ISO_DATEISO_LOCAL_DATE 这样的常量是 DateTimeFormatter 类的预定义实例。

所有的 DateTimeFormatter 实例都能用于以一定的格式创建代表特定日期或时间的字符串。比如,下面的这个例子中,我们使用了两个不同的格式器生成了字符串:

LocalDate date = LocalDate.of(2014, 3, 18);  
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE);	<-- 20140318
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);  <-- 2014-03-18

你也可以通过解析代表日期或时间的字符串重新创建该日期对象。所有的日期和时间API 都提供了表示时间点或者时间段的工厂方法,你可以使用工厂方法parse达到重创该日期对象的目的:

LocalDate date1 = LocalDate.parse("20140318", DateTimeFormatter.BASIC_ISO_DATE); 
LocalDate date2 = LocalDate.parse("2014-03-18", DateTimeFormatter.ISO_LOCAL_DATE);

和老的 java.util.DateFormat 相比较,所有的 DateTimeFormatter 实例都是线程安全的。所以,我们能够以单例模式创建格式器实例,就像 DateTimeFormatter 所定义的那些常量,并能在多个线程间共享这些实例。DateTimeFormatter 类还支持一个静态工厂方法,它可以按照某个特定的模式创建格式器,代码清单如下。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate date1 = LocalDate.of(2014, 3, 18); 
String formattedDate = date1.format(formatter); 
LocalDate date2 = LocalDate.parse(formattedDate, formatter);

标签:Java,format,创建,日期,时间,Date,LocalDate
来源: https://blog.csdn.net/github_39602765/article/details/113599331

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有