Created
August 7, 2023 11:15
-
-
Save arctic-hen7/35964ae56b1154622551cb9ddbf646a0 to your computer and use it in GitHub Desktop.
Some simple code that parses Org mode timestamps in Python. No support for ranges yet, but works nicely with repeats from my testing.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import re | |
from datetime import datetime, timedelta, date | |
class Timestamp: | |
""" | |
A representation of a timestamp parsed from Org mode. | |
""" | |
def __init__(self, timestamp_str): | |
self.timestamp_str = timestamp_str | |
self.start_date = None | |
self.end_date = None | |
self.repeater = None | |
# Regular expression to match the Org mode timestamp pattern | |
timestamp_pattern = r'<(\d{4}-\d{2}-\d{2})(?: \w{3})?(?: \d{1,2}:\d{2})?(?:-\d{1,2}:\d{2})?(?: [+-]\d+[dwmy])?>' | |
match = re.match(timestamp_pattern, self.timestamp_str) | |
if not match: | |
raise ValueError("Invalid timestamp format") | |
date_str = match.group(1) | |
self.start_date = datetime.strptime(date_str, '%Y-%m-%d') | |
# Extract the repeater if present | |
repeater_pattern = r'([+-]\d+)([dwmy])' | |
repeater_match = re.search(repeater_pattern, self.timestamp_str) | |
if repeater_match: | |
count, unit = repeater_match.groups() | |
self.repeater_count = int(count) | |
self.repeater_unit = unit | |
else: | |
self.repeater_count = None | |
self.repeater_unit = None | |
# Extract the time range if present | |
time_range_pattern = r'(\d{1,2}:\d{2})(?:-(\d{1,2}:\d{2}))?' | |
time_range_match = re.search(time_range_pattern, self.timestamp_str) | |
if time_range_match: | |
start_time_str, end_time_str = time_range_match.groups() | |
self.start_date = self.start_date.replace(hour=int(start_time_str.split(':')[0]), minute=int(start_time_str.split(':')[1])) | |
if end_time_str: | |
self.end_date = self.start_date.replace(hour=int(end_time_str.split(':')[0]), minute=int(end_time_str.split(':')[1])) | |
def includes_date(self, target_date): | |
""" | |
Checks if this timestamp would include the given date. Be careful to | |
pass a date to this function, *not* a datetime. | |
""" | |
# Check if the target date falls within the range of the timestamp | |
if self.start_date.date() == target_date: | |
return True | |
# If a repeater is provided, check for recurring occurrences | |
if self.repeater_unit: | |
if self.repeater_unit == 'd': | |
# Repeats daily | |
days_diff = (target_date - self.start_date.date()).days | |
return days_diff % self.repeater_count == 0 | |
elif self.repeater_unit == 'w': | |
# Repeats weekly | |
weeks_diff = (target_date - self.start_date.date()).days / 7 | |
return weeks_diff % self.repeater_count == 0 | |
elif self.repeater_unit == 'm': | |
# Repeats monthly | |
months_diff = (target_date.month - self.start_date.month) | |
is_same_day = target_date.day == self.start_date.day | |
return months_diff % self.repeater_count == 0 and is_same_day | |
elif self.repeater_unit == 'y': | |
# Repeats yearly | |
years_diff = (target_date.year - self.start_date.year) | |
is_same_month = target_date.month == self.start_date.month | |
is_same_day = target_date.day == self.start_date.day | |
return years_diff % self.repeater_count == 0 and is_same_month and is_same_day | |
return False |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note that a large part of this was written in collaboration with ChatGPT: I highly recommend getting it to start writing something, fixing its mistakes (functionally and stylistically), giving it the updated code, and repeating this process to get something working. It handled the repeats terribly I'll admit (GPT-3.5), but that was fairly easily fixed manually after the fact. Just get it to write good tests!