51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

在 Java 中优雅地操纵时间

在开发时候,发现有很多需要用到时间的地方,例如记录操作的时间、比较时间判断产品是否有效等。总而言之,时间是我们业务开发必须关注、时刻注意的点。但目前工程的代码中使用了非常多时间的工具类,一会儿用 java.util.Date 记录时间,一会用 java.time.LocalDateTime 记录时间,怎么才能在 Java 中优雅的操纵时间呢,我整理了相关的概念和工具类,希望帮助大家在代码开发的过程中对对时间的使用更加优雅。

这里先写一个结论:

  • 建议使用 java8 的时间 API,在安全性和易用性上都远高于 java.util.Date
  • 目前比较流行的封装 java API 的时间工具类大都基于 java.util.Date,建议在开发过程中根据业务需要基于 java.time.* 的方法封装工具类(文末给出了一个简单的实现)。

时间在计算机中的存储和展示 {#时间在计算机中的存储和展示}

时间以整数的方式进行存储:时间在计算机中存储的本质是一个整数,称为 Epoch Time(时间戳),计算从 1970 年 1 月 1 日零点(格林威治时间/GMT+00:00)到现在所经历的秒数。

在 java 程序中,时间戳通常使用 long 表示毫秒数,通过 System.currentTimeMillis() 可以获取时间戳。时间戳对我们人来说是不易理解的,因此需要将其转换为易读的时间,例如,2024-10-7 20:21:59(实际上说的是本地时间),而同一时刻不同时区的人看到的本地时间是不一样,所以在时间展示的时候需要加上时区的信息,才能精准的找到对应的时刻。

时区与世界时间标准相关:

时区与世界时间标准相关

世界时间的标准在 1972 年发生了变化,但我们在开发程序的时候可以忽略 GMTUTC 的差异, 因为计算机的时钟在联网的时候会自动与时间服务器同步时间。 本地时间等于我们所在(或者所使用)时区内的当地时间,它由与世界标准时间(UTC)之间的偏移量来定义。这个偏移量可以表示为 UTC-UTC+ ,后面接上偏移的小时和分钟数。 例如:GMT+08:00 或者 UTC+08:00 表示东八区,2024-10-7 20:21:59 UTC+08:00 便可以精准的定位一个时刻。

日期 API {#日期-api}

JDK 以版本 8 为界,有两套处理日期/时间的 API。

Java 时间 API

简单的比较如下:

| 特性 | java.util.Date | java.util.Date.Calendar | java.time.LocalDateTime | |------|------------------------------------|-------------------------|-------------------------| | 线程安全 | ❌ | ❌ | ✅ | | 时间运算 | ❌ | ✅ | ✅ | | 可读性 | Tue Oct 08 00:11:16 CST 2024 易读性较低 | ❌不易读 | ✅ yyyy-MM-dd'T'HH:mm:ss | | 常量设计 | 需要对获取的年份(+1900)月份(0-11)进行处理 | 需要对获月份(0-11)进行处理 | ✅ 不需要额外处理,符合常识 | | 时间精度 | 精确到毫秒 | 精确到毫秒 | 精确到纳秒 | | 时区 | 具体的时间调用 | 不 | - |

| 特性 | java.text.SimpleDateFormat | java.time.DateTimeFormatter | |------|-------------------------------------------------------------------------------------|-----------------------------| | 线程安全 | ❌ 在多线程环境下每个线程独立维护一份 SimpleDateFormat 对象实例,或者将 SimpleDateFormat 放到 ThreadLocal 中 | ✅ 不变对象,线程安全,可以使用单例存储 | | 使用场景 | Date | LocalDateTime |

java.util {#javautil}

在 jdk8 之前,Java 使用 java.util 中的 API 对处理时间。 在获取年月日的时候,DateCalendar 需要进行不同的转换 => 规则不统一。

Date {#date}

java.util.Date 用于表示一个日期和时间的对象,其实现很简单,实际上存储了一个 long 类型的以毫秒表示的时间戳,在通过 new Date() 获取当前时间的时候,实际上是通过 System.currentTimeMillis() 获取时间戳进行赋值。

public class Date {
    long fastTime;
public Date(long date) {
    fastTime = date;
}

public long getTime() { return fastTime; }

}

java.util.Date 承载的功能有限,且在利用 Date 类获取具体年/月/日的时候需要注意:getYear() 返回的年份必须加上 1900getMonth() 返回的月份是 0-11 分别表示 1-12 月,所以要加 1 ,而 getDate() 返回的日期范围是 1~31 ,又不能加 1

Calendar {#calendar}

Calendar 可以用于获取并设置年、月、日、时、分、秒,它和 Date 比,主要多了一个可以做简单的日期和时间运算的功能,但代码粗糙,API 不好用,性能也不好。

Calendar 对象 getTime() 可以获得 Date 对象:


import java.util.*;

public class Main { public static void main(String[] args) { // 获取当前时间: Calendar c = Calendar.getInstance(); int y = c.get(Calendar.YEAR);//返回年份不用转换 int m = 1 + c.get(Calendar.MONTH);//返回月份需要加1 int d = c.get(Calendar.DAY_OF_MONTH); int w = c.get(Calendar.DAY_OF_WEEK);//返回的 int hh = c.get(Calendar.HOUR_OF_DAY); int mm = c.get(Calendar.MINUTE); int ss = c.get(Calendar.SECOND); int ms = c.get(Calendar.MILLISECOND); System.out.println(y + "-" + m + "-" + d + " " + w + " " + hh + ":" + mm + ":" + ss + "." + ms); } }


import java.text.*;
import java.util.*;

public class Main { public static void main(String[] args) { // 当前时间: Calendar c = Calendar.getInstance(); // 清除所有: c.clear(); // 设置年月日时分秒: c.set(2019, 10 /* 11月 */, 20, 8, 15, 0); // 加5天并减去2小时: c.add(Calendar.DAY_OF_MONTH, 5); c.add(Calendar.HOUR_OF_DAY, -2); // 显示时间: var sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date d = c.getTime(); System.out.println(sdf.format(d)); // 2019-11-25 6:15:00 } }

TimeZone {#timezone}

CalendarDate 相比,它提供了时区转换的功能。时区用 TimeZone 对象表示。

时区的唯一标识是以字符串表示的 ID 。获取指定 TimeZone 对象也是以这个 ID 为参数获取,GMT+09:00Asia/Shanghai 都是有效的时区 ID 。可以通过 TimeZone.getAvailableIDs() 获取系统支持的所有 ID


import java.text.*;
import java.util.*;

public class learnTime { public static void main(String[] args) { // 当前时间: Calendar c = Calendar.getInstance(); // 清除所有字段: c.clear(); // 设置为北京时区: c.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); // 设置年月日时分秒: c.set(2024, 9 /* 10月 */, 10, 8, 15, 0); // 显示时间: SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); sdf.setTimeZone(TimeZone.getTimeZone("America/New_York")); System.out.println(sdf.format(c.getTime())); // 2024-10-09 20:15:00 } }

java.text.SimpleDateFormat {#javatextsimpledateformat}

Date 使用 SimpleDateFormat 解析和格式化时间:


// SimpleDateFormat线程不安全,每次使用都要构造新的,在初始的时候定义解析的字符串格式
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

// 将指定字符串String解析为Date Date date = format.parse("2024-10-07 16:10:22");

// 将Date格式化为String String str = format.format(date);

由于 SimpleDateFormat 线程不安全,为了提升性能,可以使用 ThreadLocalCache

如下:

static final ThreadLocal<SimpleDateFormat> SIMPLE_DATE_FORMAT_LOCAL 
    = ThreadLocal.withInitial(
        () -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
);

Java.time.* {#javatime}

开源社区开发了一个日期库 Joda,API 清晰,性能较好,提交了 JSR-310,在 java8 中称为 JDK 基础类库。

  • 本地日期和时间:LocalDateTime(日期和时间),LocalDate(日期),LocalTime(时间)(因为没有时区,所以无法与时间戳转换);
  • 带时区的日期和时间:ZonedDateTime
  • 时刻:Instant
  • 时区:ZoneIdZoneOffset
  • 时间间隔:Duration

以及一套新的用于取代 SimpleDateFormat 的格式化类型 DateTimeFormatter

LocalDate/LocalTime/LocalDateTime {#localdatelocaltimelocaldatetime}

  • 默认严格按照 ISO 8601 规定日期和时间格式进行打印(日期和时间的分隔符是 T)。

    • 日期:yyyy-MM-dd; 时间 HH:mm:ss
    • 日期和时间:yyyy-MM-dd'T'HH:mm:ss
  • 可以解析简单格式获取类型:

    LocalDateTime localDayTime=LocalDateTime.of(2024, 10, 07, 8, 15, 0);
    LocalDate localDay=LocalDate.of(2024, 10, 07); 
    LocalTime localTime=LocalTime.parse("08:15:07");
    
  • 有对日期和时间进行加减的非常简单的链式调用,通过 plusXxx()/minusXxx() 对时间进行变换:

    public class learnTime {
        public static void main(String[] args) {
            LocalDateTime dt = LocalDateTime.of(2024, 10, 10, 20, 30, 59);
            System.out.println(dt);
            // 加5天减3小时:2024-10-10T20:30:59
            LocalDateTime dt2 = dt.plusDays(5).minusHours(3);
            System.out.println(dt2); // 2024-10-15T17:30:59
            // 减1月:
            LocalDateTime dt3 = dt2.minusMonths(1); //2024-09-15T17:30:59
            System.out.println(dt3); // 2019-09-30T17:30:59
        }
    }
    
  • 对日期和时间进行调整使用 withXxx(),例如将月份调整为 9月: dataLocalTime.withMonth(9)

  • 复杂的操作:获取特殊时间

    • withTemporalAdjusters 配合使用找到特殊时间(当月的第一天)。

      public class Main {
          public static void main(String[] args) {
              LocalDateTime now = LocalDateTime.now();
      
          // 获取本月第一天0:00时刻:
          System.out.println(&quot;当月第一天0:00时刻&quot;+now.withDayOfMonth(1).atStartOfDay());
          //获取当月第一天
          System.out.println(&quot;当月第一天:&quot;+now.with(TemporalAdjusters.firstDayOfMonth()));
          //获取下月第一天
          System.out.println(&quot;下月第一天:&quot;+now.with(TemporalAdjusters.firstDayOfNextMonth()));
          //获取明年第一天
          System.out.println(&quot;明年第一天:&quot;+now.with(TemporalAdjusters.firstDayOfNextYear()));
          //获取本年第一天
          System.out.println(&quot;本年第一天:&quot;+now.with(TemporalAdjusters.firstDayOfYear()));
          //获取当月最后一天
          System.out.println(&quot;当月最后一天:&quot;+now.with(TemporalAdjusters.lastDayOfMonth()));
          //获取本年最后一天
          System.out.println(&quot;本年最后一天:&quot;+now.with(TemporalAdjusters.lastDayOfYear()));
          //获取当月第三周星期五
          System.out.println(&quot;当月第三周星期五:&quot;+now.with(TemporalAdjusters.dayOfWeekInMonth(3, DayOfWeek.FRIDAY)));
          //获取上周一
          System.out.println(&quot;上周一:&quot;+now.with(TemporalAdjusters.previous(DayOfWeek.MONDAY)));
          //获取下周日
          System.out.println(&quot;下周日:&quot;+now.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)));
      

      }

      }

  • 比较可以使用 isBefore()isAfter()

Duration 和 Period {#duration-和-period}

  • Duration: 基于时间值(Instant/LocalDateTime),表示两个时刻时间的时间间隔,适合处理较短的时间,需要更高的精确性。
    • 使用 between() 方法比较两个瞬间的差;
    • 使用 getSeconds()getNanosecends() 方法获取时间单元的值;
    • 获得具体的粒度的间隔:ofDays()ofHours()ofMillis()ofMinutes()ofNanos()ofSeconds()
    • 通过文本创建 Duration 对象,格式为 "PnDTnHnMn.nS",Duration.parse("P1DT1H10M10.5S")
    • 使用 toDays()toHours()toMillis()toMinutes() 方法把 Duration 对象可以转成其他时间单元;
    • 通过 plusX()minusX() 方法增加或减少 Duration 对象,其中 X 表示 days , hours , millis , minutes , nanosseconds
  • Period 基于日期值,表示一段时间的年、月、日:
    • 使用 between() 方法比较两个日期的差;
    • 使用 getYears()getMonhs()getDays() 方法获取具体粒度差距(返回的类型是 int);
    • 通过文本创建 Period 对象,格式为 "PnYnMnD":Period.parse("P2Y3M5D")
    • 可以通过 plusX()minusX() 方法进行增加或减少,其中 X 表示日期单元;

ZonedDateTime {#zoneddatetime}

ZonedDateTimeLocalDateTimeZoneId

  • ZonedDateTime 带时区时间的常见方法:

    • now():获取当前时区的ZonedDateTime对象。
    • now(ZoneId zone):获取指定时区的 ZonedDateTime 对象。
    • getYeargetMonthValuegetDayOfMonth 等:获取年月日、时分秒、纳秒等。
    • withXxx(时间):修改时间系列的方法。
    • minusXxx(时间):减少时间系列的方法。
    • plusXxx(时间):增加时间系列的方法。
  • 时区转换

    import java.time.*;
    

    public class Main { public static void main(String[] args) { // 以中国时区获取当前时间: 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); } }

ZoneId {#zoneid}

时区类,功能和 java.util.TimeZone 类似。

ZoneId 支持两种类型格式初始化,一种是时区偏移的格式(基于 UTC/Greenwich 时),一种是地域时区的格式(eg:Europe/Paris )。ZoneId 是抽象类,具体的逻辑实现由来子类完成,ZoneOffset 处理时区偏移类型的格式,ZoneRegion 处理基于地域时区的格式:

  • getAvailableZoneIds():获取Java中支持的所有时区。
  • systemDefault():获取系统默认时区。
  • of(String zoneId):获取一个指定时区。

| 格式 | 描述 | 示例 | |-------------------------------------------------------------------------------------|-----------------------------------------------|-------------------------------| | Z, GMT, UTC, UT | 格林尼治标准时间,和中国相差8个小时 | ZoneId.of("Z"); | | +h +hh +hh:mm -hh:mm +hhmm -hhmm +hh:mm:ss -hh:mm:ss +hhmmss -hhmmss | 表示从格林尼治标准时间偏移时间,中国用+8表示 | ZoneId.of("+8"); | | 前缀:UTC+, UTC-, GMT+, GMT-, UT+ UT-, 后缀:-h +hh +hh:mm -hh:mm... | 表示从格林尼治标准时间偏移时间 | ZoneId.of("UTC+8"); | | Asia/Aden, America/Cuiaba, Etc/GMT+9, Etc/GMT+8, Africa/Nairobi, America/Marigot... | 地区表示法,这些ID必须包含在getAvailableZoneIds集合中,否则会抛出异常 | ZoneId.of("Asia/Shanghai"); |

Instant {#instant}

时间线上的某个时刻/时间戳

通过获取 Instant 的对象可以拿到此刻的时间,该时间由两部分组成:从 1970-01-01 00:00:00 开始走到此刻的总秒数+不够 1 秒的纳秒数。

  • 作用:可以用来记录代码的执行时间,或用于记录用户操作某个事件的时间点。
  • 传统的 Date 类,只能精确到毫秒,并且是可变对象。
  • 新增的 Instant 类,可以精确到纳秒,并且是不可变对象,推荐用 Instant 代替 Date
//1、创建Instant的对象,获取此刻时间信息
Instant now = Instant.now(); //不可变对象
//2、获取总秒数
long second = now.getEpochSecond();
system.out.println(second) ;
//3、不够1秒的纳秒数
int nano = now.getNano();
system.out.println(nano) ;

system.out.println(now); //可以进行加减法 Instant instant = now.plusNanos(111);//将纳秒加111

// Instant对象的作用:做代码的性能分析,或者记录用户的操作时间点 Instant now1 = Instant.now(); //代码执行... Instant now2 = Instant.now(); //用这两个时间点相减就可以知道这段代码运行了多少时间

DateTimeFormatter {#datetimeformatter}

使用方式,传入格式化字符串,可以指定 local


import java.time.*;
import java.time.format.*;
import java.util.Locale;

public class Main { public static void main(String[] args) { 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(&quot;yyyy MMM dd EE HH:mm&quot;, Locale.CHINA);
    System.out.println(zhFormatter.format(zdt));
DateTimeFormatter usFormatter = DateTimeFormatter.ofPattern(&amp;quot;E, MMMM/dd/yyyy HH:mm&amp;quot;, Locale.US);
System.out.println(usFormatter.format(zdt));

//2024-10-08T00:25 GMT+08:00
//2024 十月 08 星期二 00:25
//Tue, October/08/2024 00:25

}

}

转换 {#转换}

LocalTimeTimeDate 的相互转换 {#localtimetime-和-date-的相互转换}

LocalDateTime 不包括时区,而 Date 代表一个具体的时间瞬间,精度为毫秒。

为了从 LocalDateTime 转换到 Date 需要提供时区。


// LocalDateTime 转换为 Date
LocalDateTime localDateTime = LocalDateTime.now();
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.systemDefault());
Date date = Date.from(zonedDateTime.toInstant());
// Date 转换为 LocalDateTime
Date date = new Date();
Instant instant = date.toInstant();
LocalDateTime localDateTime = instant.atZone(ZoneId.systemDefault()).toLocalDateTime();

数据库映射变化 {#数据库映射变化}

  • java.util.Date 和数据库映射

    <arg column="gmt_create" jdbcType="TIMESTAMP" javaType="java.util.Date"/>
    
  • java.time.* 和数据库映射

    <arg column="gmt_create" jdbcType="TIMESTAMP" javaType="java.time.LocalDateTime"/>
    
    • mybatis 3.5.0 以后已经支持,有 LocalDateTimeTypeHandler 等类型处理器支持,不需要额外操作。

    • 比较老的 mybatis 版本可能会报错,需要添加相关的依赖。

      <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis-typehandlers-jsr310</artifactId>
          <version>1.0.2</version>
      </dependency>
      

Mybatis 中和时间相关的 jdbcType 和 javaTypetypeHandler 的对照关系如下:

| TypeHandler | Java类型 | JDBC类型 | |---------------------------|-------------------------------|-----------------------| | DateTypeHandler | java.util.Date | TIMESTAMP | | DateOnlyTypeHandler | java.util.Date | DATE | | TimeOnlyTypeHandler | java.util.Date | TIME | | InstantTypeHandler | java.time.Instant | TIMESTAMP | | LocalDateTimeTypeHandler | java.time.LocalDateTime | TIMESTAMP | | LocalDateTypeHandler | java.time.LocalDate | DATE | | LocalTimeTypeHandler | java.time.LocalTime | TIME | | OffsetDateTimeTypeHandler | java.time.OffsetDateTime | TIMESTAMP | | OffsetTimeTypeHandler | java.time.OffsetTime | TIME | | ZonedDateTimeTypeHandler | java.time.ZonedDateTime | TIMESTAMP | | YearTypeHandler | java.time.Year | INTEGER | | MonthTypeHandler | java.time.Month | INTEGER | | YearMonthTypeHandler | java.time.YearMonth | VARCHAR 或 LONGVARCHAR | | JapaneseDateTypeHandler | java.time.chrono.JapaneseDate | DATE |

操作时间相关的工具 {#操作时间相关的工具}

有一些对基础的API进行了封装便于我们在开发中有效的处理时间。

  • 蚂蚁时间工具类:com.iwallet.biz.common.util.DateUtil
    • 基于 java.Util.Date,提供了广泛的日期/时间处理方法,可满足绝大部分需求。
  • org.apache.commons.lang3.time
    • 包括多种基于 java.util.Date 封装的工具类,提供了很多方便操作日期和时间的算法。

目前暂时没有发现基于 java.time* 封装的公共的时间工具类。

在很多情况下,因为已有的工具类不能满足当下的业务需求,工程内部需要自己实现类似 DateUtil 的工具类,建议基于 java.time* 实现相关的工具类。


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

public class DateUtils {

// 获取当前日期
public static LocalDate getCurrentDate() {
    return LocalDate.now();
}

// 获取当前时间 public static LocalTime getCurrentTime() { return LocalTime.now(); }

// 获取当前日期时间 public static LocalDateTime getCurrentDateTime() { return LocalDateTime.now(); }

// 格式化日期为字符串 public static String formatLocalDate(LocalDate date, String pattern) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); return date.format(formatter); }

// 解析字符串为LocalDate public static LocalDate parseLocalDate(String dateStr, String pattern) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); return LocalDate.parse(dateStr, formatter); }

// 增加指定天数 public static LocalDate addDays(LocalDate date, long days) { return date.plusDays(days); }

// 减少指定天数 public static LocalDate minusDays(LocalDate date, long days) { return date.minusDays(days); }

// 计算两个日期之间的天数差 public static long getDaysBetween(LocalDate startDate, LocalDate endDate) { return ChronoUnit.DAYS.between(startDate, endDate); }

// 获取指定日期所在月份的第一天 public static LocalDate getFirstDayOfMonth(LocalDate date) { return date.withDayOfMonth(1); }

// 获取指定日期所在月份的最后一天 public static LocalDate getLastDayOfMonth(LocalDate date) { return date.withDayOfMonth(date.lengthOfMonth()); }

// 判断两个日期是否相等 public static boolean isSameDate(LocalDate date1, LocalDate date2) { return date1.isEqual(date2); }

// 判断日期是否在指定范围内 public static boolean isDateInRange(LocalDate date, LocalDate startDate, LocalDate endDate) { return date.isAfter(startDate) &amp;&amp; date.isBefore(endDate); }

// 获取指定日期的星期几 public static DayOfWeek getDayOfWeek(LocalDate date) { return date.getDayOfWeek(); }

// 判断是否为闰年 public static boolean isLeapYear(int year) { return Year.of(year).isLeap(); }

// 获取指定月份的天数 public static int getDaysInMonth(int year, int month) { return YearMonth.of(year, month).lengthOfMonth(); }

// 获取指定日期的年份 public static int getYear(LocalDate date) { return date.getYear(); }

// 获取指定日期的月份 public static int getMonth(LocalDate date) { return date.getMonthValue(); }

// 获取指定日期的天数 public static int getDayOfMonth(LocalDate date) { return date.getDayOfMonth(); }

// 获取指定日期的小时数 public static int getHour(LocalDateTime dateTime) { return dateTime.getHour(); }

// 获取指定日期的分钟数 public static int getMinute(LocalDateTime dateTime) { return dateTime.getMinute(); }

// 获取指定日期的秒数 public static int getSecond(LocalDateTime dateTime) { return dateTime.getSecond(); }

// 判断指定日期是否在当前日期之前 public static boolean isBefore(LocalDate date) { return date.isBefore(LocalDate.now()); }

// 判断指定日期是否在当前日期之后 public static boolean isAfter(LocalDate date) { return date.isAfter(LocalDate.now()); }

// 判断指定日期是否在当前日期之前或相等 public static boolean isBeforeOrEqual(LocalDate date) { return date.isBefore(LocalDate.now()) || date.isEqual(LocalDate.now()); }

// 判断指定日期是否在当前日期之后或相等 public static boolean isAfterOrEqual(LocalDate date) { return date.isAfter(LocalDate.now()) || date.isEqual(LocalDate.now()); }

// 获取指定日期的年龄 public static int getAge(LocalDate birthDate) { LocalDate currentDate = LocalDate.now(); return Period.between(birthDate, currentDate).getYears(); }

// 获取指定日期的季度 public static int getQuarter(LocalDate date) { return (date.getMonthValue() - 1) / 3 + 1; }

// 获取指定日期的下一个工作日 public static LocalDate getNextWorkingDay(LocalDate date) { do { date = date.plusDays(1); } while (date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY); return date; }

// 获取指定日期的上一个工作日 public static LocalDate getPreviousWorkingDay(LocalDate date) { do { date = date.minusDays(1); } while (date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY); return date; }

// 获取指定日期所在周的第一天(周一) public static LocalDate getFirstDayOfWeek(LocalDate date) { return date.with(DayOfWeek.MONDAY); }

// 获取指定日期所在周的最后一天(周日) public static LocalDate getLastDayOfWeek(LocalDate date) { return date.with(DayOfWeek.SUNDAY); }

// 获取指定日期所在年的第一天 public static LocalDate getFirstDayOfYear(LocalDate date) { return date.withDayOfYear(1); }

// 获取指定日期所在年的最后一天 public static LocalDate getLastDayOfYear(LocalDate date) { return date.withDayOfYear(date.lengthOfYear()); }

// 获取指定日期所在季度的第一天 public static LocalDate getFirstDayOfQuarter(LocalDate date) { int month = (date.getMonthValue() - 1) / 3 * 3 + 1; return LocalDate.of(date.getYear(), month, 1); }

// 获取指定日期所在季度的最后一天 public static LocalDate getLastDayOfQuarter(LocalDate date) { int month = (date.getMonthValue() - 1) / 3 * 3 + 3; return LocalDate.of(date.getYear(), month, Month.of(month).maxLength()); }

// 判断指定日期是否为工作日(周一至周五) public static boolean isWeekday(LocalDate date) { return date.getDayOfWeek() != DayOfWeek.SATURDAY &amp;&amp; date.getDayOfWeek() != DayOfWeek.SUNDAY; }

// 判断指定日期是否为周末(周六或周日) public static boolean isWeekend(LocalDate date) { return date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY; }

// 获取指定日期所在月份的工作日天数 public static int getWeekdayCountOfMonth(LocalDate date) { int weekdayCount = 0; LocalDate firstDayOfMonth = getFirstDayOfMonth(date); LocalDate lastDayOfMonth = getLastDayOfMonth(date);

while (!firstDayOfMonth.isAfter(lastDayOfMonth)) {
    if (isWeekday(firstDayOfMonth)) {
        weekdayCount++;
    }
    firstDayOfMonth = firstDayOfMonth.plusDays(1);
}

return weekdayCount;

}

// 获取指定日期所在月份的周末天数 public static int getWeekendCountOfMonth(LocalDate date) { int weekendCount = 0; LocalDate firstDayOfMonth = getFirstDayOfMonth(date); LocalDate lastDayOfMonth = getLastDayOfMonth(date);

while (!firstDayOfMonth.isAfter(lastDayOfMonth)) {
    if (isWeekend(firstDayOfMonth)) {
        weekendCount++;
    }
    firstDayOfMonth = firstDayOfMonth.plusDays(1);
}

return weekendCount;

}

// 获取指定日期所在年份的工作日天数 public static int getWeekdayCountOfYear(LocalDate date) { int weekdayCount = 0; LocalDate firstDayOfYear = getFirstDayOfYear(date); LocalDate lastDayOfYear = getLastDayOfYear(date);

while (!firstDayOfYear.isAfter(lastDayOfYear)) {
    if (isWeekday(firstDayOfYear)) {
        weekdayCount++;
    }
    firstDayOfYear = firstDayOfYear.plusDays(1);
}

return weekdayCount;

}

}


Ref:https://mp.weixin.qq.com/s?__biz=MzIzOTU0NTQ0MA==&mid=2247542060&idx=1&sn=ebde870557f2f3002dacef8a43e04bfd

赞(7)
未经允许不得转载:工具盒子 » 在 Java 中优雅地操纵时间