SQLite لا يملك نوع بيانات للتاريخ فعليًا
هذه النقطة تربك كل من يأتي من Postgres أو MySQL. فـ SQLite يملك خمس فئات تخزين فقط: NULL وINTEGER وREAL وTEXT وBLOB، ولا شيء غير ذلك. لا يوجد DATE ولا DATETIME ولا TIMESTAMP. يمكنك أن تكتب created_at DATETIME داخل جملة CREATE TABLE وسيقبلها SQLite دون اعتراض، لكنه في النهاية يخزّن القيمة كنص عادي أو كرقم.
ما يقدّمه لك SQLite بدلًا من ذلك هو مجموعة من دوال التاريخ تفهم ثلاث صيغ متعارف عليها لتخزين التاريخ في sqlite:
- نص بصيغة ISO 8601 — مثل
'2026-04-23'أو'2026-04-23 10:15:00'أو'2026-04-23T10:15:00.123Z'. - Unix timestamp — عدد الثواني منذ 1970-01-01 بتوقيت UTC، ويُخزَّن كعدد صحيح.
- رقم اليوم اليولياني (Julian day) — عدد الأيام الكسرية منذ سنة 4714 ق.م، ويُخزَّن كعدد عشري.
اختر صيغة واحدة والتزم بها. صيغة ISO 8601 النصية هي الأوضح للقراءة، كما أنها تُرتَّب ترتيبًا صحيحًا حتى عند معاملتها كنص، ولهذا تُعدّ الخيار الافتراضي.
أربع طرق للسؤال عن "اللحظة الحالية" — تاريخ نصي، تاريخ ووقت نصي، ثوانٍ Unix، ويوم Julian. كلها تصف اللحظة نفسها.
دوال التاريخ الخمس في sqlite
يوفّر لك SQLite خمس دوال مدمجة تغطّي تقريبًا كل ما تحتاجه عند التعامل مع التواريخ:
date(time, ...)— تُرجع التاريخ بصيغةYYYY-MM-DD.time(time, ...)— تُرجع الوقت بصيغةHH:MM:SS.datetime(time, ...)— تجمع الاثنين معًا بصيغةYYYY-MM-DD HH:MM:SS.julianday(time, ...)— تُرجع رقمًا عشريًا (مفيد جدًا لحساب الفرق بين تاريخين).strftime(format, time, ...)— تُرجع نصًا بتنسيق التاريخ الذي تختاره أنت.
كل دالة من هذه الدوال تستقبل قيمة زمنية كوسيط أول، ثم أي عدد تريده من المُعدِّلات (modifiers) النصية بعد ذلك.
لاحظ كلمة 'unixepoch' — بهذه الكلمة تُخبر دوال التاريخ أن الرقم المُدخل هو Unix timestamp وليس يوماً جوليانياً. بدونها، يفترض SQLite أن الرقم بصيغة Julian.
strftime: تنسيق التاريخ في sqlite حسب رغبتك
دالة strftime هي حصان العمل الحقيقي. ترموز % فيها هي نفسها التي تعرفها من لغة C أو Python:
الرموز التي بتستخدمها أكثر شي:
%Y— السنة بأربع خانات.%m— الشهر (01-12).%d— اليوم من الشهر (01-31).%H,%M,%S— الساعات والدقائق والثواني.%w— يوم الأسبوع (0=الأحد).%j— اليوم من السنة (001-366).%s— توقيت Unix (timestamp).%f— الكسور من الثانية (SS.SSS).
أيضاً strftime هي الطريقة التي بتستخرج فيها أجزاء من التاريخ — ما في دالة منفصلة اسمها EXTRACT ولا YEAR() زي بقية قواعد البيانات. بكل بساطة بتنسّق التاريخ لحد الجزء التي بدك إياه، وبتعمل cast لو محتاج قيمة رقمية:
strftime تُرجع دائمًا نصًا، لذا غلّفها بـ CAST(... AS INTEGER) إذا أردت إجراء عمليات حسابية أو مقارنات رقمية.
المُعدِّلات: حسابات التواريخ دون الحاجة إلى عوامل
هذه هي الميزة التي تجعل التعامل مع التواريخ في sqlite أمرًا ممتعًا. بعد وسيط الوقت، يمكنك تمرير أي عدد تشاء من سلاسل المُعدِّلات (modifiers)، وتُطبَّق بالترتيب الذي كتبتها به:
المُعدِّلات (modifiers) التي ستستخدمها باستمرار:
'+N days'و'-N days'، وكذلك الأمر معhoursوminutesوsecondsوmonthsوyears.'start of day'و'start of month'و'start of year'— لاقتطاع التاريخ إلى بداية تلك الفترة.'weekday N'— للانتقال إلى أقرب يوم محدد من أيام الأسبوع (0 = الأحد).'localtime'و'utc'— للتحويل بين المناطق الزمنية.
حيلة "آخر يوم في الشهر" تستحق أن تحفظها: ابدأ من بداية الشهر، أضف شهراً واحداً، ثم اطرح يوماً واحداً. لا توجد في SQLite دالة باسم LAST_DAY، لكن سَلسَلة المُعدِّلات تعطيك النتيجة نفسها بالضبط.
التوقيت العالمي UTC مقابل التوقيت المحلي
'now' تُرجع دائماً التوقيت العالمي UTC. إذا أردت الوقت المحلي، فعليك طلبه صراحةً:
المُعدِّل 'localtime' يحوّل القيمة من UTC إلى المنطقة الزمنية المحلية للنظام. أما المُعدِّل 'utc' فيعمل بالعكس تمامًا — يعتبر المُدخل بالتوقيت المحلي ويحوّله إلى UTC.
العادة الآمنة: خزّن كل شيء بصيغة UTC، ولا تحوّل إلى التوقيت المحلي إلا عند العرض فقط. خلط المناطق الزمنية في التخزين يفتح الباب لأخطاء تظهر مرتين في السنة مع تغيير التوقيت الصيفي.
مقارنة التواريخ وتصفية النطاقات في sqlite
إذا خزّنت التواريخ كنصوص بصيغة ISO 8601، فإن المقارنة و BETWEEN ستعملان دون أي عناء — لأن ISO 8601 يُرتَّب أبجديًا بنفس الترتيب الزمني تمامًا. وهذا بالضبط هو السبب وراء اعتماده كصيغة افتراضية في sqlite.
النطاق نصف المفتوح (>= start و < end) عادة مفيدة جدًا، لأنه يريحك تمامًا من السؤال المزعج: "هل منتصف ليل اليوم الثلاثين دخل ضمن النطاق ولا لأ؟".
وعشان تجيب بيانات "آخر 7 أيام"، خلّ SQLite هي التي تحسب لك الحد:
حساب الفرق بين تاريخين في SQLite
ما فيه دالة DATEDIFF في SQLite، لكن في طريقتين تغطّيان كل الحالات تقريبًا:
الفروقات الناتجة عن julianday() تكون بالأيام (مع دقة كسرية)، فلو ضربتها في 24 تحصل على الساعات، وفي 1440 تحصل على الدقائق. أما الفروقات بـ strftime('%s', ...) فتأتي بالثواني، وهذا مفيد لما تحتاج قيمة عددية صحيحة.
CAST(... AS INTEGER) يقطع الجزء الكسري من الأيام إذا أردت عدد الأيام الكامل فقط:
تخزين التاريخ في sqlite: اختر صيغة واحدة والتزم بها
أمامك ثلاث خيارات معقولة، مرتبة حسب الأولوية في الاختيار:
- نص بصيغة ISO 8601 (
TEXT). مقروء عند تصدير البيانات، يُرتَّب بشكل صحيح، ويتعامل بسلاسة مع كل دوال التاريخ. هذا هو الخيار الافتراضي. - ثوانٍ يونكس (
INTEGER). حجم أصغر، ومقارنات أسرع، وبدون أي لبس في المنطقة الزمنية. مناسب حين يكون لديك ملايين الصفوف. ستحتاج إلىdatetime(col, 'unixepoch')لقراءته بصيغة مفهومة. - اليوم اليولياني (
REAL). نادراً ما يستحق العناء، إلا إذا كنت تقوم بحسابات تاريخية مكثفة وتريد دقة دون الثانية في عمود واحد.
أما ما يجب عليك تجنّبه تماماً فهو خلط الصيغ داخل العمود نفسه. صحيح أن دوال التاريخ ستقبل أي صيغة بصمت، لكن الفهارس والترتيب والمقارنات ستعطيك نتائج لا معنى لها.
DEFAULT (datetime('now')) هي المُعادِل في SQLite لِـ DEFAULT CURRENT_TIMESTAMP، فهي تختم كل صف جديد بالوقت الحالي بتوقيت UTC تلقائيًا، دون الحاجة لأي تدخل من جهة التطبيق.
تجميع البيانات حسب الفترة الزمنية
تظهر قوة strftime فعليًا حين تريد تجميع الصفوف حسب الشهر أو الأسبوع أو الساعة:
نفس الفكرة تنطبق على "الطلبات حسب ساعة اليوم"، أو "التسجيلات حسب يوم الأسبوع"، أو "الأحداث في كل دقيقة" — اختر صيغة تنسيق تلتقط مستوى التفصيل الذي تريده فقط، ثم جمّع البيانات بناءً عليها.
التالي: دوال التجميع
وبما أننا نتحدث عن التجميع — فإن COUNT(*) هي أبسط دوال التجميع في SQLite. في الدرس القادم سنستعرض المجموعة الكاملة: SUM، وAVG، وMIN، وMAX، وكيف تختصر هذه الدوال صفوفاً كثيرة إلى قيمة ملخّصة واحدة.
الأسئلة الشائعة
هل يحتوي SQLite على نوع بيانات DATE أو DATETIME؟
لا — لا يوجد في SQLite نوع مخصّص للتواريخ. يمكنك تخزين التاريخ كنص TEXT بصيغة ISO 8601 مثل '2026-04-23 10:15:00'، أو كطابع زمني Unix بنوع INTEGER، أو كـ Julian day بنوع REAL. ودوال التاريخ المدمجة تقبل الصيغ الثلاث جميعًا، وتُرجع نصًا بصيغة ISO 8601 افتراضيًا.
كيف أحصل على التاريخ والوقت الحاليين في SQLite؟
استخدم date('now') للتاريخ الحالي، وtime('now') للوقت الحالي، وdatetime('now') لكليهما معًا، وstrftime('%s', 'now') للحصول على طابع Unix الزمني. هذه الدوال تُرجع التوقيت العالمي UTC افتراضيًا — لتحويله إلى التوقيت المحلي مرّر المُعدِّل 'localtime' هكذا: datetime('now', 'localtime').
كيف أضيف أيامًا أو شهورًا إلى تاريخ في SQLite؟
مرّر سلسلة مُعدِّل إلى أي دالة تاريخ، مثل: date('2026-04-23', '+7 days') أو date('now', '-1 month') أو datetime('now', '+2 hours', '+30 minutes'). تُطبَّق المُعدِّلات بالترتيب الذي تكتبها به، والوحدات المتاحة تشمل days وhours وminutes وseconds وmonths وyears.
كيف أحسب الفرق بين تاريخين؟
لحساب الفرق بالأيام استخدم julianday(end) - julianday(start) — وبما أن Julian days قيم عشرية، فالنتيجة تتضمّن كسور اليوم. أمّا للحساب بالثواني فاطرح طابعَي Unix: strftime('%s', end) - strftime('%s', start). لا توجد دالة DATEDIFF في SQLite، لكن هاتين الطريقتين تغطّيان كل الحالات تقريبًا.