تازه با MCP آشنا شده‌اید؟ اولین اتصال رایگان را بدون نصب در ۱۰ دقیقه بسازید. آموزش قدم‌به‌قدم

Esc

ذخیره تاریخ شمسی در دیتابیس: مهارتی برای دستیار برنامه‌نویس

ذخیره تاریخ شمسی در دیتابیس به‌صورت متن، باگ گزارش ماهانه و سال کبیسه می‌سازد. مهارت تاریخ شمسی به دستیار یاد می‌دهد میلادی ذخیره کند و در لبه تبدیل کند.

تاریخ شمسی در کدنوشتهٔ ۱۰ دقیقه مطالعه

فهرست مطالب ۹ بخش
  1. تاریخ شمسی را در دیتابیس چطور ذخیره کنیم؟
  2. گزارش ماهانه شمسی در SQL بدون جا انداختن روز
  3. سال کبیسه شمسی و باگ ۲۸۲۰
  4. منطقهٔ زمانی تهران: چرا سرور هنوز +۰۴:۳۰ می‌زند؟
  5. کدام کتابخانه در JavaScript، Python، PHP و Go؟
  6. با مهارت فعال، از دستیار چه می‌گیرید؟
  7. نصب در Claude Code و claude.ai
  8. این مهارت چه کاری نمی‌کند؟
  9. سؤال‌های پرتکرار

گزارش فروش اسفند را از دیتابیس گرفته‌اید و جمعش با حساب‌های مالی نمی‌خواند. چند روز آخر ماه در گزارش نیست، چون کوئری با MONTH() میلادی نوشته شده و ماه میلادی با ماه شمسی هم‌مرز نیست. یا بدتر: روز ۳۰ اسفند ۱۴۰۳ برنامه نوشت «۱ فروردین ۱۴۰۴»، چون کسی سال‌ها پیش الگوریتم ۲۸۲۰ ساله را در کد گذاشته بود. حالا که بخش زیادی از کد را دستیار هوش مصنوعی می‌نویسد، سؤال این است که چطور از همان خط اول، ذخیره تاریخ شمسی در دیتابیس را درست انجام بدهد.

جواب کوتاه: تاریخ را میلادی ذخیره کنید (لحظه‌ها به وقت UTC، تاریخ‌های بدون ساعت در ستون DATE)، فقط در لبه‌ها به شمسی تبدیل کنید و تبدیل را در منطقهٔ زمانی Asia/Tehran و با یک کتابخانهٔ معتبر انجام بدهید. مهارت «تاریخ شمسی در کد» همین قاعده‌ها را به Claude می‌دهد تا کدی که برای نرم‌افزار ایرانی می‌نویسد از اول این‌طور باشد.

تاریخ شمسی را در دیتابیس چطور ذخیره کنیم؟

رایج‌ترین اشتباه ستون متنی با مقداری مثل 1403/01/01 است. مرتب‌سازی، مقایسه و حساب روز روی چنین ستونی یا کند است یا غلط. قاعدهٔ اول مهارت ساده است:

  • لحظه‌ها (زمان ثبت سفارش، ورود، پرداخت) به وقت UTC در timestamptz یا با قالب ISO 8601 و حرف Z ذخیره می‌شوند.
  • تاریخ‌های بدون ساعت (تاریخ تولد، سررسید قسط) در ستون DATE میلادی می‌روند.
  • متن شمسی فقط وقتی کنار مقدار میلادی نگه داشته می‌شود که یک سند حقوقی همان متنِ واردشده را لازم داشته باشد. حتی آن موقع هم منبع اصلی نیست.

تبدیل فقط در دو جا اتفاق می‌افتد: ورودی شمسی کاربر سر API به میلادی تبدیل می‌شود و خروجی در لایهٔ رابط یا گزارش به شمسی. یک نکتهٔ ریز هم هست که زیاد جا می‌ماند: تاریخ شمسی یک لحظه به منطقهٔ زمانی بستگی دارد. لحظهٔ 2025-03-20T21:00:00Z در تهران ساعت ۰۰:۳۰ روز ۱ فروردین ۱۴۰۴ است، ولی به وقت UTC هنوز ۳۰ اسفند ۱۴۰۳. پس منطقه را همیشه صریح بدهید و به منطقهٔ پیش‌فرض سرور تکیه نکنید.

برای نمایش، در جاوااسکریپت کتابخانه هم لازم نیست:

new Intl.DateTimeFormat('fa-IR-u-ca-persian', {
  timeZone: 'Asia/Tehran', year: 'numeric', month: '2-digit', day: '2-digit',
}).format(new Date('2024-03-20T12:00:00Z')); // '۱۴۰۳/۰۱/۰۱'

با fa-IR-u-ca-persian-nu-latn همین تاریخ با رقم لاتین می‌آید. اگر سال و ماه و روز را برای منطق برنامه می‌خواهید، آن‌ها را از formatToParts بخوانید، نه با شکستن رشته. Intl فقط قالب‌بندی می‌کند. برای خواندن ورودی کاربر و حساب تاریخ کتابخانه لازم است.

گزارش ماهانه شمسی در SQL بدون جا انداختن روز

MONTH() در MySQL و date_trunc('month', …) در PostgreSQL ماه میلادی را می‌شناسند و مرز ماه شمسی وسط ماه میلادی است. راهی که مهارت پیشنهاد می‌کند این است: مرز ماه شمسی را با کتابخانه به تاریخ میلادی ببرید، آن را نیمه‌شب تهران بخوانید و بازهٔ نیمه‌باز [شروع این ماه، شروع ماه بعد) را کوئری کنید.

مثال خود مهارت مهر ۱۴۰۵ است. یکم مهر ۱۴۰۵ برابر 2026-09-23 و یکم آبان برابر 2026-10-23 است:

-- PostgreSQL, timestamptz column: let the database apply the zone rules
SELECT * FROM orders
WHERE created_at >= TIMESTAMP '2026-09-23 00:00' AT TIME ZONE 'Asia/Tehran'
  AND created_at <  TIMESTAMP '2026-10-23 00:00' AT TIME ZONE 'Asia/Tehran';
-- Equivalent UTC range: [2026-09-22T20:30:00Z, 2026-10-22T20:30:00Z)

شرط < برای شروع ماه بعد همان چیزی است که روز آخر را نگه می‌دارد و هیچ سفارشی را دو بار نمی‌شمارد. برای کل یک سال شمسی هم مرزها یکم فروردین همان سال و یکم فروردین سال بعدند. اگر گزارش باید به تفکیک ماه شمسی گروه‌بندی شود، دو راه هست: جمع روزانه را از SQL بگیرید و در کد برنامه در ماه‌های شمسی بریزید، یا یک بار با کتابخانه یک جدول تقویم بسازید (تاریخ میلادی، سال و ماه و روز شمسی، روز هفته، تعطیل بودن) و گزارش را با آن join کنید.

سال کبیسه شمسی و باگ ۲۸۲۰

ماه‌های ۱ تا ۶ سی‌ویک روزه‌اند، ۷ تا ۱۱ سی روزه و اسفند ۲۹ روز، یا در سال کبیسه ۳۰ روز. تقویم رسمی ایران نجومی است و از یک فرمول ساده پیروی نمی‌کند. کبیسه‌های اخیر ۱۳۹۵، ۱۳۹۹ و ۱۴۰۳ بوده‌اند و کبیسهٔ بعدی طبق مهارت ۱۴۰۸ است. فاصلهٔ ۱۴۰۳ تا ۱۴۰۸ پنج سال است، پس هر کدی که «هر چهار سال یک بار» را فرض کرده، دوباره خراب می‌شود.

الگوریتم ۲۸۲۰ ساله (بیرشک) هم که در خیلی از کدهای قدیمی پیدا می‌شود، ۱۴۰۴ را به‌جای ۱۴۰۳ کبیسه می‌گیرد. نرم‌افزارهایی که از آن استفاده می‌کردند از ۳۰ اسفند ۱۴۰۳ تا آخر ۱۴۰۴ یک روز جلو بودند. توصیهٔ مهارت روشن است: اگر عدد 2820 در کد تاریخ‌تان هست، آن تکه را با یک کتابخانهٔ معتبر عوض کنید. قاعدهٔ کبیسه را هیچ‌وقت دستی ننویسید و نوروز را هم حساب نکنید؛ همیشه تبدیل کنید، چون یکم فروردین گاهی ۲۰ مارس است و گاهی ۲۱ مارس.

چند تاریخ مرزی از فهرست آزمون مهارت که با Intl در Node بررسی کرده‌ایم:

میلادی شمسی چرا مهم است
2021-03-20 ۳۰ اسفند ۱۳۹۹ روز آخر یک سال کبیسه
2024-03-20 ۱ فروردین ۱۴۰۳ نوروز در ۲۰ مارس
2025-03-20 ۳۰ اسفند ۱۴۰۳ الگوریتم ۲۸۲۰ این روز را ۱ فروردین ۱۴۰۴ نشان می‌دهد
2025-03-21 ۱ فروردین ۱۴۰۴ نوروز در ۲۱ مارس
2026-09-23 ۱ مهر ۱۴۰۵ شروع اولین ماه سی روزه

مهارت علاوه بر این‌ها یک آزمون رفت‌وبرگشت هم می‌خواهد: هر روز یک بازه را به شمسی ببرید، دوباره به میلادی برگردانید و ببینید همان روز درمی‌آید.

منطقهٔ زمانی تهران: چرا سرور هنوز +۰۴:۳۰ می‌زند؟

ایران بعد از تغییر ساعت ۳۰ شهریور ۱۴۰۱ (2022-09-21) ساعت تابستانی ندارد و تمام سال UTC+03:30 است. این قاعده از نسخهٔ 2022b در پایگاه دادهٔ منطقه‌های زمانی (tzdata) آمده است. سیستمی که tzdata قدیمی‌تر دارد هنوز هر بهار به +04:30 می‌پرد و تا پایان شهریور زمان‌ها یک ساعت جابه‌جا می‌شوند. جاهایی که مهارت برای به‌روزرسانی نام می‌برد: ایمیج سیستم‌عامل، ایمیج پایهٔ داکر، JVM و بستهٔ tzdata پایتون.

دو راه بررسی سریع:

  • در لینوکس، zdump -v Asia/Tehran | tail -n 3 باید آخرین تغییر را در 2022-09-21 نشان بدهد.
  • در جاوااسکریپت، Intl.DateTimeFormat با timeZone: 'Asia/Tehran' و timeZoneName: 'longOffset' باید GMT+03:30 بدهد.

دو دام دیگر هم در مهارت هست. پایتون روی ویندوز برای zoneinfo به pip install tzdata نیاز دارد و MySQL نام‌هایی مثل 'Asia/Tehran' را فقط بعد از بارگذاری جدول‌های منطقهٔ زمانی (mysql_tzinfo_to_sql) می‌شناسد. در کد هم +03:30 را ثابت ننویسید و نام منطقه را بدهید، چون قاعدهٔ ساعت در ایران سیاسی است و ممکن است دوباره عوض شود.

کدام کتابخانه در JavaScript، Python، PHP و Go؟

مهارت برای هر زبان چند گزینهٔ شناخته‌شده می‌دهد و نمونه‌کد هر کدام را هم دارد:

زبان کتابخانه‌ها
JavaScript jalaali-js، date-fns-jalali، dayjs با افزونهٔ jalaliday
React (انتخاب تاریخ) @mui/x-date-pickers با AdapterDateFnsJalali، react-multi-date-picker
Python jdatetime، persiantools
PHP morilog/jalali
Go github.com/yaa110/go-persian-calendar (بستهٔ ptime)

چند نکته که مهارت به دستیار یاد می‌دهد: moment-jalaali کنار گذاشته می‌شود، چون خود Moment.js در حالت نگهداری است. jalaali-js سال و ماه و روز می‌گیرد، نه یک لحظه، پس اول باید تاریخ را در منطقهٔ تهران به اجزایش شکست. در date-fns-jalali ماه در newDate از صفر شروع می‌شود. نسخهٔ ۳ jalaliday فقط ESM است. date-fns هم با منطقهٔ زمانی محلی همان دستگاه کار می‌کند، پس یا TZ=Asia/Tehran بگذارید یا صریح تبدیل کنید. اگر انتخاب‌گر تاریخ شمسی در shadcn/ui می‌خواهید، راهنمای تقویم شمسی در shadcn را ببینید.

با مهارت فعال، از دستیار چه می‌گیرید؟

فرض کنید از Claude می‌خواهید جدول سفارش‌ها و گزارش ماهانهٔ فروش یک فروشگاه را بنویسد. با مهارت فعال، شکل کار معمولاً این است:

  • ستون زمان سفارش از نوع timestamptz (یا معادلش در دیتابیس شما)، نه متن شمسی.
  • تابعی که ماه شمسی را به بازهٔ نیمه‌باز میلادی در منطقهٔ تهران تبدیل می‌کند و کوئری با >= و <، نه MONTH().
  • نمایش تاریخ در لایهٔ رابط با Intl یا کتابخانهٔ همان زبان، همیشه با timeZone صریح.
  • فرم ورودی با انتخاب‌گر شمسی یا سه فهرست سال و ماه و روز، چون <input type=date> همیشه میلادی است. ورودی با رقم فارسی، عربی یا لاتین پذیرفته و یکدست می‌شود، سال چهاررقمی اجباری است و ۳۰ اسفند ۱۴۰۴ رد می‌شود، چون ۱۴۰۴ کبیسه نیست.
  • آزمون‌هایی با همان تاریخ‌های مرزی جدول بالا.

اگر طرف حساب خارجی هم دارید، مهارت می‌گوید هر دو تقویم را نشان بدهید، مثل «۱ مهر ۱۴۰۵ (2026-09-23)». روز اول هفته هم شنبه است و جمعه تعطیل رسمی. تعطیلی پنج‌شنبه از سازمانی به سازمان دیگر فرق می‌کند و تعطیلی‌های قمری هر سال جابه‌جا می‌شوند، پس دستیار این‌ها را در تنظیمات یا داده می‌گذارد، نه در کد. نتیجهٔ دقیق به پروژه و درخواست شما بستگی دارد و کد را باید خودتان آزمایش کنید.

اینکه مدل‌ها بدون کمک تا چه حد در تاریخ شمسی خطا می‌کنند، موضوع آزمایش دقت هوش مصنوعی در تاریخ شمسی است.

نصب در Claude Code و claude.ai

این مهارت (Skill) است، نه سرور MCP: چیزی به حسابی وصل نمی‌شود و کلید هم نمی‌خواهد. فرق این دو را در نوشتهٔ تفاوت Skill و MCP توضیح داده‌ایم. صفحهٔ مهارت دو راه نصب دارد:

نصب دستی در پوشهٔ مهارت‌ها (Claude Code). این دستور را در ترمینال اجرا کنید و بعد Claude Code را دوباره باز کنید:

curl -fsSL --create-dirs -o ~/.claude/skills/jalali-dates/SKILL.md https://mcpfarsi.ir/skills/jalali-dates/SKILL.md

برای نصب فقط در یک پروژه، به‌جای ~/.claude پوشهٔ .claude ریشهٔ همان پروژه را بنویسید. در PowerShell ویندوز هم به‌جای curl بنویسید curl.exe و به‌جای ~ بنویسید $HOME.

سایت claude.ai و برنامهٔ دسکتاپ Claude. فایل zip مهارت را از همان صفحه دریافت کنید، بازش نکنید و در تنظیمات Claude، بخش Capabilities، همان فایل را بارگذاری کنید.

بعد کار را مثل همیشه از دستیار بخواهید، مثلاً «جدول سفارش‌ها و گزارش ماهانهٔ شمسی را بنویس». اگر مهارت را به کار نگرفت، یک گفتگوی تازه باز کنید.

این مهارت چه کاری نمی‌کند؟

  • تاریخ امروز و تعطیلی‌ها را نمی‌داند. مهارت به هیچ داده‌ای وصل نیست. برای تاریخ امروز، مناسبت‌ها و تعطیلی‌های رسمی، سرور تقویم شمسی را ببینید و برای تبدیل‌های سادهٔ تاریخ یا بررسی کد ملی، جعبه‌ابزار ایرانی را.
  • کد را اسکن نمی‌کند. فقط دستورالعمل است. پروژه‌تان را نمی‌گردد و تضمین نمی‌کند دستیار هر قاعده را رعایت کند. تاریخ‌های مرزی را خودتان آزمایش کنید.
  • همیشه به‌روز نیست. نسخه و API کتابخانه‌ها عوض می‌شود و ترتیب کلمه‌ها در قالب بلند Intl هم بین نسخه‌های ICU فرق دارد.
  • قاعده‌های سیاسی را قطعی نمی‌گیرد. ساعت تابستانی و روزهای تعطیل ممکن است دوباره تغییر کنند؛ مهارت می‌گوید این‌ها قابل تنظیم بمانند.

مهارت ساختهٔ خود بازارچه است و مجوز MIT دارد. اگر کار بعدی‌تان جدولی با ستون تاریخ دارد، مهارت تاریخ شمسی در کد را دریافت کنید و درخواست بعدی را با آن بدهید.

سؤال‌های پرتکرار

تاریخ شمسی را در دیتابیس به چه شکلی ذخیره کنیم؟

میلادی. لحظه‌ها (زمان ثبت سفارش، ورود کاربر) به وقت UTC در ستونی مثل timestamptz می‌روند و تاریخ‌های بدون ساعت (تولد، سررسید) در ستون DATE میلادی. متن شمسی مثل 1403/01/01 در ستون متنی هیچ‌وقت منبع اصلی نیست. تبدیل فقط در لبه‌ها انجام می‌شود: ورودی API و نمایش در رابط یا گزارش.

سال کبیسه شمسی بعدی کدام است؟

طبق مهارت، سال‌های کبیسهٔ اخیر ۱۳۹۵، ۱۳۹۹ و ۱۴۰۳ بوده‌اند و کبیسهٔ بعدی ۱۴۰۸ است. فاصلهٔ ۱۴۰۳ تا ۱۴۰۸ پنج سال است، پس کدی که می‌گوید «هر چهار سال یک بار» دوباره خطا می‌کند. قاعدهٔ کبیسه را دستی ننویسید و از کتابخانه یا Intl استفاده کنید.

باگ الگوریتم ۲۸۲۰ ساله چیست؟

الگوریتم ۲۸۲۰ ساله (بیرشک) سال ۱۴۰۴ را کبیسه حساب می‌کند، در حالی که کبیسهٔ واقعی ۱۴۰۳ بود. نرم‌افزارهایی که از آن استفاده می‌کردند از ۳۰ اسفند ۱۴۰۳ تا آخر ۱۴۰۴ یک روز خطا داشتند و مثلاً ۳۰ اسفند ۱۴۰۳ را یکم فروردین ۱۴۰۴ نشان می‌دادند. اگر در کد تاریخ عدد 2820 دیدید، آن را با یک کتابخانهٔ معتبر عوض کنید.

چرا سرور ساعت ایران را یک ساعت جلوتر نشان می‌دهد؟

ایران از ۳۰ شهریور ۱۴۰۱ (21 سپتامبر 2022) ساعت تابستانی ندارد و تمام سال UTC+03:30 است. سیستم‌هایی که tzdata قدیمی‌تر از 2022b دارند هنوز در بهار به +04:30 می‌پرند. tzdata سیستم‌عامل، ایمیج داکر، JVM و بستهٔ tzdata پایتون را به‌روز کنید و همیشه نام منطقه Asia/Tehran را بدهید، نه عدد ثابت.

این مهارت تاریخ امروز یا تعطیلی‌ها را می‌گوید؟

نه. مهارت فقط دستورالعمل نوشتن کد است و به هیچ داده‌ای وصل نمی‌شود. برای تاریخ امروز، مناسبت‌ها و تعطیلی‌ها سرور MCP تقویم شمسی (taghvim) به کار می‌آید و برای تبدیل‌های ساده جعبه‌ابزار ایرانی (iran-toolkit).

مهارت تاریخ شمسی را در کدام برنامه‌ها می‌شود نصب کرد؟

صفحهٔ مهارت دو راه می‌دهد: در Claude Code با یک دستور، فایل SKILL.md را در پوشهٔ ~/.claude/skills/jalali-dates/ می‌گذارید. در claude.ai و برنامهٔ دسکتاپ Claude فایل zip را بدون باز کردن در بخش Capabilities تنظیمات بارگذاری می‌کنید. کلید و حساب دیگری لازم نیست.

بازگشت به بالا

بیشتر بخوانید

نظر یا گزارش مشکل

برای فرستادن لازم نیست وارد شوید.

دربارهٔ چیست؟
۰ از ۲۰۰۰

چه کردید، چه انتظار داشتید و چه شد؟ اگر دربارهٔ ابزار خاصی است، نامش را هم بنویسید.

ایمیل یا آیدی تلگرام، اگر جواب می‌خواهید و وارد حساب نشده‌اید.

پیامتان رسید

ممنون که نوشتید. اگر وارد حساب شده‌اید، جواب در حساب، بخش «پیام‌های من» می‌آید؛ اگر راه تماس گذاشته باشید، از همان راه هم جواب می‌گیرید.