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"

フォーマット変換で指定する種類

TS = <Format.TS: 'ts'>

タイムスタンプ

Y = <Format.Y: 'y'>

年(%Y)

YM = <Format.YM: 'ym'>

年月(%Y/%m)

YMD = <Format.YMD: 'ymd'>

年月日(%Y/%m/%d)

YMDHM = <Format.YMDHM: 'ymdhm'>

年月日時分(%Y/%m/%d %H:%M)

YMDHMS = <Format.YMDHMS: 'ymdhms'>

年月日時分秒(%Y/%m/%d %H:%M:%S)

HM = <Format.HM: 'hm'>

時分(%H:%M)

HMS = <Format.HMS: 'hms'>

時分秒(%H:%M:%S)

SQL = <Format.SQL: 'sql'>

SQLite用フォーマット(%Y-%m-%d %H:%M:%S.%f)

EXT = <Format.EXT: 'ext'>

ファイル拡張子用(%Y%m%d-%H%M%S)

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    """未定義"""

区切り記号

SLASH = <Delimiter.SLASH: 'slash'>

スラッシュ(ex: %Y/%m/%d)

HYPHEN = <Delimiter.HYPHEN: 'hyphen'>

ハイフン(ex: %Y-%m-%d)

NUMBER = <Delimiter.NUMBER: 'number'>

区切り無し (ex: %Y%m%d)

JAPANESE = <Delimiter.JAPANESE: 'japanese'>

Japanese Style (ex: %Y%年m%月d日)

UNDEFINED = <Delimiter.UNDEFINED: ''>

未定義

class DateRangeSpec(typing.TypedDict):
 97class DateRangeSpec(TypedDict):
 98    """日付範囲変換キーワード用辞書"""
 99
100    keyword: list[str]
101    range: Callable[[datetime], list[datetime]]

日付範囲変換キーワード用辞書

keyword: list[str]
range: Callable[[datetime.datetime], list[datetime.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())で初期化
  • relativedelta_kwargs (dict): 初期化時にrelativedelta()に渡す引数
FMT = <enum 'Format'>
DEM = <enum 'Delimiter'>
AcceptedType: TypeAlias = str | float | datetime.datetime | ForwardRef('ExtendedDatetime')

引数として受け付ける型

  • str: 日付文字列(ISO形式など)
  • float: UNIXタイムスタンプ
  • datetime / ExtendedDatetime: オブジェクトをそのまま利用
dt: datetime.datetime
284    @property
285    def dt(self) -> datetime:
286        """datetime型を返すプロパティ"""
287        return self._dt

datetime型を返すプロパティ

def set( self, value: str | float | datetime.datetime | ExtendedDatetime) -> None:
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): 入力値
def format( self, fmt: Format, delimiter: Delimiter = <Delimiter.UNDEFINED: ''>) -> str:
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    """未定義"""

区切り記号