|
7 | 7 | reason about time — not just measure it. |
8 | 8 | """ |
9 | 9 |
|
| 10 | +from datetime import datetime, timedelta |
| 11 | +from typing import Any, List, Union |
| 12 | + |
| 13 | +from eones.core.date import Date |
| 14 | +from eones.core.parser import Parser |
| 15 | +from eones.errors import InvalidFormatError, InvalidTimezoneError |
10 | 16 | from eones.interface import Eones |
11 | 17 |
|
| 18 | + |
| 19 | +# Utility functions for backward compatibility and convenience |
| 20 | +def parse_date( |
| 21 | + value: Union[str, dict, datetime], tz: str = "UTC", formats: List[str] = None |
| 22 | +) -> Date: |
| 23 | + """Parse a date from various input formats. |
| 24 | +
|
| 25 | + Args: |
| 26 | + value: Date input (string, dict, or datetime) |
| 27 | + tz: Timezone string |
| 28 | + formats: List of format strings for parsing |
| 29 | +
|
| 30 | + Returns: |
| 31 | + Date: Parsed date object |
| 32 | + """ |
| 33 | + # Include common datetime formats including ISO with timezone if none provided |
| 34 | + if formats is None: |
| 35 | + formats = [ |
| 36 | + "%Y-%m-%d", |
| 37 | + "%d/%m/%Y", |
| 38 | + "%Y-%m-%d %H:%M:%S", |
| 39 | + "%Y-%m-%d %H:%M", |
| 40 | + "%d/%m/%Y %H:%M:%S", |
| 41 | + "%d/%m/%Y %H:%M", |
| 42 | + "%Y-%m-%dT%H:%M:%S%z", |
| 43 | + "%Y-%m-%dT%H:%M:%S", |
| 44 | + ] |
| 45 | + parser = Parser(tz=tz, formats=formats) |
| 46 | + return parser.parse(value) |
| 47 | + |
| 48 | + |
| 49 | +def format_date(date: Union[Date, datetime], fmt: str) -> str: |
| 50 | + """Format a date using the given format string. |
| 51 | +
|
| 52 | + Args: |
| 53 | + date: Date object or datetime to format |
| 54 | + fmt: Format string |
| 55 | +
|
| 56 | + Returns: |
| 57 | + str: Formatted date string |
| 58 | + """ |
| 59 | + if isinstance(date, Date): |
| 60 | + return date.format(fmt) |
| 61 | + elif isinstance(date, datetime): |
| 62 | + return date.strftime(fmt) |
| 63 | + else: |
| 64 | + raise TypeError(f"Expected Date or datetime object, got {type(date).__name__}") |
| 65 | + |
| 66 | + |
| 67 | +def add_days(date: Date, days: int) -> Date: |
| 68 | + """Add days to a date. |
| 69 | +
|
| 70 | + Args: |
| 71 | + date: Date object |
| 72 | + days: Number of days to add (can be negative) |
| 73 | +
|
| 74 | + Returns: |
| 75 | + Date: New date with days added |
| 76 | + """ |
| 77 | + if not isinstance(date, Date): |
| 78 | + raise ValueError("Expected Date object") |
| 79 | + return date.shift(timedelta(days=days)) |
| 80 | + |
| 81 | + |
| 82 | +def date_diff_days(date1: Date, date2: Date) -> int: |
| 83 | + """Calculate the difference in days between two dates. |
| 84 | +
|
| 85 | + Args: |
| 86 | + date1: First date |
| 87 | + date2: Second date |
| 88 | +
|
| 89 | + Returns: |
| 90 | + int: Difference in days (date2 - date1) |
| 91 | + """ |
| 92 | + if not isinstance(date1, Date) or not isinstance(date2, Date): |
| 93 | + raise ValueError("Expected Date objects") |
| 94 | + return (date2.to_datetime() - date1.to_datetime()).days |
| 95 | + |
| 96 | + |
| 97 | +def date_range(start: Date, end: Date, step_days: int = 1) -> List[Date]: |
| 98 | + """Generate a range of dates from start to end. |
| 99 | +
|
| 100 | + Args: |
| 101 | + start: Start date |
| 102 | + end: End date (inclusive) |
| 103 | + step_days: Step size in days |
| 104 | +
|
| 105 | + Returns: |
| 106 | + List[Date]: List of dates in the range |
| 107 | + """ |
| 108 | + if not isinstance(start, Date) or not isinstance(end, Date): |
| 109 | + raise ValueError("Expected Date objects") |
| 110 | + |
| 111 | + dates = [] |
| 112 | + current = start |
| 113 | + |
| 114 | + while current.to_datetime().date() <= end.to_datetime().date(): |
| 115 | + dates.append(current) |
| 116 | + current = current.shift(timedelta(days=step_days)) |
| 117 | + |
| 118 | + return dates |
| 119 | + |
| 120 | + |
| 121 | +def to_timestamp(date: Union[Date, datetime]) -> int: |
| 122 | + """Convert a date to Unix timestamp. |
| 123 | +
|
| 124 | + Args: |
| 125 | + date: Date object or datetime |
| 126 | +
|
| 127 | + Returns: |
| 128 | + int: Unix timestamp |
| 129 | + """ |
| 130 | + if isinstance(date, Date): |
| 131 | + return int(date.to_unix()) |
| 132 | + elif isinstance(date, datetime): |
| 133 | + return int(date.timestamp()) |
| 134 | + else: |
| 135 | + raise ValueError("Expected Date or datetime object") |
| 136 | + |
| 137 | + |
| 138 | +def from_timestamp(timestamp: Union[int, float], tz: str = "UTC") -> Date: |
| 139 | + """Create a Date from Unix timestamp. |
| 140 | +
|
| 141 | + Args: |
| 142 | + timestamp: Unix timestamp (int or float) |
| 143 | + tz: Timezone (default: "UTC") |
| 144 | +
|
| 145 | + Returns: |
| 146 | + Date: Date object |
| 147 | + """ |
| 148 | + return Date.from_unix(float(timestamp), tz=tz) |
| 149 | + |
| 150 | + |
12 | 151 | __version__ = "1.2.0" |
13 | | -__all__ = ["Eones"] |
| 152 | +__all__ = [ |
| 153 | + "Eones", |
| 154 | + "InvalidFormatError", |
| 155 | + "InvalidTimezoneError", |
| 156 | + "parse_date", |
| 157 | + "format_date", |
| 158 | + "add_days", |
| 159 | + "date_diff_days", |
| 160 | + "date_range", |
| 161 | + "to_timestamp", |
| 162 | + "from_timestamp", |
| 163 | +] |
0 commit comments