玩转Java 8新特性:日期和时间的轻松处理
在Java8之前,日期时间API一直被开发者诟病,包括:java.util.Date是可变类型,SimpleDateFormat非线程安全等问题。故此,Java8引入了一套全新的日期时间处理API,新的API基于ISO标准日历系统。
java.time包中的是类是不可变且线程安全的。新的时间及日期API位于java.time中,下面是一些关键类
类名 |
说明 |
---|---|
Instant |
时间戳 |
Duration |
持续时间、时间差 |
LocalDate |
只包含日期,比如:2018-09-24 |
LocalTime |
只包含时间,比如:10:32:10 |
LocalDateTime |
包含日期和时间,比如:2018-09-24 10:32:10 |
Peroid |
时间段 |
ZoneOffset |
时区偏移量,比如:+8:00 |
ZonedDateTime |
带时区的日期时间 |
Clock |
时钟,可用于获取当前时间戳 |
java.time.format.DateTimeFormatter |
时间格式化类 |
如何在java8中获取当天的日期
java8中有个叫LocalDate的类,能用来表示今天的日期。这个类与java.util.Date略有不同,因为它只包含日期,没有时间。
/**
* 1. 如何在java8中获取当天的日期
*/
LocalDate today = LocalDate.now();
System.out.println("today = " + today);
// today = 2020-02-06
可以看到,他创建了今天的日期却不包含时间信息,并且格式化了日期。
如何在java8中获取当前的年月日
LocalDate类中提供了一些很方便的方法可以用来提取年月日以及其他的日期属性,特别方便,只需要使用对应的getter方法就可以了,非常直观
/**
* 2. 如何在java8中获取当前的年月日
*/
LocalDate today = LocalDate.now();
int year = today.getYear();
int month = today.getMonthValue();
int day = today.getDayOfMonth();
System.out.println(year + "年" + month + "月" + day + "日");
// 2020年2月6日
在java8中如何获取某个特定的日期
通过另一个方法,可以创建出任意一个日期,它接受年月日的参数,然后返回一个等价的LocalDate实例。在这个方法里,需要的日期你填写什么就是什么,不想之前的API中月份必须从0开始
/**
* 3. 在java8中如何获取某个特定的日期
*/
LocalDate dateOfBirth = LocalDate.of(2020, 02, 02);
System.out.println("你输入的日期是: " + dateOfBirth);
// 你输入的日期是: 2020-02-02
在java8中检查两个日期是否相等
LocalDate重写了equals方法来进行日期的比较,如下所示:
/**
* 4. 在java8中检查两个日期是否相等
*/
LocalDate date = LocalDate.of(2020,02, 06);
LocalDate day = LocalDate.now();
System.out.println("今天的日期是2020-2-6吗? " + date.equals(day));
// 今天的日期是2020-2-6吗? true
在java8中如何检查重复事件,比如生日
在java中还有一个与时间日期相关的任务就是检查重复事件,比如每月的账单日
如何在java中判断是否是某个节日或者重复事件,使用MonthDay类。这个类由月日组合,不包含年信息,可以用来代表每年重复出现的一些日期或其他组合。他和新的日期库中的其他类一样也都是不可变且线程安全的,并且它还是一个值类(value class)。
/**
* 5. 在java8中如何检查重复事件,比如生日
*/
LocalDate dateOfBirth = LocalDate.of(1994, 10, 18);
LocalDate day = LocalDate.now();
MonthDay birthday = MonthDay.of(dateOfBirth.getMonth(), dateOfBirth.getDayOfMonth());
MonthDay currentMonthDay = MonthDay.from(day);
if (currentMonthDay.equals(birthday)) {
System.out.println("今天是你的生日");
} else {
System.out.println("对不起,今天不是你的生日");
}
// 对不起,今天不是你的生日
通过列子可以看到MonthDay只存储了月日,对比两个日期的月日即可知道是否重复
如何在java8中获取当前时间
这个与第一个例子获取当前日期非常相似,这里用的是LocalTime类,默认的格式是hh:mm:ss:nnn
/**
* 6. 如何在java8中获取当前时间
*/
LocalTime localTime = LocalTime.now();
System.out.println("localTime = " + localTime);
// localTime = 21:00:22.404
可以看到,这个时间是不包含日期的
如何增加时间的小时
很多时候需要对时间进行操作,比如加一个小时来计算之后的时间,java8提供了更方便的方法 如plusHours,这些方法返回的是一个新的LocalTime实例的引用,因为LocalTime是不可变的
/**
* 7. 如何增加时间的小时
*/
LocalTime localTime = LocalTime.now();
System.out.println("现在的时间是: " + localTime);
LocalTime two = localTime.plusHours(2);
System.out.println("两个小时后的时间是: " + two);
// 现在的时间是: 21:02:50.942
// 两个小时后的时间是: 23:02:50.942
如何获取1周后的日期
这个与前一个获取2小时后的时间的例子很相似,这里我们获取的是1周后的日期。LocalDate是用来表示无时间的日期,他又一个plus()方法可以用来增加日,星期,月,ChronoUnit则用来表示时间单位,LocalDate也是不可变的,因此任何修改操作都会返回一个新的实例
/**
* 8. 如何获取1周后的日期
*/
LocalDate today = LocalDate.now();
System.out.println("今天的日期是: " + today);
LocalDate oneToday = today.plus(1, ChronoUnit.WEEKS);
System.out.println("一周后的日期是: " + oneToday);
// 今天的日期是: 2020-02-06
// 一周后的日期是: 2020-02-13
可以看到一周后的日期是什么,也可以用这个方法来增加一个月,一年,一小时,一分等等
一年前后的日期
在上个例子中我们使用了LocalDate的plus()方法来给日期增加日周月,现在我们用minus()方法来找出一年前的那天
/**
* 9. 一年前后的日期
*/
LocalDate today = LocalDate.now();
System.out.println("今天的日期是: " + today);
LocalDate previousYear = today.minus(1, ChronoUnit.YEARS);
System.out.println("一年前的日期是: " + previousYear);
LocalDate nextYear = today.plus(1, ChronoUnit.YEARS);
System.out.println("一年后的日期是: " + nextYear);
// 今天的日期是: 2020-02-06
// 一年前的日期是: 2019-02-06
// 一年后的日期是: 2021-02-06
在java中如何判断某个日期在另一个日期的前面还是后面
如何判断某个日期在另一个日期的前面还是后面或者相等,在java8中,LocalDate类中使用isBefore()、isAfter()、equals()方法来比较两个日期。如果调用方法的那个日期比给定的日期要早的话,isBefore()方法会返回true。equals()方法在前面的例子中已经说明了,这里就不举例了
/**
* 11. 在java中如何判断某个日期在另一个日期的前面还是后面
*/
LocalDate today = LocalDate.now();
System.out.println("今天的日期是: " + today);
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
System.out.println("明天的日期是: " + tomorrow);
System.out.println("日期: " + tomorrow + "是否在日期: " + today + "之后: " + tomorrow.isAfter(today));
System.out.println("日期: " + tomorrow + "是否在日期: " + today + "之前: " + tomorrow.isBefore(today));
// 今天的日期是: 2020-02-06
// 明天的日期是: 2020-02-07
// 日期: 2020-02-07是否在日期: 2020-02-06之后: true
// 日期: 2020-02-07是否在日期: 2020-02-06之前: false
可以看到java8中比较日期非常简单,不再需要使用Calendar这样另外的类来完成类似的任务了
如何表示固定的日期
正如MonthDay表示的是某个重复出现的日子,YearMonth是另外一个组合,代表的是像信用卡还款日,定期存款到期日,options到期日这类的日期。你可以用这个类找出这个月有多少天,LengthOfMonth()这个方法返回的是这个YearMonth实例有多少天,这对于检查2月是否润2月很有用
/**
* 13. 如何表示固定的日期
*/
YearMonth currentYearMonth = YearMonth.now();
System.out.printf("这个月的年月 %s 有 %d 天 %n", currentYearMonth, currentYearMonth.lengthOfMonth());
YearMonth creditCardExpiry = YearMonth.of(2018, Month.JULY);
System.out.printf("你输入的年月是 %s %n", creditCardExpiry);
// 这个月的年月 2020-02 有 29 天
// 你输入的年月是 2018-07
如何在java8中检查闰年
LocalDate类由一个isLeapYear()方法来返回当前LocalDate对应的那年是否是闰年
/**
* 14. 如何在java8中检查闰年
*/
LocalDate today = LocalDate.now();
System.out.printf("%s 是否是闰年: %s ", today, today.isLeapYear());
// 2020-02-06 是否是闰年: true
两个日期之间包含多少天,多少月
计算两个日期之间包含多少天、周、月、年。可以用java.time.Period类完成该功能。下面例子中将计算日期与将来的日期之间一共有几个月
/**
* 15. 两个日期之间包含多少天,多少月
*/
LocalDate today = LocalDate.now();
LocalDate dates = LocalDate.of(2016, Month.MARCH, 14);
Period periodToNextJavaRelease = Period.between(today, dates);
System.out.printf("日期 %s 和日期 %s 相差 %s 月", today, dates, periodToNextJavaRelease.getMonths());
// 日期 2020-02-06 和日期 2016-03-14 相差 -10 月
如何在java中使用自定义的格式器来解析日期
有时预置的不能满足的时候就需要我们自定义日期格式器了,下面的例子中的日期格式是”MM dd yyyy”.你可以给DateTimeFormatter的ofPattern静态方法()传入任何的模式,它会返回一个实例,这个模式的字面量与前例中是相同的。比如M代表月,m仍代表分,无效的模式会抛异常DateTimeParseException。
/**
* 16. 如何在java中使用自定义的格式器来解析日期
*/
String goodFriday = "02 06 2020";
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM dd yyyy");
LocalDate holiday = LocalDate.parse(goodFriday, formatter);
System.out.printf("字符 %s 转换成功后的日期是 %s%n", goodFriday, holiday);
// 字符 02 06 2020 转换成功后的日期是 2020-02-06
} catch (DateTimeParseException e) {
System.out.printf("%s is not parsable!%n", goodFriday);
e.printStackTrace();
}
如何在java8中对日期进行格式化,转换成字符串
在这个例子我们相反,是把日期转换成字符。这里我们有个LocalDateTime类的实例,我们要把他转换成一个格式化好的日期串,与前例相同的是,我们仍需要制定模式串去创建一个DateTimeFormatter类的实例,但调用的是LocalDate.format()。这个方法会返回一个代表当前日期的字符串,对应的模式就是传入的DateTimeFormatter实例中定义好的。
/**
* 17. 如何在java8中对日期进行格式化,转换成字符串
*/
LocalDateTime arrivalDate = LocalDateTime.now();
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String landing = arrivalDate.format(formatter);
System.out.printf("时间日期 %s 转换后的字符是 %s %n", arrivalDate, landing);
// 时间日期 2020-02-06T22:14:00.380 转换后的字符是 2020-02-06 22:14:00
} catch (DateTimeException e) {
System.out.printf("%s 格式转换错误 %n", arrivalDate);
e.printStackTrace();
}
基于JDK 1.8的时间工具类
package com.hq.eos.utils;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Date;
public class DateUtil {
private static final String HYPHEN = "-";
private static final String COLON = ":";
/*↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 时间格式 DateTimeFormatter (Java8) ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
enum FormatEnum {
/**
* 返回 DateTimeFormatter "yyyy-MM-dd HH:mm:ss" 时间格式
*/
FORMAT_DATA_TIME(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT)),
/**
* 返回 DateTimeFormatter "yyyyMMddHHmmss"的时间格式
*/
FORMAT_DATA_TIME_NO_SYMBOL(DateTimeFormatter.ofPattern(DATETIME_FORMAT)),
/**
* 返回 DateTimeFormatter "yyyy-MM-dd"的时间格式
*/
FORMAT_DATE(DateTimeFormatter.ofPattern(DATE_FORMAT)),
/**
* 返回 DateTimeFormatter "HH:mm:ss"的时间格式
*/
FORMAT_TIME(DateTimeFormatter.ofPattern(TIME_FORMAT));
private DateTimeFormatter value;
FormatEnum(DateTimeFormatter format) {
this.value = format;
}
}
/*↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 时间格式 DateTimeFormatter (Java8) ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑*/
/*↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 时间格式 字符串 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
/**
* 年的时间格式
* <br/>
* 返回 "yyyy" 字符串
*/
public static final String YEAR_FORMAT = "yyyy";
/**
* 月的时间格式
* <br/>
* 返回 "MM" 字符串
*/
public static final String MONTH_FORMAT = "MM";
/**
* 日的时间格式
* <br/>
* 返回 "dd" 字符串
*/
public static final String DAY_FORMAT = "dd";
/**
* 时的时间格式
* <br/>
* 返回 "HH" 字符串
*/
public static final String HOUR_FORMAT = "HH";
/**
* 分的时间格式
* <br/>
* 返回 "mm" 字符串
*/
public static final String MINUTE_FORMAT = "mm";
/**
* 秒的时间格式
* <br/>
* 返回 "ss" 字符串
*/
public static final String SECOND_FORMAT = "ss";
/**
* <span color='red'>年-月-日</span>的时间格式
* <br/>
* 返回 "yyyy-MM-dd" 字符串
*/
public static final String DATE_FORMAT = YEAR_FORMAT + HYPHEN + MONTH_FORMAT + HYPHEN + DAY_FORMAT;
/**
* <span color='red'>时:分:秒</span>的时间格式
* <br/>
* 返回 "HH:mm:ss" 字符串
*/
public static final String TIME_FORMAT = HOUR_FORMAT + COLON + MINUTE_FORMAT + COLON + SECOND_FORMAT;
/**
* <span color='red'>年-月-日 时:分:秒</span>的时间格式
* <br/>
* 返回 "yyyy-MM-dd HH:mm:ss" 字符串
*/
public static final String DATE_TIME_FORMAT = DATE_FORMAT + " " + TIME_FORMAT;
/**
* <span color='red'>年月日时分秒</span>的时间格式(无符号)
* <br/>
* 返回 "yyyyMMddHHmmss" 字符串
*/
public static final String DATETIME_FORMAT = YEAR_FORMAT + MONTH_FORMAT + DAY_FORMAT + HOUR_FORMAT + MINUTE_FORMAT + SECOND_FORMAT;
/*↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 时间格式 字符串 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑*/
/*↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 时间戳 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
/**
* 获取秒级时间戳
*/
public static Long epochSecond() {
return localDateTime().toEpochSecond(ZoneOffset.of("+8"));
}
/**
* 获取毫秒级时间戳
*/
public static Long epochMilli() {
return localDateTime().toInstant(ZoneOffset.of("+8")).toEpochMilli();
}
/*↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 时间戳 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑*/
/*↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 当前时间相关 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
/**
* 获取当前详细时间,like 2018-08-27 17:20:06
*/
public static String dateTime() {
return localDateTime().format(FormatEnum.FORMAT_DATA_TIME.value);
}
/**
* 获取当前详细时间,like 20180827172006
*/
public static String dateTimeNoSymbol() {
return localDateTime().format(FormatEnum.FORMAT_DATA_TIME_NO_SYMBOL.value);
}
/**
* 获取当前日期,like 2018-08-27
*/
public static String date() {
return localDate() + "";
}
/**
* 获取当前时间,like 17:20:06
*/
public static String time() {
return localTime().format(FormatEnum.FORMAT_TIME.value);
}
/**
* 获取当前年
*/
public static Integer year() {
return localDate().getYear();
}
/**
* 获取当前月
*/
public static int month() {
return localDate().getMonthValue();
}
/**
* 获取当前年中的日
*/
public static Integer dayOfYear() {
return localDate().getDayOfYear();
}
/**
* 获取当前月中的日
*/
public static Integer dayOfMonth() {
return localDate().getDayOfMonth();
}
/**
* 获取当前星期中的日
*/
public static Integer dayOfWeek() {
return localDate().getDayOfWeek().getValue();
}
/**
* 获取当前小时
*/
public static Integer hour() {
return localTime().getHour();
}
/**
* 获取当前分钟
*/
public static Integer minute() {
return localTime().getMinute();
}
/**
* 获取当前秒
*/
public static Integer second() {
return localTime().getSecond();
}
/*↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 当前时间相关 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑*/
/*↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 未来、历史时间相关 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
/**
* 获取当前年的 前几年 的日期
* <p>
*
* @param years 前几年 正整数
* @param formatEnum 格式
* @return 当前年的 前几年 的 对应 格式 日期
*/
public static String minusYears(Long years, FormatEnum formatEnum) {
return minusOrPlusYears(-years, formatEnum);
}
/**
* 获取当前年的 后几年 的日期
* <p>
*
* @param years 后几年 正整数
* @param formatEnum 格式
* @return 当前年的 后几年 的 对应 格式 日期
*/
public static String plusYears(Long years, FormatEnum formatEnum) {
return minusOrPlusYears(years, formatEnum);
}
/**
* 获取当前月的 前几月 日期
*
* @param months 前几月 正整数
* @param formatEnum 格式
* @return 当前月的 前几月 的 对应 格式 日期
*/
public static String minusMonths(Long months, FormatEnum formatEnum) {
return minusOrPlusMonths(-months, formatEnum);
}
/**
* 获取当前月的 后几月 的日期
*
* @param months 后几月 正整数
* @param formatEnum 格式
* @return 当前月的 后几月 的 对应 格式 日期
*/
public static String plusMonths(Long months, FormatEnum formatEnum) {
return minusOrPlusMonths(months, formatEnum);
}
/**
* 获取当前日的 前几日 的日期
*
* @param days 前几日 正整数
* @param formatEnum 格式
* @return 当前日的 前几日 的 对应 格式 日期
*/
public static String minusDays(Long days, FormatEnum formatEnum) {
return minusOrPlusDays(-days, formatEnum);
}
/**
* 获取当前日的 后几日 的日期
*
* @param days 后几日 正整数
* @param formatEnum 格式
* @return 当前日的 后几日 的 对应 格式 日期
*/
public static String plusDays(Long days, FormatEnum formatEnum) {
return minusOrPlusDays(days, formatEnum);
}
/**
* 获取当前星期的 前几星期 的日期
*
* @param weeks 前几星期 正整数
* @param formatEnum 格式
* @return 当前星期的 前几星期 的 对应 格式 日期
*/
public static String minusWeeks(Long weeks, FormatEnum formatEnum) {
return minusOrPlusWeeks(-weeks, formatEnum);
}
/**
* 获取当前星期的 后几星期 的日期
*
* @param weeks 后几星期 正整数
* @param formatEnum 格式
* @return 当前星期的 后几星期 的 对应 格式 日期
*/
public static String plusWeeks(Long weeks, FormatEnum formatEnum) {
return minusOrPlusWeeks(weeks, formatEnum);
}
/**
* 获取当前小时的 前几小时 的日期
*
* @param hours 前几小时 正整数
* @param formatEnum 格式
* @return 当前小时的 前几小时 的 对应 格式 日期
*/
public static String minusHours(Long hours, FormatEnum formatEnum) {
return minusOrPlusHours(-hours, formatEnum);
}
/**
* 获取当前小时的 后几小时 的日期
*
* @param hours 后几小时 正整数
* @param formatEnum 格式
* @return 当前小时的 后几小时 的 对应 格式 日期
*/
public static String plusHours(Long hours, FormatEnum formatEnum) {
return minusOrPlusHours(hours, formatEnum);
}
/**
* 获取当前分钟的 前几分钟 的日期
*
* @param minutes 前几分钟 正整数
* @param formatEnum 格式
* @return 当前分钟的 前几分钟 的 对应 格式 日期
*/
public static String minusMinutes(Long minutes, FormatEnum formatEnum) {
return minusOrPlusMinutes(-minutes, formatEnum);
}
/**
* 获取当前分钟的 后几分钟 的日期
*
* @param minutes 后几分钟 正整数
* @param formatEnum 格式
* @return 当前分钟的 后几分钟 的 对应 格式 日期
*/
public static String plusMinutes(Long minutes, FormatEnum formatEnum) {
return minusOrPlusMinutes(minutes, formatEnum);
}
/**
* 获取当前秒的 前几秒 的日期
*
* @param seconds 前几秒 正整数
* @param formatEnum 格式
* @return 当前秒的 前几秒 的 对应 格式 日期
*/
public static String minusSeconds(Long seconds, FormatEnum formatEnum) {
return minusOrPlusSeconds(-seconds, formatEnum);
}
/**
* 获取当前秒的 前几秒/后几秒 的日期
*
* @param seconds 后几秒 正整数
* @param formatEnum 格式
* @return 当前秒的 后几秒 的 对应 格式 日期
*/
public static String plusSeconds(Long seconds, FormatEnum formatEnum) {
return minusOrPlusSeconds(seconds, formatEnum);
}
/*↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 未来、历史时间相关 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑*/
/*↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 时间转换相关 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
/**
* Date类型转LocalDateTime
* <p>
*
* @param date date类型时间
* @return LocalDateTime
*/
public static LocalDateTime toLocalDateTime(Date date) {
return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
}
/**
* Date类型转LocalDate
* <p>
*
* @param date date类型时间
* @return LocalDate
*/
public static LocalDate toLocalDate(Date date) {
return toLocalDateTime(date).toLocalDate();
}
/**
* Date类型转LocalTime
* <p>
*
* @param date date类型时间
* @return LocalTime
*/
public static LocalTime toLocalTime(Date date) {
return toLocalDateTime(date).toLocalTime();
}
/**
* LocalDateTime 类型转 Date
*
* @param localDateTime localDateTime
* @return 转换后的Date类型日期
*/
public static Date toDate(LocalDateTime localDateTime) {
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
}
/**
* LocalDate类型转Date
*
* @param localDate localDate
* @return 转换后的Date类型日期
*/
public static Date toDate(LocalDate localDate) {
return toDate(localDate.atStartOfDay());
}
/**
* LocalTime类型转Date
*
* @param localTime localTime
* @return 转换后的Date类型日期
*/
public static Date toDate(LocalTime localTime) {
return toDate(LocalDateTime.of(localDate(), localTime));
}
/*↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 时间转换相关 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑*/
/*↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 时间间隔相关 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
/**
* 获取 endDate-startDate 时间间隔天数
* <br>创建人: leigq
* <br>创建时间: 2018-11-07 09:55
* <br>
*
* @param startDate 开始时间
* @param endDate 结束时间
* @return 时间间隔天数
*/
public static Long daysInterval(LocalDate startDate, LocalDate endDate) {
return endDate.toEpochDay() - startDate.toEpochDay();
}
/**
* 获取 endDate-startDate 时间间隔天数
* <br>创建人: leigq
* <br>创建时间: 2018-11-07 09:55
* <br>
*
* @param startDate 开始时间
* @param endDate 结束时间
* @return 时间间隔天数
*/
public static Long daysInterval(String startDate, String endDate) {
return daysInterval(LocalDateTime.parse(endDate, FormatEnum.FORMAT_DATA_TIME.value).toLocalDate(),
LocalDateTime.parse(startDate, FormatEnum.FORMAT_DATA_TIME.value).toLocalDate());
}
/**
* 获取 endDate-startDate 时间间隔天数
* <br>创建人: leigq
* <br>创建时间: 2018-11-07 09:55
* <br>
*
* @param startDate 开始时间
* @param endDate 结束时间
* @return 时间间隔天数
*/
public static Long daysInterval(LocalDateTime startDate, LocalDateTime endDate) {
return daysInterval(startDate.toLocalDate(), endDate.toLocalDate());
}
/*↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 时间间隔相关 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑*/
/*↓↓↓只允许此类调用↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
/**
* 获取 当前年 的前几年/后几年的日期
* <p>
*
* @param yearsToAddOrSubtract 后几年传正整数,前几年传负数
* @param formatEnum 格式
* @return 当前年的前几年/后几年的对应 格式 日期
*/
private static String minusOrPlusYears(Long yearsToAddOrSubtract, FormatEnum formatEnum) {
return localDateTime().plusYears(yearsToAddOrSubtract).format(formatEnum.value);
}
/**
* 获取 当前月 的前几月/后几月的日期
*
* @param monthsToAddOrSubtract 后几月传正整数,前几月传负数
* @param formatEnum 格式
* @return 当前月的前几月/后几月的对应 格式 日期
*/
private static String minusOrPlusMonths(Long monthsToAddOrSubtract, FormatEnum formatEnum) {
return localDateTime().plusMonths(monthsToAddOrSubtract).format(formatEnum.value);
}
/**
* 获取 当前日 的前几日/后几日的日期
*
* @param daysToAddOrSubtract 后几日传正整数,前几日传负数
* @param formatEnum 格式
* @return 当前日的前几日/后几日的 对应 格式 日期
*/
private static String minusOrPlusDays(Long daysToAddOrSubtract, FormatEnum formatEnum) {
return localDateTime().plusDays(daysToAddOrSubtract).format(formatEnum.value);
}
/**
* 获取当前星期的前几星期/后几星期的日期
*
* @param weeksToAddOrSubtract 后几星期传正整数,前几星期传负数
* @param formatEnum 格式
* @return 当前星期的前几星期/后几星期的 对应 格式 日期
*/
private static String minusOrPlusWeeks(Long weeksToAddOrSubtract, FormatEnum formatEnum) {
return localDateTime().plusWeeks(weeksToAddOrSubtract).format(formatEnum.value);
}
/**
* 获取当前小时的前几小时/后几小时的日期
*
* @param hoursToAddOrSubtract 后几小时传正整数,前几小时传负数
* @param formatEnum 格式
* @return 当前小时的前几小时/后几小时的 对应 格式 日期
*/
private static String minusOrPlusHours(Long hoursToAddOrSubtract, FormatEnum formatEnum) {
return localDateTime().plusHours(hoursToAddOrSubtract).format(formatEnum.value);
}
/**
* 获取当前分钟的前几分钟/后几分钟的日期
*
* @param minutesToAddOrSubtract 后几分钟传正整数,前几分钟传负数
* @param formatEnum 格式
* @return 当前分钟的前几分钟/后几分钟的 对应 格式 日期
*/
private static String minusOrPlusMinutes(Long minutesToAddOrSubtract, FormatEnum formatEnum) {
return localDateTime().plusMinutes(minutesToAddOrSubtract).format(formatEnum.value);
}
/**
* 获取当前秒的前几秒/后几秒的日期
*
* @param secondsToAddOrSubtract 后几秒传正整数,前几秒传负数
* @param formatEnum 格式
* @return 当前秒的前几秒/后几秒的 对应 格式 日期
*/
private static String minusOrPlusSeconds(Long secondsToAddOrSubtract, FormatEnum formatEnum) {
return localDateTime().plusSeconds(secondsToAddOrSubtract).format(formatEnum.value);
}
/**
* 获取 LocalDate
*/
private static LocalDate localDate() {
return localDateTime().toLocalDate();
}
/**
* 获取 LocalTime
*/
private static LocalTime localTime() {
return localDateTime().toLocalTime();
}
/**
* 获取 LocalDateTime
*/
private static LocalDateTime localDateTime() {
return LocalDateTime.now();
}
}
推荐阅读
-
Java 8新特性探究(十三)JavaFX 8新特性以及开发2048游戏-JavaFX历史## 跟java在服务器端和web端成绩相比,桌面一直是java的软肋,于是Sun公司在2008年推出JavaFX,弥补桌面软件的缺陷,请看下图JavaFX一路走过来的改进 从上图看出,一开始推出时候,开发者需使用一种名为JavaFX Script的静态的、声明式的编程语言来开发JavaFX应用程序。因为JavaFX Script将会被编译为Java bytecode,程序员可以使用Java代码代替。 JavaFX 2.0之后的版本摒弃了JavaFX Script语言,而作为一个Java API来使用。因此使用JavaFX平台实现的应用程序将直接通过标准Java代码来实现。 JavaFX 2.0 包含非常丰富的 UI 控件、图形和多媒体特性用于简化可视化应用的开发,WebView可直接在应用中嵌入网页;另外 2.0 版本允许使用 FXML 进行 UI 定义,这是一个脚本化基于 XML 的标识语言。 从JDK 7u6开始,JavaFx就与JDK捆绑在一起了,JavaFX团队称,下一个版本将是8.0,目前所有的工作都已经围绕8.0库进行。这是因为JavaFX将捆绑在Java 8中,因此该团队决定跳过几个版本号,迎头赶上Java 8。 ##JavaFx8的新特性 ## ###全新现代主题:Modena 新的Modena主题来替换原来的Caspian主题。不过在Application的start方法中,可以通过setUserAgentStylesheet(STYLESHEET_CASPIAN)来继续使用Caspian主题。 参考http://fxexperience.com/2013/03/modena-theme-update/ ###JavaFX 3D 在JavaFX8中提供了3D图像处理API,包括Shape3D (Box, Cylinder, MeshView, Sphere子类),SubScene, Material, PickResult, LightBase (AmbientLight 和PointLight子类),SceneAntialiasing等。Camera类也得到了更新。从JavaDoc中可以找到更多信息。 ###富文本 强化了富文本的支持 ###TreeTableView ###日期控件DatePicker 增加日期控件 ###用于 CSS 结构的公共 API
-
玩转Java 8的LocalDateTime:毫秒和秒的时间戳获取,以及日期与字符串、LocalDateTime之间的转换
-
玩转Java 8新特性:日期和时间的轻松处理
-
【2022新手指南】Java编程进阶之路 - 六、技术架构篇 ### MySQL索引底层解析与优化实战 - 你会讲解MySQL索引的数据结构吗?性能调优技巧知多少? - Redis深度揭秘:你知道多少?从基础到哨兵、主从复制全梳理 - Redis持久化及哨兵模式详解,还有集群搭建和Leader选举黑箱打开 - Zookeeper是个啥?特性和应用场景大公开 - ZooKeeper集群搭建攻略及 Leader选举、读写一致性、共享锁实现细节 - 探究ZooKeeper中的Leader选举机制及其在分布式环境中的作用 - Zab协议深入剖析:原理、功能与在Zookeeper中的核心地位 - RabbitMQ全方位解读:工作模式、消费限流、可靠投递与配置策略 - 设计者视角:RabbitMQ过期时间、死信队列与延时队列实践指南 - RocketMQ特性和应用场景揭示:理解其精髓与差异化优势 - Kafka详细介绍:特性及广泛应用于实时数据处理的场景解析 - ElasticSearch实力揭秘:特性概述与作为搜索引擎的广泛应用 - MongoDB认知升级:非关系型数据库的优势阐述,安装与使用实战教学 - BIO/NIO/AIO网络模型对比:掌握它们的区别与在网络编程中的实际应用 - Netty带你飞:理解其超快速度背后的秘密,包括线程模型分析 - 网络通信黑科技:Netty编解码原理与常用编解码器的应用,Protostuff实战演示 - 解密Netty粘包与拆包现象,怎样有效应对这一常见问题 - 自定义Netty心跳检测机制,轻松调整检测间隔时间的艺术 - Dubbo轻骑兵介绍:核心特性概览,服务降级实战与其实现益处 - Dubbo三大神器解读:本地存根与本地伪装的实战运用与优势呈现 ----------------------- 七、结语与回顾
-
Adobe国际认证中文官方网站】Adobe中国摄影计划,免费安装正版激活--Adobe Creative Cloud中国摄影计划。与此同时,Adobe宣布天猫为Adobe Creative Cloud中国摄影计划的电商战略合作伙伴,并将与其合作上线Adobe天猫官方旗舰店。 此举无疑一方面扩大了Adobe在中国的影响力,另一方面也有助于国内用户更好地培养正版软件意识,推动Adobe软件在中国的正版化进程。 网络异常,图片无法显示 ||网络异常 Adobe Creative Cloud中国摄影计划包括Photoshop和Lightroom Classic两大桌面创意工具,以及iOS版Photoshop Express。 其中,Adobe Lightroom Classic和Adobe Photoshop作为两款常用的图像处理软件,对于那些玩摄影、后期修图的创意设计人群无疑有着巨大的帮助,而LR+PS套装对于摄影领域用户的重要性自不必说,正版产品的性能实时更新也可以放心!体验最新功能,对于新镜头(补偿)和机身(RAW 读取)都能第一时间适应。不信你看: Photoshop 图像合成 裁剪、移除对象、润饰合成照片、玩转色彩和特效,创建精美图片和艺术品! Lightroom Classic 照片编辑 轻松批量管理和编辑照片,内置专业创意控件和摄影师预设,让你的照片大放异彩。 手机 PS 便捷编辑 Photoshop Express 支持多种滤镜、贴纸,手机即可完成抠图、除雾等任务 人工智能编辑工具 神经滤镜、快速点击选区、自动选择主题等人工智能功能让图像编辑更轻松 创意画笔内容识别 定制艺术画笔工具,实现个性化效果;内容识别填充,智能去除无用物体。 Adobe Creative Cloud 中国摄影计划的推出,为中国的专业摄影师、摄影爱好者、后期修图和其他创意设计人员带来了全方位的内容和体验。 网络异常,图片无法显示 ||网络异常 当然,不可否认的是,"由于盗版软件缺乏开发、维护和升级成本,销售价格远低于正版软件。再加上很多普通人并不需要使用正版软件的复杂功能,版权观念较淡,还是有大量的创意设计人员会选择盗版软件"。 但事实上,当所有的软件都不再是单一的软件,而是变成一种服务时,单机版盗版的存在就逐渐成为鸡肋。因为有太多的服务让你即使是所谓的 "完美破解",也无法享受,Adobe Cloud 就是一个很好的例子,所谓的完美破解,你只能使用 "Adobe "的一半,对于更精彩的 "云",只能望云兴叹。更何况,越来越多的设计工具从免费走向付费,越来越多的设计师和企业已经接受了付费使用的模式。 其次,对于互联网时代的企业数字化转型而言,数字化合规至关重要。21年来,使用盗版PS和未经授权的方正字体被指侵权的事情闹得沸沸扬扬,虽然新闻真假难辨,但也给使用盗版工具的用户敲响了警钟。 付费使用正版工具,可以更放心地进行设计,不用担心版权风险!
-
F#探险之旅(二):函数式编程(上)-函数式编程范式简介 F#主要支持三种编程范式:函数式编程(Functional Programming,FP)、命令式编程(Imperative Programming)和面向对象(Object-Oriented,OO)的编程。回顾它们的历史,FP是最早的一种范式,第一种FP语言是IPL,产生于1955年,大约在Fortran一年之前。第二种FP语言是Lisp,产生于1958,早于Cobol一年。Fortan和Cobol都是命令式编程语言,它们在科学和商业领域的迅速成功使得命令式编程在30多年的时间里独领风骚。而产生于1970年代的面向对象编程则不断成熟,至今已是最流行的编程范式。有道是“*代有语言出,各领风骚数十年”。 尽管强大的FP语言(SML,Ocaml,Haskell及Clean等)和类FP语言(APL和Lisp是现实世界中最成功的两个)在1950年代就不断发展,FP仍停留在学院派的“象牙塔”里;而命令式编程和面向对象编程则分别凭着在商业领域和企业级应用的需要占据领先。今天,FP的潜力终被认识——它是用来解决更复杂的问题的(当然更简单的问题也不在话下)。 纯粹的FP将程序看作是接受参数并返回值的函数的集合,它不允许有副作用(side effect,即改变了状态),使用递归而不是循环进行迭代。FP中的函数很像数学中的函数,它们都不改变程序的状态。举个简单的例子,一旦将一个值赋给一个标识符,它就不会改变了,函数不改变参数的值,返回值是全新的值。 FP的数学基础使得它很是优雅,FP的程序看起来往往简洁、漂亮。但它无状态和递归的天性使得它在处理很多通用的编程任务时没有其它的编程范式来得方便。但对F#来说这不是问题,它的优势之一就是融合了多种编程范式,允许开发人员按照需要采用最好的范式。 关于FP的更多内容建议阅读一下这篇文章:Why Functional Programming Matters(中文版)。F#中的函数式编程 从现在开始,我将对F#中FP相关的主要语言结构逐一进行介绍。标识符(Identifier) 在F#中,我们通过标识符给值(value)取名字,这样就可以在后面的程序中引用它。通过关键字let定义标识符,如: let x = 42 这看起来像命令式编程语言中的赋值语句,两者有着关键的不同。在纯粹的FP中,一旦值赋给了标识符就不能改变了,这也是把它称为标识符而非变量(variable)的原因。另外,在某些条件下,我们可以重定义标识符;在F#的命令式编程范式下,在某些条件下标识符的值是可以修改的。 标识符也可用于引用函数,在F#中函数本质上也是值。也就是说,F#中没有真正的函数名和参数名的概念,它们都是标识符。定义函数的方式与定义值是类似的,只是会有额外的标识符表示参数: let add x y = x + y 这里共有三个标识符,add表示函数名,x和y表示它的参数。关键字和保留字关键字是指语言中一些标记,它们被编译器保留作特殊之用。在F#中,不能用作标识符或类型的名称(后面会讨论“定义类型”)。它们是: abstract and as asr assert begin class default delegate do donedowncast downto elif else end exception extern false finally forfun function if in inherit inline interface internal land lazy letlor lsr lxor match member mod module mutable namespace new nullof open or override private public rec return sig static structthen to true try type upcast use val void when while with yield 保留字是指当前还不是关键字,但被F#保留做将来之用。可以用它们来定义标识符或类型名称,但编译器会报告一个警告。如果你在意程序与未来版本编译器的兼容性,最好不要使用。它们是: atomic break checked component const constraint constructor continue eager event external fixed functor global include method mixinobject parallel process protected pure sealed trait virtual volatile 文字值(Literals) 文字值表示常数值,在构建计算代码块时很有用,F#提供了丰富的文字值集。与C#类似,这些文字值包括了常见的字符串、字符、布尔值、整型数、浮点数等,在此不再赘述,详细信息请查看F#手册。 与C#一样,F#中的字符串常量表示也有两种方式。一是常规字符串(regular string),其中可包含转义字符;二是逐字字符串(verbatim string),其中的(")被看作是常规的字符,而两个双引号作为双引号的转义表示。下面这个简单的例子演示了常见的文字常量表示: let message = "Hello World"r"n!" // 常规字符串let dir = @"C:"FS"FP" // 逐字字符串let bytes = "bytes"B // byte 数组let xA = 0xFFy // sbyte, 16进制表示let xB = 0o777un // unsigned native-sized integer,8进制表示let print x = printfn "%A" xlet main = print message; print dir; print bytes; print xA; print xB; main Printf函数通过F#的反射机制和.NET的ToString方法来解析“%A”模式,适用于任何类型的值,也可以通过F#中的print_any和print_to_string函数来完成类似的功能。值和函数(Values and Functions) 在F#中函数也是值,F#处理它们的语法也是类似的。 let n = 10let add a b = a + blet addFour = add 4let result = addFour n printfn "result = %i" result 可以看到定义值n和函数add的语法很类似,只不过add还有两个参数。对于add来说a + b的值自动作为其返回值,也就是说在F#中我们不需要显式地为函数定义返回值。对于函数addFour来说,它定义在add的基础上,它只向add传递了一个参数,这样对于不同的参数addFour将返回不同的值。考虑数学中的函数概念,F(x, y) = x + y,G(y) = F(4, y),实际上G(y) = 4 + y,G也是一个函数,它接收一个参数,这个地方是不是很类似?这种只向函数传递部分参数的特性称为函数的柯里化(curried function)。 当然对某些函数来说,传递部分参数是无意义的,此时需要强制提供所有参数,可是将参数括起来,将它们转换为元组(tuple)。下面的例子将不能编译通过: let sub(a, b) = a - blet subFour = sub 4 必须为sub提供两个参数,如sub(4, 5),这样就很像C#中的方法调用了。 对于这两种方式来说,前者具有更高的灵活性,一般可优先考虑。 如果函数的计算过程中需要定义一些中间值,我们应当将这些行进行缩进: let halfWay a b = let dif = b - a let mid = dif / 2 mid + a 需要注意的是,缩进时要用空格而不是Tab,如果你不想每次都按几次空格键,可以在VS中设置,将Tab字符自动转换为空格;虽然缩进的字符数没有限制,但一般建议用4个空格。而且此时一定要用在文件开头添加#light指令。作用域(Scope)作用域是编程语言中的一个重要的概念,它表示在何处可以访问(使用)一个标识符或类型。所有标识符,不管是函数还是值,其作用域都从其声明处开始,结束自其所处的代码块。对于一个处于最顶层的标识符而言,一旦为其赋值,它的值就不能修改或重定义了。标识符在定义之后才能使用,这意味着在定义过程中不能使用自身的值。 let defineMessage = let message = "Help me" print_endline message // error 对于在函数内部定义的标识符,一般而言,它们的作用域会到函数的结束处。 但可使用let关键字重定义它们,有时这会很有用,对于某些函数来说,计算过程涉及多个中间值,因为值是不可修改的,所以我们就需要定义多个标识符,这就要求我们去维护这些标识符的名称,其实是没必要的,这时可以使用重定义标识符。但这并不同于可以修改标识符的值。你甚至可以修改标识符的类型,但F#仍能确保类型安全。所谓类型安全,其基本意义是F#会避免对值的错误操作,比如我们不能像对待字符串那样对待整数。这个跟C#也是类似的。 let changeType = let x = 1 let x = "change me" let x = x + 1 print_string x 在本例的函数中,第一行和第二行都没问题,第三行就有问题了,在重定义x的时候,赋给它的值是x + 1,而x是字符串,与1相加在F#中是非法的。 另外,如果在嵌套函数中重定义标识符就更有趣了。 let printMessages = let message = "fun value" printfn "%s" message; let innerFun = let message = "inner fun value" printfn "%s" message innerFun printfn "%s" message printMessages 打印结果: fun value inner fun valuefun value 最后一次不是inner fun value,因为在innerFun仅仅将值重新绑定而不是赋值,其有效范围仅仅在innerFun内部。递归(Recursion)递归是编程中的一个极为重要的概念,它表示函数通过自身进行定义,亦即在定义处调用自身。在FP中常用于表达命令式编程的循环。很多人认为使用递归表示的算法要比循环更易理解。 使用rec关键字进行递归函数的定义。看下面的计算阶乘的函数: let rec factorial x = match x with | x when x < 0 -> failwith "value must be greater than or equal to 0" | 0 -> 1 | x -> x * factorial(x - 1) 这里使用了模式匹配(F#的一个很棒的特性),其C#版本为: public static long Factorial(int n) { if (n < 0) { throw new ArgumentOutOfRangeException("value must be greater than or equal to 0"); } if (n == 0) { return 1; } return n * Factorial (n - 1); } 递归在解决阶乘、Fibonacci数列这样的问题时尤为适合。但使用的时候要当心,可能会写出不能终止的递归。匿名函数(Anonymous Function) 定义函数的时候F#提供了第二种方式:使用关键字fun。有时我们没必要给函数起名,这种函数就是所谓的匿名函数,有时称为lambda函数,这也是C#3.0的一个新特性。比如有的函数仅仅作为一个参数传给另一个函数,通常就不需要起名。在后面的“列表”一节中你会看到这样的例子。除了fun,我们还可以使用function关键字定义匿名函数,它们的区别在于后者可以使用模式匹配(本文后面将做介绍)特性。看下面的例子: let x = (fun x y -> x + y) 1 2let x1 = (function x -> function y -> x + y) 1 2let x2 = (function (x, y) -> x + y) (1, 2) 我们可优先考虑fun,因为它更为紧凑,在F#类库中你能看到很多这样的例子。 注意:本文中的代码均在F# 1.9.4.17版本下编写,在F# CTP 1.9.6.0版本下可能不能通过编译。 F#系列随笔索引页面