package com.apobates.forum.utils;

import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.TimeZone;
import java.util.TreeMap;

/**
 * java.time包工具类
 * 
 * @author xiaofanku
 * @since 20190331
 */
public final class DateTimeUtils {
	public final static String Y = "Year";
	public final static String M = "Month";
	public final static String D = "Day";
	private final static String YMDHMS_L = "yyyy-MM-dd HH:mm:ss";
	private final static String YMDHMS = "yyyyMMddHHmmssSSS";

	private DateTimeUtils() throws Exception {
		throw new Exception("不需要实化公共工具类");
	}

	/**
	 * 格式化当前日期为yyyy-MM-dd HH:mm:ss<14> https://tools.ietf.org/html/rfc3339 示例:
	 * 1985-04-12T23:20:50.52Z 1996-12-19T16:39:57-08:00
	 *
	 * @param date
	 *            要求不为null,反之返回空字符串
	 * @return
	 */
	public static String getRFC3339(LocalDateTime date) {
		if (null == date) {
			return "";
		}
		return date.format(DateTimeFormatter.ofPattern(YMDHMS_L).withZone(ZoneId.systemDefault()));
	}

	/**
	 * 使用指定的时区进行格式化
	 * 
	 * @param date
	 *            要求不为null,反之返回空字符串
	 * @param zone
	 *            时区
	 * @return
	 */
	public static String getRFC3339(LocalDateTime date, ZoneId zone) {
		if (null == date) {
			return "";
		}
		return date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").withZone(zone));
	}

	/**
	 * 格式化当前日期为yyyy-MM-ddTHH:mm:ss<15>
	 * https://www.iso.org/iso-8601-date-and-time-format.html 示例:
	 * 2015-12-14T12:00:29.3387979-05:00 2008-09-15T15:53:00+05:00
	 *
	 * @param date
	 *            要求不为null,反之返回空字符串
	 * @return
	 */
	public final static String getISO8601(LocalDateTime date) {
		if (null == date) {
			return "";
		}
		return date.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
	}

	/**
	 * 格式化当前日期为yyyyMMdd<16>
	 *
	 * @return
	 */
	public static String getYMD() {
		return getYMDByLine().replaceAll("-", "");
	}

	/**
	 * 格式化日期
	 *
	 * @param date
	 * @return
	 */
	public static String formatClock(LocalDateTime date) {
		if (null == date) {
			return "-";
		}
		LocalDateTime n = LocalDateTime.now();
		if (!isSameYear(date, n)) { // 不是同一年
			return date.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
		}
		if (!isSameMonth(date, n)) { // 是同一年 // 不是同一个月{return m-d}
			return date.format(DateTimeFormatter.ofPattern("MM月dd日"));
		}
		if (!isSameDay(date, n)) { // 同一个月 // 不是同一天
			long dd = diffDays(n, date);
			return (dd <= 1) ? "昨天" : dd + "天前";
		}
		// 同一天
		long diffMin = diffMinutes(n, date);
		if (diffMin <= 1) { // 一分钟以内
			return "刚刚";
		}
		if (diffMin > 1 && diffMin < 60) { // 一小时以内
			return diffMin + "分钟前";
		}
		return Math.round(diffMin / 60) + "小时前";
	}

	/**
	 * 格式化指定日期为yyyyMMdd
	 *
	 * @param date
	 *            要求不为null,反之返回空字符串
	 * @return
	 */
	public static String getYMD(LocalDateTime date) {
		if (null == date) {
			return "";
		}
		return getYMDByLine(date.toLocalDate()).replaceAll("-", "");
	}

	/**
	 * 格式化当前日期为yyyy-MM-dd<17>
	 *
	 * @return
	 */
	public static String getYMDByLine() {
		return getYMDByLine(LocalDate.now());
	}

	/**
	 * 格式化指定日期为yyyy-MM-dd
	 *
	 * @param date
	 *            要求不为null,反之返回空字符串
	 * @return
	 */
	public static String getYMDByLine(LocalDate date) {
		if (null == date) {
			return "";
		}
		return date.toString();
	}

	/**
	 * 计算昨天的日期
	 *
	 * @return
	 */
	public static LocalDateTime getYesterDay() {
		return LocalDateTime.now().minusDays(1);
	}

	/**
	 * 格式化当前日期为HH:mm:ss<18>
	 *
	 * @return
	 */
	public static String getTimeString() {
		return LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
	}

	/**
	 * 格式化当前日期为yyyyMMddHHmmssSSS<19>
	 *
	 * @return
	 */
	public static String getDateFullFormatString() {
		return LocalDateTime.now().format(DateTimeFormatter.ofPattern(YMDHMS));
	}

	/**
	 * 指定的日期是否与当前日期是同一天<11>
	 *
	 * @param date
	 *            要求不为null,反之返回false
	 * @return
	 */
	public static boolean isSameDay(LocalDateTime date) {
		if (null == date) {
			return false;
		}
		return isSameDay(date, LocalDateTime.now());
	}

	/**
	 * 两个指定的日期是否是同一天
	 *
	 * @param date1
	 *            要求不为null,反之返回false
	 * @param date2
	 *            要求不为null,反之返回false
	 * @return
	 */
	public static boolean isSameDay(LocalDateTime date1, LocalDateTime date2) {
		if (null == date1 || null == date2) {
			return false;
		}
		//
		LocalDate ld1 = date1.toLocalDate();
		LocalDate ld2 = date2.toLocalDate();
		return ld1.getYear() == ld2.getYear() && ld1.getMonthValue() == ld2.getMonthValue()
				&& ld1.getDayOfMonth() == ld2.getDayOfMonth();
	}

	/**
	 * 指定的日期是否与当前日期是同一月<12>
	 *
	 * @param date
	 *            要求不为null,反之返回false
	 * @return
	 */
	public static boolean isSameMonth(LocalDateTime date) {
		if (null == date) {
			return false;
		}
		return isSameMonth(date, LocalDateTime.now());
	}

	/**
	 * 两个指定的日期是否是同一月
	 *
	 * @param date1
	 *            要求不为null,反之返回false
	 * @param date2
	 *            要求不为null,反之返回false
	 * @return
	 */
	public static boolean isSameMonth(LocalDateTime date1, LocalDateTime date2) {
		if (null == date1 || null == date2) {
			return false;
		}
		LocalDate ld1 = date1.toLocalDate();
		LocalDate ld2 = date2.toLocalDate();
		return ld1.getYear() == ld2.getYear() && ld1.getMonthValue() == ld2.getMonthValue();
	}

	/**
	 * 指定的日期是否与当前日期是同一年<13>
	 *
	 * @param date
	 *            要求不为null,反之返回false
	 * @return
	 */
	public static boolean isSameYear(LocalDateTime date) {
		if (null == date) {
			return false;
		}
		return isSameYear(date, LocalDateTime.now());
	}

	/**
	 * 两个指定的日期是否是同一年<br/>
	 *
	 * @param date1
	 *            要求不为null,反之返回false
	 * @param date2
	 *            要求不为null,反之返回false
	 * @return
	 */
	public static boolean isSameYear(LocalDateTime date1, LocalDateTime date2) {
		if (null == date1 || null == date2) {
			return false;
		}
		return date1.toLocalDate().getYear() == date2.toLocalDate().getYear();
	}

	/**
	 * 使用默认的时区格式化日期
	 *
	 * @param date
	 *            要求不为null,反之返回空字符串
	 * @param formatString
	 *            日期格式字符串
	 * @return
	 */
	public static String formatDateString(LocalDateTime date, String formatString) {
		if (null == date) {
			return "";
		}
		try {
			return date.format(DateTimeFormatter.ofPattern(formatString));
		} catch (Exception e) {
		}
		return "-";
	}

	/**
	 * 获取上月的第一天凌晨日期(00:00:00)<20>
	 *
	 * @return
	 */
	public static LocalDateTime getPreviousMonthFirstDayEarlyMorning() {
		return LocalDate.now().minusMonths(1).atStartOfDay();
	}

	/**
	 * 获取上月的第后一天午夜日期(23:59:59)<21>
	 *
	 * @return
	 */
	public static LocalDateTime getPreviousMonthLastDayMidnight() {
		LocalDateTime prevMonthFirstDay = getPreviousMonthFirstDayEarlyMorning();
		return prevMonthFirstDay.withHour(23).withMinute(59).withSecond(29);
	}

	/**
	 * 获取本月的第一天凌晨日期(00:00:00)<2>
	 *
	 * @return
	 */
	public static LocalDateTime getCurrentMonthFirstDayEarlyMorning() {
		return LocalDate.now().withDayOfMonth(1).atStartOfDay();
	}

	/**
	 * 获取今天的凌晨(00:00:00)<1> 适用的范围广
	 *
	 * @return
	 */
	public static LocalDateTime getTodayEarlyMorning() {
		return LocalDate.now().atStartOfDay();
	}

	/**
	 * 获取今天的午夜日期(23:59:59)
	 *
	 * @return
	 */
	public static LocalDateTime getTodayMidnight() {
		LocalDate date = LocalDate.now();
		return LocalDateTime.of(date.getYear(), date.getMonthValue(), date.getDayOfMonth(), 23, 59, 59);
	}

	/**
	 * 将指定日期设置为凌晨(00:00:00)<3> 用在几个作业(cron)类上
	 *
	 * @param date
	 *            要求不为null,否则抛出NPE
	 * @return
	 */
	public static LocalDateTime setDateEarlyMorning(LocalDateTime date) {
		Objects.requireNonNull(date);
		return LocalDateTime.of(date.getYear(), date.getMonthValue(), date.getDayOfMonth(), 0, 0, 0);
	}

	/**
	 * 将指定日期设置为午夜(23:59:59)<4>
	 *
	 * @param date
	 *            要求不为null,否则抛出NPE
	 * @return
	 */
	public static LocalDateTime setDateMidnight(LocalDateTime date) {
		Objects.requireNonNull(date);
		return LocalDateTime.of(date.getYear(), date.getMonthValue(), date.getDayOfMonth(), 23, 59, 59);
	}

	/**
	 * 昨天午夜的日期(23:59:59)<5>
	 *
	 * @return
	 */
	public static LocalDateTime getYesterdayMidnight() {
		LocalDateTime yesDay = getYesterDay();
		return LocalDateTime.of(yesDay.getYear(), yesDay.getMonthValue(), yesDay.getDayOfMonth(), 23, 59, 59);
	}

	/**
	 * 获得指定月份第一天凌晨(00:00:00)<6>
	 *
	 * @param year
	 *            年
	 * @param month
	 *            月
	 * @return
	 */
	public static LocalDateTime getFirstDayEarlyMorningOfMonth(int year, int month) {
		LocalDate ld = LocalDate.of(year, month, 1);
		return ld.atStartOfDay();
	}

	/**
	 * 获得指定月份最后一天午夜(23:59:59)<7>
	 *
	 * @param year
	 *            年
	 * @param month
	 *            月
	 * @return
	 */
	public static LocalDateTime getLastDayMidnightOfMonth(int year, int month) {
		LocalDate monthOneDay = getFirstDayEarlyMorningOfMonth(year, month).toLocalDate();
		int lastDay = monthOneDay.lengthOfMonth();
		return LocalDateTime.of(year, month, lastDay, 23, 59, 59);
	}

	/**
	 * 两个日期之间天数差<8>
	 *
	 * @param date1
	 *            要求不为null,反之返回-1
	 * @param date2
	 *            要求不为null,反之返回-1
	 * @return 最近的为date1,返回正值;反之返回负值
	 */
	public static long diffDays(LocalDateTime date1, LocalDateTime date2) {
		if (null == date1 || null == date2) {
			return -1;
		}
		return date2.until(date1, ChronoUnit.DAYS);
	}

	/**
	 * 两个日期之间分钟数差<9>
	 *
	 * @param date1
	 *            要求不为null,反之返回-1
	 * @param date2
	 *            要求不为null,反之返回-1
	 * @return 最近的为date1,返回正值;反之返回负值
	 */
	public static long diffMinutes(LocalDateTime date1, LocalDateTime date2) {
		if (null == date1 || null == date2) {
			return -1;
		}
		return date2.until(date1, ChronoUnit.MINUTES);
	}

	/**
	 * 指定的日期在{limit}{calendarUnit}后,是否小于当前日期 例: c1在5小时后，是否小于当前日期<10>
	 *
	 * @param c1
	 *            要求不为null,反之返回false
	 * @param unit
	 *            TemporalUnit单位,例:年:ChronoUnit.YEARS,月:ChronoUnit.MONTHS,日:ChronoUnit.DAYS,小时:ChronoUnit.HOURS,分钟:ChronoUnit.MINUTES,秒:ChronoUnit.SECONDS
	 * @param limit
	 *            单位值
	 * @return 如果当前日期大于c1累加后的值返回true
	 */
	public static boolean isFeatureDate(LocalDateTime c1, TemporalUnit unit, int limit) {
		if (null == c1) {
			return false;
		}
		LocalDateTime ldt = c1.plus(limit, unit);
		return LocalDateTime.now().isBefore(ldt);
	}

	/**
	 * 判断指定的日期是否是未来时
	 *
	 * @param dateTime
	 *            要求不为null,反之返回false
	 * @return
	 */
	public static boolean isFeatureDate(LocalDateTime dateTime) {
		if (null == dateTime) {
			return false;
		}
		return LocalDateTime.now().isBefore(dateTime);
	}

	/**
	 * 使用yyyy-MM-dd HH:mm:ss格式解析日期
	 *
	 * @param timeString
	 *            日期字符串.要求不为null,否则抛出NPE
	 * @return
	 */
	public static LocalDateTime parseDate(String timeString) {
		Objects.requireNonNull(timeString);
		return parseDate(timeString, YMDHMS_L);
	}

	/**
	 * 使用指定的格式解析日期<br/>
	 *
	 * @param timeString
	 *            日期字符串.要求不为null,否则抛出NPE
	 * @param formatString
	 *            日期格式字符串
	 * @return
	 */
	public static LocalDateTime parseDate(String timeString, String formatString) {
		Objects.requireNonNull(timeString);
		return LocalDateTime.parse(timeString, DateTimeFormatter.ofPattern(formatString));
	}

	/**
	 * 当前日期在指定分钟后的日期<br/>
	 *
	 * @param minutelimit
	 *            分钟数
	 * @return
	 */
	public static LocalDateTime addMinuteForDate(int minutelimit) {
		return LocalDateTime.now().plusMinutes(minutelimit);
	}

	/**
	 * 将指定的日期加上指定的分钟<br/>
	 *
	 * @param date
	 *            要求不为null,否则抛出NPE
	 * @param minutelimit
	 *            分钟数
	 * @return
	 */
	public static LocalDateTime addMinuteForDate(LocalDateTime date, int minutelimit) {
		Objects.requireNonNull(date);
		return date.plusMinutes(minutelimit);
	}

	/**
	 * 当前日期在指定小时后的日期<br/>
	 *
	 * @param hourlimit
	 *            小时数
	 * @return
	 */
	public static LocalDateTime addHourForDate(int hourlimit) {
		return LocalDateTime.now().plusHours(hourlimit);
	}

	/**
	 * 将指定的日期加上指定的小时<br/>
	 *
	 * @param date
	 *            要求不为null,否则抛出NPE
	 * @param hourlimit
	 *            小时数
	 * @return
	 */
	public static LocalDateTime addHourForDate(LocalDateTime date, int hourlimit) {
		Objects.requireNonNull(date);
		return date.plusHours(hourlimit);
	}

	/**
	 * 当前日期在指定天后的日期<br/>
	 *
	 * @param daylimit
	 *            天数
	 * @return
	 */
	public static LocalDateTime addDayForDate(int daylimit) {
		return LocalDateTime.now().plusDays(daylimit);
	}

	/**
	 * 将指定的日期加上指定的天<br/>
	 *
	 * @param date
	 *            要求不为null,否则抛出NPE
	 * @param daylimit
	 *            天数
	 * @return
	 */
	public static LocalDateTime addDayForDate(LocalDateTime date, int daylimit) {
		Objects.requireNonNull(date);
		return date.plusDays(daylimit);
	}

	/**
	 * 当前日期在指定月后的日期<br/>
	 *
	 * @param monthlimit
	 *            月数
	 * @return
	 */
	public static LocalDateTime addMonthForDate(int monthlimit) {
		return LocalDateTime.now().plusMonths(monthlimit);
	}

	/**
	 * 将指定的日期加上指定的月<br/>
	 *
	 * @param date
	 *            要求不为null,否则抛出NPE
	 * @param monthlimit
	 *            月数
	 * @return
	 */
	public static LocalDateTime addMonthForDate(LocalDateTime date, int monthlimit) {
		Objects.requireNonNull(date);
		return date.plusMonths(monthlimit);
	}

	/**
	 * 当前日期在指定年后的日期<br/>
	 *
	 * @param yearlimit
	 *            年数
	 * @return
	 */
	public static LocalDateTime addYearForDate(int yearlimit) {
		return LocalDateTime.now().plusYears(yearlimit);
	}

	/**
	 * 将指定的日期加上指定的年<br/>
	 *
	 * @param date
	 *            要求不为null,否则抛出NPE
	 * @param yearlimit
	 *            年数
	 * @return
	 */
	public static LocalDateTime addYearForDate(LocalDateTime date, int yearlimit) {
		Objects.requireNonNull(date);
		return date.plusYears(yearlimit);
	}

	/**
	 * 指定日期的前几天日期<br/>
	 *
	 * @param date
	 *            要求不为null,否则抛出NPE
	 * @param day
	 *            天数
	 * @return
	 */
	public static LocalDateTime beforeDayForDate(LocalDateTime date, int day) {
		Objects.requireNonNull(date);
		return date.minusDays(day);
	}

	/**
	 * 指定日期的前几分钟日期<br/>
	 *
	 * @param date
	 *            要求不为null,否则抛出NPE
	 * @param minute
	 *            分钟数
	 * @return
	 */
	public static LocalDateTime beforeMinuteForDate(LocalDateTime date, int minute) {
		Objects.requireNonNull(date);
		return date.minusMinutes(minute);
	}

	/**
	 * 将指定的日期转成unix timestamp
	 * 
	 * @param date
	 *            要求不为null,反之返回0
	 * @return
	 */
	public static Integer covertTimestamp(LocalDateTime date) {
		if (null == date) {
			return 0;
		}
		Long d = Timestamp.valueOf(date).getTime() / 1000L;
		return d.intValue();
	}

	/**
	 * 将指定的unix timestamp转成util.LocalDateTime
	 *
	 * @param timestamp
	 *            要求不为null,否则抛出NPE.unix 时间戳,例:1589316 时间戳是自 1970 年 1 月 1
	 *            日（00:00:00 GMT）以来的秒数。它也被称为 Unix 时间戳（Unix Timestamp）。
	 * @return
	 */
	public static LocalDateTime getDateTimeByUnixTimestamp(Integer timestamp) {
		Objects.requireNonNull(timestamp);
		return new Timestamp(timestamp * 1000L).toLocalDateTime();
	}

	/**
	 * 获得当前的unix timestamp
	 * 
	 * @return
	 */
	public static Integer getUnixTimestamp() {
		Long d = Timestamp.valueOf(LocalDateTime.now()).getTime() / 1000L;
		return d.intValue();
	}

	/**
	 * 输出当前日期所在年(yyyy)和月(MM)
	 *
	 * @return key=CommonUtils.Y(ear)|M(onth), Y对应四位数字的年,M对应两位数的月
	 */
	public static Map<String, String> getCurrentY4M2() {
		return getYearAndMonth(LocalDate.now());
	}

	/**
	 * 输出指定日期所在年(yyyy)和月(MM)
	 *
	 * @param date
	 *            java.time.LocalDate 日期,要求不为null,反之返回空集合
	 * @return key=DateTimeUtils.Y(ear)|M(onth), Y对应四位数字的年,M对应两位数的月
	 */
	public static Map<String, String> getYearAndMonth(LocalDate date) {
		if (null == date) {
			return Collections.emptyMap();
		}
		String[] ymArray = date.toString().split("-");
		Map<String, String> data = new HashMap<>();
		data.put(DateTimeUtils.Y, ymArray[0]);
		data.put(DateTimeUtils.M, ymArray[1]);
		return data;
	}

	/**
	 * 将java.util.Calendar 转为java.time.LocalDateTime
	 * 注意:Jdk8和以上的环境都不要使用java.util.Calendar,java.util.Date,java.text.SimpleDateFormat
	 *
	 * @param calendar
	 *            要求不为null,否则抛出NPE
	 * @return
	 */
	public static LocalDateTime toLocalDateTime(Calendar calendar) {
		Objects.requireNonNull(calendar);
		TimeZone tz = calendar.getTimeZone();
		ZoneId zid = (null == tz) ? ZoneId.systemDefault() : tz.toZoneId();
		return LocalDateTime.ofInstant(calendar.toInstant(), zid);
	}

	/**
	 * 将 java.time.LocalDateTime 转为java.util.Date
	 * 注意:Jdk8和以上的环境都不要使用java.util.Calendar,java.util.Date,java.text.SimpleDateFormat
	 *
	 * @param dateTime
	 *            要求不为null,否则抛出NPE
	 * @return
	 */
	public static Date toDate(LocalDateTime dateTime) {
		Objects.requireNonNull(dateTime);
		ZoneId systemZone = ZoneId.systemDefault();
		ZoneOffset currentOffsetForMyZone = systemZone.getRules().getOffset(Instant.now());
		Instant instant = dateTime.toInstant(currentOffsetForMyZone);
		return Date.from(instant);
	}

	/**
	 * 以时区ID参照转化为java.util.Date
	 * 
	 * @param dateTime
	 *            时间.要求不为null,否则抛出NPE
	 * @param zone
	 *            时区ID
	 * @return
	 */
	public static Date toZoneDate(LocalDateTime dateTime, ZoneId zone) {
		Objects.requireNonNull(dateTime);
		Instant instant = dateTime.atZone(zone).toInstant();
		return Date.from(instant);
	}
	
	/**
	 * 填充数据源中时间的空值,主要用于连续日期的统计
	 * 
	 * @param start   开始日期
	 * @param finish  结束日期
	 * @param rawdata 数据源,要求Key=YYYY-MM-DD
	 * @return
	 */
	public static TreeMap<String,Long> fillEmptyResult(LocalDateTime start, LocalDateTime finish, TreeMap<String, Long> rawdata){
		TreeMap<String,Long> data = new TreeMap<>();
		LocalDateTime loopDate=start;
		do{
			String loopKey = DateTimeUtils.getYMDByLine(loopDate.toLocalDate());
			try{
				Long size = rawdata.get(loopKey);
				if(size==null){
					size = 0L;
				}
				data.put(loopKey, size);
			}catch(NullPointerException e){
				data.put(loopKey, 0L);
			}
			loopDate = DateTimeUtils.addDayForDate(loopDate, 1);
		}while(!DateTimeUtils.isSameDay(loopDate, finish));
		return data;
	}
}
