Install the package:
pip install d8fmtFormat a date using the CLI:
python -m d8fmt -c "{YEAR4}-{MONTH#}-{DAY#}"
>> 13:12:00Use it in Python:
from d8fmt import datetime_ez
d = datetime_ez(year=2004, month=10, day=31, hour=13, minute=12, second=0)
print(d.ezftime("{HOUR24}:{MINUTE}:{SECOND} Day of year = {DOY}")) # Outputs: 13:12:00 Day of year = 305d8fmt is a Python module designed to transform canonical datetime examples into deterministic and platform-independent format strings using strftime directives. It provides two flexible ways to define readable and human-friendly format strings:
You can specify a unique, deterministic datetime example to define how dates should be formatted. The canonical example ensures that all components of the datetime are unique and easily converted into appropriate strftime directives.
Example:
Input: Sunday October 31 2004 13:12:11.000000
Output: "%A %B %d %Y %H:%M:%S.%f"
By using this approach, d8fmt automatically parses your example and generates the correct format string by detecting unique components like the day, month, year, and time.
For better readability, you can use tokenized macros that clearly represent datetime components in plain English. These macros replace cryptic strftime directives with easy-to-understand placeholders.
Example:
Input: {YEAR4}/{MONTH#}/{DAY#} {HOUR24}:{MINUTE}:{SECOND}.{MICROSEC}
Output: "%Y/%m/%d %H:%M:%S.%f"
This method is more verbose but ensures clarity and correctness. It’s especially useful for generating consistent and understandable format strings.
- Deterministic Parsing: Every component in the canonical example (e.g., October 31, 2004, 13:12:11) is unique, ensuring consistent and accurate parsing into
strftimeformats. - Flexibility: Both human-friendly macros and canonical examples work seamlessly with mixed format strings (e.g., separators like
/,-, or even text). - Versatility: If a part of the input doesn’t match a known macro or directive, it simply passes through as-is.
- Macro-Based Formatting (English Tokens):
"{YEAR4}/{MONTH#}/{DAY#}" → "%Y/%m/%d"- Canonical-Style Formatting (Format from a fixed date example):
"Sunday October 31 2004 13:12:11.000000" → "%A %B %d %Y %H:%M:%S.%f"- Mixed Styles (not really recommended):
"Oct {DAY#}, {YEAR4} at {HOUR12}:{MINUTE} {PM}" → "%b %d, %Y at %I:%M %p"- Canonical examples and macros are mutually compatible and can be mixed freely in a single format.
- Common separators like spaces, slashes (
/), or dashes (-) pass through unchanged. - Avoid using overly long text as part of your format, as it may unexpectedly coincide with valid components.
| Canonical Oct 31 2004 13:12:11 |
Macro | Description | %Format |
|---|---|---|---|
01 |
{HOUR12} |
Hour in 12-hour clock (zero-padded) | %I |
13 |
{HOUR24} |
Hour in 24-hour clock (zero-padded) | %H |
305 |
{DOY} |
Day of the year (1–366, zero-padded) | %j |
04 |
{YEAR2} |
Year without century (last two digits) | %y |
2004 |
{YEAR4} |
Year with century | %Y |
October |
{MONTH} |
Full month name | %B |
Oct |
{MONTH3} |
Abbreviated month name | %b |
10 |
{MONTH#} |
Month as a number (zero-padded, 01–12) | %m |
Sunday |
{DAY} |
Full weekday name | %A |
Sun |
{DAY3} |
Abbreviated weekday name | %a |
31 |
{DAY#} |
Day of the month (zero-padded) | %d |
12 |
{MINUTE} |
Minute (zero-padded) | %M |
11 |
{SECOND} |
Second (zero-padded) | %S |
.000000 |
{MICROSEC} |
Microsecond (zero-padded, 6 digits) | %f |
AM |
{AM} |
AM/PM marker | %p |
PM |
{PM} |
AM/PM marker | %p |
44 |
{WOY} |
Week of the year (Sunday as first day) | %U |
43 |
{WOYISO} |
ISO week number of the year (Mon as first day) | %W |
7 |
{WDAY#ISO} |
Day of the week (ISO, Monday=1 to Sunday=7) | %u |
0 |
{WDAY#} |
Day of the week (Sunday-based, 0=Sun to 6=Sat) | %w |
| N/A | {TZ} |
Timezone abbreviation | %Z |
| N/A | {UTCOFF} |
UTC offset in the form ±HHMM | %z |
Here are some examples of converting these format strings into datetime ready format strings.
>>> d8fmt.snap_fmt("2004-10-31")
'%Y-%m-%d'
>>> d8fmt.snap_fmt("2004-10-31 13-12-11")
'%Y-%m-%d %H-%M-%S'
>>>d8fmt.snap_fmt("Oct. 31 2004 01-12-11 PM")
'%b. %d %Y %I-%M-%S %p'
>>d8fmt.snap_fmt("{YEAR4}-{MONTH#}-{DAY#}T{HOUR24}:{MINUTE}:{SECOND}")
'%Y-%m-%dT%H:%M:%S'
>>d8fmt.snap_fmt("{YEAR4}-{MONTH#}-{DAY#}T{HOUR24}:{MINUTE}:{SECOND}.{MICROSEC}")
'%Y-%m-%dT%H:%M:%S.%f'Note: d8fmt does NOT yet support timezones or offsets.
- Token Conversion: Converts canonical components (e.g.,
2004,31,October) into matchingstrftimedirectives (e.g.,%Y,%d,%B). - Time Zone Validation:
- Prohibits timezone abbreviations (e.g.,
PST,GMT). - Enforces offsets like
+/-ddddto have a leading space.
- Prohibits timezone abbreviations (e.g.,
- Fractional Seconds Support: Encodes fractional seconds via
%<N>f, whereNdetermines the number of zeros in the example. - Error Handling: Raises a
ValueErrorfor unsupported formats or invalid tokens. - Round-Trip Validation: Ensures that transformed formats can accurately round-trip the
canonicalexample.
Clone the repository and ensure you have Python 3.x installed along with required libraries like pytest for testing.
git clone <https://github.com/hucker/d8fmt>
cd d8fmtfrom d8fmt import ez_format, is_zone_free, datetime_ezThe class datetime_ez is provided allowing you to just drop in the new class like this.
>>d = d8fmt.datetime_ez(year=2025,month=8,day=15,hour=13,minute=12,second=11)
>>d.ezftime("{DAY}-{DAY#}-{MONTH}-{YEAR2}")
'Friday-15-August-25'
>>d.ezftime("{DAY}-{MONTH}-{YEAR2} {HOUR12}:{MINUTE}:{SECOND} {PM}")
'Friday-August-15 01:12:11 PM'
>>d.ezftime("{DAY3}-{MONTH3}-{YEAR2} {HOUR24}:{MINUTE}:{SECOND} {PM}")
'Fri-Aug-25 13:12:11 PM'
>>d.ezftime("Sunday Oct 31 2004 13:12:11.000000")
'Friday Aug 15 2025 13:12:11.000000'It is possible that it is not convenient to just use datetime_ez objects everwhere. The easy way to make this happen is to create a datetime_ez from a datetime object like this
import datetime
>> from d8fmt import datetime_ez
>> dt_now = datetime.datetime.now()
datetime.datetime(2025, 8, 21, 1, 18, 24, 157679)
>> dt_ez = datetime_ez(dt=dt_now) #<- create a copy from an existing datetime object?
datetime.datetime(2025, 8, 21, 1, 18, 24, 157679)
>> print(dt_ez.ezftime('{Year} {Month} {Day}'))
2025 08 21You shouldn't really need the format string directly, but it is readily accessible by using the snap_fmt function
to convert an ez format string into a datetime aware format string. Internally, all the ezftime method does is
run the snap_fmt function on the format string that you provide which is then passed to the datetime's
strftime method.
from d8fmt import snap_fmt
output_format = snap_fmt("2004-10-31") # Example input
print(output_format) # Output: '%Y-%m-%d'Check if a format string is free from unsupported time zone abbreviations or patterns:
from d8fmt import is_zone_free
is_zone_free("2004-10-31T13:12:11 +0530") # Raises ValueErrorA command line interface is available for test purposes. It is very similar to the date command on Mac/PC/Linux, but it also gives you a way to interactively test yur format strings.
(d8fmt) chuck@Chucks-Mac-mini src % python -m d8fmt --help
Format a string using ezftime format specifiers and return appropriate exit codes.
positional arguments:
format_string The format string to use, with ezftime placeholders (e.g., '{HOUR12}:{MINUTE} {AM}'). If not provided, a default format will be used.
options:
-h, --help show this help message and exit
-a, --all Use a format string that shows all replacements
-c, --canonical Use the canonical datetime reference (October 31, 2004 13:12:11.000000) for formatting. If not provided, the current datetime is used.
-o, --print-original Print the original format string provided as input.
-t, --print-tokenized
Print the tokenized string after macro substitutions.
-v, --verbose Print everithing available for debugging purposes.
(d8fmt) chuck@Chucks-Mac-mini src % python -m d8fmt
Thu Aug 21 11:38:02
(d8fmt) chuck@Chucks-Mac-mini src % python -m d8fmt -c
Sun Oct 31 13:12:11
(d8fmt) chuck@Chucks-Mac-mini src % python -m d8fmt -a
day=Thursday Thu 21 mon=August Aug 08 y=25 2025 hr=11AM/11 min=38 s=17.912408 DOY=233 WeekOfYear=33 WeakOfYearIso=33 WDAY#=4 WDAY#ISO=4 LOCALE=08/21/25
(d8fmt) chuck@Chucks-Mac-mini src % python -m d8fmt -v
Original Format String:
{DAY3} {MONTH3} {DAY#} {HOUR24}:{MINUTE}:{SECOND}
Tokenized String (with macros replaced):
%a %b %d %H:%M:%S
Formatted String (with datetime values applied):
Thu Aug 21 11:38:38
(d8fmt) chuck@Chucks-Mac-mini src % python -m d8fmt "{MONTH#}/{DAY#}/{YEAR4} {HOUR24}:{MINUTE}:{SECOND}"
08/21/2025 11:43:53
Run tests to confirm functionality and validation. As it currently sits d8fmt has 100% code coverage,
using 107 tests with a lint scroe of 9.78/10.
pytest test_d8fmt.pyExample Output:
========================== test session starts =========================
collected 107 items
test_d8fmt.py ........................ [100%]
=========================== 107 passed in 0.11s =========================
Feel free to submit issues or propose enhancements via pull requests. Be sure to follow the existing code structure and guidelines.
This module adheres to strict rules for datetime handling with influences from ISO 8601 standards.