একদিন পঞ্জিকায় ষষ্ঠী, পরের দিন সরাসরি অষ্টমী—সপ্তমী কোথায় গেল? আবার দুই দিনই “একাদশী” লেখা—তিথি কি থেমে ছিল? উত্তর পেতে astronomical তিথি এবং sunrise-to-sunrise পঞ্জিকা-দিনকে আলাদা clock হিসেবে দেখতে হবে।
দুটি আলাদা clock: angular tithi ও local solar day
| Clock | Boundary | Location dependency |
|---|---|---|
| Astronomical তিথি | Sun–Moon elongation প্রতি ১২° | একটি absolute instant; display zone বদলায় |
| Panjika civil day | এক local sunrise থেকে পরের sunrise | Coordinates ও sunrise profile অনুযায়ী বদলায় |
ক্ষয়-বৃদ্ধি এই দুই clock-এর intersection-এর ফল। তিথি interval ধারাবাহিক, কিন্তু দৈনিক calendar row সাধারণত sunrise-এ যে তিথি চলছে তার নাম নেয়। তাই একটি তিথি sunrise-কে এড়িয়ে গেলে daily labels-এ অনুপস্থিত; দুটি sunrise পার হলে daily labels-এ পুনরাবৃত্ত।
তিথির astronomical সংজ্ঞা
একটি adopted zodiac/reference frame-এ চন্দ্রের দ্রাঘিমা থেকে সূর্যের দ্রাঘিমা বাদ দিয়ে ০°–৩৬০°-এ normalize করলে phase angle বা elongation পাওয়া যায়:
TithiIndex(t) = floor(E(t) ÷ ১২°) + ১
ফল: ১ থেকে ৩০
০°–১২° প্রতিপদ, ১২°–২৪° দ্বিতীয়া—এভাবে প্রতিটি ১২° sector একটি তিথি। ১৮০°-এ পূর্ণিমা boundary এবং ৩৬০°/০°-এ অমাবস্যা cycle boundary। Project যদি nirayana longitude ব্যবহার করে, Sun ও Moon দুটির জন্য একই ayanāṃśa/reference convention ব্যবহার করতে হবে; একটি sayana ও অন্যটি nirayana হলে result অর্থহীন হবে।
তিথির start/end wall-clock time নয়; root-solved instants:
Tithi n শেষ: E(t) = n × ১২°
Interval = [StartInstant, EndInstant)
সব তিথি ২৪ ঘণ্টা নয় কেন?
চন্দ্র ও সূর্যের apparent/true angular speed স্থির নয়। বিশেষত চন্দ্রের orbital motion দ্রুত-ধীর হয়; একই ১২° relative angle অতিক্রম করতে কখনও কম, কখনও বেশি সময় লাগে। তাই তিথির start/end প্রতিদিন আলাদা clock time-এ পড়ে।
কিন্তু “Moon দ্রুত হলে ক্ষয়, ধীর হলে বৃদ্ধি”—এটি কেবল প্রবণতার ব্যাখ্যা, classification algorithm নয়। ক্ষয় বা বৃদ্ধি নির্ভর করে variable tithi interval এবং সেই location-এর sunrise series পরস্পর কোথায় ছেদ করেছে তার ওপর।
Traditional Surya Siddhanta এবং modern Drik ephemeris আলাদা longitude model ব্যবহার করতে পারে, ফলে boundary কয়েক মিনিট বা তার বেশি সরতে পারে। Boundary sunrise-এর কাছে হলে model difference-এ classification-ও বদলাতে পারে। এজন্য result-এ calculation method ও engine version লিখুন।
Sunrise-based day assignment
Project-এর পঞ্জিকা day:
Day header-এ StateAt(Sunrise(D)) থেকে prevailing tithi লেখা হয়। Current sunrise অন্তর্ভুক্ত, next sunrise বাদ। Next sunrise current + ২৪ ঘণ্টা নয়; পরের local civil date ও একই solar profile দিয়ে পুনরায় solve করতে হবে।
এক sunrise থেকে পরের sunrise-এর মধ্যে একাধিক তিথি transition থাকলে daily details-এ সব transition দেখানো হবে। কিন্তু date label থাকবে start sunrise-এর prevailing tithi। এই distinction না রাখলে ক্ষয় তিথির event data হারিয়ে যাবে।
তিনটি classification: ০, ১ বা ২ sunrise
| Sunrise hits | নাম | Daily calendar-এ ফল |
|---|---|---|
| ০ | ক্ষয় (Kṣaya) | তিথিটি কোনো day-header পায় না |
| ১ | সাধারণ | একটি day-header পায় |
| ২ | বৃদ্ধি/অধিক (Vṛddhi/Adhika) | পরপর দুই day-header-এ একই তিথি |
Ordinary practical range-এ ০–২ hit-ই প্রত্যাশিত। Engine তবু generic count রাখলে historical/custom sunrise data-তে unexpected result ধরা যায়। Count ২-এর বেশি হলে silent label না দিয়ে diagnostic warning দিন।
Timeline দিয়ে বোঝা যাক
১. সাধারণ তিথি
Tithi শুরু 10:00
Sunrise B 05:59 ← তিথি চলছে
Tithi শেষ 09:00
Sunrise hits = ১ → সাধারণ
২. তিথিক্ষয়
সপ্তমী শুরু 08:10
সপ্তমী শেষ next day 05:20
Sunrise B 05:59 — অষ্টমী চলছে
সপ্তমীর মধ্যে sunrise নেই → সপ্তমী ক্ষয়
সপ্তমী বাস্তবে প্রায় একদিন ছিল; সকাল ৮:১০ থেকে পরদিন ৫:২০ পর্যন্ত তার সমস্ত ধর্মীয় interval বিদ্যমান। শুধু কোনো sunrise-এর সময় সপ্তমী ছিল না। Daily grid ষষ্ঠী থেকে অষ্টমীতে গেলেও details page-এ সপ্তমীর start/end দেখাতে হবে।
৩. তিথিবৃদ্ধি
Sunrise A 06:00 ← তিথি চলছে
Sunrise B 05:59 ← তিথি এখনও চলছে
তিথি শেষ Sunrise B-এর পরে 07:10
Sunrise hits = ২ → তিথিবৃদ্ধি
দুই দিনের cell-এ একই তিথি লিখলেও first occurrence ও second occurrence চিহ্নিত করুন। Festival rule প্রায়ই দুটির মধ্যে একটি বেছে নেয়; দুটিকে একই indiscriminate holiday বানাবেন না।
“২৪ ঘণ্টার কম/বেশি” shortcut কেন ভুল?
Duration classification-এর indirect clue, সংজ্ঞা নয়। কারণ:
- ২৫ ঘণ্টার তিথি sunrise-এর একটু পরে শুরু হলে পরের দিনের একটি sunrise-ই স্পর্শ করতে পারে;
- ২৩–২৪ ঘণ্টার interval কোনো বিশেষ sunrise-spacing বা DST context-এ boundary-এর অবস্থান অনুযায়ী unexpected hit দিতে পারে;
- স্থানীয় sunrise-to-sunrise absolute duration ঠিক ২৪ ঘণ্টা নয়;
- Coordinates বদলালে sunrise instants বদলায়, কিন্তু তিথির absolute duration বদলায় না;
- Exact start/end placement duration-এর চেয়ে decisive।
ভুল: Duration > ২৪h ⇒ Vṛddhi
সঠিক: SunriseHits = count(sunrise ∈ [TithiStart, TithiEnd))
Exact sunrise-এ তিথি বদলালে কোনটি ধরা হবে?
সব intervals half-open রাখুন:
Sunrise == Start → নতুন tithi অন্তর্ভুক্ত
Sunrise == End → পুরোনো tithi বাদ; পরের tithi অন্তর্ভুক্ত
এতে একই sunrise দুই তিথিতে count হয় না। Floating-point longitude equality দিয়ে boundary test না করে root solver-এর timestamp ব্যবহার করুন। Numerical solver tolerance, যেমন fraction of a second, metadata-তে রাখুন; UI rounding ৫:৩০ AM হলেও internal comparison full precision-এ হবে।
Printed Panjika clock time minute-এ round করলে “তিথি শেষ ৫:৩০” এবং “sunrise ৫:৩০” দেখে সিদ্ধান্ত অসম্ভব হতে পারে। Research view-তে seconds/ISO instant এবং applied inclusive/exclusive rule দেখান।
ক্ষয়-বৃদ্ধি location অনুযায়ী বদলাতে পারে
তিথির boundary absolute moment; local sunrise location-specific। ধরা যাক তিথি শেষ হয়েছে 10:05 UTC:
- এক শহরের sunrise 09:58 UTC—তিথি সেই sunrise স্পর্শ করেছে;
- অন্য শহরের sunrise 10:12 UTC—তিথি তার আগে শেষ;
- দুই শহরে sunrise-hit count আলাদা হতে পারে।
Timezone শুধু display label ও local civil-date mapping বদলায়; latitude/longitude actual sunrise instant বদলায়। তাই city নির্বাচন না করে “বিশ্বব্যাপী ক্ষয় তিথি” বলা ঠিক নয়। Kolkata, Dhaka ও New York-এর জন্য আলাদা classify করুন।
Solar profile-ও গুরুত্বপূর্ণ। Sun center/no-refraction sunrise এবং apparent upper-limb sunrise কয়েক মিনিট আলাদা হলে near-boundary classification বদলাতে পারে। Cache key-তে solarProfileId রাখুন।
উৎসব, শ্রাদ্ধ ও একাদশীতে প্রভাব
ক্ষয়/বৃদ্ধি detect করা decision-এর শেষ নয়; উৎসবের নিজস্ব rule-moment আছে:
| Use case | ক্ষয়/বৃদ্ধির পরে কী পরীক্ষা হবে |
|---|---|
| একাদশী | School profile, অরুণোদয়, Mahādvādaśī ও parana |
| মধ্যাহ্ন-ব্যাপিনী উৎসব | Candidate দিনগুলোর Madhyahna overlap |
| প্রদোষব্রত | Configured Pradosha-তে ত্রয়োদশীর overlap |
| জন্মাষ্টমী | Nishitha-তে অষ্টমী ও প্রযোজ্য নক্ষত্র |
| শ্রাদ্ধ | Selected Kutapa/Aparahna moment-এর তিথি |
| পূর্ণিমা/অমাবস্যা নিশিপালন | Declared night interval-এর vyāpti |
তাই festival service শুধু SunriseHits == 0 দেখে আগের বা পরের দিন নিতে পারবে না। সে classification-কে input হিসেবে নেবে, তারপর উৎসব-নির্দিষ্ট rule চালাবে।
Calendar grid ও daily details-এ কীভাবে দেখাবেন?
ক্ষয় তিথি
যে Panjika day window-এর মধ্যে তিথিটি পুরোপুরি ঘটেছে, সেই cell-এ auxiliary note:
ক্ষয়: সপ্তমী, সকাল ৮:১০ → পরদিন ৫:২০
পরে অষ্টমী
Header tithi label ষষ্ঠী হলেও event list-এ সপ্তমী transition থাকবে। Search page-এ “সপ্তমী” query করলে সেই day result পাওয়া উচিত।
বৃদ্ধি তিথি
দ্বিতীয় দিন: একাদশী · বৃদ্ধি (দ্বিতীয় সূর্যোদয়)
তিথি শেষ: দ্বিতীয় দিন ৭:১০ AM
Festival icon কেবল rule-selected cell-এ বসান। দুই cell-এ একই fasting icon বসালে user বিভ্রান্ত হবে। অন্য cell-এ “তিথিবৃদ্ধি” badge থাকতে পারে।
নির্ভরযোগ্য algorithm
- নির্বাচিত calculation engine দিয়ে তিথির start/end instants solve করুন;
- Start-এর আগের local date থেকে End-এর পরের local date পর্যন্ত candidate date range নিন;
- প্রত্যেক candidate date-এর actual project sunrise solve করুন;
- যে sunrise
start <= sunrise < end, hit list-এ যোগ করুন; - Hit count ০ হলে Kṣaya, ১ হলে Normal, ২ হলে Vṛddhi;
- Hit count অপ্রত্যাশিত হলে diagnostic result দিন;
- প্রতিটি hit-এর local date, ISO instant ও solar profile সংরক্ষণ করুন;
- Festival engine-কে raw span + classification দিন;
- Renderer auxiliary transition ও badges তৈরি করবে।
Candidate range local date দিয়ে বানালেও classification UTC instants compare করবে। Timezone conversion শুধু date enumeration এবং display-এর জন্য।
C# 5/.NET Framework-compatible implementation
public enum TithiSunriseClass
{
Kshaya,
Normal,
Vriddhi,
Unexpected
}
public sealed class TithiSpan
{
public int GlobalNumber { get; set; }
public int NumberInPaksha { get; set; }
public string Paksha { get; set; }
public string Name { get; set; }
public DateTimeOffset Start { get; set; }
public DateTimeOffset End { get; set; }
public bool Contains(DateTimeOffset instant)
{
DateTime utc = instant.UtcDateTime;
return utc >= Start.UtcDateTime && utc < End.UtcDateTime;
}
}
public sealed class TithiSunriseAssignment
{
public TithiSpan Tithi { get; set; }
public TithiSunriseClass Classification { get; set; }
public IList<DateTimeOffset> SunriseHits { get; set; }
public string SolarProfileId { get; set; }
public string TimeZoneId { get; set; }
}
Classifier:
public static TithiSunriseAssignment Classify(
TithiSpan tithi,
IEnumerable<DateTimeOffset> candidateSunrises,
string solarProfileId,
string timeZoneId)
{
List<DateTimeOffset> hits = candidateSunrises
.Where(x => tithi.Contains(x))
.OrderBy(x => x.UtcDateTime)
.ToList();
TithiSunriseClass kind;
if (hits.Count == 0)
kind = TithiSunriseClass.Kshaya;
else if (hits.Count == 1)
kind = TithiSunriseClass.Normal;
else if (hits.Count == 2)
kind = TithiSunriseClass.Vriddhi;
else
kind = TithiSunriseClass.Unexpected;
return new TithiSunriseAssignment
{
Tithi = tithi,
Classification = kind,
SunriseHits = hits,
SolarProfileId = solarProfileId,
TimeZoneId = timeZoneId
};
}
Candidate sunrise enumeration:
public static IList<DateTimeOffset> GetCandidateSunrises(
TithiSpan tithi,
TimeZoneInfo zone,
Func<DateTime, DateTimeOffset> solveSunrise)
{
DateTime localStart = TimeZoneInfo.ConvertTime(
tithi.Start, zone).Date.AddDays(-1);
DateTime localEnd = TimeZoneInfo.ConvertTime(
tithi.End, zone).Date.AddDays(1);
List<DateTimeOffset> values = new List<DateTimeOffset>();
for (DateTime date = localStart; date <= localEnd; date = date.AddDays(1))
values.Add(solveSunrise(date));
return values;
}
solveSunrise পরের দিনকে আগের sunrise + ২৪ ঘণ্টা বানাবে না। No-rise status হলে function-এর return type nullable/result object করুন এবং documented fallback ছাড়া fabricated sunrise দেবেন না।
Tithi index
public static int TithiIndex(double sunLongitude, double moonLongitude)
{
double elongation = Normalize360(moonLongitude - sunLongitude);
int index = (int)Math.Floor(elongation / 12.0) + 1;
return index > 30 ? 30 : index;
}
static double Normalize360(double value)
{
value %= 360.0;
return value < 0.0 ? value + 360.0 : value;
}
Boundary root solve-এ ৩৬০°→০° wrap unwrap না করলে Amāvasyā/Pratipad crossing-এ discontinuity হবে। Root function continuous unwrapped elongation ব্যবহার করবে; উপরের function কেবল state lookup-এর জন্য।
Historical Julian/Gregorian output-এ সতর্কতা
১৫৮২ বা ১৭৫২-এর civil-calendar reform sunrise-এর physical sequence বাদ দেয়নি; date label লাফ দিয়েছে। Historical dual-date export-এ:
- Internal timeline Julian Day/absolute instant-এ continuous রাখুন;
- প্রত্যেক physical sunrise একবারই enumerate করুন;
- Proleptic Gregorian label এবং historical Julian label আলাদাভাবে render করুন;
- .NET
DateTime-এর proleptic Gregorian behavior-কে historical cutover ভেবে নেবেন না; - ৫ অক্টোবর→১৫ অক্টোবর label jump-এর মধ্যে sunrise delete করবেন না;
- Kṣaya/vṛddhi count date-string নয়, instant sequence থেকে করুন।
অর্থাৎ একটি তিথির classification calendar-label policy থেকে নয়, physical local sunrise hits থেকে আসে। পরে একই hit-কে Julian ও Gregorian দুইভাবে লেখা যায়।
API ও XML output
{
"tithi": {
"globalNumber": 7,
"paksha": "krishna",
"nameBn": "সপ্তমী",
"start": "...ISO instant...",
"end": "...ISO instant..."
},
"sunriseAssignment": {
"classification": "kshaya",
"hitCount": 0,
"hits": [],
"solarProfileId": "sun-center-no-refraction-v1",
"timeZoneId": "Bangladesh Standard Time"
},
"display": {
"showAsAuxiliaryTransition": true,
"badgeBn": "ক্ষয় তিথি"
}
}
Vṛddhi example-এ hits array-তে দুইটি sunrise object থাকবে, প্রতিটিতে local date ও offset। XML:
<tithi-assignment classification="vriddhi"
hit-count="2"
solar-profile="sun-center-no-refraction-v1"
timezone="India Standard Time">
<tithi number="11" paksha="shukla" name-bn="একাদশী"
start="..." end="..." />
<sunrise-hit local-date="..." instant="..." occurrence="first" />
<sunrise-hit local-date="..." instant="..." occurrence="second" />
</tithi-assignment>
API consumer যেন classification দেখে event নিজে সরিয়ে না দেয়। Festival result আলাদা endpoint/field থেকে নেবে, কারণ সব festival একই ক্ষয়-বৃদ্ধি tie-break অনুসরণ করে না।
Validation checklist
- Sun ও Moon longitude একই reference system-এ;
- Elongation ০°–৩৬০° normalize করা;
- Tithi index ১–৩০;
- প্রতিটি boundary ১২° multiple-এ root-solved;
- Amāvasyā wrap continuousভাবে handled;
- Tithi interval half-open
[start, end); - Exact start sunrise নতুন তিথিতে count;
- Exact end sunrise পুরোনো তিথিতে count নয়;
- UI rounding internal comparison বদলায় না;
- Actual sunrise প্রতিটি local date থেকে solve করা;
- Current sunrise + ২৪h shortcut নেই;
- Candidate range start-এর একদিন আগে থেকে;
- Candidate range end-এর একদিন পরে পর্যন্ত;
- Zero hit = Kṣaya;
- One hit = Normal;
- Two hits = Vṛddhi;
- More than two hits diagnostic warning;
- Duration </> ২৪h দিয়ে classification নয়;
- Kṣaya transition daily details-এ হারায় না;
- Vṛddhi দুই cell-এ first/second marked;
- Festival icon শুধু selected day-এ;
- Festival engine raw span পায়;
- Kolkata, Dhaka ও New York fixtures আছে;
- Near-sunrise boundary বিভিন্ন city-তে tested;
- Sun center/no-refraction profile ID result-এ;
- Alternative apparent-sunrise comparison test আছে;
- DST spring-forward sunrise series tested;
- DST fall-back sunrise series tested;
- No-rise/no-set condition explicit;
- Surya Siddhanta ও Drik results আলাদা method ID পায়;
- Historical Julian/Gregorian labels hit count বদলায় না;
- 1582 cutover-এ physical sunrise sequence continuous;
- API, HTML, print ও XML একই classification ব্যবহার করে;
- Known printed Panjika kṣaya/vṛddhi cases regression-tested।
উপসংহার
তিথি হলো Sun–Moon elongation-এর ধারাবাহিক ১২° interval; পঞ্জিকার দিন হলো local sunrise-to-sunrise interval। কোনো তিথি zero sunrise স্পর্শ করলে ক্ষয়, one sunrise স্পর্শ করলে সাধারণ এবং two consecutive sunrise স্পর্শ করলে বৃদ্ধি। Astronomical তিথিটি সবক্ষেত্রেই বিদ্যমান—শুধু day-header assignment বদলায়।
সঠিক implementation duration threshold ব্যবহার করবে না; actual solved sunrise instants count করবে। Half-open boundary, location, solar profile, DST এবং historical date-label policy স্পষ্ট রাখলে classification reproducible হয়। Daily page কেবল sunrise label নয়, sunrise-to-next-sunrise window-এর সব তিথি transition দেখালে ক্ষয় তিথিও user-এর চোখের আড়াল হয় না।
Festival calculation এই classification-এর ওপর দাঁড়ালেও তার নিজস্ব rule-moment—অরুণোদয়, মধ্যাহ্ন, প্রদোষ বা নিশীথ—আলাদাভাবে পরীক্ষা করবে। এভাবে astronomy, calendar labeling এবং religious selection তিনটি স্তর স্বচ্ছ থাকে।
তথ্যসূত্র ও আরও পাঠ
- Robert Sewell ও Śaṅkara Bālkṛṣṇa Dīkṣita—The Indian Calendar; sunrise না-স্পর্শ করা tithi-র kṣaya এবং দুই sunrise স্পর্শ করা tithi-র vṛddhi/adhika সংজ্ঞা ও উদাহরণ।
- Positional Astronomy Centre/IMD—Rashtriya Panchanga; sunrise, tithi, nakṣatra এবং modern scientific Panchanga data.
- Positional Astronomy Centre—Rashtriya Panchang publication; computer-calculated tithi, nirayana longitude ও calendric data-এর official description.
- B. V. Subbarayappa—Development of Pañcāṅga from Vedic Times up to the Present; elongation-based tithi-র ঐতিহাসিক ও astronomical আলোচনা।
- What is Tithi Vridhi and Tithi Kshaya?; sunrise-based examples ও explanatory overview.
- সূর্য সিদ্ধান্ত পঞ্জিকা project specification; Sun center/no-refraction sunrise, sunrise-to-sunrise day, dual historical dates এবং absolute-instant interval semantics.
মন্তব্য, আলোচনা ও প্রশ্ন