十九、日期和时间

日期和时间

本文为书籍《Java编程的逻辑》1和《剑指Java:核心原理与应用实践》2阅读笔记

5.1 Date类

DateJDK 1.0java.util包下提供类,Date表示时刻,内部主要是一个long类型的值,表示特定的瞬间,可以精确到毫秒,如下所示:

private transient long fastTime;

Date有两个构造方法:

public Date(long date) {fastTime = date;
}
public Date() {this(System.currentTimeMillis());
}

第一个构造方法是根据传入的毫秒数进行初始化;第二个构造方法是默认构造方法,它根据System.currentTimeMillis()的返回值进行初始化。System.currentTimeMillis()是一个常用的方法,它返回当前时刻距离纪元时的毫秒数。Date中的大部分方法都已经过时了,其中没有过时的主要方法有下面这些:

public long getTime() // 返回毫秒数
public boolean equals(Object obj) // 主要就是比较内部的毫秒数是否相同
public int compareTo(Date anotherDate) // 与其他Date进行比较,如果当前Date的毫秒数小于参数中的返回-1,相同返回0,否则返回1
public boolean before(Date when) // 判断是否在给定日期之前
public boolean after(Date when) // 判断是否在给定日期之后
public int hashCode() // 哈希值算法与Long类似

5.2 TimeZone

TimeZone表示时区,它是一个抽象类,有静态方法用于获取其实例。获取当前的默认时区,代码为:

    @Testpublic void testTimeZone(){assertTrue("Asia/Shanghai".equals( TimeZone.getDefault().getID()));}

获取默认时区,并输出其ID,每个人的电脑可能根据实际情况不同,并不一定都是Asia/Shanghai

默认时区是在哪里设置的呢?可以更改吗?

更改时区可以使用TimeZone.setDefaultJVM参数指定。

使用TimeZone.setDefault设置默认时区代码如下:

    @Testpublic void testTimeZoneSetDefault(){TimeZone.setDefault(TimeZone.getTimeZone("GMT+08:00"));assertTrue("GMT+08:00".equals( TimeZone.getDefault().getID()));}

使用JVM参数修改如下所示:

java -Duser.timezone=Asia/Shanghai

TimeZone也有静态方法,可以获得任意给定时区的实例。比如,获取美国东部时区:

TimeZone tz = TimeZone.getTimeZone("US/Eastern");

ID除了可以是名称外,还可以是GMT形式表示的时区,如:

TimeZone tz = TimeZone.getTimeZone("GMT+08:00");

5.3 Locale

Locale表示国家(或地区)和语言,它有两个主要参数:一个是国家(或地区);另一个是语言,每个参数都有一个代码,不过国家(或地区)并不是必需的。比如,中国内地的代码是CN,中国台湾地区的代码是TW,美国的代码是US,中文语言的代码是zh,英文语言的代码是enLocale类中定义了一些静态变量,表示常见的Locale,比如:

  • Locale.US:表示美国英语。
  • Locale.ENGLISH:表示所有英语。
  • Locale.TAIWAN:表示中国台湾地区所用的中文。
  • Locale.CHINESE:表示所有中文。
  • Locale.SIMPLIFIED_CHINESE:表示中国内地所用的中文。

TimeZone类似,Locale也有静态方法获取默认值,如:

    @Testpublic void testLocale(){Locale locale = Locale.getDefault();assertTrue("zh_CN".equals(locale.toString()));}

5.4 Calendar

java.util.Calendar类是日期和时间操作中的主要类,它表示与TimeZoneLocale相关的日历信息,可以进行各种相关的运算。我们先来看下它的内部组成,与Date类似,Calendar内部也有一个表示时刻的毫秒数,定义为:

protected long time;

除此之外,Calendar内部还有一个数组,表示日历中各个字段的值,定义为:

protected int fields[];

这个数组的长度为17,保存一个日期中各个字段的值,都有哪些字段呢?Calendar类中定义了一些静态变量,表示这些字段,主要有:

  1. Calendar.YEAR:表示年。
  2. Calendar.MONTH:表示月, 1 1 1月是 0 0 0Calendar同样定义了表示各个月份的静态变量,如Calendar.JULY表示 7 7 7月。
  3. Calendar.DAY_OF_MONTH:表示日,每月的第一天是 1 1 1
  4. Calendar.HOUR_OF_DAY:表示小时,为 0 ~ 23 0~23 023
  5. Calendar.MINUTE:表示分钟,为 0 ~ 59 0~59 059
  6. Calendar.SECOND:表示秒,为 0 ~ 59 0~59 059
  7. Calendar.MILLISECOND:表示毫秒,为 0 ~ 999 0~999 0999
  8. Calendar.DAY_OF_WEEK:表示星期几,周日是 1 1 1,周一是 2 2 2,周六是 7 7 7Calenar同样定义了表示各个星期的静态变量,如Calendar.SUNDAY表示周日。

Calendar是抽象类,不能直接创建对象,它提供了多个静态方法,可以获取Calendar实例,比如:

public static Calendar getInstance()
{Locale aLocale = Locale.getDefault(Locale.Category.FORMAT);return createCalendar(defaultTimeZone(aLocale), aLocale);
}public static Calendar getInstance(TimeZone zone,Locale aLocale)
{return createCalendar(zone, aLocale);
}

最终调用的方法都是需要TimeZoneLocale的,如果没有,则会使用上面介绍的默认值。getInstance方法会根据TimeZoneLocale创建对应的Calendar子类对象,在中文系统中,子类一般是表示公历的GregorianCalendargetInstance方法封装了Calendar对象创建的细节。TimeZoneLocale不同,具体的子类可能不同,但都是Calendar

来看代码,输出当前时间的各种信息:

    @Testpublic void testCalendar() {Calendar calendar = Calendar.getInstance();calendar.setTimeInMillis(1709045743587L);assertTrue("year: 2024 month: 1 day: 27 hour: 22 minute: 55 second: 43 millisecond: 587 day_of_week: 3".equals("year: " + calendar.get(Calendar.YEAR) + " month: " + calendar.get(Calendar.MONTH) + " day: "+ calendar.get(Calendar.DAY_OF_MONTH) + " hour: " + calendar.get(Calendar.HOUR_OF_DAY)+ " minute: "+ calendar.get(Calendar.MINUTE) + " second: " + calendar.get(Calendar.SECOND) + " millisecond: "+ calendar.get(Calendar.MILLISECOND) + " day_of_week: " + calendar.get(Calendar.DAY_OF_WEEK)));}

Calendar内部,会将表示时刻的毫秒数,按照TimeZoneLocale对应的年历,计算各个日历字段的值,存放在fields数组中,Calendar.get方法获取的就是fields数组中对应字段的值。调用函数:setTimeInMillis(long millis)setTime(Date date)Calendar支持根据Date或毫秒数设置时间,也支持根据年月日等日历字段设置时间,比如:

public final void set(int year, int month, int date)
public final void set(int year, int month, int date, int hourOfDay, int minute, int second)
public void set(int field, int value)

除了直接设置,Calendar支持根据字段增加和减少时间:

public void add(int field, int amount)

amount为正数表示增加,负数表示减少。比如,如果想设置Calendar为第二天的下午 2 2 2 15 15 15,代码可以为:

Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, 1);
calendar.set(Calendar.HOUR_OF_DAY, 14);
calendar.set(Calendar.MINUTE, 15);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);

Calendar的这些方法中一个比较方便和强大的地方在于,它能够自动调整相关的字段。比如,我们知道 2 2 2月最多有 29 29 29天,如果当前时间为 1 1 1 30 30 30号,对Calendar.MONTH字段加 1 1 1,即增加一月,Calendar不是简单的只对月字段加 1 1 1,那样日期是 2 2 2 30 30 30号,是无效的,Calendar会自动调整为 2 2 2月最后一天,即 2 2 2 28 28 28日或 29 29 29日。再如,设置的值可以超出其字段最大范围,Calendar会自动更新其他字段,如:

Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.HOUR_OF_DAY, 48);
calendar.add(Calendar.MINUTE, -120);

相当于增加了 46 46 46​小时。

内部,根据字段设置或修改时间时,Calendar会更新fields数组对应字段的值,但一般不会立即更新其他相关字段或内部的毫秒数的值,不过在获取时间或字段值的时候, Calendar会重新计算并更新相关字段。

简单总结下,Calenar做了一项非常烦琐的工作,根据TimeZoneLocale,在绝对时间毫秒数和日历字段之间自动进行转换,且对不同日历字段的修改进行自动同步更新。除了add方法,Calendar还有一个类似的方法:

calendar.roll(Calendar.MINUTE, 3);

add方法的区别是,roll方法不影响时间范围更大的字段值。比如:

Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, 13);
calendar.set(Calendar.MINUTE, 59);
calendar.add(Calendar.MINUTE, 3);

calendar首先设置为13:59,然后分钟字段加 3 3 3,执行后的calendar时间为14:02。如果add改为roll,即:

calendar.roll(Calendar.MINUTE, 3);

则执行后的calendar时间会变为13:02,在分钟字段上执行roll方法不会改变小时的值。Calendar可以方便地转换为Date或毫秒数,方法是:

public final Date getTime()
public long getTimeInMillis()

Date类似,Calendar之间也可以进行比较,也实现了Comparable接口,相关方法有:

public boolean equals(Object obj)
public int compareTo(Calendar anotherCalendar)
public boolean after(Object when)
public boolean before(Object when)

5.5 DateFormat

DateFormat类主要在Date和字符串表示之间进行相互转换,它有两个主要的方法:

public final String format(Date date)
public Date parse(String source)

formatDate转换为字符串,parse将字符串转换为DateDate的字符串表示与TimeZoneLocale都是相关的,除此之外,还与两个格式化风格有关,一个是日期的格式化风格,另一个是时间的格式化风格。DateFormat定义了4个静态变量,表示 4 4 4种风格:SHORTMEDIUMLONGFULL;还定义了一个静态变量DEFAULT,表示默认风格,值为MEDIUM,不同风格输出的信息详细程度不同。

Calendar类似,DateFormat也是抽象类,也用工厂方法创建对象,提供了多个静态方法创建DateFormat对象,有三类方法:

public static final DateFormat getDateInstance();
public static final DateFormat getDateTimeInstance();
public static final DateFormat getTimeInstance();

getDateTimeInstance方法既处理日期也处理时间,getDateInstance方法只处理日期,getTimeInstance方法只处理时间。看下面的代码:

    @Testpublic void testDateFormat() {Calendar calendar = Calendar.getInstance();// 2016-08-15 14:15:20calendar.set(2016, 07, 15, 14, 15, 20);assertTrue("2016年8月15日 14:15:20".equals(DateFormat.getDateTimeInstance().format(calendar.getTime())));assertTrue("2016年8月15日".equals(DateFormat.getDateInstance().format(calendar.getTime())));assertTrue("14:15:20".equals(DateFormat.getTimeInstance().format(calendar.getTime())));}

每类工厂方法都有两个重载的方法,接受日期和时间风格以及Locale作为参数:

public static final DateFormat getDateTimeInstance(int dateStyle, int timeStyle);
public static final DateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale aLocale)

比如,看下面的代码:

    @Testpublic void testDataFormatStyleLocale() {Calendar calendar = Calendar.getInstance();// 2016-08-15 14:15:20calendar.set(2016, 07, 15, 14, 15, 20);assertTrue("2016年8月15日 CST 14:15:20".equals(DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, Locale.CHINESE).format(calendar.getTime())));}

DateFormat的工厂方法里,我们没看到TimeZone参数,不过,DateFormat提供了一个setter方法,可以设置TimeZone

    public void setTimeZone(TimeZone zone){calendar.setTimeZone(zone);}

DateFormat虽然比较方便,但如果我们要对字符串格式有更精确的控制,则应该使用SimpleDateFormat这个类。

5.6 SimpleDateFormat

SimpleDateFormatDateFormat的子类,相比DateFormat,它的一个主要不同是,它可以接受一个自定义的模式(pattern)作为参数,这个模式规定了Date的字符串形式。

    @Testpublic void testSimpleDateFormatFormat() {Calendar calendar = Calendar.getInstance();// 2016-08-15 14:15:20calendar.set(2016, 07, 15, 14, 15, 20);SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 E HH时mm分ss秒");assertTrue("2016年08月15日 周一 14时15分20秒".equals(sdf.format(calendar.getTime())));}

SimpleDateFormat有个构造方法,可以接受一个pattern作为参数,上面例子pattern是:yyyy年MM月dd日 E HH时mm分ss秒

pattern中含义如下表格所示:

LetterDate or Time ComponentPresentationExamples
GEra designatorTextAD
yYearYear1996; 96
YWeek yearYear2009; 09
MMonth in year (context sensitive)MonthJuly; Jul; 07
LMonth in year (standalone form)MonthJuly; Jul; 07
wWeek in yearNumber27
WWeek in monthNumber2
DDay in yearNumber189
dDay in monthNumber10
FDay of week in monthNumber2
EDay name in weekTextTuesday; Tue
uDay number of week (1 = Monday, …, 7 = Sunday)Number1
aAm/pm markerTextPM
HHour in day (0-23)Number0
kHour in day (1-24)Number24
KHour in am/pm (0-11)Number0
hHour in am/pm (1-12)Number12
mMinute in hourNumber30
sSecond in minuteNumber55
SMillisecondNumber978
zTime zoneGeneral time zonePacific Standard Time; PST; GMT-08:00
ZTime zoneRFC 822 time zone-0800
XTime zoneISO 8601 time zone-08; -0800; -08:00

常见例子如下所示:

Date and Time PatternResult
"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
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX"2001-07-04T12:08:56.235-07:00
"YYYY-'W'ww-u"2001-W27-3

除了将Date转换为字符串,SimpleDateFormat也可以方便地将字符串转化为Date,如下所示代码:

    @Testpublic void testSimpleDateFormatParse() throws ParseException {String str = "2016-08-15 14:15:20.456";SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");try {Date date = sdf.parse(str);SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy年M月d h:m:s.S a");System.out.println(sdf2.format(date));assertTrue("2016年8月15 2:15:20.456 下午".equals(sdf2.format(date)));} catch (ParseException e) {throw e;}}

代码将字符串解析为了一个Date对象,然后使用另外一个格式进行了输出,这里SSS表示三位的毫秒数。需要注意的是,parse会抛出一个受检异常,异常类型为ParseException,调用者必须进行处理。

5.7 局限性

Date表示时刻,与年月日无关,Calendar表示日历,与时区和Locale相关,可进行各种运算,是日期时间操作的主要类,DateFormat/SimpleDateFormatDate和字符串之间进行相互转换。这些API存在着一些局限性,如下:

  1. 可变性:像日期和时间这样的类应该是不可变的,某一个日期时间对象都只能代表某一个特定的瞬间。
  2. 偏移性:Date类中的年份是从 1900 1900 1900开始的,月份都是从 0 0 0开始的,这不符合常规编程习惯。
  3. 格式化:用于日期格式化及解析的SimpleDateFormat只对Date类有用,Calendar类则不行。
  4. 线程不安全性。
  5. 不能处理闰秒:由于地球自转的不均匀性和长期变慢性(主要由潮汐摩擦引起的),所以在世界时(民用时)和原子时之间相差超过到 ± 0.9 ±0.9 ±0.9秒时,人们就把协调世界时向前拨 1 1 1秒(负闰秒,最后一分钟为 59 59 59秒)或向后拨 1 1 1秒(正闰秒,最后一分钟为 61 61 61秒)。目前,全球已经进行了 27 27 27次闰秒,均为正闰秒。最近一次闰秒是北京时间 2017 2017 2017 1 1 1 1 1 1 7 7 7 59 59 59 59 59 59秒。

5.8 Java 8的日期和时间API

Java 8中引入的java.time纠正了过去的缺陷,引入的类主要有:

  1. Instant:表示时刻,不直接对应年月日信息,需要通过时区转换;
  2. LocalDateTime:表示与时区无关的日期和时间,不直接对应时刻,需要通过时区转换;
  3. ZoneId/ZoneOffset:表示时区;
  4. LocalDate:表示与时区无关的日期,与LocalDateTime相比,只有日期,没有时间信息;
  5. LocalTime:表示与时区无关的时间,与LocalDateTime相比,只有时间,没有日期信息;
  6. ZonedDateTime:表示特定时区的日期和时间;
  7. Duration:持续时间;

5.9 Instant

Instant表示时刻,获取当前时刻,代码为:

Instant now = Instant.now();

可以根据Epoch Time(纪元时)创建Instant。比如,另一种获取当前时刻的代码可以为:

Instant now = Instant.ofEpochMilli(System.currentTimeMillis());

我们知道,Date也表示时刻,InstantDate可以通过纪元时相互转换,代码为:

    @Testpublic void testInstantDate(){Instant instant = Instant.ofEpochMilli(1709045743587L);// Instant 转为 DateDate d = Date.from(instant);assertTrue("Tue Feb 27 22:55:43 CST 2024".equals(d.toString()));// Date 转为 InstantInstant i = d.toInstant();assertTrue("2024-02-27T14:55:43.587Z".equals(i.toString()));}

5.10 LocalDateTime

LocalDateTime表示与时区无关的日期和时间,获取系统默认时区的当前日期和时间,代码为:

LocalDateTime ldt = LocalDateTime.now();

还可以直接用年月日等信息构建LocalDateTime。比如,表示 2017 2017 2017 7 7 7 11 11 11 20 20 20 45 45 45 5 5 5秒,代码可以为:

LocalDateTime ldt = LocalDateTime.of(2017, 7, 11, 20, 45, 5);

LocalDateTime有很多方法,可以获取年月日时分秒等日历信息,比如:

public int getYear()
public int getMonthValue()
public int getDayOfMonth()
public int getHour()
public int getMinute()
public int getSecond()
public DayOfWeek getDayOfWeek()

5.11 LocalDate/LocalTime

可以认为LocalDateTime由两部分组成,一部分是日期LocalDate,另一部分是时间LocalTime。它们的用法也很直观,比如:

//表示2017年7月11日
LocalDate ld = LocalDate.of(2017, 7, 11);
//当前时刻按系统默认时区解读的日期
LocalDate now = LocalDate.now();
//表示21点10分34秒
LocalTime lt = LocalTime.of(21, 10, 34);
//当前时刻按系统默认时区解读的时间
LocalTime time = LocalTime.now();

LocalDateTimeLocalDateLocalTime构成,LocalDate加上时间可以构成LocalDateTimeLocalTime加上日期可以构成LocalDateTime,比如:

LocalDateTime ldt = LocalDateTime.of(2017, 7, 11, 20, 45, 5);
LocalDate ld = ldt.toLocalDate(); //2017-07-11
LocalTime lt = ldt.toLocalTime(); // 20:45:05
//LocalDate加上时间,结果为2017-07-11 21:18:39
LocalDateTime ldt2 = ld.atTime(21, 18, 39);
//LocalTime加上日期,结果为2016-03-24 20:45:05
LocalDateTime ldt3 = lt.atDate(LocalDate.of(2016, 3, 24));

5.12 ZoneId/ZoneOffset

LocalDateTime不能直接转为时刻Instant,转换需要一个参数ZoneOffsetZoneOffset表示相对于格林尼治的时区差,北京是 + 08 : 00 +08:00 +08:00​。比如,转换一个LocalDateTime为北京的时刻,方法为:

public static Instant toBeijingInstant(LocalDateTime ldt) {return ldt.toInstant(ZoneOffset.of("+08:00"));
}

给定一个时刻,使用不同时区解读,日历信息是不同的,Instant有方法根据时区返回一个ZonedDateTime

public ZonedDateTime atZone(ZoneId zone)

默认时区是ZoneId.systemDefault(),可以这样构建ZoneId

// 北京时区
ZoneId bjZone = ZoneId.of("GMT+08:00")

ZoneOffsetZoneId的子类,可以根据时区差构造。

5.13 ZonedDateTime

ZonedDateTime表示特定时区的日期和时间,获取系统默认时区的当前日期和时间,代码为:

ZonedDateTime zdt = ZonedDateTime.now();

LocalDateTime.now也是获取默认时区的当前日期和时间,有什么区别呢?LocalDateTime内部不会记录时区信息,只会单纯记录年月日时分秒等信息,而ZonedDateTime除了记录日历信息,还会记录时区,它的其他大部分构建方法都需要显式传递时区,比如:

// 根据 Instant 和时区构建 ZonedDateTime
public static ZonedDateTime ofInstant(Instant instant, ZoneId zone)
// 根据 LocalDate、LocalTime 和 ZoneId 构造
public static ZonedDateTime of(LocalDate date, LocalTime time, ZoneId zone)

ZonedDateTime可以直接转换为Instant,比如:

ZonedDateTime ldt = ZonedDateTime.now();
Instant now = ldt.toInstant();

5.14 格式化

Java 8中,主要的格式化类是java.time.format.DateTimeFormatter,它是线程安全的,看个例子:[插图]输出为:[插图]将字符串转化为日期和时间对象,可以使用对应类的parse方法,比如:

    @Testpublic void testDateTimeFormatterFormat(){DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");LocalDateTime ldt = LocalDateTime.of(2016,8,18,14,20,45);assertTrue("2016-08-18 14:20:45".equals(formatter.format(ldt)));}

将字符串转化为日期和时间对象,可以使用对应类的parse方法,比如:

    @Testpublic void testDateTimeFormatterParse(){DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");String str = "2016-08-18 14:20:45";LocalDateTime ldt = LocalDateTime.parse(str, formatter);assertTrue("2016-08-18T14:20:45".equals(ldt.toString()));}

5.15 设置和修改时间

修改时期和时间有两种方式,一种是直接设置绝对值,另一种是在现有值的基础上进行相对增减操作,Java 8的大部分类都支持这两种方式。另外,Java 8的大部分类都是不可变类,修改操作是通过创建并返回新对象来实现的,原对象本身不会变。我们来看一些例子。

调整时间为下午 3 3 3 20 20 20分,代码为:

LocalDateTime ldt = LocalDateTime.now();
ldt = ldt.withHour(15).withMinute(20).withSecond(0).withNano(0);

还可以为:

LocalDateTime ldt = LocalDateTime.now();
ldt = ldt.toLocalDate().atTime(15, 20);

3 3 3小时 5 5 5分钟后,示例代码为:

LocalDateTime ldt = LocalDateTime.now();
ldt = ldt.plusHours(3).plusMinutes(5);

LocalDateTime有很多plusXXXminusXXX方法,分别用于相对增加和减少时间。

今天 0 0 0点,可以为:

LocalDateTime ldt = LocalDateTime.now();
ldt = ldt.with(ChronoField.MILLI_OF_DAY, 0);

ChronoField是一个枚举,里面定义了很多表示日历的字段,MILLI_OF_DAY表示在一天中的毫秒数,值从 0 0 0 ( 24 ∗ 60 ∗ 60 ∗ 1000 ) − 1 (24 * 60 * 60 * 1000)-1 (2460601000)1。还可以为:

LocalDateTime ldt = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);

LocalTime.MIN表示 00 : 00 00:00 00:00。也可以为:

LocalDateTime ldt = LocalDate.now().atTime(0, 0);

下周二上午 10 10 10点整,可以为:

LocalDateTime ldt = LocalDateTime.now();
ldt = ldt.plusWeeks(1).with(ChronoField.DAY_OF_WEEK, 2).with(ChronoField.MILLI_OF_DAY, 0).withHour(10);

上面下周二指定是下周,如果是下一个周二呢?这与当前是周几有关,如果当前是周一,则下一个周二就是明天,而其他情况则是下周,代码可以为:

LocalDate ld = LocalDate.now();
if(! ld.getDayOfWeek().equals(DayOfWeek.MONDAY)){ld = ld.plusWeeks(1);
}
LocalDateTime ldt = ld.with(ChronoField.DAY_OF_WEEK, 2).atTime(10, 0);

针对这种复杂一点的调整,Java 8有一个专门的接口TemporalAdjuster,这是一个函数式接口,定义为:

public interface TemporalAdjuster {Temporal adjustInto(Temporal temporal);
}

Temporal是一个接口,表示日期或时间对象,InstantLocalDateTimeLocalDate等都实现了它,这个接口就是对日期或时间进行调整,还有一个专门的类TemporalAdjusters,里面提供了很多TemporalAdjuster的实现。比如,针对下一个周几的调整,方法是:

public static TemporalAdjuster next(DayOfWeek dayOfWeek)

针对上面的例子,代码可以为:

LocalDate ld = LocalDate.now();
LocalDateTime ldt = ld.with(TemporalAdjusters.next(DayOfWeek.TUESDAY)).atTime(10, 0);

TemporalAdjusters中还有很多方法,部分方法如下:

public static TemporalAdjuster firstDayOfMonth()
public static TemporalAdjuster lastDayOfMonth()
public static TemporalAdjuster firstInMonth(DayOfWeek dayOfWeek)
public static TemporalAdjuster lastInMonth(DayOfWeek dayOfWeek)
public static TemporalAdjuster previous(DayOfWeek dayOfWeek)
public static TemporalAdjuster nextOrSame(DayOfWeek dayOfWeek)

一些例子如下:

    @Testpublic void testTemporalAdjusters() {LocalDateTime ldt = LocalDateTime.of(2017, 7, 11, 20, 45, 5);// 下周二上午 10 点整LocalDateTime nextThu = ldt.with(TemporalAdjusters.next(DayOfWeek.TUESDAY)).toLocalDate().atTime(10, 0);assertTrue("2017-07-18T10:00".equals(nextThu.toString()));// 本月最后一天最后一刻LocalDateTime thisMonthLastDayLastTime = ldt.with(TemporalAdjusters.lastDayOfMonth()).toLocalDate().atTime(LocalTime.MAX);assertTrue("2017-07-31T23:59:59.999999999".equals(thisMonthLastDayLastTime.toString()));long maxDayOfMonth = ldt.range(ChronoField.DAY_OF_MONTH).getMaximum();LocalDateTime thisMonthLastDayLastTime2 = ldt.withDayOfMonth((int) maxDayOfMonth).toLocalDate().atTime(LocalTime.MAX);assertTrue("2017-07-31T23:59:59.999999999".equals(thisMonthLastDayLastTime2.toString()));// 下个月第一个周一的下午5点整LocalDateTime nextMonthFirstMondaySeventeen = ldt.plusMonths(1).with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)).toLocalDate().atTime(17, 0);assertTrue("2017-08-07T17:00".equals(nextMonthFirstMondaySeventeen.toString()));}

5.16 时间段的计算

Java 8中表示时间段的类主要有两个:PeriodDurationPeriod表示日期之间的差,用年月日表示,不能表示时间;Duration表示时间差,用时分秒等表示,也可以用天表示,一天严格等于 24 24 24小时,不能用年月表示。下面看一些例子。计算两个日期之间的差,看个Period的例子:

    @Testpublic void testPeriod() {LocalDate ld1 = LocalDate.of(2016, 3, 24);LocalDate ld2 = LocalDate.of(2017, 7, 12);Period period = Period.between(ld1, ld2);assertTrue("1年3月18天".equals(period.getYears() + "年" + period.getMonths() + "月" + period.getDays() + "天"));}

根据生日计算年龄,示例代码可以为:

LocalDate born = LocalDate.of(1990,06,20);
int year = Period.between(born, LocalDate.now()).getYears();

计算迟到分钟数,假定早上 9 9 9点是上班时间,过了 9 9 9点算迟到,迟到要统计迟到的分钟数,怎么计算呢?看代码:

    @Testpublic void testDuration() {long lateMinutes = Duration.between(LocalTime.of(9, 0), LocalDateTime.of(2017, 7, 11, 20, 45, 5)).toMinutes();assertTrue(705 == lateMinutes);}

5.17 与 Date/Calendar 对象的转换

Java 8的日期和时间API没有提供与老的Date/Calendar相互转换的方法,但在实际中,我们可能是需要的。前面介绍了Date可以与Instant通过毫秒数相互转换,对于其他类型,也可以通过毫秒数/Instant相互转换。比如,将LocalDateTime按默认时区转换为Date,代码可以为:

public static Date toDate(LocalDateTime ldt){return new Date(ldt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
}

ZonedDateTime转换为Calendar,代码可以为:

public static Calendar toCalendar(ZonedDateTime zdt) {TimeZone tz = TimeZone.getTimeZone(zdt.getZone());Calendar calendar = Calendar.getInstance(tz);calendar.setTimeInMillis(zdt.toInstant().toEpochMilli());return calendar;
}

Calendar保持了ZonedDateTime的时区信息。

Date按默认时区转换为LocalDateTime,代码可以为:

public static LocalDateTime toLocalDateTime(Date date) {return LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()),ZoneId.systemDefault());
}

Calendar转换为ZonedDateTime,代码可以为:

public static ZonedDateTime toZonedDateTime(Calendar calendar) {ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.ofEpochMilli(calendar.getTimeInMillis()),calendar.getTimeZone().toZoneId());return zdt;}

  1. 马俊昌.Java编程的逻辑[M].北京:机械工业出版社,2018. ↩︎

  2. 尚硅谷教育.剑指Java:核心原理与应用实践[M].北京:电子工业出版社,2023. ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/708962.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenAI划时代大模型——文本生成视频模型Sora作品欣赏(十一)

Sora介绍 Sora是一个能以文本描述生成视频的人工智能模型,由美国人工智能研究机构OpenAI开发。 Sora这一名称源于日文“空”(そら sora),即天空之意,以示其无限的创造潜力。其背后的技术是在OpenAI的文本到图像生成模…

家庭游泳池:阳台上可安装的泳池

游泳池可根据场地大小选择安装在室内或室外,这种的泳池规格尺寸相对来说较大,较适合于大型体育场馆、小区配套、健身房等场所。这款家庭泳池与之前的不太一样,不论是从池体材料还是装饰面层都有着很大的差异。 该家庭泳池规格尺寸比较固定&a…

书籍推荐|《使用 ESP32 开发物联网项目(第二版)》

随着物联网技术的迅猛发展,ESP32 因其强大的功能而备受物联网开发者的青睐。在此背景下,资深物联网专家 Vedat Ozan Oner 撰写的《使用 ESP32 开发物联网项目(第二版)》,为开发者提供了全面且深入的指南读物。 资深物…

SpringBoot整合rabbitmq-直连队列,没有交换机(一)

说明&#xff1a;本文章只是springboot和rabbitmq的直连整合&#xff0c;只使用队列生产和消费消息&#xff0c;最简单整合&#xff01; 工程图&#xff1a; A.总体pom.xml <?xml version"1.0" encoding"UTF-8"?><project xmlns"http://…

【精选】Java项目介绍和界面搭建——拼图小游戏 上

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏 …

SpringBoot之自定义Jackson反序列化日期类型时类型转换配置类

SpringBoot之自定义Jackson反序列化日期类型转换配置类 文章目录 SpringBoot之自定义Jackson反序列化日期类型转换配置类1. SpringBoot版本2. Jackson反序列化日期类型配置类 全局自定义Jackson反序列化日期类型时,类型转换配置类,仅用于RequestBody注解接收参数时使用注意&…

实施顾问的 IDoc 基础知识

目录 一、前言二、概览三、EDI 标准和 IDOC四、IDOC 术语4.1 IDOC&#xff08;基本&#xff09;类型4.2 IDOC 扩展&#xff08;EXTENSION&#xff09;4.3 IDoc 段4.4 父项 IDoc 段与子项 IDoc 段4.5 入站/出站 IDOC4.6 IDOC方向4.7 合作伙伴4.8 合作伙伴类型4.9 消息类型4.10 流…

在Ubuntu中安装Anaconda和创建虚拟环境(保姆级教学,值得借鉴与信任)

一、下载linux版本的Anaconda 1.方法一&#xff1a;官网下载 https://www.anaconda.com/download#downloads2.方法二&#xff1a;在清华大学开源软件镜像站里下载 https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/最后选择了Anaconda3-2022.10-Linux-x86_64.sh进行下…

LeetCode #1327 列出指定时间段内所有的下单产品

题目 表: Products --------------------------- | Column Name | Type | --------------------------- | product_id | int | | product_name | varchar | | product_category | varchar | --------------------------- product_id 是该表主键(具有唯…

vue、react和小程序的状态管理

vue2的状态管理 vuex vuex官网&#xff1a;https://vuex.vuejs.org/ vuex安装 在vue2中使用vuex的3版本&#xff1a;npm i vuex3 在vue3中使用vuex的4版本&#xff1a;npm i vuex4 vuex全局配置使用 导出vuex的状态配置: index.js import Vue from vue import Vuex fro…

【EFK】基于K8S构建EFK+logstash+kafka日志平台

基于K8S构建EFKlogstashkafka日志平台 一、常见日志收集方案1.1、EFK1.2、ELK Stack1.3、ELK filbeat1.4、其他方案 二、EFK组件介绍2.1、Elasticsearch组件2.2、Filebeat组件【1】 Filebeat和beat关系【2】Filebeat是什么【3】Filebeat工作原理【4】传输方案 2.3、Logstash组件…

python 中常用的热门库介绍

阅读本文之前请参阅-----如何系统的自学python Python 是一种非常流行的编程语言&#xff0c;它的一个主要优势是拥有一个庞大的生态系统&#xff0c;其中包括许多强大的库。这些库为各种任务提供了解决方案&#xff0c;从数据分析到机器学习&#xff0c;从网络爬虫到图像处理。…

IOS 设置UIViewController为背景半透明浮层弹窗,查看富文本图片详情

使用场景&#xff1a;UIViewController1 打开 UIViewController2&#xff08;背景半透明弹窗&#xff09; 案例&#xff1a;打开富文本网页<img>图片的url查看图片详情 WKWebView WKNavigationDelegate代理方法设置js代码点击事件 ///注册添加图片标签点击js方法 - …

ArmSoM Rockchip系列产品 通用教程 之 CAN 使用

CAN 使用 1. CAN 简介 CAN (controller Area Network)&#xff1a;控制器局域网络总线&#xff0c;是一种有效支持分布式控制或实时控制的串行通信网络。 目前世界上绝大多数汽车制造厂商都采用CAN总线来实现汽车内部控制系统之间的数据通信。 RK3568/RK3588的CAN驱动文件&a…

推荐一款ssh工具 xshell替代品 electerm

下载地址 electerm&#xff1a; https://electerm.github.io/electerm/ windows版本 产品优势 复制粘贴&#xff0c;可以直接使用ctrlc/v 非常的方便 而且不想xshell 需要账号登陆&#xff0c;有更新弹窗&#xff0c;自身集成了sftp 界面设计更新&#xff0c;比MobaXterm的…

2024年值得关注的5款国产低代码开发平台

最近几年&#xff0c;低代码\无代码\零代码技术和快速开发平台比较热门&#xff0c;全球知名低代码平台厂商有&#xff1a;微软Power Platform、西门子Mendix、OutSystems等。我们国内最近几年也有一些信创国产化低代码平台涌现出来&#xff0c;比如&#xff1a;云程、氚云、轻…

【计算机网络】一些乱七八糟内容

MAC Media Access Control 用于在局域网&#xff08;LAN&#xff09;或广域网&#xff08;WAN&#xff09;中实现设备自动接入网络 "载波侦听多路访问"(Carrier Sense Multiple Access) CSMA/CD 是CSMA的升级版本&#xff0c;加入了序列号检测机制。 CSMA/CA 是CSM…

阿里又放大招 EMO:一张照片+音频即可生成会说话唱歌的视频

项目简介 你只需要提供一张你的照片任意的音频文件&#xff0c;就能实现你说任何话或唱任何歌曲的动态视频。同时生成视频的长度和你音频长度相匹配&#xff01;表情非常到位&#xff0c;支持任意语音、任意语速、任意图像。 主要特点和功能 1、音频驱动的人像视频生成&#…

如何利用IP代理高效采集产品数据,打造爆品?

文章目录 一、什么是网络爬虫&#xff1f;二、普通人如何通过网络爬虫赚钱&#xff1f;2.1、心得分享2.2、工具自动化收集信息 三、 动态IP代理3.1、覆盖范围3.2、性价比3.3、教程中心F&Q使用教程 3.4、在网络数据采集中的重要性 四、实战应用案例一&#xff1a;ebay电商【…

lv19 多态 4

1 虚函数 虚函数&#xff08; 基类指针可指向派生类对象&#xff0c; 动态联编&#xff09; 先看示例&#xff0c;不加virtual&#xff0c;不认对象认指针。 #include <iostream>using namespace std;class A{ public:A(){ }~A(){ }void show(){cout<<"AAA…