# راهنمای جامع تقویم دابودبی و محاسبه اوقات شرعی این مستند برای آشنایی همکاران فنی (مانند تیم فلاتر) و غیر فنی با سازوکار تقویم پروژه «امام جواد» و همچنین منطق محاسبه اوقات شرعی تهیه شده است. مطالب شامل معرفی ساختار دیتای تقویم، توضیح کامل API تنظیمات قمری (`adjustemnts`)، نحوه بهره‌گیری در سناریوهای واقعی و نمونه‌کدهای کاربردی است. ## نمای کلی سیستم تقویم - **ماژول:** `apps/dobodbi_calendar` - **مدل اصلی:** `CalendarOccasions` - فیلدهای کلیدی: `title` عنوان مناسبت، `occasion_type` نوع تاریخ (میلادی یا قمری)، `dates` لیست تاریخ‌ها، `event_type` دسته‌بندی (ملی، مذهبی و ...)، `is_yearly` تکرار سالانه، تایم‌استمپ‌های `created_at` و `updated_at`. - **سریالایزر:** `CalendarSerializer` که تاریخ‌ها را به ساختار قابل مصرف برای کلاینت تبدیل می‌کند و نوع مناسبت را در فیلد `type` قرار می‌دهد. - **نمای لیستی:** `CalendarList` با مسیر `/api/calendar/occasions/` که بدون صفحه‌بندی پاسخ می‌دهد و آخرین زمان به‌روزرسانی را برمی‌گرداند تا همگام‌سازی کلاینت راحت‌تر انجام شود. - **تنظیمات پویا:** با استفاده از `dynamic_preferences` و کلید `calendar__Adjustment` ذخیره می‌شود. مقدار خام این تنظیمات در پایگاه داده نگهداری شده و از طریق پنل ادمین قابل ویرایش است. ## API تنظیمات قمری (Adjustemnts) - **مسیر:** `GET /api/calendar/adjustemnts/` - **منبع داده:** ترجمهٔ مقدار ذخیره شده در `dynamic_preferences` برای کلید `calendar__Adjustment`. - **کارکرد کلی:** ارائهٔ سناریوهای از پیش تعریف‌شده برای تطبیق تقویم قمری با واقعیت رؤیت هلال یا تقویم رسمی کشورها. ### ساختار پاسخ نمونه ```json [ { "adjust": 0, "current": 0, "map": { "1444": [354, 30, 30, 29, 30, 29, 29, 30, 29, 30, 29, 30, 29], "1445": [354, 30, 30, 30, 29, 30, 29, 29, 30, 29, 30, 29, 29], "1446": [355, 30, 30, 30, 29, 30, 30, 29, 30, 29, 29, 29, 30], "1447": [355, 29, 30, 30, 29, 30, 30, 29, 30, 29, 29, 30, 29] } }, { "adjust": -1, "current": 0, "map": { "...": "..." } }, { "adjust": 1, "current": 0, "map": { "...": "..." } } ] ``` ### معنی فیلدها | فیلد | توضیح | | --- | --- | | `adjust` | مقدار جبرانی: `-1` یک روز کم می‌کند، `0` حالت مرجع، `+1` یک روز اضافه می‌کند. | | `current` | وضعیت فعال که می‌تواند توسط کلاینت یا پنل ادمین برای علامت‌گذاری حالت انتخابی استفاده شود (در نمونه فعلی صفر است). | | `map` | دیکشنری سال قمری ↦ آرایه شامل ۱ + ۱۲ مقدار: عدد اول تعداد روزهای سال (۳۵۴ یا ۳۵۵)، ۱۲ عدد بعدی طول هر ماه قمری. | ### انتخاب حالت مناسب با مثال | حالت | زمان استفاده پیشنهادی | مثال عملی | | --- | --- | --- | | `adjust = 0` | حالت مرجع و عمومی | نمایش تقویم در سامانه آموزشی بدون نیاز به تصحیح محلی. | | `adjust = -1` | مناطقی که شروع ماه قمری را یک روز زودتر اعلام می‌کنند یا پس از رصد مشخص می‌شود هلال یک روز زودتر دیده شده | اگر در ایران رمضان ۲۹ روز اعلام شود اما محاسبه‌گر ۳۰ روزه باشد، با `-1` آخرین روز حذف می‌شود. | | `adjust = +1` | مناطقی که آغاز ماه را دیرتر اعلام می‌کنند یا برای همگام‌سازی با تقویم رسمی نیاز به افزودن روز دارند | زمانی که کشوری عید قربان را یک روز دیرتر می‌گیرد؛ با `+1` روز اضافه می‌شود. | ### سناریوی قدم‌به‌قدم: تعیین تاریخ عید فطر 1. دریافت داده: `configs = GET /api/calendar/adjustemnts/`. 2. انتخاب حالت: `defaultConfig = configs.find(c => c.adjust === 0)`. 3. استخراج سال هدف: `ramadanProfile = defaultConfig.map['1445']`. 4. محاسبه طول رمضان: مقدار شاخص ۹ (ماه نهم). اگر ۲۹ بود → عید فطر روز ۲۹ رمضان، اگر ۳۰ بود → روز ۳۰. 5. در صورت اختلاف رسمی، کافی است پیکربندی دیگری را انتخاب کنید (مثلاً `adjust = +1`) تا روز اضافه لحاظ شود. ### سناریوی همگام‌سازی تقویم در اپلیکیشن 1. در اولین اجرا، هرسه پیکربندی را ذخیره کنید. 2. متناسب با موقعیت کاربر (یا انتخاب کاربر)، `adjust` مناسب را فعال نگه دارید. مقدار انتخابی را می‌توانید در کلاینت یا سرور ذخیره کنید. 3. برای تبدیل «روز سال قمری» به روز میلادی: - اختلاف روز تا ابتدای سال قمری را با جمع ۱۲ ماه محاسبه کنید. - با استفاده از تاریخ میلادی مرجع (مثلاً شروع سال قمری در تقویم رسمی)، اختلاف را اعمال کنید تا تاریخ میلادی به‌دست آید. 4. اگر داده‌های `CalendarOccasions` نوع `lunar` داشتند، از نقشه انتخاب‌شده برای محاسبه تاریخ معادل استفاده کنید و مقدار نهایی را به رابط کاربری نمایش دهید. ## راهنمای محاسبه اوقات شرعی مبنای پروژه فایل `prayer_times_calculation_guide.html` است که مراحل استفاده از الگوریتم **PrayTimes** را تشریح کرده است. خلاصه فرآیند: 1. **ورودی‌ها:** تاریخ میلادی، مختصات (عرض/طول جغرافیایی، ارتفاع)، روش محاسبه (مثلاً Tehran، MWL)، روش جبران عرض‌های بالا و فرمت خروجی (۱۲ یا ۲۴ ساعته). 2. **محاسبه تاریخ ژولیَن (Julian Date):** `jdate = julian(year, month, day) - longitude / (15 × 24)`. 3. **موقعیت خورشید:** با تابعی مشابه `sunPosition(jdate)` زاویه میل خورشید (`declination`) و معادله زمان (`equation of time`) به‌دست می‌آید. 4. **محاسبه اوقات پایه:** - فجر، طلوع، مغرب، عشا: با `sunAngleTime` و زاویه‌های مخصوص هر روش محاسبه می‌شوند. - ظهر: با `midDay`. - عصر: با `asrTime` و فاکتور مربوط به مذهب (1 برای شافعی/جعفری، 2 برای حنفی). 5. **تنظیم اختلاف طول جغرافیایی و منطقه زمانی:** - `offset = timezoneHours - longitude / 15` - جمع این مقدار با همه زمان‌های محاسبه‌شده. 6. **جبران عرض‌های جغرافیایی بالا:** در صورت انتخاب روش‌هایی مثل `NightMiddle` یا `AngleBased`، طول شب محاسبه و زمان‌های فجر/عشا تعدیل می‌شوند. 7. **تبدیل خروجی اعشاری به ساعت:** - ساعت = بخش صحیح عدد. - دقیقه = `(عدد - ساعت) × 60`. - در صورت نیاز به فرمت ۱۲ ساعته، AM/PM مطابق قواعد اضافه می‌شود. ### چه ابزاری برای محاسبه استفاده کنیم؟ - **بک‌اند (Python/Django):** در صورت نیاز به محاسبه سمت سرور می‌توانید از پیاده‌سازی استاندارد PrayTimes یا کتابخانه‌های معتبری مانند `praytimes` (نسخه پایتونی) بهره ببرید. نتیجه را می‌توان کش کرد و فقط در صورت تغییر مختصات یا تاریخ دوباره محاسبه نمود. - **کلاینت فلاتر:** استفاده از کلاس سفارشی پروژه یا پکیج‌های آماده نظیر [`adhan_dart`](https://pub.dev/packages/adhan_dart) برای محاسبات سریع و دقیق توصیه می‌شود. - **وب/جاوااسکریپت:** کتابخانه‌هایی مثل [`adhan`](https://github.com/batoulapps/adhan-js) یا نسخهٔ رسمی PrayTimes.js به‌خوبی نیاز را پوشش می‌دهند. ## نمونه کد فلاتر ### دریافت و استفاده از تنظیمات قمری ```dart import 'dart:convert'; import 'package:http/http.dart' as http; class LunarAdjustConfig { final int adjust; final Map> map; LunarAdjustConfig({required this.adjust, required this.map}); factory LunarAdjustConfig.fromJson(Map json) { return LunarAdjustConfig( adjust: json['adjust'] as int, map: (json['map'] as Map).map( (key, value) => MapEntry(key, List.from(value)), ), ); } } Future> fetchAdjustments() async { final res = await http.get(Uri.parse('https://example.com/api/calendar/adjustemnts/')); final data = jsonDecode(res.body) as List; return data.map((item) => LunarAdjustConfig.fromJson(item)).toList(); } DateTime applyLunarOffset({ required DateTime hijriYearStart, required LunarAdjustConfig config, required String hijriYear, required int hijriMonthIndex, required int hijriDay, }) { final months = config.map[hijriYear] ?? []; if (months.length != 13) { throw ArgumentError('ساختار سال قمری نامعتبر است'); } final daysFromYearStart = months .sublist(1, hijriMonthIndex) .fold(0, (acc, value) => acc + value); final totalOffset = daysFromYearStart + (hijriDay - 1) + config.adjust; return hijriYearStart.add(Duration(days: totalOffset)); } ``` ### محاسبه اوقات شرعی با `adhan_dart` ```dart import 'package:adhan_dart/adhan_dart.dart'; PrayerTimes calculatePrayerTimes({ required DateTime date, required Coordinates coordinates, }) { final params = CalculationMethod.tehran(); params.madhab = Madhab.shafi; final times = PrayerTimes(coordinates, date, params); return times; } void main() async { final configs = await fetchAdjustments(); final defaultConfig = configs.firstWhere((c) => c.adjust == 0, orElse: () => configs.first); final hijriStart = DateTime(2023, 7, 19); // تاریخ میلادی شروع سال 1445 به تقویم رسمی final eidDate = applyLunarOffset( hijriYearStart: hijriStart, config: defaultConfig, hijriYear: '1445', hijriMonthIndex: 10, // ماه شوال (پس از رمضان) hijriDay: 1, ); final times = calculatePrayerTimes( date: eidDate, coordinates: Coordinates(35.6892, 51.3890), ); print('اذان صبح: ${times.fajrTime}'); print('اذان مغرب: ${times.maghribTime}'); } ``` ## نمونه کد جاوااسکریپت (وب یا Node.js) ### دریافت تنظیمات و محاسبه تاریخ قمری ```js import fetch from 'node-fetch'; async function fetchAdjustments() { const res = await fetch('https://example.com/api/calendar/adjustemnts/'); return res.json(); } function applyLunarOffset({ hijriYearStart, config, hijriYear, hijriMonthIndex, hijriDay }) { const months = config.map[hijriYear]; if (!months || months.length !== 13) { throw new Error('ساختار سال قمری نامعتبر است'); } const daysBeforeMonth = months.slice(1, hijriMonthIndex).reduce((sum, value) => sum + value, 0); const totalOffset = daysBeforeMonth + (hijriDay - 1) + config.adjust; const result = new Date(hijriYearStart); result.setDate(result.getDate() + totalOffset); return result; } // مثال استفاده const configs = await fetchAdjustments(); const preferred = configs.find((c) => c.adjust === 1) ?? configs[0]; const eidAlAdha = applyLunarOffset({ hijriYearStart: new Date('2024-06-08'), // شروع سال 1446 در تقویم رسمی منطقه هدف config: preferred, hijriYear: '1446', hijriMonthIndex: 12, // ذی‌الحجه hijriDay: 10, }); console.log('تاریخ میلادی عید قربان:', eidAlAdha.toISOString().slice(0, 10)); ``` ### محاسبه اوقات شرعی با کتابخانه `adhan` ```js import { PrayerTimes, Coordinates, CalculationMethod } from 'adhan'; function calculatePrayerTimes(date, lat, lng) { const params = CalculationMethod.Tehran(); const coordinates = new Coordinates(lat, lng); const times = new PrayerTimes(coordinates, date, params); return { fajr: times.fajr.toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' }), sunrise: times.sunrise.toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' }), dhuhr: times.dhuhr.toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' }), asr: times.asr.toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' }), maghrib: times.maghrib.toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' }), isha: times.isha.toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' }), }; } const todayTimes = calculatePrayerTimes(new Date(), 35.6892, 51.3890); console.log(todayTimes); ``` ## جمع‌بندی - تقویم پروژه از ترکیب مدل `CalendarOccasions` و تنظیمات پویا `calendar__Adjustment` تشکیل شده و API ویژهٔ `adjustemnts` سه سناریوی جبران قمری را ارائه می‌کند. - برای محاسبهٔ اوقات شرعی می‌توان از الگوریتم مبتنی بر `PrayTimes` استفاده کرد که با ورودی‌های تاریخ، مختصات و روش محاسبه، زمان‌های اذان را بازمی‌گرداند. - نمونه کدهای فلاتر و جاوااسکریپت نشان می‌دهند چگونه می‌توان در کلاینت هم تاریخ‌های قمری را به میلادی تبدیل کرد و هم اوقات شرعی را به‌صورت محلی محاسبه نمود.