codekingpro/portable-devtools
114k
1import re2from calendar import day_abbr, day_name, month_abbr, month_name3from datetime import datetime as datetime_4from datetime import timedelta, timezone5from functools import lru_cache, partial6from time import localtime, strftime7 8tokens = r"H{1,2}|h{1,2}|m{1,2}|s{1,2}|S+|YYYY|YY|M{1,4}|D{1,4}|Z{1,2}|zz|A|X|x|E|Q|dddd|ddd|d"9 10pattern = re.compile(r"(?:{0})|\[(?:{0}|!UTC|)\]".format(tokens))11 12 13def _builtin_datetime_formatter(is_utc, format_string, dt):14 if is_utc:15 dt = dt.astimezone(timezone.utc)16 return dt.strftime(format_string)17 18 19def _loguru_datetime_formatter(is_utc, format_string, formatters, dt):20 if is_utc:21 dt = dt.astimezone(timezone.utc)22 t = dt.timetuple()23 args = tuple(f(t, dt) for f in formatters)24 return format_string % args25 26 27def _default_datetime_formatter(dt):28 return "%04d-%02d-%02d %02d:%02d:%02d.%03d" % (29 dt.year,30 dt.month,31 dt.day,32 dt.hour,33 dt.minute,34 dt.second,35 dt.microsecond // 1000,36 )37 38 39def _format_timezone(tzinfo, *, sep):40 offset = tzinfo.utcoffset(None).total_seconds()41 sign = "+" if offset >= 0 else "-"42 (h, m), s = divmod(abs(offset // 60), 60), abs(offset) % 6043 z = "%s%02d%s%02d" % (sign, h, sep, m)44 if s > 0:45 if s.is_integer():46 z += "%s%02d" % (sep, s)47 else:48 z += "%s%09.06f" % (sep, s)49 return z50 51 52@lru_cache(maxsize=32)53def _compile_format(spec):54 if spec == "YYYY-MM-DD HH:mm:ss.SSS":55 return _default_datetime_formatter56 57 is_utc = spec.endswith("!UTC")58 59 if is_utc:60 spec = spec[:-4]61 62 if not spec:63 spec = "%Y-%m-%dT%H:%M:%S.%f%z"64 65 if "%" in spec:66 return partial(_builtin_datetime_formatter, is_utc, spec)67 68 if "SSSSSSS" in spec:69 raise ValueError(70 "Invalid time format: the provided format string contains more than six successive "71 "'S' characters. This may be due to an attempt to use nanosecond precision, which "72 "is not supported."73 )74 75 rep = {76 "YYYY": ("%04d", lambda t, dt: t.tm_year),77 "YY": ("%02d", lambda t, dt: t.tm_year % 100),78 "Q": ("%d", lambda t, dt: (t.tm_mon - 1) // 3 + 1),79 "MMMM": ("%s", lambda t, dt: month_name[t.tm_mon]),80 "MMM": ("%s", lambda t, dt: month_abbr[t.tm_mon]),81 "MM": ("%02d", lambda t, dt: t.tm_mon),82 "M": ("%d", lambda t, dt: t.tm_mon),83 "DDDD": ("%03d", lambda t, dt: t.tm_yday),84 "DDD": ("%d", lambda t, dt: t.tm_yday),85 "DD": ("%02d", lambda t, dt: t.tm_mday),86 "D": ("%d", lambda t, dt: t.tm_mday),87 "dddd": ("%s", lambda t, dt: day_name[t.tm_wday]),88 "ddd": ("%s", lambda t, dt: day_abbr[t.tm_wday]),89 "d": ("%d", lambda t, dt: t.tm_wday),90 "E": ("%d", lambda t, dt: t.tm_wday + 1),91 "HH": ("%02d", lambda t, dt: t.tm_hour),92 "H": ("%d", lambda t, dt: t.tm_hour),93 "hh": ("%02d", lambda t, dt: (t.tm_hour - 1) % 12 + 1),94 "h": ("%d", lambda t, dt: (t.tm_hour - 1) % 12 + 1),95 "mm": ("%02d", lambda t, dt: t.tm_min),96 "m": ("%d", lambda t, dt: t.tm_min),97 "ss": ("%02d", lambda t, dt: t.tm_sec),98 "s": ("%d", lambda t, dt: t.tm_sec),99 "S": ("%d", lambda t, dt: dt.microsecond // 100000),100 "SS": ("%02d", lambda t, dt: dt.microsecond // 10000),101 "SSS": ("%03d", lambda t, dt: dt.microsecond // 1000),102 "SSSS": ("%04d", lambda t, dt: dt.microsecond // 100),103 "SSSSS": ("%05d", lambda t, dt: dt.microsecond // 10),104 "SSSSSS": ("%06d", lambda t, dt: dt.microsecond),105 "A": ("%s", lambda t, dt: "AM" if t.tm_hour < 12 else "PM"),106 "Z": ("%s", lambda t, dt: _format_timezone(dt.tzinfo or timezone.utc, sep=":")),107 "ZZ": ("%s", lambda t, dt: _format_timezone(dt.tzinfo or timezone.utc, sep="")),108 "zz": ("%s", lambda t, dt: (dt.tzinfo or timezone.utc).tzname(dt) or ""),109 "X": ("%d", lambda t, dt: dt.timestamp()),110 "x": ("%d", lambda t, dt: int(dt.timestamp() * 1000000 + dt.microsecond)),111 }112 113 format_string = ""114 formatters = []115 pos = 0116 117 for match in pattern.finditer(spec):118 start, end = match.span()119 format_string += spec[pos:start]120 pos = end121 122 token = match.group(0)123 124 try:125 specifier, formatter = rep[token]126 except KeyError:127 format_string += token[1:-1]128 else:129 format_string += specifier130 formatters.append(formatter)131 132 format_string += spec[pos:]133 134 return partial(_loguru_datetime_formatter, is_utc, format_string, formatters)135 136 137class datetime(datetime_): # noqa: N801138 139 def __format__(self, fmt):140 return _compile_format(fmt)(self)141 142 143def aware_now():144 now = datetime_.now()145 timestamp = now.timestamp()146 local = localtime(timestamp)147 148 try:149 seconds = local.tm_gmtoff150 zone = local.tm_zone151 except AttributeError:152 # Workaround for Python 3.5.153 utc_naive = datetime_.fromtimestamp(timestamp, tz=timezone.utc).replace(tzinfo=None)154 offset = datetime_.fromtimestamp(timestamp) - utc_naive155 seconds = offset.total_seconds()156 zone = strftime("%Z")157 158 tzinfo = timezone(timedelta(seconds=seconds), zone)159 160 return datetime.combine(now.date(), now.time().replace(tzinfo=tzinfo))161 