# -*- coding:utf-8 -*-
# https://pendulum.eustace.io/docs/#addition-and-subtraction
import math
import time
import datetime
import re
from calendar import monthrange
import random
from functools import wraps
import pendulum
from pot_libs.utils.pendulum_wrapper import my_pendulum

CST = "Asia/Shanghai"
YMD = "YYYYMMDD"
YMD_U = "YYYY-MM-DD"
YM = "YYYYMM"
YMD_Hm = "YYYY-MM-DD HH:mm"
YMD_Hms = "YYYY-MM-DD HH:mm:ss"

NO_DATA_EXPRESS = ""


def time_pick_transf(start, end, is_range=0):
    """获取intervel和slots"""
    start_f = my_pendulum.from_format(start, YMD_Hms)
    end_f = my_pendulum.from_format(end, YMD_Hms)
    diff = end_f.int_timestamp - start_f.int_timestamp
    # 1. 计算intervel
    # 1.1 区间3小时之内, 返回1min
    if diff <= 3 * 3600:
        intervel = 60
    # 1.2 区间48小时之内, 返回15min
    elif diff <= 48 * 3600:
        intervel = 15 * 60
    # 1.3 区间在60天以内, 返回1day
    elif diff <= 60 * 86400:
        intervel = 86400
    # 1.4 选择年, 返回1个月
    else:
        intervel = 30 * 86400
    # 2. 计算slots
    # 2.1 取到点的个数, 比如15min的96个点
    slots = []
    slot_num = round(
        (end_f.int_timestamp - start_f.int_timestamp) / intervel)
    for i in range(slot_num):
        # 区间3小时之内
        if diff <= 3 * 3600:
            dt = start_f.add(minutes=1 * i).format(YMD_Hm)
            dt_str = str(dt).split()[1]
            if is_range:
                dt_str = str(dt)
        # 区间24小时之内
        elif diff < 24 * 3600:
            dt = start_f.add(minutes=15 * i).format(YMD_Hm)
            dt_str = str(dt).split()[1]
            if is_range:
                dt_str = str(dt)
        # 区间48小时之内
        elif 24 * 3600 <= diff <= 48 * 3600:
            dt = start_f.add(minutes=15 * i).format(YMD_Hm)
            dt_str = str(dt)
        # 区间在60天以内
        elif 48 * 3600 < diff <= 60 * 86400:
            dt = start_f.add(days=1 * i).format("YYYY-MM-DD")
            dt_str = str(dt).split("-", 1)[1]
        else:
            dt = start_f.add(months=1 * i).format("YYYY-MM")
            dt_str = str(dt)
        slots.append(dt_str)
    return intervel, slots


def time_pick_transf_new(start, end):
    """获取intervel和slots, 详细显示时间轴信息，新接口都使用这个来获取时间"""
    start_f = my_pendulum.from_format(start, YMD_Hms)
    end_f = my_pendulum.from_format(end, YMD_Hms)
    diff = end_f.int_timestamp - start_f.int_timestamp
    # 1. 计算intervel
    # 1.1 区间48小时之内, 返回15min
    if diff <= 48 * 3600:
        intervel = 15 * 60
    # 1.2 区间在60天以内, 返回1day
    elif 48 * 3600 < diff <= 60 * 86400:
        intervel = 86400
    # 1.3 选择年, 返回1个月
    else:
        intervel = 30 * 86400
    # 2. 计算slots
    # 2.1 取到点的个数, 比如15min的96个点
    slots = []
    slot_num = round((end_f.int_timestamp - start_f.int_timestamp) / intervel)
    for i in range(slot_num):
        # 区间48小时之内
        if diff <= 48 * 3600:
            dt = start_f.add(minutes=15 * i).format(YMD_Hm)
            dt_str = str(dt)
        # 区间在60天以内
        elif 48 * 3600 < diff <= 60 * 86400:
            dt = start_f.add(days=1 * i).format("YYYY-MM-DD")
            dt_str = str(dt)
        else:
            dt = start_f.add(months=1 * i).format("YYYY-MM")
            dt_str = str(dt)
        slots.append(dt_str)
    return intervel, slots


def power_slots(start, end):
    """电量电费,用电统计,time=range slots计算"""
    start_f = my_pendulum.from_format(start, YMD_Hms)
    end_f = my_pendulum.from_format(end, YMD_Hms)
    # 1.求出时间区间多少分钟
    diff_mm = (end_f - start_f).in_minutes()
    diff_hours = (end_f - start_f).in_hours()
    # 2.如果小于一天, 24 * 60分
    slots = []
    if diff_mm <= 24 * 60:
        for i in range(24):
            dt = start_f.add(hours=1 * i).format(YMD_Hm)
            dt_str = str(dt)
            slots.append(dt_str)
        return slots, "day"
    # 2.1. 区间在48小时以内
    elif 24 * 60 < diff_mm <= 48 * 60:
        slot_num = math.floor(diff_mm / 60)
        for i in range(slot_num + 1):
            dt = start_f.add(hours=1 * i).format(YMD_Hm)
            dt_str = str(dt)
            slots.append(dt_str)
        return slots, "day"
    # 2.2 区间在2天到31天内
    elif 2 * 24 <= diff_hours <= 31 * 24:
        for i in range(31):
            dt = start_f.add(hours=24 * i).format("YYYY-MM-DD")
            dt_str = str(dt)
            slots.append(dt_str)
        return slots, "month"
    # 2.3 区间在31天到60天内
    else:
        slot_num = math.floor(diff_hours / 24)
        for i in range(slot_num + 1):
            dt = start_f.add(hours=24 * i).format("YYYY-MM-DD")
            dt_str = str(dt)
            slots.append(dt_str)
        return slots, "month"


def proxy_power_slots(start, end, date_format="MM-DD", is_duration=False):
    start_f = my_pendulum.from_format(start, YMD_Hms)
    if is_duration:
        # 计算区间
        end_f = my_pendulum.from_format(end, YMD_Hms)
        m_number = (end_f - start_f).in_days() + 1
    else:
        # 只判断当前这个月有几天
        m_number = start_f.days_in_month
    slots = []
    for i in range(m_number):
        dt = start_f.add(hours=24 * i).format(date_format)
        slots.append(dt)
    return slots


def day_of_month(start):
    """这个月有几天"""
    start_f = my_pendulum.from_format(start, YMD_Hms)
    # 这个月有几天
    m_number = start_f.days_in_month
    return m_number


def year_slots(start, end):
    """年计算slots"""
    start_f = my_pendulum.from_format(start, YMD_Hms)
    slots = []
    for i in range(12):
        dt = start_f.add(months=i).format("YYYY-MM-DD")
        dt_str = str(dt)[:7]
        slots.append(dt_str)
    return slots


def day_slots(type='minutes'):
    """
        获取一天时间点
    """
    dt = my_pendulum.now().start_of('day')
    if type == 'minutes':
        slots = [
            dt.add(minutes=i).format("HH:mm") for i in range(1440)
        ]
    else:
        slots = [
            dt.add(hours=i).format("HH") for i in range(24)
        ]
    return slots


def range_to_type(start, end):
    """时间范围,转换为date_type"""
    start_f = my_pendulum.from_format(start, YMD_Hms)
    end_f = my_pendulum.from_format(end, YMD_Hms)
    # 1.求出时间区间多少分钟
    diff_mm = (end_f - start_f).in_minutes()
    diff_hours = (end_f - start_f).in_hours()
    # 2.如果小于一天, 24 * 60分
    if diff_mm <= 24 * 60:
        return "day"
    # 2.2 区间31天内
    elif diff_hours <= 31 * 24:
        return "month"
    # 2.3 其他为年
    else:
        return "year"


def last30_day():
    """求最近30天"""
    dt_now = pendulum.now()
    dt_slot = []
    for i in range(30):
        dt = dt_now.add(days=-1 * i).format("MM-DD")
        dt_str = str(dt)
        dt_slot.append(dt_str)
    dt_slot.reverse()
    return dt_slot


def last30_day_range_today():
    """求最近30天起止时间, 包括今天"""
    now_s = pendulum.now().start_of('day')
    now_e = pendulum.now().end_of('day')
    start = now_s.add(days=-1 * 29).format(YMD_Hms)
    end = now_e.format(YMD_Hms)
    return start, end


def last_month_start_end():
    """上个月起始时间"""
    now = pendulum.now()
    last = now.add(months=-1)
    last_start = last.start_of('month').format(YMD_Hms)
    last_end = last.end_of("month").format(YMD_Hms)
    return last_start, last_end


def last30_day_range():
    """求最近30天起止时间, 不包括今天"""
    now_s = pendulum.now().start_of('day')
    now_e = pendulum.now().end_of('day')
    start = now_s.add(days=-1 * 30).format(YMD_Hms)
    end = now_e.add(days=-1).format(YMD_Hms)
    return start, end


def last7_day_range():
    """求最近7天起止时间, 不包括今天"""
    now_s = pendulum.now().start_of('day')
    now_e = pendulum.now().end_of('day')
    start = now_s.add(days=-1 * 7).format(YMD_Hms)
    end = now_e.add(days=-1).format(YMD_Hms)
    return start, end


def yesterday_range():
    """昨天起始时间"""
    now_s = pendulum.now().start_of('day')
    now_e = pendulum.now().end_of('day')
    start = now_s.add(days=-1).format(YMD_Hms)
    end = now_e.add(days=-1).format(YMD_Hms)
    return start, end


def last_n_day(date_str, days):
    """求某个日期最近7天"""
    date_time = my_pendulum.from_format(date_str, 'YYYY-MM-DD')
    dt_slot = []
    for i in range(days):
        dt = date_time.add(days=-1 * i).format('YYYY-MM-DD')
        dt_str = str(dt)
        dt_slot.append(dt_str)
    return dt_slot


def last_15min_range():
    """求最近5分钟"""
    now = pendulum.now()
    start = now.add(minutes=-1 * 5).format(YMD_Hms)
    end = now.format(YMD_Hms)
    return start, end


def today_month_date():
    """今日本月和上一周期时间 今天10:00/昨日10:00"""
    # today_start, today_end, month_start, month_end
    now = pendulum.now(tz=CST)
    # 今天开始到结束时间
    today_end = str(now.format(YMD_Hms))
    today_start = str(now.start_of('day').format(YMD_Hms))
    # 本月开始到结束时间
    month_start = str(now.start_of('month').format(YMD_Hms))
    month_end = str(now.format(YMD_Hms))
    return today_start, today_end, month_start, month_end


def start_end_date():
    """获取今日、本月起始时间"""
    now = pendulum.now()
    today_start = str(now.start_of('day').format(YMD_Hms))
    today_end = str(now.end_of('day').format(YMD_Hms))
    month_start = str(now.start_of('month').format(YMD_Hms))
    month_end = str(now.end_of('month').format(YMD_Hms))
    return str(today_start), str(today_end), str(month_start), str(month_end)


def time_str_to_str(date_str, format="HH:mm"):
    """YYYY-MM-DD HH:mm:ss格式转换为format格式"""
    start_f = my_pendulum.parse(date_str)
    start_f = start_f.format(format)
    return start_f


def time_str_to_str1(date_str, format="HH:mm"):
    """YYYY-MM-DD HH:mm:ss格式转换为format格式"""
    start_f = my_pendulum.from_format(date_str, format)
    start_f = start_f.format(format)
    return start_f


def srv_time():
    """当前系统时间"""
    dt = pendulum.now()
    now_date = dt.to_datetime_string()
    timestamp = dt.int_timestamp
    return now_date, timestamp  # 返回格式2020-07-23 10:15:51  时间搓1595989401


def convert_es_str(str1: object) -> object:
    """str date转换为str es日期格式"""
    es_date = my_pendulum.from_format(str1, YMD_Hms)
    return str(es_date)


def end_now_str(str1: object) -> object:
    """str date转换为str es日期格式"""
    end_date = my_pendulum.from_format(str1, YMD_Hms)
    now_date = pendulum.now()
    if end_date > now_date:
        time_format = "%Y-%m-%dT%H:%M:%S+08:00"
        end_date = now_date.strftime(time_format)
    return str(end_date)


def convert_to_es_str(str1, format=YMD_Hms):
    """str date转换为str es日期格式"""
    es_date = my_pendulum.from_format(str1, format)
    return str(es_date)


def last_time_str(start, end, date_type, date_end=False):
    """年月日, 获取上一周期时间"""
    if date_type not in ("day", "month", "year"):
        return None
    start_f = my_pendulum.from_format(start, YMD_Hms)
    end_f = my_pendulum.from_format(end, YMD_Hms)
    if date_type == "day":
        start_last = start_f.subtract(days=1)
        if date_end:
            end_last = start_last.end_of(unit=date_type)
        else:
            end_last = end_f.subtract(days=1)
    elif date_type == "month":
        start_last = start_f.subtract(months=1)
        if date_end:
            end_last = start_last.end_of(unit=date_type)
        else:
            end_last = end_f.subtract(months=1)
    else:
        start_last = start_f.subtract(years=1)
        if date_end:
            end_last = start_last.end_of(unit=date_type)
        else:
            end_last = end_f.subtract(years=1)
    return start_last.format(YMD_Hms), end_last.format(YMD_Hms)


def esstr_to_dthoutstr(str1, format='%H:%M'):
    """
    es的str，转换为datetime的str  '%Y-%m-%d %H:%M:%S'
    """
    str_result = datetime.datetime.strptime(
        str1, "%Y-%m-%dT%H:%M:%S+08:00").strftime(format)
    return str_result


def get_date_timestamp(date):
    '''
    根据日期获取时间戳
    :return:
    '''
    dt_obj = convert_to_dt(date)
    return int(time.mktime(dt_obj.timetuple()))


def get_datetime_str(timestamp):
    '''
    根据时间戳获取（年-月-日 时:分:秒）格式时间
    :param timestamp:
    :return:
    '''
    if not timestamp:
        timestamp = time.time()
    
    time_array = time.localtime(timestamp)
    return time.strftime("%Y-%m-%d %H:%M:%S", time_array)


def get_current_date_time():
    """
    获取当前时间 datetime obj
    return current datetime obj
    :return:
    """
    return datetime.datetime.now()


def get_current_date():
    """
    获取当前日期 datetime obj
    return current datetime obj
    :return:
    """
    now = get_current_date_time()
    return datetime.datetime(now.year, now.month, now.day)


def get_current_datetime_str(time_format="%Y-%m-%d %H:%M:%S"):
    return get_current_date_time().strftime(time_format)


def convert_time_str(datetime_obj):
    """
    日期转换为es格式str
    """
    time_format = "%Y-%m-%dT%H:%M:%S+08:00"
    return datetime_obj.strftime(time_format)


def esstr_to_dtstr(str):
    """
    es的str，转换为datetime的str
    """
    str_result = datetime.datetime.strptime(
        str, "%Y-%m-%dT%H:%M:%S+08:00").strftime('%Y-%m-%d %H:%M:%S')
    return str_result


def get_dict_key(d, value):
    """

    :param d:
    :param value:
    :return:
    """
    for k, v in d.items():
        if v == value:
            return k


############# number process ###################

def check_value_is_null(value):
    """
    check if the value is None. In database, null include ``, Null and `NULL`
    :param value:
    :return:
    """
    return value in (None, "null", "NULL", "")


def convert_number(number, divisor=10000, precision=2, convert_to_str=False):
    """

    :param number:
    :param divisor:
    :param precision:
    :return:
    """
    try:
        number = float(number) / divisor
        # round函数返回浮点数四舍五入
        number = round(number, precision)
        if convert_to_str:
            str_format = "%%.%sf" % precision
            return str_format % number
        return number
    except Exception as e:
        return number


def summary_number(number_list, summary_type="sum"):
    """
    统计数值,
    :param number_list:
    :return:
    """
    new_list = []
    for number in number_list:
        if check_value_is_null(number):
            continue
        try:
            number = float(number)
        except:
            continue
        new_list.append(number)
    if len(new_list) == 0:
        return NO_DATA_EXPRESS
    if summary_type == "sum":
        return sum(new_list)
    elif summary_type == "avg":
        sum_val = sum(new_list)
        return sum_val / len(new_list)
    else:
        raise Exception(
            "Not support this kind of summary number: %s" % summary_type)


def summary_list(number_list, summary_type="sum"):
    """
    统计列表
    :param number_list: [[...], [...], ...]
    :param summary_type: avg or sum
    :return: [...]
    """
    new_list = []
    for i in range(len(number_list[0])):
        new_list.append([])
        for j in range(len(number_list)):
            new_list[i].append(number_list[j][i])
    return [summary_number(l, summary_type) for l in new_list]


###################### date time process ##############

def convert_timestamp_to_str_with_tz(timestamp):
    dt = convert_timestamp_to_dt(timestamp)
    return convert_dt_to_str(dt, date_type="tz")


def convert_timestamp_to_dt(timestamp):
    timestamp = int(timestamp)
    return datetime.datetime.fromtimestamp(timestamp)


def convert_timestamp_to_str(timestamp):
    return convert_dt_to_str(convert_timestamp_to_dt(timestamp))


def convert_timestamp_to_str_no_sec(timestamp):
    dt = convert_timestamp_to_dt(timestamp)
    return datetime.datetime.strftime(dt, "%Y-%m-%d %H:%M")


def convert_str_to_timestamp(date):
    return convert_dt_to_timestamp(convert_to_dt(date))


def get_random_date(start, end, date_format):
    seconds = (end - start).total_seconds()
    return (start + datetime.timedelta(
        seconds=random.randint(0, int(seconds)))).strftime(date_format)


def shift_dt(dt, days=1):
    """
    shift datetime
    :param dt:
    :param days:
    :return:
    """
    dt = convert_to_dt((dt))
    return dt + datetime.timedelta(days=days)


def convert_dt_to_timestamp(dt):
    return dt.timestamp()


def summary_dics(dics, summary_keys=None):
    """
    summary dictionary
    :param dics:
    :return:
    """
    keys = dics[0].keys()
    if not summary_keys:
        summary_keys = keys
    summary = {}
    for k in keys:
        summary[k] = 0
    for dic in dics:
        for k in keys:
            if not k in summary_keys:
                summary[k] = dic[k]
            else:
                if not dic[k] == NO_DATA_EXPRESS:
                    summary[k] += dic[k]
    return summary


def convert_dt_to_str(dt, date_type=None, just_month=False):
    """
    convert datetime object to string
    :param dt:
    :return:
    """
    if type(dt) is str:
        return dt
    if type(dt) is int:
        return dt
    if date_type == "day":
        return datetime.datetime.strftime(dt, "%m-%d")
    elif date_type == "hour":
        return datetime.datetime.strftime(dt, "%H:00")
    elif date_type == "15min":
        return datetime.datetime.strftime(dt, "%H:%M")
    elif date_type == "minute":
        return datetime.datetime.strftime(dt, "%H:%M")
    elif date_type == "year":
        return datetime.datetime.strftime(dt, "%Y")
    elif date_type == "month":
        if just_month:
            return datetime.datetime.strftime(dt, "%m月")
        return datetime.datetime.strftime(dt, "%Y-%m")
    elif date_type == "tz":
        return datetime.datetime.strftime(dt, "%Y-%m-%dT%H:%M:%S+08:00")
    else:
        return datetime.datetime.strftime(dt, "%Y-%m-%dT%H:%M:%S")


def convert_dt_to_datestr(dt):
    """
    convert datetime object to date string
    :param dt:
    :return:
    """
    return dt.strftime("%Y-%m-%d")


def convert_dt_to_timestr(dt):
    """
    convert datetime object to ”%Y-%m-%d %H:%M:%S“ string
    :param dt:
    :return:
    """
    return dt.strftime("%Y-%m-%d %H:%M:%S")


def get_day_start_end(date=None):
    """
    获取某天的开始和结束
    :return: datetime obj
    """
    if not date:
        dt = get_current_date_time()
    else:
        dt = convert_to_dt(date)
    start = datetime.datetime(dt.year, dt.month, dt.day)
    end = shift_dt(start, 1)
    return start, end


def get_last_several_month_dt(month_num=12, date=None,
                              include_this_month=False):
    """
    获取最近几个月的开始日期（datetime）
    :param include_this_month:
    :return:
    """
    if date:
        dt = convert_to_dt(date)
    else:
        dt = get_current_date_time()
    dt, _ = get_month_start_end(dt)
    year = dt.year
    month = dt.month
    date_times = [dt]
    for i in range(month_num):
        month -= 1
        if month == 0:
            month = 12
            year -= 1
        date_times.append(datetime.datetime(year=year, month=month, day=1))
    date_times.sort()
    return date_times[1:] if include_this_month else date_times[:month_num]


def get_last_several_month_str(month_num=12, date=None,
                               include_this_month=False):
    """
    获取最近几个月(字符串)
    :param include_this_month:
    :return: ["1月", "２月"...]
    """
    dts = get_last_several_month_dt(month_num, date, include_this_month)
    return ["%s月" % dt.month for dt in dts]


def get_last_3month_start_end(date=None):
    """
    获取前三个月的开始和结束
    :param dt: datetime obj
    :return:
    """
    
    if not date:
        dt = get_current_date_time()
    else:
        dt = convert_to_dt(date)
    end, _ = get_month_start_end(date)
    year = dt.year
    month = dt.month
    if month <= 3:
        year -= 1
        month = month - 3 + 12
    else:
        month -= 3
    start, _ = get_month_start_end(datetime.datetime(year, month, 1))
    return start, end


def get_growth_rate_period(date_type, start=None, end=None):
    """
    增长率上个周期的起止时间
    :param dt: datetime obj
    :return:
    """
    if start is None:
        start = get_current_date()
    if end is None:
        end = start
    interval = int((end - start).total_seconds())
    if date_type == "range":
        if interval == 24 * 3600:
            start, end = shift_dt(start, -1), shift_dt(end, -1)
        else:
            tmp_start = get_previous_month_date(start)
            if tmp_start is None:
                start = get_last_month_start_end(tmp_start)[1]
                start = shift_dt(start, -1)
            else:
                start = tmp_start
            end = start + datetime.timedelta(seconds=interval)
    elif date_type in ["today", "yesterday"]:
        start, end = shift_dt(start, -1), shift_dt(end, -1)
    elif date_type in ["this_month", "last_month"]:
        start, end = get_last_month_start_end(start)
    elif date_type == "recent_three_month":
        start = get_last_month_start_end(start)[0]
        end = shift_dt(start, 31 * 3)
        end = get_month_start_end(end)[0]
    elif date_type == "recent_year":
        _, end = get_month_start_end(start)
        temp_start = shift_dt(end, -interval // 86400)
        start = get_month_start_end(temp_start)[1]
    else:
        raise Exception("Invalid date type: %s" % date_type)
    return [start, end]


def get_this_year_start_end(date=None):
    """
    获取上一年的开始和结束
    :param dt: datetime obj
    :return:
    """
    
    if not date:
        dt = get_current_date_time()
    else:
        dt = convert_to_dt(date)
    start = datetime.datetime(dt.year, 1, 1)
    end = datetime.datetime(dt.year + 1, 1, 1)
    return start, end


def get_last_year_start_end(date=None):
    """
    获取上一年的开始和结束
    :param dt: datetime obj
    :return:
    """
    
    if not date:
        dt = get_current_date_time()
    else:
        dt = convert_to_dt(date)
    start = datetime.datetime(dt.year - 1, 1, 1)
    end = datetime.datetime(dt.year, 1, 1)
    return start, end


def get_last_month_start_end(date=None):
    """
    获取上个月的开始和结束
    :param dt: datetime obj
    :return:
    """
    
    if not date:
        dt = get_current_date_time()
    else:
        dt = convert_to_dt(date)
    year = dt.year
    month = dt.month
    if month == 1:
        year -= 1
        month = 12
    else:
        month -= 1
    return get_month_start_end(datetime.datetime(year, month, 1))


def get_last_12_month_start(date=None, format=""):
    """
    获取前12个月
    :param date:
    :param format:
    :return:
    """
    start_month = get_last_month_start_end(date)[0]
    months = [start_month]
    for i in range(11):
        start_month = get_last_month_start_end(start_month)[0]
        months.append(start_month)
    if format:
        months = [dt.strftime(format) for dt in months]
    months.reverse()
    return months


def get_month_start_end(date=None):
    """
    获取某月的开始和结束
    :return: datetime obj
    """
    if not date:
        dt = get_current_date_time()
    else:
        if type(date) is str:
            dt = convert_to_dt(date)
        else:
            dt = date
    start = datetime.datetime(dt.year, dt.month, 1)
    end = datetime.datetime(dt.year, dt.month,
                            monthrange(dt.year, dt.month)[1])
    end = shift_dt(end, 1)
    return [start, end]


def convert_to_dt(date):
    """
    convert date string to datatime obj
    :param date: 2018-01-01, 2018-01-01 12:00:00
    :return:
    """
    if type(date) is datetime.datetime:
        return date
    if date is None or date == "":
        return get_current_date_time()
    if re.match("\d\d\d\d-\d\d-\d\d$", date):
        return datetime.datetime.strptime(date, "%Y-%m-%d")
    elif re.match("\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$", date):
        return datetime.datetime.strptime(date, "%Y-%m-%d %H:%M:%S")
    elif re.match("\d\d\d\d$", date):
        return datetime.datetime.strptime(date, "%Y")
    elif re.match("\d\d\d\d-\d\d$", date):
        return datetime.datetime.strptime(date, "%Y-%m")
    elif re.match("\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d\+08:00$", date):
        return datetime.datetime.strptime(date, "%Y-%m-%dT%H:%M:%S+08:00")
    else:
        raise Exception("Invalid datetime string: %s" % date)


def convert_dt_of_es_result(results, date_key):
    """
    把es搜索出来的结果的时间全转换为datetime
    :param results:
    :return:
    """
    for result in results:
        result[date_key] = convert_to_dt(result[date_key])


def get_current_start_n_end_dt(date_type, include_end_day=False):
    """
    根据时间类型获取开始和结束的datetime object, 精确到日
    :param date_type: today, yesterday, this_month, last_month, recent_three_month, recent_year

    :return:
    """
    current_dt = get_current_date_time()
    if date_type == "year":
        start = datetime.datetime(current_dt.year, 1, 1)
        end = datetime.datetime(current_dt.year + 1, 1,
                                1) - datetime.timedelta(days=1)
    if date_type == "recent_year":
        end = datetime.datetime(current_dt.year, current_dt.month,
                                monthrange(current_dt.year, current_dt.month)[
                                    1])
        start_year = current_dt.year - 1
        start_month = current_dt.month + 1
        if start_month > 12:
            start_year += 1
            start_month = start_month - 12
        start = datetime.datetime(start_year, start_month, 1)
    
    elif date_type == "season":
        season_number = int((current_dt.month - 1) / 3)
        start = datetime.datetime(current_dt.year, season_number * 3 + 1, 1)
        end = datetime.datetime(current_dt.year, season_number * 3 + 3,
                                monthrange(current_dt.year,
                                           season_number * 3 + 3)[1])
    elif date_type == "recent_three_month":
        end = datetime.datetime(current_dt.year, current_dt.month,
                                monthrange(current_dt.year, current_dt.month)[
                                    1])
        start_year = current_dt.year
        start_month = current_dt.month - 2
        if start_month <= 0:
            start_year -= 1
            start_month = start_month + 12
        start = datetime.datetime(start_year, start_month, 1)
    elif date_type == "this_month":
        start = datetime.datetime(current_dt.year, current_dt.month, 1)
        end = datetime.datetime(current_dt.year, current_dt.month,
                                monthrange(current_dt.year, current_dt.month)[
                                    1])
    elif date_type == "last_month":
        year = current_dt.year
        month = current_dt.month
        month -= 1
        if month == 0:
            month = 12
            year -= 1
        start = datetime.datetime(year, month, 1)
        end = datetime.datetime(year, month, monthrange(year, month)[1])
    elif date_type == "today" or date_type == "day":
        start = datetime.datetime(current_dt.year, current_dt.month,
                                  current_dt.day)
        end = start
    elif date_type == "yesterday":
        current_dt = shift_dt(current_dt, -1)
        start = datetime.datetime(current_dt.year, current_dt.month,
                                  current_dt.day)
        end = start
    if include_end_day:
        end = shift_dt(end, 1)
    return start, end


def get_previous_month_dt(date, months):
    """

    :param date:2018-01-01
    :param months: 2
    :return: datetime obj: 2017-11-01
    """
    dt = convert_to_dt(date)
    years = int(months / 12)
    months = months % 12
    pre_start_month = dt.month - months
    pre_start_year = dt.year - years
    if pre_start_month <= 0:
        pre_start_month += 12
        pre_start_year = dt.year - 1
    try:
        return datetime.datetime(year=pre_start_year, month=pre_start_month,
                                 day=dt.day)
    except:
        return datetime.datetime(year=pre_start_year, month=pre_start_month,
                                 day=1)


def get_previous_month_date(date):
    """
    获取上月同日
    :param date:
    :return:
    """
    dt = convert_to_dt(date)
    prev_month_dt = get_previous_month_dt(date, 1)
    try:
        return datetime.datetime(prev_month_dt.year, prev_month_dt.month,
                                 dt.day, dt.hour, dt.minute, dt.second)
    except:
        return None


def get_previous_slot(slots, date_type, compare_type=None):
    start = slots[0][0]
    end = slots[-1][0]
    if date_type == "day":
        if compare_type == "month":
            pre_start_month = start.month - 1
            pre_start_year = start.year
            if pre_start_month == 0:
                pre_start_month = 12
                pre_start_year = start.year - 1
            pre_month_days = monthrange(pre_start_year, pre_start_month)[1]
            previous_slots = get_slots_between_date(
                datetime.datetime(year=pre_start_year, month=pre_start_month,
                                  day=1),
                datetime.datetime(year=pre_start_year, month=pre_start_month,
                                  day=pre_month_days), date_type)
            slots_len = len(slots)
            previous_slots_len = len(previous_slots)
            if previous_slots_len > slots_len:
                previous_slots = previous_slots[:slots_len]
            # if previous_slots_len < slots_len:
            #     previous_slots.extend([0] * (slots_len - previous_slots_len))
            return previous_slots
        else:
            days = int((end - start).total_seconds() / 86400) + 1
            return get_slots_between_date(
                start - datetime.timedelta(days=days),
                end - datetime.timedelta(days=days), date_type)
    
    elif date_type == "hour":
        hours = int((end - start).total_seconds() / 3600) + 1
        return get_slots_between_date(start - datetime.timedelta(hours=hours),
                                      end - datetime.timedelta(hours=hours),
                                      date_type)
    elif date_type == "15min":
        minutes = int((end - start).total_seconds() / 60) + 1
        return get_slots_between_date(
            start - datetime.timedelta(minutes=minutes),
            end - datetime.timedelta(minutes=minutes), date_type)
    elif date_type == "month":
        months = (end.year - start.year) * 12 + end.month - start.month
        pre_start = get_previous_month_dt(start, months)
        pre_end = get_previous_month_dt(end, months)
        return get_slots_between_date(pre_start, pre_end, date_type)


def sync_length(standard_list, other_list):
    """
    把 other list 的长度变得和 standard_list 一样长
    :param standard_list:
    :param other_list:
    :return:
    """
    if len(other_list) >= len(standard_list):
        return standard_list, other_list[:len(other_list)]
    else:
        other_list.extend(["--"] * (len(standard_list) - len(other_list)))
        return standard_list, other_list


def get_slots_between_date(start, end, interval_type, growth_type=None):
    date_dts = []
    start = convert_to_dt(start)
    end = convert_to_dt(end)
    if interval_type == "day":
        if growth_type == "month":
            days = monthrange(start.year, start.month)[1]
            start = datetime.datetime(year=start.year, month=start.month,
                                      day=1)
            for i in range(days):
                date_dts.append([start, start + datetime.timedelta(days=1)])
                start += datetime.timedelta(days=1)
        else:
            while start <= end:
                date_dts.append([start, start + datetime.timedelta(days=1)])
                start += datetime.timedelta(days=1)
    
    
    elif interval_type == "month":
        start_months = start.year * 12 + start.month
        end_months = end.year * 12 + end.month
        year = start.year
        month = start.month
        for i in range(end_months - start_months + 1):
            if month > 12:
                year += 1
                month = 1
            if month == 12:
                sub_end_year = year + 1
                sub_end_month = 1
            else:
                sub_end_year = year
                sub_end_month = month + 1
            date_dts.append([datetime.datetime(year=year, month=month, day=1),
                             datetime.datetime(year=sub_end_year,
                                               month=sub_end_month, day=1)])
            month += 1
    elif interval_type == "year":
        for i in range(end.year - start.year + 1):
            date_dts.append(
                [datetime.datetime(year=(start.year + i), month=1, day=1),
                 datetime.datetime(year=(start.year + i + 1), month=1, day=1)])
    elif interval_type == "15min":
        end += datetime.timedelta(days=1)
        slot_numbers = int((end.timestamp() - start.timestamp()) / 900)
        for i in range(slot_numbers):
            date_dts.append([start + datetime.timedelta(minutes=15 * i),
                             start + datetime.timedelta(minutes=15 * i + 1)])
    elif interval_type == "hour":
        end += datetime.timedelta(days=1)
        slot_numbers = int((end.timestamp() - start.timestamp()) / 3600)
        for i in range(slot_numbers):
            date_dts.append([start + datetime.timedelta(hours=i),
                             start + datetime.timedelta(hours=i + 1)])
    return date_dts


def get_slots_between_closure_date(start, end, interval_type,
                                   growth_type=None):
    """
    获取闭合时间区间的各个时间槽
    :param start:
    :param end:
    :param date_type:
    :param growth_type:
    :return:
    """
    start = convert_to_dt(start)
    end = convert_to_dt(end)
    end = shift_dt(end, -1)
    return get_slots_between_date(start, end, interval_type, growth_type)


def get_times_between_closure_date(start, end, interval):
    """

    :param start:
    :param end:
    :param interval: seconds
    :return:
    """
    start = convert_to_dt(start)
    end = convert_to_dt(end)
    times = []
    while start < end:
        times.append(start)
        start += datetime.timedelta(seconds=interval)
    return times


def init_input_chart_datas(start, end, interval, keys):
    times = get_times_between_closure_date(start, end, interval)
    data = {}
    for t in times:
        data[t] = {}
        for k in keys:
            data[t][k] = NO_DATA_EXPRESS
    return data


def get_growth_n_growth_rate(before_num, current_num):
    """
    返回增长值和增长率
    :param before_num:
    :param current_num:
    :return:
    """
    if before_num == NO_DATA_EXPRESS or current_num == NO_DATA_EXPRESS:
        return NO_DATA_EXPRESS, NO_DATA_EXPRESS
    growth = current_num - before_num
    if before_num == 0:
        growth_rate = 200 if current_num != 0 else 0
    else:
        growth_rate = growth / float(before_num) * 100
    return "%.2f" % growth, "%.2f" % growth_rate


####################################
#  装饰器
####################################

# 如果参数里有对date,start,end 赋值, 把它们转为datetime 格式
def deco_convert_date_to_dt(f):
    @wraps(f)
    def deco(*args, **kwargs):
        for kwarg in ["date", "start", "end"]:
            if kwarg in kwargs and kwargs[kwarg]:
                kwargs[kwarg] = convert_to_dt(kwargs[kwarg])
        return f(*args, **kwargs)
    
    return deco


# fake functions
def fake_data(start=0, end=100, precision=2):
    if precision == 0:
        return random.randint(start, end)
    precision_rate = "%%.%sf" % precision
    return float(precision_rate % (random.randint(start * 10 ** precision,
                                                  end * 10 ** precision) / float(
        10 ** precision)))


def fake_day_data(date, start=0, end=100, precesion=2):
    """

    :param date:
    :param precesion:
    :return:
    """
    current_dt = get_current_date_time()
    dt = convert_to_dt(date)
    data = [0] * 24
    if (current_dt - dt).total_seconds() >= 3600 * 24:
        hour = 24
    else:
        hour = dt.hour
    for i in range(hour):
        data[i] = fake_data(start, end, precesion)
    return data


def fake_month_data(date, start=0, end=100, precesion=2):
    """
    fake month data
    :param date:
    :param start:
    :param end:
    :param precesion:
    :return:
    """
    current_dt = get_current_date_time()
    dt = convert_to_dt(date)
    days = monthrange(dt.year, dt.month)[1]
    data = [0] * days
    if dt > current_dt:
        return data
    else:
        for i in range(dt.day):
            data[i] = fake_data(start, end, precesion)
    return data


# ==================================================================

def formatValue(value, unit="", precision=2, divisor=1, empty=''):
    if value is None:
        return ""
    if unit:
        return "%s%s" % (convert_number(value, divisor, precision), unit)
    return convert_number(value, divisor, precision)


def formatValueByUnit(value, key, withUnit=False):
    mappings = {
        "万kWh": {"precision": 3, "divisor": 10000, 'unit': '万kWh'},
        "万元": {"precision": 3, "divisor": 10000, 'unit': '万元'},
        "percentage": {"precision": 1, "divisor": 0.01, 'unit': '%'},
        "kWh": {"precision": 0, "divisor": 1, 'unit': 'kWh'},
        "元": {"precision": 4, "divisor": 1, 'unit': '元'},
        "元/kW": {"precision": 2, "divisor": 1, 'unit': '元/kW'},
        "元/kVA*月": {"precision": 2, "divisor": 1, 'unit': '元/kVA*月'},
        "V": {"precision": 1, "divisor": 1, 'unit': 'V'},
        "A": {"precision": 1, "divisor": 1, 'unit': 'A'},
        "kW": {"precision": 2, "divisor": 1, 'unit': 'kW'},
        "kVar": {"precision": 2, "divisor": 1, 'unit': 'kVar'},
        "cos": {"precision": 2, "divisor": 1, 'unit': ''},
        "var_percentage": {"precision": 2, "divisor": 1, 'unit': '%'},
        "var_percentage1": {"precision": 2, "divisor": 0.01, 'unit': '%'},
        "Hz": {"precision": 2, "divisor": 1, 'unit': 'Hz'},
    }
    unit = mappings[key]['unit'] if withUnit and key in mappings else ""
    pricison = mappings[key]['precision'] if key in mappings else 2
    divisor = mappings[key]['divisor'] if key in mappings else 1
    return formatValue(value, unit, pricison, divisor)


def getValueItemOfDict(item, field):
    return item[field] if item and type(
        item) is dict and field in item else None


def get_this_period_start_end(date, date_type="day"):
    """
    获取当前周期的开始和结束,闭合区间
    :param date:
    :param date_type:
    :return:
    """
    # 多日用电分析，add by eason
    if date_type == "range":
        tmp_date = []
        for key, dt in enumerate(date):
            date = convert_to_dt(dt)
            date = datetime.datetime(year=date.year, month=date.month,
                                     day=date.day)
            if key == 0:
                tmp_date.append(date)
            else:
                tmp_date.append(shift_dt(date, 1))
        return tmp_date
    date = convert_to_dt(date)
    date = datetime.datetime(year=date.year, month=date.month, day=date.day)
    if date_type == "day":
        return [date, shift_dt(date, 1)]
    elif date_type == "month":
        return get_month_start_end(date)


def get_last_period_start_end(date, date_type="day"):
    """
    获取上个周期的开始和结束, 闭合区间
    :param date:
    :param date_type:
    :return:
    """
    date = convert_to_dt(date)
    if date_type == "day":
        return [shift_dt(date, -1), date]
    elif date_type == "month":
        return get_last_month_start_end(date)


def format_slot(slot):
    """
    格式化slot
    :param slot:[start, end]
    :return: str, "2019-01-01 00:00~01:00"
    """
    start, end = slot
    interval = int((end - start).total_seconds())
    if interval < 3600 * 24:
        return "%s~%s" % (
            start.strftime("%Y-%m-%d %H:%M"), end.strftime("%H:%M"))
    else:
        return "%s~%s" % (start.strftime("%Y-%m-%d"), end.strftime("%Y-%m-%d"))


def format_chart_data(datas, date_type="day", data_type="default"):
    """

    :param datas:
    {
        "2019-01-01": {
            "v1": 1,
            "v2": 2,
            "v3": 3
        },
        "2019-01-02": {
            "v1": 1,
            "v2": 2,
            "v3": 3
        }...
    }
    :param date_type: day, month, year, hour or 15min
    :return:
    {
        "v1": [["01:00", "02:00", ...], [1, 2, ...]],
        "v2": [["01:00", "02:00", ...], [1, 2, ...]],
        ...
    }
    """
    dates = list(datas.keys())
    dates.sort()
    date_strs = [convert_dt_to_str(date, date_type) for date in dates]
    sub_data_types = datas[dates[0]].keys()
    data = {}
    for sub_data_type in sub_data_types:
        data[sub_data_type] = []
        data[sub_data_type].append(date_strs)
        data[sub_data_type].append(list(
            convert_number(datas[date][sub_data_type], 1, 2) for date in
            dates))
    return data


def return_page_datas(datas, page_num, page_record_num):
    """

    :param datas:
    :param page_num:
    :param data_number:
    :return:
    """
    totals = len(datas)
    start_num = (page_num - 1) * page_record_num + 1
    end_num = page_num * page_record_num
    if end_num > totals:
        end_num = totals
    if start_num > totals:
        all_datas = []
    else:
        all_datas = datas[start_num - 1: end_num]
    return {
        'totals': totals,
        'record_num': len(all_datas),
        'data': all_datas
    }


def get_start_end_by_tz_time(time_str):
    """
    根据日期字符串获取当天起始时间
    :param time_str:2020-07-02T17:32:31+08:00
    :return:2020-07-02 00:00:00,2020-07-02 23:59:59
    """
    date = convert_to_dt(time_str)
    start = datetime.datetime(year=date.year, month=date.month, day=date.day)
    end = datetime.datetime(year=date.year, month=date.month, day=date.day,
                            hour=23, minute=59, second=59)
    start_str = convert_dt_to_timestr(start)
    end_str = convert_dt_to_timestr(end)
    return start_str, end_str


def get_start_end_by_tz_time_new(time_str, from_fmt=YMD_U, to_fmt=YMD_Hms):
    """
    根据日期字符串获取当天起始时间
    :param time_str:2022-08-03 17:53:53
    :return:2022-08-03 00:00:00,2022-08-03 23:59:59
    """
    date = my_pendulum.from_format(time_str, from_fmt)
    start = date.start_of("day").format(to_fmt)
    end = date.end_of("day").format(to_fmt)
    return start, end


def get_day_start(dts, dt_fmt=None):
    if dt_fmt:
        dt = pendulum.from_format(str(dts), dt_fmt, tz=CST)
    else:
        dt = pendulum.parse(str(dts), tz=CST)
    return dt.start_of("day")


def get_time_duration(start_time, end_time, is_timestamp=True,
                      is_need_trans=True):
    """
    根据开始、结束时间获取格式化的时间差
    """
    if is_timestamp:
        start_time = convert_timestamp_to_dt(start_time)
        end_time = convert_timestamp_to_dt(end_time)
    else:
        start_time = convert_to_dt(start_time)
        end_time = convert_to_dt(end_time)
    # 计算时间差
    duration = end_time - start_time
    # 如果不需要转换 则直接返回
    duration_str = duration.seconds + duration.days * 24 * 60 * 60
    if not is_need_trans:
        return duration_str
    # 返回
    return_str = get_time_duration_by_str(duration_str)
    return return_str


def get_time_duration_by_str(duration_str):
    """
    根据时间戳获取格式化的时间差
    """
    return_str = ''
    days = int(duration_str / (60 * 60 * 24))
    if days > 0:
        return_str += "%s天" % str(days)
    
    hours = int(duration_str % (60 * 60 * 24) / (60 * 60))
    if hours > 0:
        return_str += "%s时" % str(hours)
    minutes = int(duration_str % (60 * 60 * 24) % (60 * 60) / 60)
    if minutes > 0:
        return_str += "%s分" % str(minutes)
    seconds = int(duration_str % (60 * 60 * 24) % (60 * 60) % 60)
    if seconds > 0:
        return_str += "%s秒" % str(seconds)
    return return_str


def get_time_diff(start, end):
    """
    获取时间段内的间隔
    :param start:
    :param end:
    :return:
    """
    start_f = my_pendulum.from_format(start, YMD_Hms)
    end_f = my_pendulum.from_format(end, YMD_Hms)
    diff = end_f.int_timestamp - start_f.int_timestamp
    return diff


def get_15min_ago(time_fmt="YYYY-MM-DD HH:mm:ss"):
    return my_pendulum.now(tz=CST).subtract(minutes=15).format(time_fmt)


def timestamp2dts(timestamp, dt_fmt):
    dt = pendulum.from_timestamp(timestamp, tz=CST)
    return dt.format(dt_fmt)