Looly 6 years ago
parent
commit
8e70a2aca7

+ 3 - 0
CHANGELOG.md

@@ -7,7 +7,10 @@
 
 ### 新特性
 * 【core】        ImgUtil.rotate支持负数(issue#543@Github)
+* 【http】        body方法传null跳过而非报错(issue#I12AP2@Github)
+* 【core】        TimeInterval增加intervalPretty方法(issue#I12A6T@Github)
 ### Bug修复
+* 【core】        修复DateUtil.offset导致的时区错误问题(issue#I1294O@Github)
 
 -------------------------------------------------------------------------------------------------------------
 

+ 114 - 103
hutool-core/src/main/java/cn/hutool/core/date/DateTime.java

@@ -17,23 +17,28 @@ import cn.hutool.core.util.StrUtil;
 
 /**
  * 包装java.util.Date
- * 
- * @author xiaoleilu
  *
+ * @author xiaoleilu
  */
 public class DateTime extends Date {
 	private static final long serialVersionUID = -5395712593979185936L;
 
-	/** 是否可变对象 */
+	/**
+	 * 是否可变对象
+	 */
 	private boolean mutable = true;
-	/** 一周的第一天,默认是周一, 在设置或获得 WEEK_OF_MONTH 或 WEEK_OF_YEAR 字段时,Calendar 必须确定一个月或一年的第一个星期,以此作为参考点。 */
+	/**
+	 * 一周的第一天,默认是周一, 在设置或获得 WEEK_OF_MONTH 或 WEEK_OF_YEAR 字段时,Calendar 必须确定一个月或一年的第一个星期,以此作为参考点。
+	 */
 	private Week firstDayOfWeek = Week.MONDAY;
-	/** 时区 */
+	/**
+	 * 时区
+	 */
 	private TimeZone timeZone;
-	
+
 	/**
 	 * 转换时间戳为 DateTime
-	 * 
+	 *
 	 * @param timeMillis 时间戳,毫秒数
 	 * @return DateTime
 	 * @since 4.6.3
@@ -44,7 +49,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 转换JDK date为 DateTime
-	 * 
+	 *
 	 * @param date JDK Date
 	 * @return DateTime
 	 */
@@ -57,7 +62,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 转换 {@link Calendar} 为 DateTime
-	 * 
+	 *
 	 * @param calendar {@link Calendar}
 	 * @return DateTime
 	 */
@@ -67,11 +72,11 @@ public class DateTime extends Date {
 
 	/**
 	 * 构造
-	 * 
-	 * @see DatePattern
+	 *
 	 * @param dateStr Date字符串
-	 * @param format 格式
+	 * @param format  格式
 	 * @return {@link DateTime}
+	 * @see DatePattern
 	 */
 	public static DateTime of(String dateStr, String format) {
 		return new DateTime(dateStr, format);
@@ -79,7 +84,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 现在的时间
-	 * 
+	 *
 	 * @return 现在的时间
 	 */
 	public static DateTime now() {
@@ -87,9 +92,9 @@ public class DateTime extends Date {
 	}
 
 	// -------------------------------------------------------------------- Constructor start
+
 	/**
 	 * 当前时间
-	 * 
 	 */
 	public DateTime() {
 		this(TimeZone.getDefault());
@@ -97,7 +102,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 当前时间
-	 * 
+	 *
 	 * @param timeZone 时区
 	 * @since 4.1.2
 	 */
@@ -107,17 +112,20 @@ public class DateTime extends Date {
 
 	/**
 	 * 给定日期的构造
-	 * 
+	 *
 	 * @param date 日期
 	 */
 	public DateTime(Date date) {
-		this(date.getTime(), TimeZone.getDefault());
+		this(
+				date.getTime(),//
+				(date instanceof DateTime) ? ((DateTime) date).timeZone : TimeZone.getDefault()
+		);
 	}
 
 	/**
 	 * 给定日期的构造
-	 * 
-	 * @param date 日期
+	 *
+	 * @param date     日期
 	 * @param timeZone 时区
 	 * @since 4.1.2
 	 */
@@ -127,28 +135,28 @@ public class DateTime extends Date {
 
 	/**
 	 * 给定日期的构造
-	 * 
+	 *
 	 * @param calendar {@link Calendar}
 	 */
 	public DateTime(Calendar calendar) {
-		this(calendar.getTime(), (TimeZone) null);
+		this(calendar.getTime(), calendar.getTimeZone());
 	}
 
 	/**
 	 * 给定日期毫秒数的构造
-	 * 
+	 *
 	 * @param timeMillis 日期毫秒数
 	 * @since 4.1.2
 	 */
 	public DateTime(long timeMillis) {
-		this(timeMillis, (TimeZone) null);
+		this(timeMillis, TimeZone.getDefault());
 	}
 
 	/**
 	 * 给定日期毫秒数的构造
-	 * 
+	 *
 	 * @param timeMillis 日期毫秒数
-	 * @param timeZone 时区
+	 * @param timeZone   时区
 	 * @since 4.1.2
 	 */
 	public DateTime(long timeMillis, TimeZone timeZone) {
@@ -160,10 +168,10 @@ public class DateTime extends Date {
 
 	/**
 	 * 构造
-	 * 
-	 * @see DatePattern
+	 *
 	 * @param dateStr Date字符串
-	 * @param format 格式
+	 * @param format  格式
+	 * @see DatePattern
 	 */
 	public DateTime(String dateStr, String format) {
 		this(dateStr, new SimpleDateFormat(format));
@@ -171,10 +179,10 @@ public class DateTime extends Date {
 
 	/**
 	 * 构造
-	 * 
-	 * @see DatePattern
-	 * @param dateStr Date字符串
+	 *
+	 * @param dateStr    Date字符串
 	 * @param dateFormat 格式化器 {@link SimpleDateFormat}
+	 * @see DatePattern
 	 */
 	public DateTime(String dateStr, DateFormat dateFormat) {
 		this(parse(dateStr, dateFormat), dateFormat.getTimeZone());
@@ -182,24 +190,25 @@ public class DateTime extends Date {
 
 	/**
 	 * 构造
-	 * 
-	 * @see DatePattern
-	 * @param dateStr Date字符串
+	 *
+	 * @param dateStr    Date字符串
 	 * @param dateParser 格式化器 {@link DateParser},可以使用 {@link FastDateFormat}
+	 * @see DatePattern
 	 */
 	public DateTime(String dateStr, DateParser dateParser) {
 		this(parse(dateStr, dateParser), dateParser.getTimeZone());
 	}
-	
+
 	// -------------------------------------------------------------------- Constructor end
 
 	// -------------------------------------------------------------------- offset start
+
 	/**
 	 * 调整日期和时间<br>
 	 * 如果此对象为可变对象,返回自身,否则返回新对象,设置是否可变对象见{@link #setMutable(boolean)}
-	 * 
+	 *
 	 * @param datePart 调整的部分 {@link DateField}
-	 * @param offset 偏移量,正数为向后偏移,负数为向前偏移
+	 * @param offset   偏移量,正数为向后偏移,负数为向前偏移
 	 * @return 如果此对象为可变对象,返回自身,否则返回新对象
 	 */
 	public DateTime offset(DateField datePart, int offset) {
@@ -213,9 +222,9 @@ public class DateTime extends Date {
 	/**
 	 * 调整日期和时间<br>
 	 * 返回调整后的新{@link DateTime},不影响原对象
-	 * 
+	 *
 	 * @param datePart 调整的部分 {@link DateField}
-	 * @param offset 偏移量,正数为向后偏移,负数为向前偏移
+	 * @param offset   偏移量,正数为向后偏移,负数为向前偏移
 	 * @return 如果此对象为可变对象,返回自身,否则返回新对象
 	 * @since 3.0.9
 	 */
@@ -229,10 +238,11 @@ public class DateTime extends Date {
 	// -------------------------------------------------------------------- offset end
 
 	// -------------------------------------------------------------------- Part of Date start
+
 	/**
 	 * 获得日期的某个部分<br>
 	 * 例如获得年的部分,则使用 getField(DatePart.YEAR)
-	 * 
+	 *
 	 * @param field 表示日期的哪个部分的枚举 {@link DateField}
 	 * @return 某个部分的值
 	 */
@@ -243,7 +253,7 @@ public class DateTime extends Date {
 	/**
 	 * 获得日期的某个部分<br>
 	 * 例如获得年的部分,则使用 getField(Calendar.YEAR)
-	 * 
+	 *
 	 * @param field 表示日期的哪个部分的int值 {@link Calendar}
 	 * @return 某个部分的值
 	 */
@@ -254,7 +264,7 @@ public class DateTime extends Date {
 	/**
 	 * 设置日期的某个部分<br>
 	 * 如果此对象为可变对象,返回自身,否则返回新对象,设置是否可变对象见{@link #setMutable(boolean)}
-	 * 
+	 *
 	 * @param field 表示日期的哪个部分的枚举 {@link DateField}
 	 * @param value 值
 	 * @return {@link DateTime}
@@ -266,7 +276,7 @@ public class DateTime extends Date {
 	/**
 	 * 设置日期的某个部分<br>
 	 * 如果此对象为可变对象,返回自身,否则返回新对象,设置是否可变对象见{@link #setMutable(boolean)}
-	 * 
+	 *
 	 * @param field 表示日期的哪个部分的int值 {@link Calendar}
 	 * @param value 值
 	 * @return {@link DateTime}
@@ -293,7 +303,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 获得年的部分
-	 * 
+	 *
 	 * @return 年的部分
 	 */
 	public int year() {
@@ -306,7 +316,7 @@ public class DateTime extends Date {
 	 * 2:第二季度<br>
 	 * 3:第三季度<br>
 	 * 4:第四季度<br>
-	 * 
+	 *
 	 * @return 第几个季度
 	 * @deprecated 请使用{@link Quarter}代替
 	 */
@@ -317,7 +327,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 获得当前日期所属季度<br>
-	 * 
+	 *
 	 * @return 第几个季度 {@link Season}
 	 * @deprecated 请使用{@link #quarterEnum}代替
 	 */
@@ -328,7 +338,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 获得当前日期所属季度,从1开始计数<br>
-	 * 
+	 *
 	 * @return 第几个季度 {@link Quarter}
 	 */
 	public int quarter() {
@@ -337,7 +347,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 获得当前日期所属季度<br>
-	 * 
+	 *
 	 * @return 第几个季度 {@link Quarter}
 	 */
 	public Quarter quarterEnum() {
@@ -346,7 +356,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 获得月份,从0开始计数
-	 * 
+	 *
 	 * @return 月份
 	 */
 	public int month() {
@@ -356,7 +366,7 @@ public class DateTime extends Date {
 	/**
 	 * 获得月份,从1开始计数<br>
 	 * 由于{@link Calendar} 中的月份按照0开始计数,导致某些需求容易误解,因此如果想用1表示一月,2表示二月则调用此方法
-	 * 
+	 *
 	 * @return 月份
 	 */
 	public int monthStartFromOne() {
@@ -365,7 +375,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 获得月份
-	 * 
+	 *
 	 * @return {@link Month}
 	 */
 	public Month monthEnum() {
@@ -378,7 +388,7 @@ public class DateTime extends Date {
 	 * 2016年1月3日为周日,如果一周的第一天为周日,那这天是第二周(返回2)<br>
 	 * 如果一周的第一天为周一,那这天是第一周(返回1)<br>
 	 * 跨年的那个星期得到的结果总是1
-	 * 
+	 *
 	 * @return 周
 	 * @see #setFirstDayOfWeek(Week)
 	 */
@@ -391,7 +401,7 @@ public class DateTime extends Date {
 	 * 此方法返回值与一周的第一天有关,比如:<br>
 	 * 2016年1月3日为周日,如果一周的第一天为周日,那这天是第二周(返回2)<br>
 	 * 如果一周的第一天为周一,那这天是第一周(返回1)
-	 * 
+	 *
 	 * @return 周
 	 * @see #setFirstDayOfWeek(Week)
 	 */
@@ -401,7 +411,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 获得指定日期是这个日期所在月份的第几天<br>
-	 * 
+	 *
 	 * @return 天
 	 */
 	public int dayOfMonth() {
@@ -410,7 +420,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 获得指定日期是星期几,1表示周日,2表示周一
-	 * 
+	 *
 	 * @return 星期几
 	 */
 	public int dayOfWeek() {
@@ -419,7 +429,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 获得天所在的周是这个月的第几周
-	 * 
+	 *
 	 * @return 天
 	 */
 	public int dayOfWeekInMonth() {
@@ -428,7 +438,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 获得指定日期是星期几
-	 * 
+	 *
 	 * @return {@link Week}
 	 */
 	public Week dayOfWeekEnum() {
@@ -437,7 +447,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 获得指定日期的小时数部分<br>
-	 * 
+	 *
 	 * @param is24HourClock 是否24小时制
 	 * @return 小时数
 	 */
@@ -448,7 +458,7 @@ public class DateTime extends Date {
 	/**
 	 * 获得指定日期的分钟数部分<br>
 	 * 例如:10:04:15.250 =》 4
-	 * 
+	 *
 	 * @return 分钟数
 	 */
 	public int minute() {
@@ -457,7 +467,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 获得指定日期的秒数部分<br>
-	 * 
+	 *
 	 * @return 秒数
 	 */
 	public int second() {
@@ -466,7 +476,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 获得指定日期的毫秒数部分<br>
-	 * 
+	 *
 	 * @return 毫秒数
 	 */
 	public int millsecond() {
@@ -475,7 +485,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 是否为上午
-	 * 
+	 *
 	 * @return 是否为上午
 	 */
 	public boolean isAM() {
@@ -484,7 +494,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 是否为下午
-	 * 
+	 *
 	 * @return 是否为下午
 	 */
 	public boolean isPM() {
@@ -493,7 +503,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 是否为周末,周末指周六或者周日
-	 * 
+	 *
 	 * @return 是否为周末,周末指周六或者周日
 	 * @since 4.1.14
 	 */
@@ -505,9 +515,9 @@ public class DateTime extends Date {
 
 	/**
 	 * 是否闰年
-	 * 
-	 * @see DateUtil#isLeapYear(int)
+	 *
 	 * @return 是否闰年
+	 * @see DateUtil#isLeapYear(int)
 	 */
 	public boolean isLeapYear() {
 		return DateUtil.isLeapYear(year());
@@ -515,7 +525,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 转换为Calendar, 默认 {@link Locale}
-	 * 
+	 *
 	 * @return {@link Calendar}
 	 */
 	public Calendar toCalendar() {
@@ -524,7 +534,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 转换为Calendar
-	 * 
+	 *
 	 * @param locale 地域 {@link Locale}
 	 * @return {@link Calendar}
 	 */
@@ -534,7 +544,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 转换为Calendar
-	 * 
+	 *
 	 * @param zone 时区 {@link TimeZone}
 	 * @return {@link Calendar}
 	 */
@@ -544,8 +554,8 @@ public class DateTime extends Date {
 
 	/**
 	 * 转换为Calendar
-	 * 
-	 * @param zone 时区 {@link TimeZone}
+	 *
+	 * @param zone   时区 {@link TimeZone}
 	 * @param locale 地域 {@link Locale}
 	 * @return {@link Calendar}
 	 */
@@ -562,7 +572,7 @@ public class DateTime extends Date {
 	/**
 	 * 转换为 {@link Date}<br>
 	 * 考虑到很多框架(例如Hibernate)的兼容性,提供此方法返回JDK原生的Date对象
-	 * 
+	 *
 	 * @return {@link Date}
 	 * @since 3.2.2
 	 */
@@ -572,7 +582,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 转为{@link Timestamp}
-	 * 
+	 *
 	 * @return {@link Timestamp}
 	 */
 	public Timestamp toTimestamp() {
@@ -581,7 +591,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 转为 {@link java.sql.Date}
-	 * 
+	 *
 	 * @return {@link java.sql.Date}
 	 */
 	public java.sql.Date toSqlDate() {
@@ -590,7 +600,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 计算相差时长
-	 * 
+	 *
 	 * @param date 对比的日期
 	 * @return {@link DateBetween}
 	 */
@@ -600,7 +610,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 计算相差时长
-	 * 
+	 *
 	 * @param date 对比的日期
 	 * @param unit 单位 {@link DateUnit}
 	 * @return 相差时长
@@ -611,9 +621,9 @@ public class DateTime extends Date {
 
 	/**
 	 * 计算相差时长
-	 * 
-	 * @param date 对比的日期
-	 * @param unit 单位 {@link DateUnit}
+	 *
+	 * @param date        对比的日期
+	 * @param unit        单位 {@link DateUnit}
 	 * @param formatLevel 格式化级别
 	 * @return 相差时长
 	 */
@@ -624,9 +634,9 @@ public class DateTime extends Date {
 	/**
 	 * 当前日期是否在日期指定范围内<br>
 	 * 起始日期和结束日期可以互换
-	 * 
+	 *
 	 * @param beginDate 起始日期
-	 * @param endDate 结束日期
+	 * @param endDate   结束日期
 	 * @return 是否在范围内
 	 * @since 3.0.8
 	 */
@@ -640,7 +650,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 是否在给定日期之前
-	 * 
+	 *
 	 * @param date 日期
 	 * @return 是否在给定日期之前
 	 * @since 4.1.3
@@ -654,7 +664,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 是否在给定日期之前或与给定日期相等
-	 * 
+	 *
 	 * @param date 日期
 	 * @return 是否在给定日期之前或与给定日期相等
 	 * @since 3.0.9
@@ -668,7 +678,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 是否在给定日期之后
-	 * 
+	 *
 	 * @param date 日期
 	 * @return 是否在给定日期之后
 	 * @since 4.1.3
@@ -682,7 +692,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 是否在给定日期之后或与给定日期相等
-	 * 
+	 *
 	 * @param date 日期
 	 * @return 是否在给定日期之后或与给定日期相等
 	 * @since 3.0.9
@@ -703,7 +713,7 @@ public class DateTime extends Date {
 	 * <li>{@link DateTime#setField(int, int)}</li>
 	 * </ul>
 	 * 如果为不可变对象,{@link DateTime#setTime(long)}将抛出异常
-	 * 
+	 *
 	 * @return 对象是否可变
 	 */
 	public boolean isMutable() {
@@ -718,7 +728,7 @@ public class DateTime extends Date {
 	 * <li>{@link DateTime#setField(int, int)}</li>
 	 * </ul>
 	 * 如果为不可变对象,{@link DateTime#setTime(long)}将抛出异常
-	 * 
+	 *
 	 * @param mutable 是否可变
 	 * @return this
 	 */
@@ -729,7 +739,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 获得一周的第一天,默认为周一
-	 * 
+	 *
 	 * @return 一周的第一天
 	 */
 	public Week getFirstDayOfWeek() {
@@ -740,7 +750,7 @@ public class DateTime extends Date {
 	 * 设置一周的第一天<br>
 	 * JDK的Calendar中默认一周的第一天是周日,Hutool中将此默认值设置为周一<br>
 	 * 设置一周的第一天主要影响{@link #weekOfMonth()}和{@link #weekOfYear()} 两个方法
-	 * 
+	 *
 	 * @param firstDayOfWeek 一周的第一天
 	 * @return this
 	 * @see #weekOfMonth()
@@ -753,7 +763,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 设置时区
-	 * 
+	 *
 	 * @param timeZone 时区
 	 * @return this
 	 * @since 4.1.2
@@ -764,10 +774,11 @@ public class DateTime extends Date {
 	}
 
 	// -------------------------------------------------------------------- toString start
+
 	/**
 	 * 转为"yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串<br>
 	 * 如果时区被设置,会转换为其时区对应的时间,否则转换为当前地点对应的时区
-	 * 
+	 *
 	 * @return "yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串
 	 */
 	@Override
@@ -778,7 +789,7 @@ public class DateTime extends Date {
 	/**
 	 * 转为"yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串<br>
 	 * 时区使用当前地区的默认时区
-	 * 
+	 *
 	 * @return "yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串
 	 * @since 4.1.14
 	 */
@@ -789,7 +800,7 @@ public class DateTime extends Date {
 	/**
 	 * 转为"yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串<br>
 	 * 如果时区不为{@code null},会转换为其时区对应的时间,否则转换为当前时间对应的时区
-	 * 
+	 *
 	 * @param timeZone 时区
 	 * @return "yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串
 	 * @since 4.1.14
@@ -805,7 +816,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 转为"yyyy-MM-dd " 格式字符串
-	 * 
+	 *
 	 * @return "yyyy-MM-dd " 格式字符串
 	 * @since 4.0.0
 	 */
@@ -820,7 +831,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 转为"HH:mm:ss" 格式字符串
-	 * 
+	 *
 	 * @return "HH:mm:ss" 格式字符串
 	 * @since 4.1.4
 	 */
@@ -835,7 +846,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 转为字符串
-	 * 
+	 *
 	 * @param format 日期格式,常用格式见: {@link DatePattern}
 	 * @return String
 	 */
@@ -850,7 +861,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 转为字符串
-	 * 
+	 *
 	 * @param format {@link DatePrinter} 或 {@link FastDateFormat}
 	 * @return String
 	 */
@@ -860,7 +871,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 转为字符串
-	 * 
+	 *
 	 * @param format {@link SimpleDateFormat}
 	 * @return String
 	 */
@@ -878,8 +889,8 @@ public class DateTime extends Date {
 
 	/**
 	 * 转换字符串为Date
-	 * 
-	 * @param dateStr 日期字符串
+	 *
+	 * @param dateStr    日期字符串
 	 * @param dateFormat {@link SimpleDateFormat}
 	 * @return {@link Date}
 	 */
@@ -899,9 +910,9 @@ public class DateTime extends Date {
 
 	/**
 	 * 转换字符串为Date
-	 * 
+	 *
 	 * @param dateStr 日期字符串
-	 * @param parser {@link FastDateFormat}
+	 * @param parser  {@link FastDateFormat}
 	 * @return {@link Date}
 	 */
 	private static Date parse(String dateStr, DateParser parser) {
@@ -916,7 +927,7 @@ public class DateTime extends Date {
 
 	/**
 	 * 设置日期时间
-	 * 
+	 *
 	 * @param time 日期时间毫秒
 	 * @return this
 	 */

+ 2 - 5
hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java

@@ -1325,7 +1325,7 @@ public class DateUtil {
 	}
 
 	/**
-	 * 获取指定日期偏移指定时间后的时间
+	 * 获取指定日期偏移指定时间后的时间,生成的偏移日期不影响原日期
 	 * 
 	 * @param date 基准日期
 	 * @param dateField 偏移的粒度大小(小时、天、月等){@link DateField}
@@ -1333,10 +1333,7 @@ public class DateUtil {
 	 * @return 偏移后的日期
 	 */
 	public static DateTime offset(Date date, DateField dateField, int offset) {
-		Calendar cal = Calendar.getInstance();
-		cal.setTime(date);
-		cal.add(dateField.getValue(), offset);
-		return new DateTime(cal.getTime());
+		return dateNew(date).offset(dateField, offset);
 	}
 
 	/**

+ 36 - 18
hutool-core/src/main/java/cn/hutool/core/date/TimeInterval.java

@@ -5,13 +5,12 @@ import java.io.Serializable;
 /**
  * 计时器<br>
  * 计算某个过程花费的时间,精确到毫秒
- * 
- * @author Looly
  *
+ * @author Looly
  */
-public class TimeInterval implements Serializable{
+public class TimeInterval implements Serializable {
 	private static final long serialVersionUID = 1L;
-	
+
 	private long time;
 	private boolean isNano;
 
@@ -41,73 +40,92 @@ public class TimeInterval implements Serializable{
 		time = now;
 		return d;
 	}
-	
+
 	/**
 	 * 重新开始计算时间(重置开始时间)
+	 *
 	 * @return this
 	 * @since 3.0.1
 	 */
-	public TimeInterval restart(){
+	public TimeInterval restart() {
 		time = DateUtil.current(isNano);
 		return this;
 	}
 
 	//----------------------------------------------------------- Interval
+
 	/**
 	 * 从开始到当前的间隔时间(毫秒数)<br>
 	 * 如果使用纳秒计时,返回纳秒差,否则返回毫秒差
+	 *
 	 * @return 从开始到当前的间隔时间(毫秒数)
 	 */
 	public long interval() {
 		return DateUtil.current(isNano) - time;
 	}
-	
+
+	/**
+	 * 从开始到当前的间隔时间(毫秒数),返回XX天XX小时XX分XX秒XX毫秒
+	 *
+	 * @return 从开始到当前的间隔时间(毫秒数)
+	 * @since 4.6.7
+	 */
+	public String intervalPretty() {
+		return DateUtil.formatBetween(intervalMs());
+	}
+
 	/**
 	 * 从开始到当前的间隔时间(毫秒数)
+	 *
 	 * @return 从开始到当前的间隔时间(毫秒数)
 	 */
 	public long intervalMs() {
 		return isNano ? interval() / 1000000L : interval();
 	}
-	
+
 	/**
 	 * 从开始到当前的间隔秒数,取绝对值
+	 *
 	 * @return 从开始到当前的间隔秒数,取绝对值
 	 */
-	public long intervalSecond(){
+	public long intervalSecond() {
 		return intervalMs() / DateUnit.SECOND.getMillis();
 	}
-	
+
 	/**
 	 * 从开始到当前的间隔分钟数,取绝对值
+	 *
 	 * @return 从开始到当前的间隔分钟数,取绝对值
 	 */
-	public long intervalMinute(){
+	public long intervalMinute() {
 		return intervalMs() / DateUnit.MINUTE.getMillis();
 	}
-	
+
 	/**
 	 * 从开始到当前的间隔小时数,取绝对值
+	 *
 	 * @return 从开始到当前的间隔小时数,取绝对值
 	 */
-	public long intervalHour(){
+	public long intervalHour() {
 		return intervalMs() / DateUnit.HOUR.getMillis();
 	}
-	
+
 	/**
 	 * 从开始到当前的间隔天数,取绝对值
+	 *
 	 * @return 从开始到当前的间隔天数,取绝对值
 	 */
-	public long intervalDay(){
+	public long intervalDay() {
 		return intervalMs() / DateUnit.DAY.getMillis();
 	}
-	
+
 	/**
 	 * 从开始到当前的间隔周数,取绝对值
+	 *
 	 * @return 从开始到当前的间隔周数,取绝对值
 	 */
-	public long intervalWeek(){
+	public long intervalWeek() {
 		return intervalMs() / DateUnit.WEEK.getMillis();
 	}
-	
+
 }

+ 37 - 34
hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java

@@ -1,31 +1,23 @@
 package cn.hutool.core.date;
 
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.TimeZone;
-
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.BetweenFormater.Level;
 import org.junit.Assert;
 import org.junit.Test;
 
-import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.date.BetweenFormater.Level;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
 
 /**
  * 时间工具单元测试<br>
  * 此单元测试依赖时区为中国+08:00
- * 
+ *
  * <pre>
  * export TZ=Asia/Shanghai
  * </pre>
- * 
- * @author Looly
  *
+ * @author Looly
  */
 public class DateUtilTest {
 
@@ -289,13 +281,13 @@ public class DateUtilTest {
 	}
 
 	@Test
-	public void parseTest4() throws ParseException {
+	public void parseTest4() {
 		String ymd = DateUtil.parse("2019-3-21 12:20:15", "yyyy-MM-dd").toString(DatePattern.PURE_DATE_PATTERN);
 		Assert.assertEquals("20190321", ymd);
 	}
 
 	@Test
-	public void parseTest5() throws ParseException {
+	public void parseTest5() {
 		// 测试时间解析
 		String time = DateUtil.parse("22:12:12").toString(DatePattern.NORM_TIME_FORMAT);
 		Assert.assertEquals("22:12:12", time);
@@ -321,21 +313,32 @@ public class DateUtilTest {
 	}
 
 	@Test
-	public void parseTest6() throws ParseException {
+	public void parseTest6() {
 		String str = "Tue Jun 4 16:25:15 +0800 2019";
 		DateTime dateTime = DateUtil.parse(str);
 		Assert.assertEquals("2019-06-04 16:25:15", dateTime.toString());
 	}
-	
+
 	@Test
-	public void parseTest7() throws ParseException {
+	public void parseTest7() {
 		String str = "2019-06-01T19:45:43.000 +0800";
 		DateTime dateTime = DateUtil.parse(str, "yyyy-MM-dd'T'HH:mm:ss.SSS Z");
 		Assert.assertEquals("2019-06-01 19:45:43", dateTime.toString());
 	}
 
 	@Test
-	public void parseDateTest() throws ParseException {
+	public void parseAndOffsetTest() {
+		// 检查UTC时间偏移是否准确
+		String str = "2019-09-17T13:26:17.948Z";
+		DateTime dateTime = DateUtil.parse(str);
+		Assert.assertEquals("2019-09-17 13:26:17", dateTime.toString());
+
+		DateTime offset = DateUtil.offsetHour(dateTime, 8);
+		Assert.assertEquals("2019-09-17 21:26:17", offset.toString());
+	}
+
+	@Test
+	public void parseDateTest() {
 		String dateStr = "2018-4-10";
 		Date date = DateUtil.parseDate(dateStr);
 		String format = DateUtil.format(date, DatePattern.NORM_DATE_PATTERN);
@@ -411,7 +414,7 @@ public class DateUtilTest {
 	}
 
 	@Test
-	public void parseUTCTest() throws ParseException {
+	public void parseUTCTest() {
 		String dateStr1 = "2018-09-13T05:34:31Z";
 		DateTime dt = DateUtil.parseUTC(dateStr1);
 
@@ -426,44 +429,44 @@ public class DateUtilTest {
 		// 使用当前(上海)时区
 		dateStr = dt.toString(TimeZone.getTimeZone("GMT+8:00"));
 		Assert.assertEquals("2018-09-13 13:34:31", dateStr);
-		
+
 		dateStr1 = "2018-09-13T13:34:32+0800";
 		dt = DateUtil.parseUTC(dateStr1);
 		dateStr = dt.toString(TimeZone.getTimeZone("GMT+8:00"));
 		Assert.assertEquals("2018-09-13 13:34:32", dateStr);
-		
+
 		dateStr1 = "2018-09-13T13:34:33+08:00";
 		dt = DateUtil.parseUTC(dateStr1);
 		dateStr = dt.toString(TimeZone.getTimeZone("GMT+8:00"));
 		Assert.assertEquals("2018-09-13 13:34:33", dateStr);
-		
+
 		dateStr1 = "2018-09-13T13:34:34+0800";
 		dt = DateUtil.parse(dateStr1);
 		dateStr = dt.toString(TimeZone.getTimeZone("GMT+8:00"));
 		Assert.assertEquals("2018-09-13 13:34:34", dateStr);
-		
+
 		dateStr1 = "2018-09-13T13:34:35+08:00";
 		dt = DateUtil.parse(dateStr1);
 		dateStr = dt.toString(TimeZone.getTimeZone("GMT+8:00"));
 		Assert.assertEquals("2018-09-13 13:34:35", dateStr);
-		
+
 		dateStr1 = "2018-09-13T13:34:36.999+0800";
 		dt = DateUtil.parseUTC(dateStr1);
 		final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DatePattern.NORM_DATETIME_MS_PATTERN);
 		simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
 		dateStr = dt.toString(simpleDateFormat);
 		Assert.assertEquals("2018-09-13 13:34:36.999", dateStr);
-		
+
 		dateStr1 = "2018-09-13T13:34:37.999+08:00";
 		dt = DateUtil.parseUTC(dateStr1);
 		dateStr = dt.toString(simpleDateFormat);
 		Assert.assertEquals("2018-09-13 13:34:37.999", dateStr);
-		
+
 		dateStr1 = "2018-09-13T13:34:38.999+0800";
 		dt = DateUtil.parse(dateStr1);
 		dateStr = dt.toString(simpleDateFormat);
 		Assert.assertEquals("2018-09-13 13:34:38.999", dateStr);
-		
+
 		dateStr1 = "2018-09-13T13:34:39.999+08:00";
 		dt = DateUtil.parse(dateStr1);
 		dateStr = dt.toString(simpleDateFormat);
@@ -471,7 +474,7 @@ public class DateUtilTest {
 	}
 
 	@Test
-	public void parseJDkTest() throws ParseException {
+	public void parseJDkTest() {
 		String dateStr = "Thu May 16 17:57:18 GMT+08:00 2019";
 		DateTime time = DateUtil.parse(dateStr);
 		Assert.assertEquals("2019-05-16 17:57:18", time.toString());
@@ -484,7 +487,7 @@ public class DateUtilTest {
 		DateTime endOfYear = DateUtil.endOfYear(date);
 		Assert.assertEquals("2019-12-31 23:59:59", endOfYear.toString());
 	}
-	
+
 	@Test
 	public void endOfWeekTest() {
 		// 周日
@@ -521,7 +524,7 @@ public class DateUtilTest {
 		try {
 			range.next();
 			Assert.fail("已超过边界,下一个元素不应该存在!");
-		} catch (NoSuchElementException e) {
+		} catch (NoSuchElementException ignored) {
 		}
 
 		// 测试多步进的情况
@@ -535,7 +538,7 @@ public class DateUtilTest {
 		try {
 			range.next();
 			Assert.fail("不包含结束时间情况下,下一个元素不应该存在!");
-		} catch (NoSuchElementException e) {
+		} catch (NoSuchElementException ignored) {
 		}
 	}
 

+ 153 - 118
hutool-http/src/main/java/cn/hutool/http/HttpRequest.java

@@ -41,7 +41,7 @@ import cn.hutool.json.JSON;
 /**
  * http请求类<br>
  * Http请求类用于构建Http请求并同步获取结果,此类通过CookieManager持有域名对应的Cookie值,再次请求时会自动附带Cookie信息
- * 
+ *
  * @author Looly
  */
 public class HttpRequest extends HttpBase<HttpRequest> {
@@ -53,10 +53,10 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	private static final String CONTENT_TYPE_MULTIPART_PREFIX = "multipart/form-data; boundary=";
 	private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n\r\n";
-	
+
 	/**
 	 * 设置全局默认的连接和读取超时时长
-	 * 
+	 *
 	 * @param customTimeout 超时时长
 	 * @see HttpGlobalConfig#setTimeout(int)
 	 * @since 4.6.2
@@ -67,10 +67,10 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 获取Cookie管理器,用于自定义Cookie管理
-	 * 
+	 *
 	 * @return {@link CookieManager}
-	 * @since 4.1.0
 	 * @see GlobalCookieManager#getCookieManager()
+	 * @since 4.1.0
 	 */
 	public static CookieManager getCookieManager() {
 		return GlobalCookieManager.getCookieManager();
@@ -78,10 +78,10 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 自定义{@link CookieManager}
-	 * 
+	 *
 	 * @param customCookieManager 自定义的{@link CookieManager}
-	 * @since 4.5.14
 	 * @see GlobalCookieManager#setCookieManager(CookieManager)
+	 * @since 4.5.14
 	 */
 	public static void setCookieManager(CookieManager customCookieManager) {
 		GlobalCookieManager.setCookieManager(customCookieManager);
@@ -89,9 +89,9 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 关闭Cookie
-	 * 
-	 * @since 4.1.9
+	 *
 	 * @see GlobalCookieManager#setCookieManager(CookieManager)
+	 * @since 4.1.9
 	 */
 	public static void closeCookie() {
 		GlobalCookieManager.setCookieManager(null);
@@ -100,42 +100,72 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	private String url;
 	private URLStreamHandler urlHandler;
 	private Method method = Method.GET;
-	/** 默认连接超时 */
+	/**
+	 * 默认连接超时
+	 */
 	private int connectionTimeout = HttpGlobalConfig.timeout;
-	/** 默认读取超时 */
+	/**
+	 * 默认读取超时
+	 */
 	private int readTimeout = HttpGlobalConfig.timeout;
-	/** 存储表单数据 */
+	/**
+	 * 存储表单数据
+	 */
 	private Map<String, Object> form;
-	/** 文件表单对象,用于文件上传 */
+	/**
+	 * 文件表单对象,用于文件上传
+	 */
 	private Map<String, Resource> fileForm;
-	/** Cookie */
+	/**
+	 * Cookie
+	 */
 	private String cookie;
 
-	/** 连接对象 */
+	/**
+	 * 连接对象
+	 */
 	private HttpConnection httpConnection;
-	/** 是否禁用缓存 */
+	/**
+	 * 是否禁用缓存
+	 */
 	private boolean isDisableCache;
-	/** 是否对url中的参数进行编码 */
+	/**
+	 * 是否对url中的参数进行编码
+	 */
 	private boolean encodeUrlParams;
-	/** 是否是REST请求模式 */
+	/**
+	 * 是否是REST请求模式
+	 */
 	private boolean isRest;
-	/** 重定向次数计数器,内部使用 */
+	/**
+	 * 重定向次数计数器,内部使用
+	 */
 	private int redirectCount;
-	/** 最大重定向次数 */
+	/**
+	 * 最大重定向次数
+	 */
 	private int maxRedirectCount;
-	/** Chuncked块大小,0或小于0表示不设置Chuncked模式 */
+	/**
+	 * Chuncked块大小,0或小于0表示不设置Chuncked模式
+	 */
 	private int blockSize;
-	/** 代理 */
+	/**
+	 * 代理
+	 */
 	private Proxy proxy;
 
-	/** HostnameVerifier,用于HTTPS安全连接 */
+	/**
+	 * HostnameVerifier,用于HTTPS安全连接
+	 */
 	private HostnameVerifier hostnameVerifier;
-	/** SSLSocketFactory,用于HTTPS安全连接 */
+	/**
+	 * SSLSocketFactory,用于HTTPS安全连接
+	 */
 	private SSLSocketFactory ssf;
 
 	/**
 	 * 构造
-	 * 
+	 *
 	 * @param url URL
 	 */
 	public HttpRequest(String url) {
@@ -146,9 +176,10 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	}
 
 	// ---------------------------------------------------------------- static Http Method start
+
 	/**
 	 * POST请求
-	 * 
+	 *
 	 * @param url URL
 	 * @return HttpRequest
 	 */
@@ -158,7 +189,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * GET请求
-	 * 
+	 *
 	 * @param url URL
 	 * @return HttpRequest
 	 */
@@ -168,7 +199,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * HEAD请求
-	 * 
+	 *
 	 * @param url URL
 	 * @return HttpRequest
 	 */
@@ -178,7 +209,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * OPTIONS请求
-	 * 
+	 *
 	 * @param url URL
 	 * @return HttpRequest
 	 */
@@ -188,7 +219,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * PUT请求
-	 * 
+	 *
 	 * @param url URL
 	 * @return HttpRequest
 	 */
@@ -198,7 +229,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * PATCH请求
-	 * 
+	 *
 	 * @param url URL
 	 * @return HttpRequest
 	 * @since 3.0.9
@@ -209,7 +240,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * DELETE请求
-	 * 
+	 *
 	 * @param url URL
 	 * @return HttpRequest
 	 */
@@ -219,7 +250,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * TRACE请求
-	 * 
+	 *
 	 * @param url URL
 	 * @return HttpRequest
 	 */
@@ -230,7 +261,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 获取请求URL
-	 * 
+	 *
 	 * @return URL字符串
 	 * @since 4.1.8
 	 */
@@ -240,7 +271,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 设置URL
-	 * 
+	 *
 	 * @param url url字符串
 	 * @since 4.1.8
 	 */
@@ -257,7 +288,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	 * 因此weblogic不支持https的sni协议的主机名验证,此时需要将此值设置为sun.net.www.protocol.https.Handler对象。
 	 * <p>
 	 * 相关issue见:https://gitee.com/loolly/hutool/issues/IMD1X
-	 * 
+	 *
 	 * @param urlHandler {@link URLStreamHandler}
 	 * @since 4.1.9
 	 */
@@ -268,7 +299,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 获取Http请求方法
-	 * 
+	 *
 	 * @return {@link Method}
 	 * @since 4.1.8
 	 */
@@ -278,7 +309,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 设置请求方法
-	 * 
+	 *
 	 * @param method HTTP方法
 	 * @return HttpRequest
 	 * @see #method(Method)
@@ -291,7 +322,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	/**
 	 * 获取{@link HttpConnection}<br>
 	 * 在{@link #execute()} 执行前此对象为null
-	 * 
+	 *
 	 * @return {@link HttpConnection}
 	 * @since 4.2.2
 	 */
@@ -301,7 +332,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 设置请求方法
-	 * 
+	 *
 	 * @param method HTTP方法
 	 * @return HttpRequest
 	 */
@@ -312,14 +343,15 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 		} else {
 			this.method = method;
 		}
-		
+
 		return this;
 	}
 
 	// ---------------------------------------------------------------- Http Request Header start
+
 	/**
 	 * 设置contentType
-	 * 
+	 *
 	 * @param contentType contentType
 	 * @return HttpRequest
 	 */
@@ -330,7 +362,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 设置是否为长连接
-	 * 
+	 *
 	 * @param isKeepAlive 是否长连接
 	 * @return HttpRequest
 	 */
@@ -353,7 +385,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 获取内容长度
-	 * 
+	 *
 	 * @return String
 	 */
 	public String contentLength() {
@@ -362,7 +394,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 设置内容长度
-	 * 
+	 *
 	 * @param value 长度
 	 * @return HttpRequest
 	 */
@@ -374,7 +406,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	/**
 	 * 设置Cookie<br>
 	 * 自定义Cookie后会覆盖Hutool的默认Cookie行为
-	 * 
+	 *
 	 * @param cookies Cookie值数组,如果为{@code null}则设置无效,使用默认Cookie行为
 	 * @return this
 	 * @since 3.1.1
@@ -389,7 +421,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	/**
 	 * 设置Cookie<br>
 	 * 自定义Cookie后会覆盖Hutool的默认Cookie行为
-	 * 
+	 *
 	 * @param cookie Cookie值,如果为{@code null}则设置无效,使用默认Cookie行为
 	 * @return this
 	 * @since 3.0.7
@@ -403,7 +435,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	 * 禁用默认Cookie行为,此方法调用后会将Cookie置为空。<br>
 	 * 如果想重新启用Cookie,请调用:{@link #cookie(String)}方法自定义Cookie。<br>
 	 * 如果想启动默认的Cookie行为(自动回填服务器传回的Cookie),则调用{@link #enableDefaultCookie()}
-	 * 
+	 *
 	 * @return this
 	 * @since 3.0.7
 	 */
@@ -413,7 +445,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 打开默认的Cookie行为(自动回填服务器传回的Cookie)
-	 * 
+	 *
 	 * @return this
 	 */
 	public HttpRequest enableDefaultCookie() {
@@ -422,10 +454,11 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	// ---------------------------------------------------------------- Http Request Header end
 
 	// ---------------------------------------------------------------- Form start
+
 	/**
 	 * 设置表单数据<br>
-	 * 
-	 * @param name 名
+	 *
+	 * @param name  
 	 * @param value 值
 	 * @return this
 	 */
@@ -469,12 +502,11 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 设置表单数据
-	 * 
-	 * @param name 名
-	 * @param value 值
+	 *
+	 * @param name       
+	 * @param value      
 	 * @param parameters 参数对,奇数为名,偶数为值
 	 * @return this
-	 * 
 	 */
 	public HttpRequest form(String name, Object value, Object... parameters) {
 		form(name, value);
@@ -488,10 +520,9 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 设置map类型表单数据
-	 * 
+	 *
 	 * @param formMap 表单内容
 	 * @return this
-	 * 
 	 */
 	public HttpRequest form(Map<String, Object> formMap) {
 		if (MapUtil.isNotEmpty(formMap)) {
@@ -505,8 +536,8 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	/**
 	 * 文件表单项<br>
 	 * 一旦有文件加入,表单变为multipart/form-data
-	 * 
-	 * @param name 名
+	 *
+	 * @param name  
 	 * @param files 需要上传的文件
 	 * @return this
 	 */
@@ -521,7 +552,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	/**
 	 * 文件表单项<br>
 	 * 一旦有文件加入,表单变为multipart/form-data
-	 * 
+	 *
 	 * @param name 名
 	 * @param file 需要上传的文件
 	 * @return this
@@ -533,9 +564,9 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	/**
 	 * 文件表单项<br>
 	 * 一旦有文件加入,表单变为multipart/form-data
-	 * 
-	 * @param name 名
-	 * @param file 需要上传的文件
+	 *
+	 * @param name     
+	 * @param file     需要上传的文件
 	 * @param fileName 文件名,为空使用文件默认的文件名
 	 * @return this
 	 */
@@ -549,10 +580,10 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	/**
 	 * 文件byte[]表单项<br>
 	 * 一旦有文件加入,表单变为multipart/form-data
-	 * 
-	 * @param name 名
+	 *
+	 * @param name      
 	 * @param fileBytes 需要上传的文件
-	 * @param fileName 文件名
+	 * @param fileName  文件名
 	 * @return this
 	 * @since 4.1.0
 	 */
@@ -566,8 +597,8 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	/**
 	 * 文件表单项<br>
 	 * 一旦有文件加入,表单变为multipart/form-data
-	 * 
-	 * @param name 名
+	 *
+	 * @param name     
 	 * @param resource 数据源,文件可以使用{@link FileResource}包装使用
 	 * @return this
 	 * @since 4.0.9
@@ -589,7 +620,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 获取表单数据
-	 * 
+	 *
 	 * @return 表单Map
 	 */
 	public Map<String, Object> form() {
@@ -598,7 +629,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 获取文件表单数据
-	 * 
+	 *
 	 * @return 文件表单Map
 	 * @since 3.3.0
 	 */
@@ -608,15 +639,16 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	// ---------------------------------------------------------------- Form end
 
 	// ---------------------------------------------------------------- Body start
+
 	/**
 	 * 设置内容主体<br>
 	 * 请求体body参数支持两种类型:
-	 * 
+	 *
 	 * <pre>
 	 * 1. 标准参数,例如 a=1&amp;b=2 这种格式
 	 * 2. Rest模式,此时body需要传入一个JSON或者XML字符串,Hutool会自动绑定其对应的Content-Type
 	 * </pre>
-	 * 
+	 *
 	 * @param body 请求体
 	 * @return this
 	 */
@@ -627,13 +659,13 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	/**
 	 * 设置内容主体<br>
 	 * 请求体body参数支持两种类型:
-	 * 
+	 *
 	 * <pre>
 	 * 1. 标准参数,例如 a=1&amp;b=2 这种格式
 	 * 2. Rest模式,此时body需要传入一个JSON或者XML字符串,Hutool会自动绑定其对应的Content-Type
 	 * </pre>
-	 * 
-	 * @param body 请求体
+	 *
+	 * @param body        请求体
 	 * @param contentType 请求体类型,{@code null}表示自动判断类型
 	 * @return this
 	 */
@@ -668,7 +700,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	/**
 	 * 设置JSON内容主体<br>
 	 * 设置默认的Content-Type为 application/json 需在此方法调用前使用charset方法设置编码,否则使用默认编码UTF-8
-	 * 
+	 *
 	 * @param json JSON请求体
 	 * @return this
 	 * @deprecated 未来可能去除此方法,使用{@link #body(String)} 传入JSON字符串即可
@@ -681,13 +713,14 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	/**
 	 * 设置主体字节码<br>
 	 * 需在此方法调用前使用charset方法设置编码,否则使用默认编码UTF-8
-	 * 
+	 *
 	 * @param bodyBytes 主体
 	 * @return this
 	 */
 	public HttpRequest body(byte[] bodyBytes) {
-		Assert.notNull(bodyBytes, "Body must be not null !");
-		this.bodyBytes = bodyBytes;
+		if (null != bodyBytes) {
+			this.bodyBytes = bodyBytes;
+		}
 		return this;
 	}
 	// ---------------------------------------------------------------- Body end
@@ -695,12 +728,12 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	/**
 	 * 设置超时,单位:毫秒<br>
 	 * 超时包括:
-	 * 
+	 *
 	 * <pre>
 	 * 1. 连接超时
 	 * 2. 读取响应超时
 	 * </pre>
-	 * 
+	 *
 	 * @param milliseconds 超时毫秒数
 	 * @return this
 	 * @see #setConnectionTimeout(int)
@@ -714,7 +747,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 设置连接超时,单位:毫秒
-	 * 
+	 *
 	 * @param milliseconds 超时毫秒数
 	 * @return this
 	 * @since 4.5.6
@@ -726,7 +759,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 设置连接超时,单位:毫秒
-	 * 
+	 *
 	 * @param milliseconds 超时毫秒数
 	 * @return this
 	 * @since 4.5.6
@@ -738,7 +771,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 禁用缓存
-	 * 
+	 *
 	 * @return this
 	 */
 	public HttpRequest disableCache() {
@@ -748,7 +781,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 是否对URL中的参数进行编码
-	 * 
+	 *
 	 * @param isEncodeUrlParams 是否对URL中的参数进行编码
 	 * @return this
 	 * @since 4.4.1
@@ -761,7 +794,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	/**
 	 * 设置是否打开重定向,如果打开默认重定向次数为2<br>
 	 * 此方法效果与{@link #setMaxRedirectCount(int)} 一致
-	 * 
+	 *
 	 * @param isFollowRedirects 是否打开重定向
 	 * @return this
 	 */
@@ -772,7 +805,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	/**
 	 * 设置最大重定向次数<br>
 	 * 如果次数小于1则表示不重定向,大于等于1表示打开重定向
-	 * 
+	 *
 	 * @param maxRedirectCount 最大重定向次数
 	 * @return this
 	 * @since 3.3.0
@@ -789,7 +822,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	/**
 	 * 设置域名验证器<br>
 	 * 只针对HTTPS请求,如果不设置,不做验证,所有域名被信任
-	 * 
+	 *
 	 * @param hostnameVerifier HostnameVerifier
 	 * @return this
 	 */
@@ -801,7 +834,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 设置代理
-	 * 
+	 *
 	 * @param proxy 代理 {@link Proxy}
 	 * @return this
 	 */
@@ -814,7 +847,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	 * 设置SSLSocketFactory<br>
 	 * 只针对HTTPS请求,如果不设置,使用默认的SSLSocketFactory<br>
 	 * 默认SSLSocketFactory为:SSLSocketFactoryBuilder.create().build();
-	 * 
+	 *
 	 * @param ssf SSLScketFactory
 	 * @return this
 	 */
@@ -825,17 +858,17 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 设置HTTPS安全连接协议,只针对HTTPS请求,可以使用的协议包括:<br>
-	 * 
+	 *
 	 * <pre>
 	 * 1. TLSv1.2
 	 * 2. TLSv1.1
 	 * 3. SSLv3
 	 * ...
 	 * </pre>
-	 * 
-	 * @see SSLSocketFactoryBuilder
+	 *
 	 * @param protocol 协议
 	 * @return this
+	 * @see SSLSocketFactoryBuilder
 	 */
 	public HttpRequest setSSLProtocol(String protocol) {
 		if (null == this.ssf) {
@@ -850,7 +883,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 设置是否rest模式
-	 * 
+	 *
 	 * @param isRest 是否rest模式
 	 * @return this
 	 * @since 4.5.0
@@ -859,11 +892,11 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 		this.isRest = isRest;
 		return this;
 	}
-	
+
 	/**
 	 * 采用流方式上传数据,无需本地缓存数据。<br>
 	 * HttpUrlConnection默认是将所有数据读到本地缓存,然后再发送给服务器,这样上传大文件时就会导致内存溢出。
-	 * 
+	 *
 	 * @param blockSize 块大小(bytes数),0或小于0表示不设置Chuncked模式
 	 * @return this
 	 * @since 4.6.5
@@ -875,7 +908,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 执行Reuqest请求
-	 * 
+	 *
 	 * @return this
 	 */
 	public HttpResponse execute() {
@@ -886,11 +919,10 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	 * 异步请求<br>
 	 * 异步请求后获取的{@link HttpResponse} 为异步模式,执行完此方法后发送请求到服务器,但是并不立即读取响应内容。<br>
 	 * 此时保持Http连接不关闭,直调用获取内容方法为止。
-	 * 
+	 *
 	 * <p>
 	 * 一般执行完execute之后会把响应内容全部读出来放在一个 byte数组里,如果你响应的内容太多内存就爆了,此法是发送完请求不直接读响应内容,等有需要的时候读。
-
-	 * 
+	 *
 	 * @return 异步对象,使用get方法获取HttpResponse对象
 	 */
 	public HttpResponse executeAsync() {
@@ -899,7 +931,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 执行Reuqest请求
-	 * 
+	 *
 	 * @param isAsync 是否异步
 	 * @return this
 	 */
@@ -928,7 +960,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 简单验证
-	 * 
+	 *
 	 * @param username 用户名
 	 * @param password 密码
 	 * @return HttpRequest
@@ -943,15 +975,16 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	}
 
 	// ---------------------------------------------------------------- Private method start
+
 	/**
 	 * 初始化网络连接
 	 */
 	private void initConnecton() {
-		if(null != this.httpConnection) {
+		if (null != this.httpConnection) {
 			// 执行下次请求时自动关闭上次请求(常用于转发)
 			this.httpConnection.disconnectQuietly();
 		}
-		
+
 		this.httpConnection = HttpConnection.create(URLUtil.toUrlForHttp(this.url, this.urlHandler), this.proxy)//
 				.setMethod(this.method)//
 				.setHttpsInfo(this.hostnameVerifier, this.ssf)//
@@ -965,7 +998,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 				.setChunkedStreamingMode(this.blockSize)
 				// 覆盖默认Header
 				.header(this.headers, true);
-		
+
 		// 读取全局Cookie信息并附带到请求中
 		GlobalCookieManager.add(this.httpConnection);
 
@@ -992,7 +1025,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 调用转发,如果需要转发返回转发结果,否则返回<code>null</code>
-	 * 
+	 *
 	 * @return {@link HttpResponse},无转发返回 <code>null</code>
 	 */
 	private HttpResponse sendRedirectIfPosible() {
@@ -1026,7 +1059,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 发送数据流
-	 * 
+	 *
 	 * @throws IOException
 	 */
 	private void send() throws HttpException {
@@ -1050,7 +1083,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	/**
 	 * 发送普通表单<br>
 	 * 发送数据后自动关闭输出流
-	 * 
+	 *
 	 * @throws IOException
 	 */
 	private void sendFormUrlEncoded() throws IOException {
@@ -1071,13 +1104,13 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	/**
 	 * 发送多组件请求(例如包含文件的表单)<br>
 	 * 发送数据后自动关闭输出流
-	 * 
+	 *
 	 * @throws IOException
 	 */
 	private void sendMultipart() throws IOException {
 		setMultipart();// 设置表单类型为Multipart
 
-		try(OutputStream out = this.httpConnection.getOutputStream()) {
+		try (OutputStream out = this.httpConnection.getOutputStream()) {
 			writeFileForm(out);
 			writeForm(out);
 			formEnd(out);
@@ -1087,9 +1120,10 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	}
 
 	// 普通字符串数据
+
 	/**
 	 * 发送普通表单内容
-	 * 
+	 *
 	 * @param out 输出流
 	 * @throws IOException
 	 */
@@ -1107,7 +1141,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 发送文件对象表单
-	 * 
+	 *
 	 * @param out 输出流
 	 * @throws IOException
 	 */
@@ -1119,10 +1153,10 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 添加Multipart表单的数据项
-	 * 
+	 *
 	 * @param formFieldName 表单名
-	 * @param resource 资源,可以是文件等
-	 * @param out Http流
+	 * @param resource      资源,可以是文件等
+	 * @param out           Http流
 	 * @since 4.1.0
 	 */
 	private void appendPart(String formFieldName, Resource resource, OutputStream out) {
@@ -1152,9 +1186,10 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	}
 
 	// 添加结尾数据
+
 	/**
 	 * 上传表单结束
-	 * 
+	 *
 	 * @param out 输出流
 	 * @throws IOException
 	 */
@@ -1165,7 +1200,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 
 	/**
 	 * 设置表单类型为Multipart(文件上传)
-	 * 
+	 *
 	 * @return HttpConnection
 	 */
 	private void setMultipart() {
@@ -1175,7 +1210,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
 	/**
 	 * 是否忽略读取响应body部分<br>
 	 * HEAD、CONNECT、OPTIONS、TRACE方法将不读取响应体
-	 * 
+	 *
 	 * @return 是否需要忽略响应body部分
 	 * @since 3.1.2
 	 */