frist 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of frist might be problematic. Click here for more details.

frist-0.7.0/PKG-INFO ADDED
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: frist
3
+ Version: 0.7.0
4
+ Summary: Test Package
5
+ Author: Chuck Bass
frist-0.7.0/README.md ADDED
@@ -0,0 +1,213 @@
1
+
2
+
3
+ # Frist
4
+
5
+ Frist is a property-based date and time utility for Python, designed to make relative date calculations and calendar logic simple and intuitive—without manual math. You create a Frist object with a target time (when something happened) and a reference time (usually "now").
6
+
7
+ Frist lets you answer questions like "Did this event happen today, this month, or this year?" using properties such as `in_day`, `in_month`, and `in_year`. These properties check if the target time lands anywhere in the current time unit window—even exactly at the start or end. You can also use ranges, like `in_days(-7, 0)` for "in the last 7 days" or `in_months(-1, 1)` for "from last month to next month." This is different from `.age.days`, which gives you the precise floating-point age in days between the target and reference times.
8
+
9
+ With Frist, you get instant answers to time-based questions with a single property, instead of writing complex date math. This makes it easy to work with calendar windows, fiscal periods, and custom time ranges.
10
+
11
+ ---
12
+
13
+ **Note:** In German, "Frist" means "deadline" or "time limit." This reflects the package's focus on time windows, periods, and calendar logic.
14
+
15
+ ## Key Concepts
16
+
17
+ - **Property-based API:** Most calculations are exposed as properties (e.g., `.age.days`, `.fiscal_year`, `.holiday`), not methods, so you rarely need to call functions or do arithmetic.
18
+ - **Minimal math required:** You can answer most date and calendar questions by accessing properties, not by writing formulas.
19
+ - **Flexible reference time:** Zeit lets you compare any target date/time to any reference date/time, not just "now".
20
+ - **Calendar and fiscal logic:** Built-in support for calendar windows (days, weeks, months, quarters, years) and fiscal year/quarter calculations.
21
+ - **Holiday detection:** Pass a set of holiday dates and instantly check if a date is a holiday.
22
+
23
+
24
+ ## Quick Start
25
+
26
+ ```python
27
+ import datetime as dt
28
+ from frist import Frist
29
+
30
+ # Create a Frist object for a target date
31
+ meeting = Frist(target_time=dt.datetime(2025, 12, 25))
32
+
33
+ # Check age properties
34
+ print(meeting.age.days) # Days since meeting
35
+ print(meeting.age.hours) # Hours since meeting
36
+
37
+ # Calendar windows
38
+ if meeting.cal.in_days(0):
39
+ print("Meeting is today!")
40
+ if meeting.cal.in_weeks(-1):
41
+ print("Meeting was last week.")
42
+
43
+ # Fiscal year and quarter
44
+ print(meeting.fiscal_year) # Fiscal year for the meeting
45
+ print(meeting.fiscal_quarter) # Fiscal quarter for the meeting
46
+
47
+ # Holiday detection
48
+ holidays = {'2025-12-25', '2025-01-01'}
49
+ meeting = Frist(target_time=dt.datetime(2025, 12, 25), holidays=holidays)
50
+ if meeting.holiday:
51
+ print("This date is a holiday!")
52
+
53
+ # Compare to a custom reference time
54
+ project = Frist(target_time=dt.datetime(2025, 1, 1), reference_time=dt.datetime(2025, 2, 1))
55
+ print(project.age.days) # Days between Jan 1 and Feb 1, 2025
56
+ ```
57
+
58
+ ## Features
59
+
60
+ ## Time Scale Properties Table
61
+
62
+ | Time Scale | Age Property | Window Function(s) |
63
+ |-------------- |----------------------|---------------------------------------|
64
+ | Seconds | `.age.seconds` | `.cal.in_seconds(start, end)` |
65
+ | Minutes | `.age.minutes` | `.cal.in_minutes(start, end)` |
66
+ | Hours | `.age.hours` | `.cal.in_hours(start, end)` |
67
+ | Days | `.age.days` | `.cal.in_days(start, end)` |
68
+ | Weeks | `.age.weeks` | `.cal.in_weeks(start, end)` |
69
+ | Months | `.age.months` | `.cal.in_months(start, end)` |
70
+ | Quarters | `.age.quarters` | `.cal.in_quarters(start, end)` |
71
+ | Years | `.age.years` | `.cal.in_years(start, end)` |
72
+ | Fiscal Years | `.age.fiscal_year` | `.cal.in_fiscal_years(start, end)` |
73
+ | Fiscal Qtrs | `.age.fiscal_quarter`| `.cal.in_fiscal_quarters(start, end)` |
74
+
75
+ Each age property gives the precise floating-point difference in that unit. Each window function generates a boolean if the target time falls within the specified range of time units relative to the reference time.
76
+ Age properties (like `.age.days`) are designed for direct comparisons—use them to ask questions like `>`, `<`, `==`, or `!=` between the target and reference times. In contrast, the `in_*` window functions (like `.cal.in_days()`) return `True` or `False` depending on whether the target time falls within the specified range.
77
+ Note: The `end` parameter is optional. If omitted, the function checks for a single time unit (e.g., `.cal.in_days(0)` means "today").
78
+
79
+
80
+ ## Fiscal Year & Quarter Example
81
+
82
+ Frist supports fiscal year and quarter calculations with customizable fiscal year start months. For example:
83
+
84
+ ```python
85
+ # Fiscal year starts in April (fy_start_month=4)
86
+ meeting = Frist(target_time=dt.datetime(2025, 7, 15), fy_start_month=4)
87
+ print(meeting.fiscal_year) # 2025 (fiscal year for July 15, 2025)
88
+ print(meeting.fiscal_quarter) # 2 (Q2: July–September for April start)
89
+
90
+ # Check if a date is in a fiscal quarter or year window
91
+ if meeting.cal.in_fiscal_quarters(0):
92
+ print("Meeting is in the current fiscal quarter.")
93
+ if meeting.cal.in_fiscal_years(0):
94
+ print("Meeting is in the current fiscal year.")
95
+ ```
96
+
97
+
98
+ ## Holiday Detection Example
99
+
100
+ Frist can instantly check if a date is a holiday using a set of holiday dates:
101
+
102
+ ```python
103
+ holidays = {
104
+ '2025-12-25', # Christmas
105
+ '2025-01-01', # New Year's Day
106
+ '2025-07-04', # Independence Day
107
+ }
108
+
109
+ # Check a specific date
110
+ meeting = Frist(target_time=dt.datetime(2025, 12, 25), holidays=holidays)
111
+ if meeting.holiday:
112
+ print("Meeting date is a holiday!")
113
+
114
+ # Check multiple dates
115
+ for date_str in holidays:
116
+ date = dt.datetime.strptime(date_str, '%Y-%m-%d')
117
+ c = Frist(target_time=date, holidays=holidays)
118
+ print(f"{date.date()}: Holiday? {c.holiday}")
119
+
120
+ # Use with custom reference time
121
+ project = Frist(target_time=dt.datetime(2025, 7, 4), reference_time=dt.datetime(2025, 7, 5), holidays=holidays)
122
+ if project.holiday:
123
+ print("Project start date is a holiday!")
124
+ ```
125
+
126
+ ## Short Examples
127
+
128
+
129
+ ### Age Calculation
130
+
131
+ ```python
132
+ person = Frist(target_time=dt.datetime(1990, 5, 1), reference_time=dt.datetime(2025, 5, 1))
133
+ print(f"Age in days: {person.age.days}, Age in years: {person.age.years:.2f}")
134
+ ```
135
+
136
+
137
+ ### Calendar Windows
138
+
139
+ ```python
140
+ meeting = Frist(target_time=dt.datetime(2025, 12, 25))
141
+ if meeting.cal.in_days(0):
142
+ print("Meeting is today!")
143
+ if meeting.cal.in_weeks(-1):
144
+ print("Meeting was last week.")
145
+ ```
146
+
147
+ ## API Reference
148
+
149
+
150
+ ### Frist
151
+
152
+ `Frist(target_time: datetime, reference_time: datetime = None, fy_start_month: int = 1, holidays: set[str] = None)`
153
+
154
+ - **Properties:**
155
+ - `age`: Age object with properties for `.days`, `.hours`, `.minutes`, `.seconds`, `.weeks`, `.months`, `.quarters`, `.years`, `.fiscal_year`, `.fiscal_quarter`.
156
+ - `cal`: Cal object for calendar window logic.
157
+ - `fiscal_year`: Fiscal year for the target time.
158
+ - `fiscal_quarter`: Fiscal quarter for the target time.
159
+ - `holiday`: True if target time is a holiday (if holidays set provided).
160
+
161
+ ### Cal
162
+
163
+ `Cal(time_span: TimeSpan, fy_start_month: int = 1, holidays: set[str] = None)`
164
+
165
+ - **Properties:**
166
+ - `dt_val`: Target datetime.
167
+ - `base_time`: Reference datetime.
168
+ - `fiscal_year`: Fiscal year for `dt_val`.
169
+ - `fiscal_quarter`: Fiscal quarter for `dt_val`.
170
+ - `holiday`: True if `dt_val` is a holiday.
171
+ - `time_span`: The TimeSpan object.
172
+
173
+ - **Interval Methods:**
174
+ - `in_minutes(start: int = 0, end: int | None = None) -> bool`
175
+ - `in_hours(start: int = 0, end: int | None = None) -> bool`
176
+ - `in_days(start: int = 0, end: int | None = None) -> bool`
177
+ - `in_weeks(start: int = 0, end: int | None = None, week_start: str = "monday") -> bool`
178
+ - `in_months(start: int = 0, end: int | None = None) -> bool`
179
+ - `in_quarters(start: int = 0, end: int | None = None) -> bool`
180
+ - `in_years(start: int = 0, end: int | None = None) -> bool`
181
+ - `in_fiscal_quarters(start: int = 0, end: int | None = None) -> bool`
182
+ - `in_fiscal_years(start: int = 0, end: int | None = None) -> bool`
183
+
184
+ - **Static Methods:**
185
+ - `get_fiscal_year(dt: datetime, fy_start_month: int) -> int`
186
+ - `get_fiscal_quarter(dt: datetime, fy_start_month: int) -> int`
187
+
188
+ - **Exceptions:**
189
+ - All interval methods raise `ValueError` if `start > end`.
190
+ - `normalize_weekday(day_spec: str) -> int` raises `ValueError` for invalid day specifications, with detailed error messages.
191
+
192
+ ### Age
193
+
194
+ `Age(target_time: datetime, reference_time: datetime)`
195
+
196
+ - **Properties:**
197
+ - `days`, `hours`, `minutes`, `seconds`, `weeks`, `months`, `quarters`, `years`, `fiscal_year`, `fiscal_quarter`
198
+
199
+ ### TimeSpan (Protocol)
200
+
201
+ `TimeSpan`
202
+
203
+ - **Properties:**
204
+ - `target_dt`: Target datetime.
205
+ - `ref_dt`: Reference datetime.
206
+
207
+ ---
208
+
209
+ ## Testing and Support
210
+
211
+ [![Python](https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12%20|%203.13%20|%203.14-blue?logo=python&logoColor=white)](https://www.python.org/)
212
+ [![Coverage](https://img.shields.io/badge/coverage-99%25-brightgreen)](https://github.com/hucker/zeit/actions)
213
+ [![Ruff](https://img.shields.io/badge/ruff-100%25%20clean-brightgreen?logo=ruff&logoColor=white)](https://github.com/charliermarsh/ruff)
@@ -0,0 +1,14 @@
1
+
2
+ [build-system]
3
+ requires = ["setuptools", "wheel"]
4
+ build-backend = "setuptools.build_meta"
5
+
6
+ [project]
7
+ name = "frist"
8
+ version = "0.7.0"
9
+ description = "Test Package"
10
+ authors = [{name = "Chuck Bass"}]
11
+
12
+ [tool.setuptools]
13
+ packages = ["frist"]
14
+ package-dir = {"" = "src"}
frist-0.7.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,26 @@
1
+ """
2
+ Frist: Standalone datetime utility package
3
+
4
+ Provides robust tools for:
5
+ - Age and duration calculations across multiple time units
6
+ - Calendar window filtering (days, weeks, months, quarters, years)
7
+ - Fiscal year/quarter logic and holiday detection
8
+ - Flexible datetime parsing and normalization
9
+
10
+ Designed for use in any Python project requiring advanced datetime analysis, not limited to file operations.
11
+
12
+ Exports:
13
+ Frist -- Main datetime utility class
14
+ Age -- Duration and age calculations
15
+ Cal -- Calendar window and filtering logic
16
+ TimeSpan -- Time span representation for advanced calculations
17
+ """
18
+
19
+ from ._age import Age
20
+ from ._cal import Cal, TimeSpan
21
+ from ._frist import Frist
22
+
23
+ __version__ = "0.7.0"
24
+ __author__ = "Chuck Bass"
25
+
26
+ __all__ = ["Frist", "Age", "Cal", "TimeSpan"]
@@ -0,0 +1,132 @@
1
+ """
2
+ Age property implementation for Frist package.
3
+
4
+ Handles age calculations in various time units, supporting both file-based and standalone usage.
5
+ """
6
+
7
+ import datetime as dt
8
+ import re
9
+ from pathlib import Path
10
+
11
+ from ._constants import (
12
+ DAYS_PER_MONTH,
13
+ DAYS_PER_YEAR,
14
+ SECONDS_PER_DAY,
15
+ SECONDS_PER_HOUR,
16
+ SECONDS_PER_MINUTE,
17
+ SECONDS_PER_MONTH,
18
+ SECONDS_PER_WEEK,
19
+ SECONDS_PER_YEAR,
20
+ )
21
+
22
+
23
+ class Age:
24
+ """Property class for handling age calculations in various time units."""
25
+
26
+ def __init__(self, path: Path | None, timestamp: float, base_time: dt.datetime):
27
+ self.path = path
28
+ self.timestamp = timestamp
29
+ self.base_time = base_time
30
+
31
+ @property
32
+ def seconds(self) -> float:
33
+ """Get age in seconds."""
34
+ # Only check file existence if we have a path
35
+ if self.path is not None and not self.path.exists():
36
+ return 0
37
+ file_time = dt.datetime.fromtimestamp(self.timestamp)
38
+ return (self.base_time - file_time).total_seconds()
39
+
40
+ @property
41
+ def minutes(self) -> float:
42
+ """Get age in minutes."""
43
+ return self.seconds / SECONDS_PER_MINUTE
44
+
45
+ @property
46
+ def hours(self) -> float:
47
+ """Get age in hours."""
48
+ return self.seconds / SECONDS_PER_HOUR
49
+
50
+ @property
51
+ def days(self) -> float:
52
+ """Get age in days."""
53
+ return self.seconds / SECONDS_PER_DAY
54
+
55
+ @property
56
+ def weeks(self) -> float:
57
+ """Get age in weeks."""
58
+ return self.days / 7
59
+
60
+ @property
61
+ def months(self) -> float:
62
+ """Get age in months (approximate - 30.44 days)."""
63
+ return self.days / DAYS_PER_MONTH
64
+
65
+ @property
66
+ def years(self) -> float:
67
+ """Get age in years (approximate - 365.25 days, can be negative)."""
68
+ # Allow negative ages if base_time is before timestamp
69
+ return self.days / DAYS_PER_YEAR
70
+
71
+ @staticmethod
72
+ def parse(age_str: str) -> float:
73
+ """
74
+ Parse an age string and return the age in seconds.
75
+
76
+ Examples:
77
+ "30" -> 30 seconds
78
+ "5m" -> 300 seconds (5 minutes)
79
+ "2h" -> 7200 seconds (2 hours)
80
+ "3d" -> 259200 seconds (3 days)
81
+ "1w" -> 604800 seconds (1 week)
82
+ "2months" -> 5260032 seconds (2 months)
83
+ "1y" -> 31557600 seconds (1 year)
84
+ """
85
+ age_str = age_str.strip().lower()
86
+
87
+ # Handle plain numbers (seconds)
88
+ if age_str.isdigit():
89
+ return float(age_str)
90
+
91
+ # Regular expression to parse age with unit
92
+ match = re.match(r"^(\d+(?:\.\d+)?)\s*([a-zA-Z]+)$", age_str)
93
+ if not match:
94
+ raise ValueError(f"Invalid age format: {age_str}")
95
+
96
+ value: float = float(match.group(1))
97
+ unit: str = match.group(2).lower()
98
+
99
+ # Define multipliers (convert to seconds)
100
+ unit_multipliers = {
101
+ "s": 1,
102
+ "sec": 1,
103
+ "second": 1,
104
+ "seconds": 1,
105
+ "m": SECONDS_PER_MINUTE,
106
+ "min": SECONDS_PER_MINUTE,
107
+ "minute": SECONDS_PER_MINUTE,
108
+ "minutes": SECONDS_PER_MINUTE,
109
+ "h": SECONDS_PER_HOUR,
110
+ "hr": SECONDS_PER_HOUR,
111
+ "hour": SECONDS_PER_HOUR,
112
+ "hours": SECONDS_PER_HOUR,
113
+ "d": SECONDS_PER_DAY,
114
+ "day": SECONDS_PER_DAY,
115
+ "days": SECONDS_PER_DAY,
116
+ "w": SECONDS_PER_WEEK,
117
+ "week": SECONDS_PER_WEEK,
118
+ "weeks": SECONDS_PER_WEEK,
119
+ "month": SECONDS_PER_MONTH,
120
+ "months": SECONDS_PER_MONTH,
121
+ "y": SECONDS_PER_YEAR,
122
+ "year": SECONDS_PER_YEAR,
123
+ "years": SECONDS_PER_YEAR,
124
+ }
125
+
126
+ if unit not in unit_multipliers:
127
+ raise ValueError(f"Unknown unit: {unit}")
128
+
129
+ return value * unit_multipliers[unit]
130
+
131
+
132
+ __all__ = ["Age"]