2025, Oct 15 15:03

CPython C API में datetime/timedelta गणित: PyNumber_Subtract से एक दिन घटाएँ

जानें कैसे PyDelta_FromDSU और PyNumber_Subtract से CPython C API पर datetime/timedelta अंकगणित करें: start न होने पर finish से 1 दिन घटाकर निकालें आसानी से.

CPython के C-लेयर पर तिथियों के साथ काम करना, हाई-लेवल Python हिस्से की तुलना में, अक्सर कम दस्तावेजीकृत महसूस होता है। एक आम ज़रूरत है एक तारीख को दूसरी के आधार पर डिफॉल्ट मानना — उदाहरण के लिए, अगर शुरुआती timestamp गायब है, तो उसे समापन timestamp से एक दिन पहले मान लेना — और यह सब Python C API से बाहर गए बिना करना।

संदर्भ में समस्या

मान लें कि एक नेटिव फ़ंक्शन दो Python ऑब्जेक्ट लेता है: start और finish। यदि start पास नहीं किया गया, तो फ़ंक्शन को उसे “finish माइनस एक दिन” के रूप में निकालना है। गैर-तिथि इनपुट का रूपांतरण संभाल लिया गया है और एक दिन का timedelta भी बना लिया गया है, लेकिन बचा हुआ काम है उस डेल्टा को C API स्तर पर लागू करना।

/* यदि end-date पहले से date नहीं है, तो उसे कन्वर्ट करने का प्रयास करें */
if (!PyDate_Check(endObj)) {
    tmp = PyDate_FromTimestamp(endObj);
    if (tmp == NULL)
        return PyErr_Format(PyExc_ValueError,
            "%R is not a valid date", endObj);
    endObj = tmp;
}
/* यदि begin-date निर्दिष्ट नहीं है, तो end date से एक दिन पहले मानें */
if (beginObj == NULL) {
    PyObject *deltaOne = PyDelta_FromDSU(1, 0, 0);
    /* किसी तरह एक दिन का डेल्टा लगाकर endObj से beginObj निकालें */
}

असल में हो क्या रहा है

Python C API तिथि और timedelta प्रकारों के लिए कन्स्ट्रक्टर्स तो देता है, लेकिन तिथि-अंकगणित के सीधे हेल्पर्स नहीं देता। इसका मतलब यह नहीं कि काम असंभव है — बस गणना को जनरल नंबर प्रोटोकॉल के ज़रिए व्यक्त करना होता है। व्यवहार में, यही PyNumber API है, जो वही ऑपरेशन ट्रिगर करती है जो Python कोड करता है (जैसे अंदरूनी तौर पर __sub__ कॉल करना)।

समाधान: PyNumber API का उपयोग करें

PyDelta_FromDSU से timedelta बनाने के बाद, PyNumber_Subtract के साथ ऑफ़सेट निकालें। नीचे दिया गया छोटा उदाहरण Windows पर ctypes के जरिये बुलाए गए एक नेटिव फ़ंक्शन को दिखाता है: अगर start दिया गया है तो वही लौटाता है, और अगर start None है तो finish से एक दिन घटा देता है। datetime की C परिभाषाएँ इस्तेमाल करने से पहले <datetime.h> शामिल करना और PyDateTime_IMPORT कॉल करना ज़रूरी है।

cl /LD /W4 /Ic:\python313\include test.c -link /libpath:c:\python313\libs

#include <Python.h>
#include <datetime.h>
__declspec(dllexport)
PyObject* derive_begin(PyObject* begin_obj, PyObject* end_obj) {
    PyDateTime_IMPORT;
    if (begin_obj == Py_None) {
        PyObject* delta1 = PyDelta_FromDSU(1, 0, 0);
        begin_obj = PyNumber_Subtract(end_obj, delta1);
        Py_XDECREF(delta1);
    }
    return begin_obj;
}

और बिना पूरा एक्सटेंशन मॉड्यूल लिखे व्यवहार दिखाने के लिए छोटा-सा ड्राइवर:

import datetime as dt
import ctypes as ct
lib = ct.PyDLL('./test')
derive_begin = lib.derive_begin
derive_begin.argtypes = ct.py_object, ct.py_object
derive_begin.restype = ct.py_object
beg = dt.datetime(2025, 8, 14)
end_ = dt.datetime(2025, 8, 14, 1)
print(f'begin  = {beg}')
print(f'end    = {end_}')
print(derive_begin(beg, end_))   # begin लौटाता है
print(derive_begin(None, end_))  # end से 1 दिन पहले लौटाता है

नमूना आउटपुट:

begin  = 2025-08-14 00:00:00
end    = 2025-08-14 01:00:00
2025-08-14 00:00:00
2025-08-13 01:00:00

यह क्यों मायने रखता है

नेटिव कोड को अक्सर Python के datetime ऑब्जेक्ट्स के साथ काम करना पड़ता है, बिना कस्टम C दिनांक-तर्क में चक्कर लगाए। PyDelta_FromDSU के साथ PyNumber_Subtract का उपयोग करने से सब कुछ Python ऑब्जेक्ट मॉडल के भीतर रहता है और struct tm या mktime-शैली के API में आना-जाना नहीं करना पड़ता। जब datetime C API अंकगणित के हेल्पर्स नहीं देती, तब PyNumber API वही पुल है जिसके जरिए आप वही ऑपरेशन बुला सकते हैं जो Python कोड करता।

मुख्य बातें

यदि आपको Python C API से तारीख-अंकगणित चाहिए, तो PyDelta_FromDSU से timedelta बनाएँ और PyNumber API (जैसे PyNumber_Subtract) के माध्यम से उसे लागू करें। <datetime.h> शामिल करें, उपयोग से पहले PyDateTime_IMPORT कॉल करें, और जो अस्थायी रेफ़रेंस आप बनाते हैं उन्हें मैनेज करें। अगर ज़रूरत सिर्फ आर्गुमेंट डिफॉल्टिंग की है, तो एक व्यावहारिक तरीका यह है कि इसे C रूटीन को बुलाने से पहले Python स्तर पर ही निपटा लें, ताकि C साइड मूल तर्क पर केंद्रित रहे।

यह लेख StackOverflow पर प्रश्न (लेखक: Mikhail T.) और Mark Tolonen के उत्तर पर आधारित है।