libs.utils.timekit
timekit - datetime 拡張ユーティリティ
ExtendedDatetime: 柔軟な初期化と書式変換ができる datetime 拡張クラスExtendedDatetimeList: ExtendedDatetimeを要素とする日付リストを扱う補助クラス
Examples:
>>> from cls.timekit import ExtendedDatetime >>> t = ExtendedDatetime("2025-04-19 12:34:56") >>> t.format("ymdhm") '2025/04/19 12:34'>>> t.set("2025-05-01 00:00:00") >>> t.format("sql") '2025-05-01 00:00:00.000000'>>> from dateutil.relativedelta import relativedelta >>> t2 = t + relativedelta(days=93) >>> t2.format("ymd") '2025/08/02'>>> t + {"days": 1, "months": 2} 2025-07-02 00:00:00.000000>>> ExtendedDatetime.range("今月").format("ymdhm") ['2025/04/01 00:00', '2025/04/30 23:59']>>> ExtendedDatetime.range("今月").dict_format("ymd", "ja") {'start': '2025年04月01日', 'end': '2025年04月30日'}>>> ExtendedDatetime("2025-01-01 01:23:45", hours=-12).range("今年") [2024-01-01 00:00:00.000000, 2024-12-31 23:59:59.999999]
1""" 2timekit - datetime 拡張ユーティリティ 3 4- `ExtendedDatetime`: 柔軟な初期化と書式変換ができる datetime 拡張クラス 5- `ExtendedDatetimeList`: ExtendedDatetimeを要素とする日付リストを扱う補助クラス 6 7Examples: 8 >>> from cls.timekit import ExtendedDatetime 9 >>> t = ExtendedDatetime("2025-04-19 12:34:56") 10 >>> t.format("ymdhm") 11 '2025/04/19 12:34' 12 13 >>> t.set("2025-05-01 00:00:00") 14 >>> t.format("sql") 15 '2025-05-01 00:00:00.000000' 16 17 >>> from dateutil.relativedelta import relativedelta 18 >>> t2 = t + relativedelta(days=93) 19 >>> t2.format("ymd") 20 '2025/08/02' 21 22 >>> t + {"days": 1, "months": 2} 23 2025-07-02 00:00:00.000000 24 25 >>> ExtendedDatetime.range("今月").format("ymdhm") 26 ['2025/04/01 00:00', '2025/04/30 23:59'] 27 28 >>> ExtendedDatetime.range("今月").dict_format("ymd", "ja") 29 {'start': '2025年04月01日', 'end': '2025年04月30日'} 30 31 >>> ExtendedDatetime("2025-01-01 01:23:45", hours=-12).range("今年") 32 [2024-01-01 00:00:00.000000, 2024-12-31 23:59:59.999999] 33 34""" 35 36from datetime import datetime 37from enum import StrEnum 38from functools import total_ordering 39from typing import Any, Callable, Optional, TypeAlias, TypedDict, Union 40 41from dateutil.relativedelta import MO, SU, relativedelta 42 43 44class Format(StrEnum): 45 """フォーマット変換で指定する種類""" 46 47 TS = "ts" 48 """タイムスタンプ""" 49 Y = "y" 50 """年(%Y)""" 51 YM = "ym" 52 """年月(%Y/%m)""" 53 YMD = "ymd" 54 """年月日(%Y/%m/%d)""" 55 YMDHM = "ymdhm" 56 """年月日時分(%Y/%m/%d %H:%M)""" 57 YMDHMS = "ymdhms" 58 """年月日時分秒(%Y/%m/%d %H:%M:%S)""" 59 HM = "hm" 60 """時分(%H:%M)""" 61 HMS = "hms" 62 """時分秒(%H:%M:%S)""" 63 SQL = "sql" 64 """SQLite用フォーマット(%Y-%m-%d %H:%M:%S.%f)""" 65 EXT = "ext" 66 """ファイル拡張子用(%Y%m%d-%H%M%S)""" 67 68 JY = "jy" 69 JYM = "jym" 70 JYMD = "jymd" 71 72 JY_O = "jy_o" 73 JYM_O = "jym_o" 74 JYMD_O = "jymd_o" 75 76 Y_O = "y_o" 77 YM_O = "ym_o" 78 YMD_O = "ymd_o" 79 80 81class Delimiter(StrEnum): 82 """区切り記号""" 83 84 SLASH = "slash" 85 """スラッシュ(ex: %Y/%m/%d)""" 86 HYPHEN = "hyphen" 87 """ハイフン(ex: %Y-%m-%d)""" 88 NUMBER = "number" 89 """区切り無し (ex: %Y%m%d)""" 90 JAPANESE = "japanese" 91 """Japanese Style (ex: %Y%年m%月d日)""" 92 UNDEFINED = "" 93 """未定義""" 94 95 96class DateRangeSpec(TypedDict): 97 """日付範囲変換キーワード用辞書""" 98 99 keyword: list[str] 100 range: Callable[[datetime], list[datetime]] 101 102 103DATE_RANGE_MAP: dict[str, DateRangeSpec] = { 104 "today": { 105 "keyword": ["今日", "本日", "当日"], 106 "range": lambda x: [ 107 x.replace(hour=0, minute=0, second=0, microsecond=0), 108 x.replace(hour=23, minute=59, second=59, microsecond=999999), 109 ], 110 }, 111 "yesterday": { 112 "keyword": ["昨日"], 113 "range": lambda x: [ 114 x + relativedelta(days=-1, hour=0, minute=0, second=0, microsecond=0), 115 x + relativedelta(days=-1, hour=23, minute=59, second=59, microsecond=999999), 116 ], 117 }, 118 "this_week": { 119 "keyword": ["今週"], 120 "range": lambda x: [ 121 x + relativedelta(weekday=MO(-1), hour=0, minute=0, second=0, microsecond=0), 122 x + relativedelta(weekday=SU, hour=23, minute=59, second=59, microsecond=999999), 123 ], 124 }, 125 "last_week": { 126 "keyword": ["先週"], 127 "range": lambda x: [ 128 x + relativedelta(weekday=MO(-2), hour=0, minute=0, second=0, microsecond=0), 129 x + relativedelta(weekday=SU(-1), hour=23, minute=59, second=59, microsecond=999999), 130 ], 131 }, 132 "this_month": { 133 "keyword": ["今月"], 134 "range": lambda x: [ 135 x + relativedelta(day=1, hour=0, minute=0, second=0, microsecond=0), 136 x + relativedelta(day=31, hour=23, minute=59, second=59, microsecond=999999), 137 ], 138 }, 139 "last_month": { 140 "keyword": ["先月", "昨月"], 141 "range": lambda x: [ 142 x + relativedelta(months=-1, day=1, hour=0, minute=0, second=0, microsecond=0), 143 x + relativedelta(months=-1, day=31, hour=23, minute=59, second=59, microsecond=999999), 144 ], 145 }, 146 "two_months_ago": { 147 "keyword": ["先々月"], 148 "range": lambda x: [ 149 x + relativedelta(months=-2, day=1, hour=0, minute=0, second=0, microsecond=0), 150 x + relativedelta(months=-2, day=31, hour=23, minute=59, second=59, microsecond=999999), 151 ], 152 }, 153 "this_year": { 154 "keyword": ["今年"], 155 "range": lambda x: [ 156 x + relativedelta(month=1, day=1, hour=0, minute=0, second=0, microsecond=0), 157 x + relativedelta(month=12, day=31, hour=23, minute=59, second=59, microsecond=999999), 158 ], 159 }, 160 "last_year": { 161 "keyword": ["去年", "昨年"], 162 "range": lambda x: [ 163 x + relativedelta(years=-1, month=1, day=1, hour=0, minute=0, second=0, microsecond=0), 164 x + relativedelta(years=-1, month=12, day=31, hour=23, minute=59, second=59, microsecond=999999), 165 ], 166 }, 167 "year_before_last": { 168 "keyword": ["一昨年"], 169 "range": lambda x: [ 170 x + relativedelta(years=-2, month=1, day=1, hour=0, minute=0, second=0, microsecond=0), 171 x + relativedelta(years=-2, month=12, day=31, hour=23, minute=59, second=59, microsecond=999999), 172 ], 173 }, 174 "first_day": { 175 "keyword": ["最初"], 176 "range": lambda x: [ 177 x + relativedelta(year=1900, month=1, day=1, hour=0, minute=0, second=0, microsecond=0), 178 ], 179 }, 180 "last_day": { 181 "keyword": ["最後"], 182 "range": lambda x: [ 183 x + relativedelta(days=1, hour=23, minute=59, second=59, microsecond=999999), 184 ], 185 }, 186 "all": { 187 "keyword": ["全部"], 188 "range": lambda x: [ 189 x + relativedelta(year=1900, month=1, day=1, hour=0, minute=0, second=0, microsecond=0), 190 x + relativedelta(days=1, hour=23, minute=59, second=59, microsecond=999999), 191 ], 192 }, 193} 194"""キーワードと日付範囲のマッピングリスト""" 195 196 197@total_ordering 198class ExtendedDatetime: 199 """datetime拡張クラス""" 200 201 FMT = Format 202 DEM = Delimiter 203 204 _dt: datetime 205 """操作対象""" 206 207 # 型アノテーション用定数 208 AcceptedType: TypeAlias = Union[str, float, datetime, "ExtendedDatetime"] 209 """引数として受け付ける型 210 - **str**: 日付文字列(ISO形式など) 211 - **float**: UNIXタイムスタンプ 212 - **datetime** / **ExtendedDatetime**: オブジェクトをそのまま利用 213 """ 214 215 def __init__(self, value: Optional[AcceptedType] = None, **relativedelta_kwargs: Any): 216 """ 217 ExtendedDatetimeの初期化 218 219 Args: 220 value (Optional[AcceptedType], optional): 引数. Defaults to None. 221 - None: 現在時刻(`datetime.now()`)で初期化 222 relativedelta_kwargs (dict): 初期化時にrelativedelta()に渡す引数 223 224 """ 225 self._dt = self.convert(value) if value else datetime.now() 226 if relativedelta_kwargs: 227 self._dt += relativedelta(**relativedelta_kwargs) 228 229 def __str__(self) -> str: 230 return self.format(Format.SQL) 231 232 def __repr__(self) -> str: 233 return self.format(Format.SQL) 234 235 def __eq__(self, other: Any) -> bool: 236 if isinstance(other, ExtendedDatetime): 237 return self.dt == other.dt 238 if isinstance(other, datetime): 239 return self.dt == other 240 if isinstance(other, str): 241 return self.format(Format.SQL) == other 242 return NotImplemented 243 244 def __lt__(self, other: Any) -> bool: 245 if isinstance(other, ExtendedDatetime): 246 return self.dt < other.dt 247 if isinstance(other, datetime): 248 return self.dt < other 249 return NotImplemented 250 251 def __add__(self, other: Union[relativedelta, dict[Any, Any]]) -> "ExtendedDatetime": 252 if isinstance(other, dict): 253 delta = relativedelta(**other) 254 elif isinstance(other, relativedelta): 255 delta = other 256 else: 257 raise TypeError("Expected dict or relativedelta") 258 259 return ExtendedDatetime(self._dt + delta) 260 261 def __sub__(self, other: Union[relativedelta, dict[Any, Any]]) -> "ExtendedDatetime": 262 if isinstance(other, dict): 263 delta = relativedelta(**other) 264 elif isinstance(other, relativedelta): 265 delta = other 266 else: 267 raise TypeError("Expected dict or relativedelta") 268 269 return ExtendedDatetime(self._dt - delta) 270 271 def __radd__(self, other: Union[relativedelta, dict[Any, Any]]) -> "ExtendedDatetime": 272 return self.__add__(other) 273 274 def __rsub__(self, other: Union[relativedelta, dict[Any, Any]]) -> "ExtendedDatetime": 275 return self.__sub__(other) 276 277 def __hash__(self) -> int: 278 return hash(self.dt) 279 280 def __getattr__(self, name: str) -> Any: 281 return getattr(self._dt, name) 282 283 @property 284 def dt(self) -> datetime: 285 """datetime型を返すプロパティ""" 286 return self._dt 287 288 @dt.setter 289 def dt(self, value: AcceptedType) -> None: 290 """dtに対するsetter""" 291 self._dt = self.convert(value) 292 293 def set(self, value: AcceptedType) -> None: 294 """ 295 渡された値をdatetime型に変換して保持 296 297 Args: 298 value (AcceptedType): 入力値 299 300 """ 301 self._dt = self.convert(value) 302 303 def format(self, fmt: Format, delimiter: Delimiter = Delimiter.UNDEFINED) -> str: 304 """ 305 フォーマット変換 306 307 Args: 308 fmt (Format): 変換形式 309 delimiter (Delimiter): 区切り 310 311 Raises: 312 ValueError: 受け付けない変換形式 313 314 Returns: 315 str: 変換文字列 316 317 """ 318 ret: str 319 320 if fmt.name.startswith("J"): 321 delimiter = Delimiter.JAPANESE 322 323 match fmt: 324 case Format.TS: 325 ret = str(self._dt.timestamp()) 326 case Format.Y | Format.JY | Format.Y_O | Format.JY_O: 327 match delimiter: 328 case Delimiter.JAPANESE: 329 ret = self._dt.strftime("%Y年") 330 case _: 331 ret = self._dt.strftime("%Y") 332 case Format.YM | Format.JYM | Format.YM_O | Format.JYM_O: 333 match delimiter: 334 case Delimiter.SLASH: 335 ret = self._dt.strftime("%Y/%m") 336 case Delimiter.HYPHEN: 337 ret = self._dt.strftime("%Y-%m") 338 case Delimiter.JAPANESE: 339 ret = self._dt.strftime("%Y年%m月") 340 case Delimiter.NUMBER: 341 ret = self._dt.strftime("%Y%m") 342 case _: 343 ret = self._dt.strftime("%Y/%m") 344 case Format.YMD | Format.JYMD | Format.YMD_O | Format.JYMD_O: 345 match delimiter: 346 case Delimiter.SLASH: 347 ret = self._dt.strftime("%Y/%m/%d") 348 case Delimiter.HYPHEN: 349 ret = self._dt.strftime("%Y-%m-%d") 350 case Delimiter.JAPANESE: 351 ret = self._dt.strftime("%Y年%m月%d日") 352 case Delimiter.NUMBER: 353 ret = self._dt.strftime("%Y%m%d") 354 case _: 355 ret = self._dt.strftime("%Y/%m/%d") 356 case Format.YMDHM: 357 match delimiter: 358 case Delimiter.SLASH: 359 ret = self._dt.strftime("%Y/%m/%d %H:%M") 360 case Delimiter.HYPHEN: 361 ret = self._dt.strftime("%Y-%m-%d %H:%M") 362 case Delimiter.JAPANESE: 363 ret = self._dt.strftime("%Y年%m月%d日 %H時%M分") 364 case Delimiter.NUMBER: 365 ret = self._dt.strftime("%Y%m%d%H%M") 366 case _: 367 ret = self._dt.strftime("%Y/%m/%d %H:%M") 368 case Format.YMDHMS: 369 match delimiter: 370 case Delimiter.SLASH: 371 ret = self._dt.strftime("%Y/%m/%d %H:%M:%S") 372 case Delimiter.HYPHEN: 373 ret = self._dt.strftime("%Y-%m-%d %H:%M:%S") 374 case Delimiter.JAPANESE: 375 ret = self._dt.strftime("%Y年%m月%d日 %H時%M分%S秒") 376 case Delimiter.NUMBER: 377 ret = self._dt.strftime("%Y%m%d%H%M%S") 378 case _: 379 ret = self._dt.strftime("%Y/%m/%d %H:%M:%S") 380 case Format.HM: 381 match delimiter: 382 case Delimiter.SLASH: 383 ret = self._dt.strftime("%H:%M") 384 case Delimiter.HYPHEN: 385 ret = self._dt.strftime("%H:%M") 386 case Delimiter.JAPANESE: 387 ret = self._dt.strftime("%H時%M分") 388 case Delimiter.NUMBER: 389 ret = self._dt.strftime("%H%M") 390 case _: 391 ret = self._dt.strftime("%H:%M") 392 case Format.HMS: 393 match delimiter: 394 case Delimiter.SLASH: 395 ret = self._dt.strftime("%H:%M:%S") 396 case Delimiter.HYPHEN: 397 ret = self._dt.strftime("%H:%M:%S") 398 case Delimiter.JAPANESE: 399 ret = self._dt.strftime("%H時%M分%S秒") 400 case Delimiter.NUMBER: 401 ret = self._dt.strftime("%H%M%S") 402 case _: 403 ret = self._dt.strftime("%H:%M:%S") 404 case Format.SQL: 405 match delimiter: 406 case Delimiter.SLASH: 407 ret = self._dt.strftime("%Y/%m/%d %H:%M:%S.%f") 408 case Delimiter.HYPHEN: 409 ret = self._dt.strftime("%Y-%m-%d %H:%M:%S.%f") 410 case Delimiter.NUMBER: 411 ret = self._dt.strftime("%Y%m%d%H%M%S%f") 412 case _: 413 ret = self._dt.strftime("%Y-%m-%d %H:%M:%S.%f") 414 case Format.EXT: 415 ret = self._dt.strftime("%Y%m%d-%H%M%S") 416 case _: 417 raise ValueError(f"Unknown format: {fmt}") 418 419 return ret 420 421 def range( 422 self, 423 value: Union[str, list[str], list["ExtendedDatetime"], "ExtendedDatetimeList"], 424 ) -> "ExtendedDatetimeList": 425 """ 426 キーワードが示す範囲をリストで返す 427 428 Args: 429 value (Union[...]): 範囲取得キーワード 430 - str: スペース区切りで分割してリスト化 431 - list: スペース区切りで再分割 432 - list[ExtendedDatetime]: リスト化されたExtendedDatetime型 433 - ExtendedDatetimeList: ExtendedDatetimeList型 434 435 Returns: 436 ExtendedDatetimeList: 日付リスト 437 438 """ 439 if isinstance(value, str): 440 check_list = value.split() 441 else: 442 check_list = sum([str(x).split() for x in value], []) # 平坦化 443 444 ret: list[datetime] = [] 445 for word in check_list: 446 for _, range_map in DATE_RANGE_MAP.items(): 447 if word in range_map["keyword"]: 448 ret.extend(range_map["range"](self._dt)) 449 break 450 else: 451 try: 452 try_time = self.convert(str(word)) 453 ret.append(try_time.replace(hour=0, minute=0, second=0, microsecond=0)) 454 ret.append(try_time.replace(hour=23, minute=59, second=59, microsecond=999999)) 455 except ValueError: 456 pass 457 458 continue 459 460 return ExtendedDatetimeList([ExtendedDatetime(x) for x in ret]) 461 462 @classmethod 463 def valid_keywords(cls) -> list[str]: 464 """ 465 有効なキーワード一覧 466 467 Returns: 468 list[str]: キーワード一覧 469 470 """ 471 ret: list[str] = [] 472 for _, range_map in DATE_RANGE_MAP.items(): 473 ret.extend(range_map["keyword"]) 474 475 return ret 476 477 @classmethod 478 def print_range(cls) -> str: 479 """ 480 指定可能キーワードで取得できる範囲の一覧 481 482 Returns: 483 str: 出力メッセージ 484 485 """ 486 base_instance = cls() 487 ret: str = "" 488 489 for _, val in DATE_RANGE_MAP.items(): 490 for label in val["keyword"]: 491 scope = " ~ ".join(base_instance.range(label).format(Format.YMD)) 492 ret += f"{label}:{scope}\n" 493 494 return ret.strip() 495 496 @staticmethod 497 def convert(value: AcceptedType) -> datetime: 498 """ 499 引数の型を判定してdatetimeへ変換 500 501 Args: 502 value (AcceptedType): 変換対象 503 504 Raises: 505 TypeError: str型が変換できない場合 506 507 Returns: 508 datetime: 変換した型 509 510 """ 511 if isinstance(value, ExtendedDatetime): 512 return value.dt 513 if isinstance(value, datetime): 514 return value 515 if isinstance(value, float): 516 return datetime.fromtimestamp(value) 517 if isinstance(value, str): 518 try: 519 return datetime.fromisoformat(value) 520 except ValueError: 521 return datetime.strptime(value, "%Y/%m/%d %H:%M") 522 523 raise TypeError("Unsupported type for datetime conversion") 524 525 526class ExtendedDatetimeList(list): # type: ignore[type-arg] 527 """ExtendedDatetimeを要素とする日付リストを扱う補助クラス""" 528 529 Delimiter: TypeAlias = Delimiter 530 531 def __add__(self, other: Any) -> "ExtendedDatetimeList": 532 if isinstance(other, dict): 533 return ExtendedDatetimeList([dt + other for dt in self]) 534 return NotImplemented 535 536 def __sub__(self, other: Any) -> "ExtendedDatetimeList": 537 if isinstance(other, dict): 538 return ExtendedDatetimeList([dt - other for dt in self]) 539 return NotImplemented 540 541 @property 542 def start(self) -> ExtendedDatetime | None: 543 """最小日付を返す。空ならNone。""" 544 return min(self) if self else None 545 546 @property 547 def end(self) -> ExtendedDatetime | None: 548 """最大日付を返す。空ならNone。""" 549 return max(self) if self else None 550 551 @property 552 def period(self) -> list[ExtendedDatetime | None]: 553 """最小値と最大値をリストで返す""" 554 min_dt = min(self) if self else None 555 max_dt = max(self) if self else None 556 557 return [min_dt, max_dt] 558 559 def format(self, fmt: Format = Format.SQL, delimiter: Delimiter = Delimiter.UNDEFINED) -> list[str]: 560 """ 561 全要素にformatを適用した文字列リストを返す 562 563 Args: 564 fmt (Format, optional): フォーマット変換. Defaults to "sql". 565 delimiter (Delimiter, optional): 区切り記号指定. Defaults to None. 566 567 Returns: 568 list[str]: 生成したリスト 569 570 """ 571 return [dt.format(fmt, delimiter) for dt in self if isinstance(dt, ExtendedDatetime)] 572 573 def dict_format(self, fmt: Format = Format.SQL, delimiter: Delimiter = Delimiter.UNDEFINED) -> dict[str, str]: 574 """ 575 全要素にformatを適用し、最小日付と最大日付を辞書で返す 576 577 Args: 578 fmt (Format, optional): フォーマット変換. Defaults to "sql". 579 delimiter (Delimiter, optional): 区切り記号指定. Defaults to None. 580 581 Returns: 582 dict[str, str]: 生成した辞書 583 584 """ 585 date_range = [dt for dt in self if isinstance(dt, ExtendedDatetime)] 586 587 if not date_range: 588 return {} 589 590 return {"start": min(date_range).format(fmt, delimiter), "end": max(date_range).format(fmt, delimiter)}
class
Format(enum.StrEnum):
45class Format(StrEnum): 46 """フォーマット変換で指定する種類""" 47 48 TS = "ts" 49 """タイムスタンプ""" 50 Y = "y" 51 """年(%Y)""" 52 YM = "ym" 53 """年月(%Y/%m)""" 54 YMD = "ymd" 55 """年月日(%Y/%m/%d)""" 56 YMDHM = "ymdhm" 57 """年月日時分(%Y/%m/%d %H:%M)""" 58 YMDHMS = "ymdhms" 59 """年月日時分秒(%Y/%m/%d %H:%M:%S)""" 60 HM = "hm" 61 """時分(%H:%M)""" 62 HMS = "hms" 63 """時分秒(%H:%M:%S)""" 64 SQL = "sql" 65 """SQLite用フォーマット(%Y-%m-%d %H:%M:%S.%f)""" 66 EXT = "ext" 67 """ファイル拡張子用(%Y%m%d-%H%M%S)""" 68 69 JY = "jy" 70 JYM = "jym" 71 JYMD = "jymd" 72 73 JY_O = "jy_o" 74 JYM_O = "jym_o" 75 JYMD_O = "jymd_o" 76 77 Y_O = "y_o" 78 YM_O = "ym_o" 79 YMD_O = "ymd_o"
フォーマット変換で指定する種類
JY =
<Format.JY: 'jy'>
JYM =
<Format.JYM: 'jym'>
JYMD =
<Format.JYMD: 'jymd'>
JY_O =
<Format.JY_O: 'jy_o'>
JYM_O =
<Format.JYM_O: 'jym_o'>
JYMD_O =
<Format.JYMD_O: 'jymd_o'>
Y_O =
<Format.Y_O: 'y_o'>
YM_O =
<Format.YM_O: 'ym_o'>
YMD_O =
<Format.YMD_O: 'ymd_o'>
class
Delimiter(enum.StrEnum):
82class Delimiter(StrEnum): 83 """区切り記号""" 84 85 SLASH = "slash" 86 """スラッシュ(ex: %Y/%m/%d)""" 87 HYPHEN = "hyphen" 88 """ハイフン(ex: %Y-%m-%d)""" 89 NUMBER = "number" 90 """区切り無し (ex: %Y%m%d)""" 91 JAPANESE = "japanese" 92 """Japanese Style (ex: %Y%年m%月d日)""" 93 UNDEFINED = "" 94 """未定義"""
区切り記号
class
DateRangeSpec(typing.TypedDict):
97class DateRangeSpec(TypedDict): 98 """日付範囲変換キーワード用辞書""" 99 100 keyword: list[str] 101 range: Callable[[datetime], list[datetime]]
日付範囲変換キーワード用辞書
DATE_RANGE_MAP: dict[str, DateRangeSpec] =
{'today': {'keyword': ['今日', '本日', '当日'], 'range': <function <lambda>>}, 'yesterday': {'keyword': ['昨日'], 'range': <function <lambda>>}, 'this_week': {'keyword': ['今週'], 'range': <function <lambda>>}, 'last_week': {'keyword': ['先週'], 'range': <function <lambda>>}, 'this_month': {'keyword': ['今月'], 'range': <function <lambda>>}, 'last_month': {'keyword': ['先月', '昨月'], 'range': <function <lambda>>}, 'two_months_ago': {'keyword': ['先々月'], 'range': <function <lambda>>}, 'this_year': {'keyword': ['今年'], 'range': <function <lambda>>}, 'last_year': {'keyword': ['去年', '昨年'], 'range': <function <lambda>>}, 'year_before_last': {'keyword': ['一昨年'], 'range': <function <lambda>>}, 'first_day': {'keyword': ['最初'], 'range': <function <lambda>>}, 'last_day': {'keyword': ['最後'], 'range': <function <lambda>>}, 'all': {'keyword': ['全部'], 'range': <function <lambda>>}}
キーワードと日付範囲のマッピングリスト
@total_ordering
class
ExtendedDatetime:
198@total_ordering 199class ExtendedDatetime: 200 """datetime拡張クラス""" 201 202 FMT = Format 203 DEM = Delimiter 204 205 _dt: datetime 206 """操作対象""" 207 208 # 型アノテーション用定数 209 AcceptedType: TypeAlias = Union[str, float, datetime, "ExtendedDatetime"] 210 """引数として受け付ける型 211 - **str**: 日付文字列(ISO形式など) 212 - **float**: UNIXタイムスタンプ 213 - **datetime** / **ExtendedDatetime**: オブジェクトをそのまま利用 214 """ 215 216 def __init__(self, value: Optional[AcceptedType] = None, **relativedelta_kwargs: Any): 217 """ 218 ExtendedDatetimeの初期化 219 220 Args: 221 value (Optional[AcceptedType], optional): 引数. Defaults to None. 222 - None: 現在時刻(`datetime.now()`)で初期化 223 relativedelta_kwargs (dict): 初期化時にrelativedelta()に渡す引数 224 225 """ 226 self._dt = self.convert(value) if value else datetime.now() 227 if relativedelta_kwargs: 228 self._dt += relativedelta(**relativedelta_kwargs) 229 230 def __str__(self) -> str: 231 return self.format(Format.SQL) 232 233 def __repr__(self) -> str: 234 return self.format(Format.SQL) 235 236 def __eq__(self, other: Any) -> bool: 237 if isinstance(other, ExtendedDatetime): 238 return self.dt == other.dt 239 if isinstance(other, datetime): 240 return self.dt == other 241 if isinstance(other, str): 242 return self.format(Format.SQL) == other 243 return NotImplemented 244 245 def __lt__(self, other: Any) -> bool: 246 if isinstance(other, ExtendedDatetime): 247 return self.dt < other.dt 248 if isinstance(other, datetime): 249 return self.dt < other 250 return NotImplemented 251 252 def __add__(self, other: Union[relativedelta, dict[Any, Any]]) -> "ExtendedDatetime": 253 if isinstance(other, dict): 254 delta = relativedelta(**other) 255 elif isinstance(other, relativedelta): 256 delta = other 257 else: 258 raise TypeError("Expected dict or relativedelta") 259 260 return ExtendedDatetime(self._dt + delta) 261 262 def __sub__(self, other: Union[relativedelta, dict[Any, Any]]) -> "ExtendedDatetime": 263 if isinstance(other, dict): 264 delta = relativedelta(**other) 265 elif isinstance(other, relativedelta): 266 delta = other 267 else: 268 raise TypeError("Expected dict or relativedelta") 269 270 return ExtendedDatetime(self._dt - delta) 271 272 def __radd__(self, other: Union[relativedelta, dict[Any, Any]]) -> "ExtendedDatetime": 273 return self.__add__(other) 274 275 def __rsub__(self, other: Union[relativedelta, dict[Any, Any]]) -> "ExtendedDatetime": 276 return self.__sub__(other) 277 278 def __hash__(self) -> int: 279 return hash(self.dt) 280 281 def __getattr__(self, name: str) -> Any: 282 return getattr(self._dt, name) 283 284 @property 285 def dt(self) -> datetime: 286 """datetime型を返すプロパティ""" 287 return self._dt 288 289 @dt.setter 290 def dt(self, value: AcceptedType) -> None: 291 """dtに対するsetter""" 292 self._dt = self.convert(value) 293 294 def set(self, value: AcceptedType) -> None: 295 """ 296 渡された値をdatetime型に変換して保持 297 298 Args: 299 value (AcceptedType): 入力値 300 301 """ 302 self._dt = self.convert(value) 303 304 def format(self, fmt: Format, delimiter: Delimiter = Delimiter.UNDEFINED) -> str: 305 """ 306 フォーマット変換 307 308 Args: 309 fmt (Format): 変換形式 310 delimiter (Delimiter): 区切り 311 312 Raises: 313 ValueError: 受け付けない変換形式 314 315 Returns: 316 str: 変換文字列 317 318 """ 319 ret: str 320 321 if fmt.name.startswith("J"): 322 delimiter = Delimiter.JAPANESE 323 324 match fmt: 325 case Format.TS: 326 ret = str(self._dt.timestamp()) 327 case Format.Y | Format.JY | Format.Y_O | Format.JY_O: 328 match delimiter: 329 case Delimiter.JAPANESE: 330 ret = self._dt.strftime("%Y年") 331 case _: 332 ret = self._dt.strftime("%Y") 333 case Format.YM | Format.JYM | Format.YM_O | Format.JYM_O: 334 match delimiter: 335 case Delimiter.SLASH: 336 ret = self._dt.strftime("%Y/%m") 337 case Delimiter.HYPHEN: 338 ret = self._dt.strftime("%Y-%m") 339 case Delimiter.JAPANESE: 340 ret = self._dt.strftime("%Y年%m月") 341 case Delimiter.NUMBER: 342 ret = self._dt.strftime("%Y%m") 343 case _: 344 ret = self._dt.strftime("%Y/%m") 345 case Format.YMD | Format.JYMD | Format.YMD_O | Format.JYMD_O: 346 match delimiter: 347 case Delimiter.SLASH: 348 ret = self._dt.strftime("%Y/%m/%d") 349 case Delimiter.HYPHEN: 350 ret = self._dt.strftime("%Y-%m-%d") 351 case Delimiter.JAPANESE: 352 ret = self._dt.strftime("%Y年%m月%d日") 353 case Delimiter.NUMBER: 354 ret = self._dt.strftime("%Y%m%d") 355 case _: 356 ret = self._dt.strftime("%Y/%m/%d") 357 case Format.YMDHM: 358 match delimiter: 359 case Delimiter.SLASH: 360 ret = self._dt.strftime("%Y/%m/%d %H:%M") 361 case Delimiter.HYPHEN: 362 ret = self._dt.strftime("%Y-%m-%d %H:%M") 363 case Delimiter.JAPANESE: 364 ret = self._dt.strftime("%Y年%m月%d日 %H時%M分") 365 case Delimiter.NUMBER: 366 ret = self._dt.strftime("%Y%m%d%H%M") 367 case _: 368 ret = self._dt.strftime("%Y/%m/%d %H:%M") 369 case Format.YMDHMS: 370 match delimiter: 371 case Delimiter.SLASH: 372 ret = self._dt.strftime("%Y/%m/%d %H:%M:%S") 373 case Delimiter.HYPHEN: 374 ret = self._dt.strftime("%Y-%m-%d %H:%M:%S") 375 case Delimiter.JAPANESE: 376 ret = self._dt.strftime("%Y年%m月%d日 %H時%M分%S秒") 377 case Delimiter.NUMBER: 378 ret = self._dt.strftime("%Y%m%d%H%M%S") 379 case _: 380 ret = self._dt.strftime("%Y/%m/%d %H:%M:%S") 381 case Format.HM: 382 match delimiter: 383 case Delimiter.SLASH: 384 ret = self._dt.strftime("%H:%M") 385 case Delimiter.HYPHEN: 386 ret = self._dt.strftime("%H:%M") 387 case Delimiter.JAPANESE: 388 ret = self._dt.strftime("%H時%M分") 389 case Delimiter.NUMBER: 390 ret = self._dt.strftime("%H%M") 391 case _: 392 ret = self._dt.strftime("%H:%M") 393 case Format.HMS: 394 match delimiter: 395 case Delimiter.SLASH: 396 ret = self._dt.strftime("%H:%M:%S") 397 case Delimiter.HYPHEN: 398 ret = self._dt.strftime("%H:%M:%S") 399 case Delimiter.JAPANESE: 400 ret = self._dt.strftime("%H時%M分%S秒") 401 case Delimiter.NUMBER: 402 ret = self._dt.strftime("%H%M%S") 403 case _: 404 ret = self._dt.strftime("%H:%M:%S") 405 case Format.SQL: 406 match delimiter: 407 case Delimiter.SLASH: 408 ret = self._dt.strftime("%Y/%m/%d %H:%M:%S.%f") 409 case Delimiter.HYPHEN: 410 ret = self._dt.strftime("%Y-%m-%d %H:%M:%S.%f") 411 case Delimiter.NUMBER: 412 ret = self._dt.strftime("%Y%m%d%H%M%S%f") 413 case _: 414 ret = self._dt.strftime("%Y-%m-%d %H:%M:%S.%f") 415 case Format.EXT: 416 ret = self._dt.strftime("%Y%m%d-%H%M%S") 417 case _: 418 raise ValueError(f"Unknown format: {fmt}") 419 420 return ret 421 422 def range( 423 self, 424 value: Union[str, list[str], list["ExtendedDatetime"], "ExtendedDatetimeList"], 425 ) -> "ExtendedDatetimeList": 426 """ 427 キーワードが示す範囲をリストで返す 428 429 Args: 430 value (Union[...]): 範囲取得キーワード 431 - str: スペース区切りで分割してリスト化 432 - list: スペース区切りで再分割 433 - list[ExtendedDatetime]: リスト化されたExtendedDatetime型 434 - ExtendedDatetimeList: ExtendedDatetimeList型 435 436 Returns: 437 ExtendedDatetimeList: 日付リスト 438 439 """ 440 if isinstance(value, str): 441 check_list = value.split() 442 else: 443 check_list = sum([str(x).split() for x in value], []) # 平坦化 444 445 ret: list[datetime] = [] 446 for word in check_list: 447 for _, range_map in DATE_RANGE_MAP.items(): 448 if word in range_map["keyword"]: 449 ret.extend(range_map["range"](self._dt)) 450 break 451 else: 452 try: 453 try_time = self.convert(str(word)) 454 ret.append(try_time.replace(hour=0, minute=0, second=0, microsecond=0)) 455 ret.append(try_time.replace(hour=23, minute=59, second=59, microsecond=999999)) 456 except ValueError: 457 pass 458 459 continue 460 461 return ExtendedDatetimeList([ExtendedDatetime(x) for x in ret]) 462 463 @classmethod 464 def valid_keywords(cls) -> list[str]: 465 """ 466 有効なキーワード一覧 467 468 Returns: 469 list[str]: キーワード一覧 470 471 """ 472 ret: list[str] = [] 473 for _, range_map in DATE_RANGE_MAP.items(): 474 ret.extend(range_map["keyword"]) 475 476 return ret 477 478 @classmethod 479 def print_range(cls) -> str: 480 """ 481 指定可能キーワードで取得できる範囲の一覧 482 483 Returns: 484 str: 出力メッセージ 485 486 """ 487 base_instance = cls() 488 ret: str = "" 489 490 for _, val in DATE_RANGE_MAP.items(): 491 for label in val["keyword"]: 492 scope = " ~ ".join(base_instance.range(label).format(Format.YMD)) 493 ret += f"{label}:{scope}\n" 494 495 return ret.strip() 496 497 @staticmethod 498 def convert(value: AcceptedType) -> datetime: 499 """ 500 引数の型を判定してdatetimeへ変換 501 502 Args: 503 value (AcceptedType): 変換対象 504 505 Raises: 506 TypeError: str型が変換できない場合 507 508 Returns: 509 datetime: 変換した型 510 511 """ 512 if isinstance(value, ExtendedDatetime): 513 return value.dt 514 if isinstance(value, datetime): 515 return value 516 if isinstance(value, float): 517 return datetime.fromtimestamp(value) 518 if isinstance(value, str): 519 try: 520 return datetime.fromisoformat(value) 521 except ValueError: 522 return datetime.strptime(value, "%Y/%m/%d %H:%M") 523 524 raise TypeError("Unsupported type for datetime conversion")
datetime拡張クラス
ExtendedDatetime( value: str | float | datetime.datetime | ExtendedDatetime | None = None, **relativedelta_kwargs: Any)
216 def __init__(self, value: Optional[AcceptedType] = None, **relativedelta_kwargs: Any): 217 """ 218 ExtendedDatetimeの初期化 219 220 Args: 221 value (Optional[AcceptedType], optional): 引数. Defaults to None. 222 - None: 現在時刻(`datetime.now()`)で初期化 223 relativedelta_kwargs (dict): 初期化時にrelativedelta()に渡す引数 224 225 """ 226 self._dt = self.convert(value) if value else datetime.now() 227 if relativedelta_kwargs: 228 self._dt += relativedelta(**relativedelta_kwargs)
ExtendedDatetimeの初期化
Arguments:
- value (Optional[AcceptedType], optional): 引数. Defaults to None.
- None: 現在時刻(
datetime.now())で初期化
- None: 現在時刻(
- relativedelta_kwargs (dict): 初期化時にrelativedelta()に渡す引数
AcceptedType: TypeAlias =
str | float | datetime.datetime | ForwardRef('ExtendedDatetime')
引数として受け付ける型
- str: 日付文字列(ISO形式など)
- float: UNIXタイムスタンプ
- datetime / ExtendedDatetime: オブジェクトをそのまま利用
294 def set(self, value: AcceptedType) -> None: 295 """ 296 渡された値をdatetime型に変換して保持 297 298 Args: 299 value (AcceptedType): 入力値 300 301 """ 302 self._dt = self.convert(value)
渡された値をdatetime型に変換して保持
Arguments:
- value (AcceptedType): 入力値
304 def format(self, fmt: Format, delimiter: Delimiter = Delimiter.UNDEFINED) -> str: 305 """ 306 フォーマット変換 307 308 Args: 309 fmt (Format): 変換形式 310 delimiter (Delimiter): 区切り 311 312 Raises: 313 ValueError: 受け付けない変換形式 314 315 Returns: 316 str: 変換文字列 317 318 """ 319 ret: str 320 321 if fmt.name.startswith("J"): 322 delimiter = Delimiter.JAPANESE 323 324 match fmt: 325 case Format.TS: 326 ret = str(self._dt.timestamp()) 327 case Format.Y | Format.JY | Format.Y_O | Format.JY_O: 328 match delimiter: 329 case Delimiter.JAPANESE: 330 ret = self._dt.strftime("%Y年") 331 case _: 332 ret = self._dt.strftime("%Y") 333 case Format.YM | Format.JYM | Format.YM_O | Format.JYM_O: 334 match delimiter: 335 case Delimiter.SLASH: 336 ret = self._dt.strftime("%Y/%m") 337 case Delimiter.HYPHEN: 338 ret = self._dt.strftime("%Y-%m") 339 case Delimiter.JAPANESE: 340 ret = self._dt.strftime("%Y年%m月") 341 case Delimiter.NUMBER: 342 ret = self._dt.strftime("%Y%m") 343 case _: 344 ret = self._dt.strftime("%Y/%m") 345 case Format.YMD | Format.JYMD | Format.YMD_O | Format.JYMD_O: 346 match delimiter: 347 case Delimiter.SLASH: 348 ret = self._dt.strftime("%Y/%m/%d") 349 case Delimiter.HYPHEN: 350 ret = self._dt.strftime("%Y-%m-%d") 351 case Delimiter.JAPANESE: 352 ret = self._dt.strftime("%Y年%m月%d日") 353 case Delimiter.NUMBER: 354 ret = self._dt.strftime("%Y%m%d") 355 case _: 356 ret = self._dt.strftime("%Y/%m/%d") 357 case Format.YMDHM: 358 match delimiter: 359 case Delimiter.SLASH: 360 ret = self._dt.strftime("%Y/%m/%d %H:%M") 361 case Delimiter.HYPHEN: 362 ret = self._dt.strftime("%Y-%m-%d %H:%M") 363 case Delimiter.JAPANESE: 364 ret = self._dt.strftime("%Y年%m月%d日 %H時%M分") 365 case Delimiter.NUMBER: 366 ret = self._dt.strftime("%Y%m%d%H%M") 367 case _: 368 ret = self._dt.strftime("%Y/%m/%d %H:%M") 369 case Format.YMDHMS: 370 match delimiter: 371 case Delimiter.SLASH: 372 ret = self._dt.strftime("%Y/%m/%d %H:%M:%S") 373 case Delimiter.HYPHEN: 374 ret = self._dt.strftime("%Y-%m-%d %H:%M:%S") 375 case Delimiter.JAPANESE: 376 ret = self._dt.strftime("%Y年%m月%d日 %H時%M分%S秒") 377 case Delimiter.NUMBER: 378 ret = self._dt.strftime("%Y%m%d%H%M%S") 379 case _: 380 ret = self._dt.strftime("%Y/%m/%d %H:%M:%S") 381 case Format.HM: 382 match delimiter: 383 case Delimiter.SLASH: 384 ret = self._dt.strftime("%H:%M") 385 case Delimiter.HYPHEN: 386 ret = self._dt.strftime("%H:%M") 387 case Delimiter.JAPANESE: 388 ret = self._dt.strftime("%H時%M分") 389 case Delimiter.NUMBER: 390 ret = self._dt.strftime("%H%M") 391 case _: 392 ret = self._dt.strftime("%H:%M") 393 case Format.HMS: 394 match delimiter: 395 case Delimiter.SLASH: 396 ret = self._dt.strftime("%H:%M:%S") 397 case Delimiter.HYPHEN: 398 ret = self._dt.strftime("%H:%M:%S") 399 case Delimiter.JAPANESE: 400 ret = self._dt.strftime("%H時%M分%S秒") 401 case Delimiter.NUMBER: 402 ret = self._dt.strftime("%H%M%S") 403 case _: 404 ret = self._dt.strftime("%H:%M:%S") 405 case Format.SQL: 406 match delimiter: 407 case Delimiter.SLASH: 408 ret = self._dt.strftime("%Y/%m/%d %H:%M:%S.%f") 409 case Delimiter.HYPHEN: 410 ret = self._dt.strftime("%Y-%m-%d %H:%M:%S.%f") 411 case Delimiter.NUMBER: 412 ret = self._dt.strftime("%Y%m%d%H%M%S%f") 413 case _: 414 ret = self._dt.strftime("%Y-%m-%d %H:%M:%S.%f") 415 case Format.EXT: 416 ret = self._dt.strftime("%Y%m%d-%H%M%S") 417 case _: 418 raise ValueError(f"Unknown format: {fmt}") 419 420 return ret
フォーマット変換
Arguments:
- fmt (Format): 変換形式
- delimiter (Delimiter): 区切り
Raises:
- ValueError: 受け付けない変換形式
Returns:
str: 変換文字列
def
range( self, value: str | list[str] | list[ExtendedDatetime] | ExtendedDatetimeList) -> ExtendedDatetimeList:
422 def range( 423 self, 424 value: Union[str, list[str], list["ExtendedDatetime"], "ExtendedDatetimeList"], 425 ) -> "ExtendedDatetimeList": 426 """ 427 キーワードが示す範囲をリストで返す 428 429 Args: 430 value (Union[...]): 範囲取得キーワード 431 - str: スペース区切りで分割してリスト化 432 - list: スペース区切りで再分割 433 - list[ExtendedDatetime]: リスト化されたExtendedDatetime型 434 - ExtendedDatetimeList: ExtendedDatetimeList型 435 436 Returns: 437 ExtendedDatetimeList: 日付リスト 438 439 """ 440 if isinstance(value, str): 441 check_list = value.split() 442 else: 443 check_list = sum([str(x).split() for x in value], []) # 平坦化 444 445 ret: list[datetime] = [] 446 for word in check_list: 447 for _, range_map in DATE_RANGE_MAP.items(): 448 if word in range_map["keyword"]: 449 ret.extend(range_map["range"](self._dt)) 450 break 451 else: 452 try: 453 try_time = self.convert(str(word)) 454 ret.append(try_time.replace(hour=0, minute=0, second=0, microsecond=0)) 455 ret.append(try_time.replace(hour=23, minute=59, second=59, microsecond=999999)) 456 except ValueError: 457 pass 458 459 continue 460 461 return ExtendedDatetimeList([ExtendedDatetime(x) for x in ret])
キーワードが示す範囲をリストで返す
Arguments:
- value (Union[...]): 範囲取得キーワード
- str: スペース区切りで分割してリスト化
- list: スペース区切りで再分割
- list[ExtendedDatetime]: リスト化されたExtendedDatetime型
- ExtendedDatetimeList: ExtendedDatetimeList型
Returns:
ExtendedDatetimeList: 日付リスト
@classmethod
def
valid_keywords(cls) -> list[str]:
463 @classmethod 464 def valid_keywords(cls) -> list[str]: 465 """ 466 有効なキーワード一覧 467 468 Returns: 469 list[str]: キーワード一覧 470 471 """ 472 ret: list[str] = [] 473 for _, range_map in DATE_RANGE_MAP.items(): 474 ret.extend(range_map["keyword"]) 475 476 return ret
有効なキーワード一覧
Returns:
list[str]: キーワード一覧
@classmethod
def
print_range(cls) -> str:
478 @classmethod 479 def print_range(cls) -> str: 480 """ 481 指定可能キーワードで取得できる範囲の一覧 482 483 Returns: 484 str: 出力メッセージ 485 486 """ 487 base_instance = cls() 488 ret: str = "" 489 490 for _, val in DATE_RANGE_MAP.items(): 491 for label in val["keyword"]: 492 scope = " ~ ".join(base_instance.range(label).format(Format.YMD)) 493 ret += f"{label}:{scope}\n" 494 495 return ret.strip()
指定可能キーワードで取得できる範囲の一覧
Returns:
str: 出力メッセージ
@staticmethod
def
convert( value: str | float | datetime.datetime | ExtendedDatetime) -> datetime.datetime:
497 @staticmethod 498 def convert(value: AcceptedType) -> datetime: 499 """ 500 引数の型を判定してdatetimeへ変換 501 502 Args: 503 value (AcceptedType): 変換対象 504 505 Raises: 506 TypeError: str型が変換できない場合 507 508 Returns: 509 datetime: 変換した型 510 511 """ 512 if isinstance(value, ExtendedDatetime): 513 return value.dt 514 if isinstance(value, datetime): 515 return value 516 if isinstance(value, float): 517 return datetime.fromtimestamp(value) 518 if isinstance(value, str): 519 try: 520 return datetime.fromisoformat(value) 521 except ValueError: 522 return datetime.strptime(value, "%Y/%m/%d %H:%M") 523 524 raise TypeError("Unsupported type for datetime conversion")
引数の型を判定してdatetimeへ変換
Arguments:
- value (AcceptedType): 変換対象
Raises:
- TypeError: str型が変換できない場合
Returns:
datetime: 変換した型
class
ExtendedDatetimeList(builtins.list):
527class ExtendedDatetimeList(list): # type: ignore[type-arg] 528 """ExtendedDatetimeを要素とする日付リストを扱う補助クラス""" 529 530 Delimiter: TypeAlias = Delimiter 531 532 def __add__(self, other: Any) -> "ExtendedDatetimeList": 533 if isinstance(other, dict): 534 return ExtendedDatetimeList([dt + other for dt in self]) 535 return NotImplemented 536 537 def __sub__(self, other: Any) -> "ExtendedDatetimeList": 538 if isinstance(other, dict): 539 return ExtendedDatetimeList([dt - other for dt in self]) 540 return NotImplemented 541 542 @property 543 def start(self) -> ExtendedDatetime | None: 544 """最小日付を返す。空ならNone。""" 545 return min(self) if self else None 546 547 @property 548 def end(self) -> ExtendedDatetime | None: 549 """最大日付を返す。空ならNone。""" 550 return max(self) if self else None 551 552 @property 553 def period(self) -> list[ExtendedDatetime | None]: 554 """最小値と最大値をリストで返す""" 555 min_dt = min(self) if self else None 556 max_dt = max(self) if self else None 557 558 return [min_dt, max_dt] 559 560 def format(self, fmt: Format = Format.SQL, delimiter: Delimiter = Delimiter.UNDEFINED) -> list[str]: 561 """ 562 全要素にformatを適用した文字列リストを返す 563 564 Args: 565 fmt (Format, optional): フォーマット変換. Defaults to "sql". 566 delimiter (Delimiter, optional): 区切り記号指定. Defaults to None. 567 568 Returns: 569 list[str]: 生成したリスト 570 571 """ 572 return [dt.format(fmt, delimiter) for dt in self if isinstance(dt, ExtendedDatetime)] 573 574 def dict_format(self, fmt: Format = Format.SQL, delimiter: Delimiter = Delimiter.UNDEFINED) -> dict[str, str]: 575 """ 576 全要素にformatを適用し、最小日付と最大日付を辞書で返す 577 578 Args: 579 fmt (Format, optional): フォーマット変換. Defaults to "sql". 580 delimiter (Delimiter, optional): 区切り記号指定. Defaults to None. 581 582 Returns: 583 dict[str, str]: 生成した辞書 584 585 """ 586 date_range = [dt for dt in self if isinstance(dt, ExtendedDatetime)] 587 588 if not date_range: 589 return {} 590 591 return {"start": min(date_range).format(fmt, delimiter), "end": max(date_range).format(fmt, delimiter)}
ExtendedDatetimeを要素とする日付リストを扱う補助クラス
start: ExtendedDatetime | None
542 @property 543 def start(self) -> ExtendedDatetime | None: 544 """最小日付を返す。空ならNone。""" 545 return min(self) if self else None
最小日付を返す。空ならNone。
end: ExtendedDatetime | None
547 @property 548 def end(self) -> ExtendedDatetime | None: 549 """最大日付を返す。空ならNone。""" 550 return max(self) if self else None
最大日付を返す。空ならNone。
period: list[ExtendedDatetime | None]
552 @property 553 def period(self) -> list[ExtendedDatetime | None]: 554 """最小値と最大値をリストで返す""" 555 min_dt = min(self) if self else None 556 max_dt = max(self) if self else None 557 558 return [min_dt, max_dt]
最小値と最大値をリストで返す
def
format( self, fmt: Format = <Format.SQL: 'sql'>, delimiter: Delimiter = <Delimiter.UNDEFINED: ''>) -> list[str]:
560 def format(self, fmt: Format = Format.SQL, delimiter: Delimiter = Delimiter.UNDEFINED) -> list[str]: 561 """ 562 全要素にformatを適用した文字列リストを返す 563 564 Args: 565 fmt (Format, optional): フォーマット変換. Defaults to "sql". 566 delimiter (Delimiter, optional): 区切り記号指定. Defaults to None. 567 568 Returns: 569 list[str]: 生成したリスト 570 571 """ 572 return [dt.format(fmt, delimiter) for dt in self if isinstance(dt, ExtendedDatetime)]
全要素にformatを適用した文字列リストを返す
Arguments:
- fmt (Format, optional): フォーマット変換. Defaults to "sql".
- delimiter (Delimiter, optional): 区切り記号指定. Defaults to None.
Returns:
list[str]: 生成したリスト
def
dict_format( self, fmt: Format = <Format.SQL: 'sql'>, delimiter: Delimiter = <Delimiter.UNDEFINED: ''>) -> dict[str, str]:
574 def dict_format(self, fmt: Format = Format.SQL, delimiter: Delimiter = Delimiter.UNDEFINED) -> dict[str, str]: 575 """ 576 全要素にformatを適用し、最小日付と最大日付を辞書で返す 577 578 Args: 579 fmt (Format, optional): フォーマット変換. Defaults to "sql". 580 delimiter (Delimiter, optional): 区切り記号指定. Defaults to None. 581 582 Returns: 583 dict[str, str]: 生成した辞書 584 585 """ 586 date_range = [dt for dt in self if isinstance(dt, ExtendedDatetime)] 587 588 if not date_range: 589 return {} 590 591 return {"start": min(date_range).format(fmt, delimiter), "end": max(date_range).format(fmt, delimiter)}
全要素にformatを適用し、最小日付と最大日付を辞書で返す
Arguments:
- fmt (Format, optional): フォーマット変換. Defaults to "sql".
- delimiter (Delimiter, optional): 区切り記号指定. Defaults to None.
Returns:
dict[str, str]: 生成した辞書
class
ExtendedDatetimeList.Delimiter(enum.StrEnum):
82class Delimiter(StrEnum): 83 """区切り記号""" 84 85 SLASH = "slash" 86 """スラッシュ(ex: %Y/%m/%d)""" 87 HYPHEN = "hyphen" 88 """ハイフン(ex: %Y-%m-%d)""" 89 NUMBER = "number" 90 """区切り無し (ex: %Y%m%d)""" 91 JAPANESE = "japanese" 92 """Japanese Style (ex: %Y%年m%月d日)""" 93 UNDEFINED = "" 94 """未定義"""
区切り記号