dately 2.1.4__tar.gz → 2.2.0b2__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.
- {dately-2.1.4 → dately-2.2.0b2}/PKG-INFO +4 -6
- dately-2.2.0b2/dately/mold/include/clean_str.h +7 -0
- dately-2.2.0b2/dately/mold/include/iso8601T.h +12 -0
- dately-2.2.0b2/dately/mold/include/iso8601Z.h +6 -0
- dately-2.2.0b2/dately/mold/include/root_dir_search.h +14 -0
- dately-2.2.0b2/dately/mold/include/time_zones.h +21 -0
- dately-2.2.0b2/dately/mold/pyx/Compiled.pyx +186 -0
- dately-2.2.0b2/dately/mold/pyx/UniversalDateFormatter.pyx +189 -0
- dately-2.2.0b2/dately/mold/pyx/clean_str.pyx +10 -0
- dately-2.2.0b2/dately/mold/pyx/iso8601T.pyx +5 -0
- dately-2.2.0b2/dately/mold/pyx/iso8601Z.pyx +12 -0
- dately-2.2.0b2/dately/mold/pyx/time_zones.pyx +41 -0
- dately-2.2.0b2/dately/mold/pyx/whichformat.pyx +331 -0
- dately-2.2.0b2/dately/mold/src/Compiled.c +13474 -0
- dately-2.2.0b2/dately/mold/src/UniversalDateFormatter.c +15740 -0
- dately-2.2.0b2/dately/mold/src/clean_str.c +6572 -0
- dately-2.2.0b2/dately/mold/src/clean_str_impl.c +35 -0
- dately-2.2.0b2/dately/mold/src/iso8601T.c +6178 -0
- dately-2.2.0b2/dately/mold/src/iso8601T_impl.c +160 -0
- dately-2.2.0b2/dately/mold/src/iso8601Z.c +6275 -0
- dately-2.2.0b2/dately/mold/src/iso8601Z_impl.c +19 -0
- dately-2.2.0b2/dately/mold/src/root_dir_search_impl.c +53 -0
- dately-2.2.0b2/dately/mold/src/time_zones.c +6234 -0
- dately-2.2.0b2/dately/mold/src/time_zones_impl.c +2166 -0
- dately-2.2.0b2/dately/mold/src/whichformat.c +18038 -0
- {dately-2.1.4 → dately-2.2.0b2}/dately/mskutils.py +5 -5
- dately-2.2.0b2/dately/sources/__init__.py +2 -0
- dately-2.2.0b2/dately/sources/iana_zones.json +1 -0
- dately-2.2.0b2/dately/sources/timezone_data.json +4178 -0
- {dately-2.1.4 → dately-2.2.0b2}/dately/timezone.py +188 -304
- {dately-2.1.4 → dately-2.2.0b2}/dately/utils.py +3 -0
- {dately-2.1.4 → dately-2.2.0b2}/dately.egg-info/PKG-INFO +4 -6
- dately-2.2.0b2/dately.egg-info/SOURCES.txt +51 -0
- dately-2.2.0b2/setup.py +149 -0
- dately-2.1.4/dately.egg-info/SOURCES.txt +0 -24
- dately-2.1.4/setup.py +0 -53
- {dately-2.1.4 → dately-2.2.0b2}/README.md +0 -0
- {dately-2.1.4 → dately-2.2.0b2}/dately/__init__.py +0 -0
- {dately-2.1.4 → dately-2.2.0b2}/dately/core.py +0 -0
- {dately-2.1.4 → dately-2.2.0b2}/dately/mold/__init__.py +0 -0
- {dately-2.1.4 → dately-2.2.0b2}/dately/mold/pyd/Compiled.cp38-win_amd64.pyd +0 -0
- {dately-2.1.4 → dately-2.2.0b2}/dately/mold/pyd/__init__.py +0 -0
- {dately-2.1.4 → dately-2.2.0b2}/dately/mold/pyd/cdatetime/UniversalDateFormatter.cp38-win_amd64.pyd +0 -0
- {dately-2.1.4 → dately-2.2.0b2}/dately/mold/pyd/cdatetime/__init__.py +0 -0
- {dately-2.1.4 → dately-2.2.0b2}/dately/mold/pyd/cdatetime/iso8601T.cp38-win_amd64.pyd +0 -0
- {dately-2.1.4 → dately-2.2.0b2}/dately/mold/pyd/cdatetime/iso8601Z.cp38-win_amd64.pyd +0 -0
- {dately-2.1.4 → dately-2.2.0b2}/dately/mold/pyd/cdatetime/whichformat.cp38-win_amd64.pyd +0 -0
- {dately-2.1.4 → dately-2.2.0b2}/dately/mold/pyd/clean_str.cp38-win_amd64.pyd +0 -0
- {dately-2.1.4 → dately-2.2.0b2}/dately/mold/pyd/time_zones.cp38-win_amd64.pyd +0 -0
- {dately-2.1.4 → dately-2.2.0b2}/dately/sysutils.py +0 -0
- {dately-2.1.4 → dately-2.2.0b2}/dately/timeutils.py +0 -0
- {dately-2.1.4 → dately-2.2.0b2}/dately.egg-info/dependency_links.txt +0 -0
- {dately-2.1.4 → dately-2.2.0b2}/dately.egg-info/requires.txt +0 -0
- {dately-2.1.4 → dately-2.2.0b2}/dately.egg-info/top_level.txt +0 -0
- {dately-2.1.4 → dately-2.2.0b2}/setup.cfg +0 -0
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dately
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0b2
|
|
4
4
|
Summary: A comprehensive Python library for advanced date and time manipulation.
|
|
5
|
-
Home-page: https://github.com/cedricmoorejr/dately
|
|
5
|
+
Home-page: https://github.com/cedricmoorejr/dately/tree/v2.2.0-b.2
|
|
6
6
|
Author: Cedric Moore Jr.
|
|
7
7
|
Author-email: cedricmoorejunior5@gmail.com
|
|
8
8
|
License: MIT
|
|
9
|
-
Project-URL: Source Code, https://github.com/cedricmoorejr/dately/
|
|
9
|
+
Project-URL: Source Code, https://github.com/cedricmoorejr/dately/releases/tag/v2.2.0-beta.2
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
12
12
|
Classifier: Operating System :: Microsoft :: Windows
|
|
13
|
-
Classifier: Development Status ::
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: Natural Language :: English
|
|
16
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
-
Classifier: Programming Language :: Python :: 3
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.8
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.9
|
|
20
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#ifndef ISO8601T_H
|
|
2
|
+
#define ISO8601T_H
|
|
3
|
+
|
|
4
|
+
int is_leap_year(int year);
|
|
5
|
+
int validate_date(int year, int month, int day);
|
|
6
|
+
int validate_time(int hour, int minute, int second);
|
|
7
|
+
int is_digit(char c);
|
|
8
|
+
int check_basic_format(const char* date_string);
|
|
9
|
+
int check_extended_format(const char* date_string);
|
|
10
|
+
int find_date_match(const char* date_string);
|
|
11
|
+
|
|
12
|
+
#endif // ISO8601T_H
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// time_zones.h
|
|
2
|
+
#ifndef TIME_ZONES_H
|
|
3
|
+
#define TIME_ZONES_H
|
|
4
|
+
|
|
5
|
+
typedef struct {
|
|
6
|
+
char full_name[50];
|
|
7
|
+
char region[50];
|
|
8
|
+
char offset[11];
|
|
9
|
+
char type[10];
|
|
10
|
+
char dst[7];
|
|
11
|
+
} TimeZoneInfo;
|
|
12
|
+
|
|
13
|
+
typedef struct {
|
|
14
|
+
char code[7];
|
|
15
|
+
TimeZoneInfo info;
|
|
16
|
+
} TimeZone;
|
|
17
|
+
|
|
18
|
+
extern TimeZone time_zones[];
|
|
19
|
+
extern int time_zones_count;
|
|
20
|
+
|
|
21
|
+
#endif // TIME_ZONES_H
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Compiled.pyx
|
|
2
|
+
import re
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from libc.string cimport strdup
|
|
6
|
+
from libc.stdlib cimport free # Import free to release allocated memory
|
|
7
|
+
from cython cimport unicode, boundscheck, wraparound
|
|
8
|
+
from cpython cimport array
|
|
9
|
+
|
|
10
|
+
cdef extern from "root_dir_search.h":
|
|
11
|
+
char* find_directory(const char *start_path, const char *dir_name)
|
|
12
|
+
|
|
13
|
+
def get_directory_path(directory_name):
|
|
14
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
15
|
+
directory_path_c = find_directory(script_dir.encode('utf-8'), directory_name.encode('utf-8'))
|
|
16
|
+
if directory_path_c:
|
|
17
|
+
directory_path = directory_path_c.decode('utf-8')
|
|
18
|
+
free(directory_path_c) # Free the allocated memory
|
|
19
|
+
return directory_path
|
|
20
|
+
else:
|
|
21
|
+
raise EnvironmentError(f"{directory_name} directory not found.")
|
|
22
|
+
|
|
23
|
+
# Find the dately path
|
|
24
|
+
dately_path = get_directory_path("dately")
|
|
25
|
+
if dately_path:
|
|
26
|
+
sys.path.append(dately_path)
|
|
27
|
+
else:
|
|
28
|
+
raise EnvironmentError("dately directory not found.")
|
|
29
|
+
|
|
30
|
+
from mold.pyd.time_zones import time_zones_dict
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
cdef dict __datetime_named_group_patterns__ = {
|
|
36
|
+
'%a': r'(?P<weekday>Mon|Tue|Wed|Thu|Fri|Sat|Sun)',
|
|
37
|
+
'%A': r'(?P<weekday>Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)',
|
|
38
|
+
'%w': r'(?P<weekday>\d)',
|
|
39
|
+
'%u': r'\d',
|
|
40
|
+
'%b': r'(?P<month>[A-Za-z]{3})',
|
|
41
|
+
'%B': r'(?P<month>[A-Za-z]+)',
|
|
42
|
+
'%m': r'(?P<month>\d{1,2})',
|
|
43
|
+
'%-m': r'(?P<month>\d{1,2})',
|
|
44
|
+
'%d': r'(?P<day>\d{1,2})',
|
|
45
|
+
'%-d': r'(?P<day>\d{1,2})',
|
|
46
|
+
'%j': r'(?P<day>\d{1,3})',
|
|
47
|
+
'%-H': r'(?P<hour24>(?:[0-9]|1[0-9]|2[0-3]|\d{1,2}))',
|
|
48
|
+
'%H': r'(?P<hour24>(?:\d{1,2}|0?[0-9]|1[0-9]|2[0-3]))',
|
|
49
|
+
'%I': r'(?P<hour12>(?:\d{1,2}|0?[1-9]|1[0-2]))',
|
|
50
|
+
'%-I': r'(?P<hour12>(?:\d{1,2}|0?[1-9]|1[0-2]))',
|
|
51
|
+
'%M': r'(?P<minute>\d{1,2})',
|
|
52
|
+
'%-M': r'(?P<minute>\d{1,2})',
|
|
53
|
+
'%S': r'(?P<second>\d{1,2})',
|
|
54
|
+
'%-S': r'(?P<second>\d{1,2})',
|
|
55
|
+
'%f': r'(?P<microsecond>\d{1,6})',
|
|
56
|
+
'%p': r'(?P<am_pm>(?:AM|PM))',
|
|
57
|
+
'%-y': r'(?P<year>\d{2})',
|
|
58
|
+
'%y': r'(?P<year>\d{2})',
|
|
59
|
+
'%Y': r'(?P<year>\d{4})',
|
|
60
|
+
'%z': r'(?P<timezone>[\+\-](?:\d{4}|\d{2}:?\d{2}))',
|
|
61
|
+
'%Z': r'(?P<timezone>(?:[\+\-]\d{2}:[0-9]{2}|[A-Za-z\s]+|[A-Za-z]{2,4}|UTC|[\+\-]\d{2}:?\d{2}))',
|
|
62
|
+
'%q': r'\d',
|
|
63
|
+
'%U': r'\d{1,2}',
|
|
64
|
+
'%V': r'\d{1,2})',
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
cpdef object datetime(unicode fmt):
|
|
68
|
+
cdef unicode regex = fmt
|
|
69
|
+
cdef unicode key, value
|
|
70
|
+
for key, value in __datetime_named_group_patterns__.items():
|
|
71
|
+
regex = regex.replace(key, value)
|
|
72
|
+
return re.compile(u'^' + regex + u'$')
|
|
73
|
+
|
|
74
|
+
# Create regex patterns for timezone abbreviations and full timezone names
|
|
75
|
+
timezone_abbrv = list(time_zones_dict.keys())
|
|
76
|
+
timezone_abbrv_pattern = r'\b(' + '|'.join(timezone_abbrv) + r')\b'
|
|
77
|
+
full_names = [info['full_name'] for info in time_zones_dict.values()]
|
|
78
|
+
full_names_pattern = r'\b(' + '|'.join(full_names) + r')\b'
|
|
79
|
+
|
|
80
|
+
# Define the regex patterns in a dictionary
|
|
81
|
+
regex_patterns = {
|
|
82
|
+
"timemeridiem": r'\s*\b(AM|PM)\b\s*',
|
|
83
|
+
# Purpose: Matches a time string in the format HH:MM:SS or HH:MM:SS.microseconds.
|
|
84
|
+
"timeonly": r'(?P<hours>\d{1,2}):(?P<minutes>\d{2}):(?P<seconds>\d{2})(?:\.(?P<microseconds>\d+))?',
|
|
85
|
+
"timezone_offset": r'(?<!\d)[+-]?(?:\d{1,2}(?::\d{1,2})?|\d{3,4})(?!\d)',
|
|
86
|
+
"iana_timezone_identifier": r'\b[A-Za-z_]+/[A-Za-z_]+\b',
|
|
87
|
+
"anytime": (
|
|
88
|
+
r"(?<!\d)(\d{1,2}:\d{2}:\d{2}|\d{6})"
|
|
89
|
+
r"(?:\.\d{1,6})?"
|
|
90
|
+
r"(?:\s*[AP]M)?"
|
|
91
|
+
r"(?:\s*(?:[+-]\d{2}:?\d{2}|[+-]\d{4}|[A-Z]{3,4}|Z))?"
|
|
92
|
+
r"(?=\s|$)"
|
|
93
|
+
),
|
|
94
|
+
# Purpose: Matches various time string formats, including HH:MM:SS, HHMMSS, with optional microseconds, AM/PM indicators, and time zone information.
|
|
95
|
+
"timeplus": (
|
|
96
|
+
r"(\d{1,2}:\d{2}:\d{2}|\d{6})"
|
|
97
|
+
r"(?:\.\d{1,6})?"
|
|
98
|
+
r"(?:\s*[AP]M)?"
|
|
99
|
+
r"(?:\s*(?:[+-]\d{2}:?\d{2}|[+-]\d{4}|[A-Z]{3,4}|Z))?"
|
|
100
|
+
),
|
|
101
|
+
"establish_time_boundary": r'(?<!\d)(\d{1,2}:\S.*)',
|
|
102
|
+
"datetime_second": r'\d{2}:\d{2}:(\d{2})(?:\.\d+)?',
|
|
103
|
+
"datetime_minute": r'\d{2}:(\d{2})(:\d{2}(?:\.\d+)?)?',
|
|
104
|
+
"datetime_hour": r'(\d{1,2}):(\d{2})(:\d{2}(?:\.\d+)?)?',
|
|
105
|
+
"datetime_microsecond": r'\d{2}:\d{2}:\d{2}\.(\d+)',
|
|
106
|
+
"datetime_timezone": (
|
|
107
|
+
r'\b\d{1,2}(:\d{2})?Z\b|' # Zulu time (UTC)
|
|
108
|
+
r'[\+\-]\d{2}:?\d{2}|' # UTC offset
|
|
109
|
+
r'\b[A-Za-z]+/[A-Za-z_]+\b|' # Continent/City format
|
|
110
|
+
r'\bAM\b|\bPM\b' # AM/PM indicator
|
|
111
|
+
),
|
|
112
|
+
"timezone_abbreviation": timezone_abbrv_pattern,
|
|
113
|
+
"full_timezone_name": full_names_pattern
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Function to compile the regex patterns
|
|
117
|
+
cpdef dict compile_regex_patterns():
|
|
118
|
+
cdef dict compiled_patterns = {}
|
|
119
|
+
for key, pattern in regex_patterns.items():
|
|
120
|
+
compiled_patterns[key] = re.compile(pattern, re.IGNORECASE)
|
|
121
|
+
return compiled_patterns
|
|
122
|
+
|
|
123
|
+
# Compile the regex patterns at the module level
|
|
124
|
+
compiled_patterns = compile_regex_patterns()
|
|
125
|
+
|
|
126
|
+
# Function to get compiled pattern by name
|
|
127
|
+
cdef object get_pattern(str name):
|
|
128
|
+
return compiled_patterns.get(name, None)
|
|
129
|
+
|
|
130
|
+
# Call the get_pattern function to retrieve the compiled regex
|
|
131
|
+
second_regex = get_pattern("datetime_second")
|
|
132
|
+
minute_regex = get_pattern("datetime_minute")
|
|
133
|
+
hour_regex = get_pattern("datetime_hour")
|
|
134
|
+
microsecond_regex = get_pattern("datetime_microsecond")
|
|
135
|
+
timezone_regex = get_pattern("datetime_timezone")
|
|
136
|
+
timezone_offset_regex = get_pattern("timezone_offset")
|
|
137
|
+
iana_timezone_identifier_regex = get_pattern("iana_timezone_identifier")
|
|
138
|
+
timemeridiem_regex = get_pattern("timemeridiem")
|
|
139
|
+
time_only_regex = get_pattern("timeonly")
|
|
140
|
+
anytime_regex = get_pattern("anytime")
|
|
141
|
+
timeboundary_regex = get_pattern("establish_time_boundary")
|
|
142
|
+
timezone_abbreviation_regex = get_pattern("timezone_abbreviation")
|
|
143
|
+
full_timezone_name_regex = get_pattern("full_timezone_name")
|
|
144
|
+
timeplus_regex = get_pattern("timeplus")
|
|
145
|
+
|
|
146
|
+
cdef class cRegexps:
|
|
147
|
+
cdef dict time_component_patterns
|
|
148
|
+
|
|
149
|
+
def __cinit__(self):
|
|
150
|
+
self.time_component_patterns = {
|
|
151
|
+
"second": second_regex,
|
|
152
|
+
"minute": minute_regex,
|
|
153
|
+
"hour": hour_regex,
|
|
154
|
+
"microsecond": microsecond_regex,
|
|
155
|
+
"tzinfo": timezone_regex
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
cpdef object get_time_fragments(self, str component):
|
|
159
|
+
return self.time_component_patterns.get(component)
|
|
160
|
+
|
|
161
|
+
# Create an instance of the cRegexps class
|
|
162
|
+
cdef cRegexps regexps = cRegexps()
|
|
163
|
+
|
|
164
|
+
# Define module-level variables for compiled regex patterns
|
|
165
|
+
datetime_regex = datetime
|
|
166
|
+
get_time_fragment = regexps.get_time_fragments
|
|
167
|
+
|
|
168
|
+
# Define public interface
|
|
169
|
+
__all__ = [
|
|
170
|
+
'datetime_regex',
|
|
171
|
+
'timemeridiem_regex',
|
|
172
|
+
'anytime_regex',
|
|
173
|
+
'timezone_regex',
|
|
174
|
+
'timeboundary_regex',
|
|
175
|
+
'second_regex',
|
|
176
|
+
'minute_regex',
|
|
177
|
+
'hour_regex',
|
|
178
|
+
'microsecond_regex',
|
|
179
|
+
'get_time_fragment',
|
|
180
|
+
'time_only_regex',
|
|
181
|
+
'timeplus_regex',
|
|
182
|
+
'iana_timezone_identifier_regex',
|
|
183
|
+
'timezone_offset_regex',
|
|
184
|
+
'timezone_abbreviation_regex',
|
|
185
|
+
'full_timezone_name_regex',
|
|
186
|
+
]
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
from cython cimport cdivision
|
|
2
|
+
from cpython cimport bool
|
|
3
|
+
from cpython.datetime cimport datetime
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
'zero_handling_date_formats',
|
|
8
|
+
'non_padded_to_zero_padded_specifiers',
|
|
9
|
+
'has_leading_zero',
|
|
10
|
+
'date_format_leading_zero',
|
|
11
|
+
'replace_non_padded_with_padded',
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Declare the dictionary types for better performance
|
|
16
|
+
cdef dict _zero_handling_date_formats
|
|
17
|
+
cdef dict _non_padded_to_zero_padded_specifiers
|
|
18
|
+
|
|
19
|
+
# Initialize the dictionaries
|
|
20
|
+
_zero_handling_date_formats = {
|
|
21
|
+
'day': {
|
|
22
|
+
'no_leading_zero': {
|
|
23
|
+
'format': '%-d',
|
|
24
|
+
'description': 'Day of the month as a decimal number without leading zero (1 to 31)'
|
|
25
|
+
},
|
|
26
|
+
'zero_padded': {
|
|
27
|
+
'format': '%d',
|
|
28
|
+
'description': 'Day of the month as a zero-padded decimal number (01 to 31)'
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
'month': {
|
|
32
|
+
'no_leading_zero': {
|
|
33
|
+
'format': '%-m',
|
|
34
|
+
'description': 'Month as a decimal number without leading zero (1 to 12)'
|
|
35
|
+
},
|
|
36
|
+
'zero_padded': {
|
|
37
|
+
'format': '%m',
|
|
38
|
+
'description': 'Month as a zero-padded decimal number (01 to 12)'
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
'year': {
|
|
42
|
+
'no_leading_zero': {
|
|
43
|
+
'format': '%-y',
|
|
44
|
+
'description': 'Year without century as a decimal number without leading zero (0 to 99)'
|
|
45
|
+
},
|
|
46
|
+
'zero_padded': {
|
|
47
|
+
'format': '%y',
|
|
48
|
+
'description': 'Year without century as a zero-padded decimal number (00 to 99)'
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
'hour24': {
|
|
52
|
+
'no_leading_zero': {
|
|
53
|
+
'format': '%-H',
|
|
54
|
+
'description': 'Hour (24-hour clock) as a decimal number without leading zero (0 to 23)'
|
|
55
|
+
},
|
|
56
|
+
'zero_padded': {
|
|
57
|
+
'format': '%H',
|
|
58
|
+
'description': 'Hour (24-hour clock) as a zero-padded decimal number (00 to 23)'
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
'hour12': {
|
|
62
|
+
'no_leading_zero': {
|
|
63
|
+
'format': '%-I',
|
|
64
|
+
'description': 'Hour (12-hour clock) as a decimal number without leading zero (1 to 12)'
|
|
65
|
+
},
|
|
66
|
+
'zero_padded': {
|
|
67
|
+
'format': '%I',
|
|
68
|
+
'description': 'Hour (12-hour clock) as a zero-padded decimal number (01 to 12)'
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
'minute': {
|
|
72
|
+
'no_leading_zero': {
|
|
73
|
+
'format': '%-M',
|
|
74
|
+
'description': 'Minute as a decimal number without leading zero (0 to 59)'
|
|
75
|
+
},
|
|
76
|
+
'zero_padded': {
|
|
77
|
+
'format': '%M',
|
|
78
|
+
'description': 'Minute as a zero-padded decimal number (00 to 59)'
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
'second': {
|
|
82
|
+
'no_leading_zero': {
|
|
83
|
+
'format': '%-S',
|
|
84
|
+
'description': 'Second as a decimal number without leading zero (0 to 59)'
|
|
85
|
+
},
|
|
86
|
+
'zero_padded': {
|
|
87
|
+
'format': '%S',
|
|
88
|
+
'description': 'Second as a zero-padded decimal number (00 to 59)'
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
_non_padded_to_zero_padded_specifiers = {
|
|
94
|
+
'%-d': '%d',
|
|
95
|
+
'%-m': '%m',
|
|
96
|
+
'%-y': '%y',
|
|
97
|
+
'%-H': '%H',
|
|
98
|
+
'%-I': '%I',
|
|
99
|
+
'%-M': '%M',
|
|
100
|
+
'%-S': '%S'
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# Use cdef to declare return types for functions
|
|
104
|
+
cdef dict get_zero_handling_date_formats():
|
|
105
|
+
return _zero_handling_date_formats
|
|
106
|
+
|
|
107
|
+
cdef dict get_non_padded_to_zero_padded_specifiers():
|
|
108
|
+
return _non_padded_to_zero_padded_specifiers
|
|
109
|
+
|
|
110
|
+
def zero_handling_date_formats():
|
|
111
|
+
return get_zero_handling_date_formats()
|
|
112
|
+
|
|
113
|
+
def non_padded_to_zero_padded_specifiers(ret_format=None):
|
|
114
|
+
if ret_format == 'no_leading_zero':
|
|
115
|
+
return list(_non_padded_to_zero_padded_specifiers.keys())
|
|
116
|
+
elif ret_format == 'leading_zero':
|
|
117
|
+
return list(_non_padded_to_zero_padded_specifiers.values())
|
|
118
|
+
else:
|
|
119
|
+
return _non_padded_to_zero_padded_specifiers
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@cdivision(True)
|
|
123
|
+
def has_leading_zero(object input_str):
|
|
124
|
+
cdef str integer_part
|
|
125
|
+
cdef int len_integer_part
|
|
126
|
+
|
|
127
|
+
input_str = str(input_str)
|
|
128
|
+
|
|
129
|
+
# Check if the converted input string is a valid number
|
|
130
|
+
if not input_str.replace('-', '', 1).replace('.', '', 1).isdigit():
|
|
131
|
+
return None # Return None if it's not a valid number
|
|
132
|
+
|
|
133
|
+
# Special case for "0"
|
|
134
|
+
if input_str == "0" or input_str == "-0":
|
|
135
|
+
return None # Return None for "0" or "-0"
|
|
136
|
+
|
|
137
|
+
# Check if the integer part has exactly two digits
|
|
138
|
+
integer_part = input_str.split('.')[0].strip('-')
|
|
139
|
+
len_integer_part = len(integer_part)
|
|
140
|
+
|
|
141
|
+
if len_integer_part == 2 and integer_part != '00':
|
|
142
|
+
return None # Return None for whole numbers with exactly two digits, except '00'
|
|
143
|
+
|
|
144
|
+
# Check if the integer part has more than two digits
|
|
145
|
+
if len_integer_part > 2:
|
|
146
|
+
return None # Return None if the integer part has more than two digits
|
|
147
|
+
|
|
148
|
+
# Check for leading zero in whole numbers or in the integer part of decimal numbers
|
|
149
|
+
if input_str[0] == '-' and input_str[1] == '0':
|
|
150
|
+
return True
|
|
151
|
+
elif input_str[0] == '0' and input_str[1] != '.':
|
|
152
|
+
return True
|
|
153
|
+
elif input_str.startswith('0.') and input_str != '0.':
|
|
154
|
+
return True
|
|
155
|
+
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# Helper function to check format specifications
|
|
160
|
+
cdef bint has_non_padded_specifier(str format_string):
|
|
161
|
+
non_padded_specifiers = non_padded_to_zero_padded_specifiers(ret_format='no_leading_zero')
|
|
162
|
+
return any(spec in format_string for spec in non_padded_specifiers)
|
|
163
|
+
|
|
164
|
+
cpdef str date_format_leading_zero(datetime date, str format_string):
|
|
165
|
+
cdef str platform_compatible_format, formatted_date, orig, repl
|
|
166
|
+
if has_non_padded_specifier(format_string):
|
|
167
|
+
platform_compatible_format = format_string
|
|
168
|
+
for orig, repl in non_padded_to_zero_padded_specifiers().items():
|
|
169
|
+
platform_compatible_format = platform_compatible_format.replace(orig, repl)
|
|
170
|
+
|
|
171
|
+
formatted_date = date.strftime(platform_compatible_format)
|
|
172
|
+
|
|
173
|
+
for orig in non_padded_to_zero_padded_specifiers(ret_format='no_leading_zero'):
|
|
174
|
+
if orig in format_string:
|
|
175
|
+
formatted_date = re.sub(r'\b0(?=\d)', '', formatted_date)
|
|
176
|
+
return formatted_date
|
|
177
|
+
else:
|
|
178
|
+
return date.strftime(format_string)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
cpdef str replace_non_padded_with_padded(str input_format):
|
|
182
|
+
cdef str category, non_padded, zero_padded
|
|
183
|
+
# Directly use the dictionary without additional function calls for better performance
|
|
184
|
+
for category in _zero_handling_date_formats:
|
|
185
|
+
non_padded = _zero_handling_date_formats[category]['no_leading_zero']['format']
|
|
186
|
+
zero_padded = _zero_handling_date_formats[category]['zero_padded']['format']
|
|
187
|
+
input_format = input_format.replace(non_padded, zero_padded)
|
|
188
|
+
return input_format
|
|
189
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# clean_str.pyx
|
|
2
|
+
cdef extern from "clean_str.h":
|
|
3
|
+
void clean_string(const char* input, char* output)
|
|
4
|
+
|
|
5
|
+
def cleanstr(input_string):
|
|
6
|
+
cdef bytes input_bytes = input_string.encode('utf-8') # Ensure the bytes object persists
|
|
7
|
+
cdef const char* input_c = input_bytes # Safe cast to a C string
|
|
8
|
+
cdef char output_c[150] # Adjust the size based on expected input lengths
|
|
9
|
+
clean_string(input_c, output_c)
|
|
10
|
+
return output_c.decode('utf-8')
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
cdef extern from "iso8601z.h":
|
|
2
|
+
void replace_zulu_suffix_with_utc(char *datetime_string)
|
|
3
|
+
|
|
4
|
+
cpdef str replaceZ(str datetime_string):
|
|
5
|
+
cdef bytes datetime_bytes = datetime_string.encode('utf-8')
|
|
6
|
+
cdef char *datetime_cstring = datetime_bytes
|
|
7
|
+
|
|
8
|
+
# Call the C function
|
|
9
|
+
replace_zulu_suffix_with_utc(datetime_cstring)
|
|
10
|
+
|
|
11
|
+
# Convert the C string back to a Python string
|
|
12
|
+
return datetime_cstring.decode('utf-8')
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# time_zones.pyx
|
|
2
|
+
cdef extern from "time_zones.h":
|
|
3
|
+
ctypedef struct TimeZoneInfo:
|
|
4
|
+
char full_name[50]
|
|
5
|
+
char region[50]
|
|
6
|
+
char offset[11]
|
|
7
|
+
char type[10]
|
|
8
|
+
char dst[7]
|
|
9
|
+
|
|
10
|
+
ctypedef struct TimeZone:
|
|
11
|
+
char code[7]
|
|
12
|
+
TimeZoneInfo info
|
|
13
|
+
|
|
14
|
+
extern TimeZone time_zones[]
|
|
15
|
+
extern int time_zones_count
|
|
16
|
+
|
|
17
|
+
cdef dict get_time_zones_impl():
|
|
18
|
+
cdef dict result = {}
|
|
19
|
+
cdef int i
|
|
20
|
+
cdef TimeZone tz
|
|
21
|
+
for i in range(time_zones_count):
|
|
22
|
+
tz = time_zones[i]
|
|
23
|
+
result[tz.code.decode('utf-8')] = {
|
|
24
|
+
"full_name": tz.info.full_name.decode('utf-8'),
|
|
25
|
+
"region": tz.info.region.decode('utf-8'),
|
|
26
|
+
"offset": tz.info.offset.decode('utf-8'),
|
|
27
|
+
"type": tz.info.type.decode('utf-8'),
|
|
28
|
+
"dst": tz.info.dst.decode('utf-8')
|
|
29
|
+
}
|
|
30
|
+
return result
|
|
31
|
+
|
|
32
|
+
cpdef dict get_time_zones():
|
|
33
|
+
return get_time_zones_impl()
|
|
34
|
+
|
|
35
|
+
# Assign dict
|
|
36
|
+
time_zones_dict = get_time_zones()
|
|
37
|
+
|
|
38
|
+
# Define public interface
|
|
39
|
+
__all__ = [
|
|
40
|
+
'time_zones_dict'
|
|
41
|
+
]
|