View Full Version : آموزش: ساخت یک کلاس و توابع مورد نیاز برای کار تاریخ ایرانی Persian Date Class and Functions
mazoolagh
شنبه 26 مهر 1404, 13:44 عصر
دلیل ساخت این آموزش در تاپیک زیر اومده (پست 8):
سوال: فیلد محاسباتی در After Update (https://barnamenevis.org/showthread.php?593312-%D9%81%DB%8C%D9%84%D8%AF-%D9%85%D8%AD%D8%A7%D8%B3%D8%A8%D8%A7%D8%AA%DB%8C-%D8%AF%D8%B1-After-Update)
و بخشی از توضیحات رو در پست شماره 10 همون تاپیک میتونین بخونین.
کدهای این تاپیک ساده شده یک مجموعه نسبتا سنگین و پیچیده کدهای یک کلاس و یک helper بوده
که:
بخشهای زمان و محاسبات روی اونها کلا کنار گذاشته شده
روش کبیسه گیری و شمارش روزهای سال کلا از محاسبه به کار با دیتا پیش محاسبه شده تغییر داده شده (برای سادگی و همچنین عدم نیاز در برنامه های متعارف و غیرعلمی)
محدوده مجاز تاریخ از 1 فروردین 1300 تا 20 اسفند 1420 تعریف شده (که برای کاربردهای عادی کاملا پاسخگو هست)،
با این وجود دست کاربر برای تغییر این بازه از 1 فروردین 1 تا 30 اسفند 9377 باز هست (فقط با ویرایش چند ثابت و یک array بدون تغییر در کدها)
چیزهایی که باید درنظر داشته باشین:
تاریخ، به طور کلی و بودن توجه به اینکه ایرانی خورشیدی یا گرگورین یا ماهشمار چینی و ... باشه همیشه یک شماره است (چون همه عملیات روی تاریخ باید با شماره ها انجام بشه) اینجا هم همه در توابع (بدون استثناء) چه خود تاریخ و چه اجزای اون (روز ماه سال) به شکل long تعریف شدن.
فرمت هایی مانند YYYY/MM/DD فقط برای نمایش هست (گزارش و فرم و ...) و نه محاسبه! پس اگر دیتا شما string و فرمت بندی شده است، باید اون رو به شکل صحیح به توابع پاس کنین (long YYYYMMDD).
فرصت کنم بعد از تمام شدن تاپیک در مورد اینکه چجوری تابعی بنویسیم که با همه مقادیر پاس شده (string/formatted string/long number) درست کار کنه و خروجی رو هم به همون شکل دریافتی بده.
مجموعه توابع مستقل و کلا همه کد مورد نیاز در یک module به اسم PersianDateHelper آمده، پس اگر تاریخ ایرانی رو بسادگی با یک مقدار long نشون میدین فقط افزودن همین ماجول به برنامه تون کافی هست.
یک calss module هم هست به اسم PersianDate که تاریخ ایرانی رو بعنوان object تعریف میکنه و متدها و پراپرتی های اون وابسته به PersianDateHelper هست.
در پست های بعدی فهرست توابع PersianDateHelper و همچنین متدها و پراپرتی های PersianDate رو میارم.
mazoolagh
شنبه 26 مهر 1404, 13:44 عصر
declaration / initialize
Option Compare Database
Option Explicit
Public Const MinYear As Long = 1300
Public Const MaxYear As Long = 1420
Public Const MinPrDate As Long = 13000101
Public Const MaxPrDate As Long = 14201230
Public Const MinGrDate As Date = #3/21/1921# ' Persian 1300/01/01
Public Const MaxGrDate As Date = #3/20/2042# ' Persian 1420/12/30
Public LeapYears
Public MonthNames
Public WeekDayNames
Public Type DateSpan
Year As Long
Month As Long
Day As Long
TotalDays As Long
Sign As Long
End Type
Public Enum WeekRule
FirstDay = 1
FirstSaturDay = 2
End Enum
Public Sub InitializeData()
If IsInitialized Then Exit Sub
Calendar = vbCalGreg
WeekDayNames = Array("", "شنبه", "یکشنبه", "دوشنبه", "سهشنبه", "چهارشنبه", "پنجشنبه", "آدینه")
MonthNames = Array("", "فروردین", "اردیبهشت", "خرداد", "تیر", "امرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند")
Dim ly: ly = Array(0, 4, 9, 13, 17, 21, 25, 29, 33, 37, 42, 46, 50, 54, 58, 62, 66, 70, 75, 79, 83, 87, 91, 95, 99, 103, 108, 112, 116, 120)
ReDim LeapYears(MinYear To MaxYear)
Dim i As Integer
For i = MinYear To MaxYear
LeapYears(i) = False
Next i
For i = LBound(ly) To UBound(ly)
LeapYears(ly(i) + MinYear) = True
Next i
End Sub
Public Function IsInitialized() As Boolean
IsInitialized = (VarType(MonthNames) > (vbEmpty + vbNull))
End Function
mazoolagh
شنبه 26 مهر 1404, 13:45 عصر
validation
Public Function IsValidYear(YYYY As Long) As Boolean
IsValidYear = (YYYY >= MinYear And YYYY <= MaxYear)
End Function
Public Function IsValidMonth(MM As Long) As Boolean
IsValidMonth = (MM > 0 And MM < 13)
End Function
Public Function IsValidDateParts(YYYY As Long, MM As Long, DD As Long) As Boolean
IsValidDateParts = ( _
IsValidYear(YYYY) And _
IsValidMonth(MM) And _
DD > 0 And _
DD <= GetDaysInMonth(YYYY, MM))
End Function
Public Function IsValidGregorianDate(gDate As Date) As Boolean
IsValidGregorianDate = (gDate >= MinGrDate Or gDate <= MaxGrDate)
End Function
Public Sub ValidateYear(YYYY As Long)
If Not IsValidYear(YYYY) Then
Err.Raise _
Number:=1001, _
Description:="Invalid value for Year: " & YYYY & vbCrLf & _
"Valid range from " & MinYear & " to " & MaxYear
End If
End Sub
Public Sub ValidateMonth(MM As Long)
If Not IsValidMonth(MM) Then
Err.Raise _
Number:=1002, _
Description:="Invalid value for Month: " & MM & vbCrLf & _
"Valid range of 1 to 12"
End If
End Sub
Public Sub ValidateDateValue(pdate As Long)
Dim pYear As Long, pMonth As Long, pDay As Long
pDay = GetDay(pdate)
pMonth = GetMonth(pdate)
pYear = GetYear(pdate)
ValidateDateParts pYear, pMonth, pDay
End Sub
Public Sub ValidateDateParts(YYYY As Long, MM As Long, DD As Long)
ValidateYear YYYY
ValidateMonth MM
If DD < 1 Or DD > GetDaysInMonth(YYYY, MM) Then
Err.Raise _
Number:=1003, _
Description:="Invalid value for DayOfMonth: " & MM & "/" & DD
End If
End Sub
Public Sub ValidateGregorianDate(gDate As Date)
If Not IsValidGregorianDate(gDate) Then
Err.Raise 1004, , "Invalid Gregorian date : " & gDate _
& vbCrLf & "Min= " & MinGrDate _
& vbCrLf & "Max= " & MaxGrDate
End If
End Sub
mazoolagh
شنبه 26 مهر 1404, 13:46 عصر
Persian date creation
Public Function FromDateValue(pDate As Long) As Long
Dim pYear As Long, pMonth As Long, pDay As Long
pYear = GetYear(pDate)
pMonth = GetMonth(pDate)
pDay = GetDay(pDate)
FromDateValue = FromDateParts(pYear, pMonth, pDay)
End Function
Public Function FromDateParts(Year As Long, Month As Long, Day As Long) As Long
ValidateDateParts Year, Month, Day
FromDateParts = Year * 10000 + Month * 100 + Day
End Function
Public Function FromGregorian(gDate As Date) As Long
FromGregorian = Gr2Pr(gDate)
End Function
mazoolagh
شنبه 26 مهر 1404, 13:47 عصر
date conversion
Public Function Gr2Pr(gDate As Date) As Long ' Gregorian to Persian
ValidateGregorianDate gDate
Dim TotalDays As Long, pYear As Long, pMonth As Long, pDay As Long
TotalDays = DateDiff("d", MinGrDate, gDate)
pYear = MinYear
Do Until TotalDays < GetDaysInYear(pYear)
TotalDays = TotalDays - GetDaysInYear(pYear)
pYear = pYear + 1
Loop
pMonth = 1
Do Until TotalDays < GetDaysInMonth(pYear, pMonth)
TotalDays = TotalDays - GetDaysInMonth(pYear, pMonth)
pMonth = pMonth + 1
Loop
pDay = TotalDays + 1
Gr2Pr = FromDateParts(pYear, pMonth, pDay)
End Function
Public Function Pr2Gr(pDate As Long) As Date ' Persian to Gregorian
ValidateDateValue pDate
Dim TotalDays As Long, pYear As Long
pYear = GetYear(pDate)
TotalDays = GetDayOfYear(pDate)
Dim i As Long
For i = MinYear To pYear - 1
TotalDays = TotalDays + GetDaysInYear(i)
Next i
Dim gDate As Date
gDate = VBA.DateAdd("d", TotalDays - 1, MinGrDate)
Pr2Gr = DateSerial(Year(gDate), Month(gDate), Day(gDate))
End Function
mazoolagh
دوشنبه 28 مهر 1404, 10:19 صبح
Persian date parts / properties
Public Function GetYear(pDate As Long) As Long
GetYear = (pDate \ 10000)
End Function
Public Function GetMonth(pDate As Long) As Long
GetMonth = (pDate \ 100) Mod 100
End Function
Public Function GetDay(pDate As Long) As Long
GetDay = pDate Mod 100
End Function
Public Function GetDaysInYear(YYYY As Long) As Long
ValidateYear YYYY
If IsLeapYear(YYYY) Then
GetDaysInYear = 366
Else
GetDaysInYear = 365
End If
End Function
Public Function GetDaysInMonth(YYYY As Long, MM As Long) As Long
ValidateYear YYYY
ValidateMonth MM
Select Case MM
Case 1 To 6
GetDaysInMonth = 31
Exit Function
Case 7 To 11
GetDaysInMonth = 30
Exit Function
Case 12
If IsLeapYear(YYYY) Then
GetDaysInMonth = 30
Else
GetDaysInMonth = 29
End If
End Select
End Function
Public Function GetDayOfYear(pDate As Long)
Dim pYear As Long, pMonth As Long, pDay As Long
pMonth = GetMonth(pDate)
pDay = GetDay(pDate)
If pMonth < 7 Then
GetDayOfYear = (pMonth - 1) * 31 + pDay
Else
GetDayOfYear = 186 + (pMonth - 7) * 30 + pDay
End If
End Function
Public Function GetMonthName(pDate As Long) As String
InitializeData
Dim pMonth As Long
pMonth = GetMonth(pDate)
GetMonthName = MonthNames(pMonth)
End Function
Public Function GetWeekDay(pDate As Long) As Long
Dim gDate As Date
gDate = Pr2Gr(pDate)
GetWeekDay = VBA.WeekDay(gDate, vbSaturday)
End Function
Public Function GetWeekDayName(pDate As Long) As String
InitializeData
GetWeekDayName = WeekDayNames(GetWeekDay(pDate))
End Function
Public Function GetFirstShanbehOfYear(YYYY As Long) As Long
ValidateYear YYYY
GetFirstShanbehOfYear = GetFirstShanbehOfMonth(YYYY, 1)
End Function
Public Function GetFirstShanbehOfMonth(YYYY As Long, MM As Long) As Long
Dim pDate As Long
pDate = FromDateParts(YYYY, MM, 1)
Do Until GetWeekDay(pDate) = 1
pDate = pDate + 1
Loop
GetFirstShanbehOfMonth = pDate
End Function
Public Function GetWeekOfYear(pDate As Long, Optional Rule As WeekRule = WeekRule.FirstDay) As Long
Dim days As Long
days = GetDayOfYear(pDate)
If Rule = WeekRule.FirstDay Then
GetWeekOfYear = -Int(-days / 7)
Else
Dim k As Long
k = GetFirstShanbehOfYear(GetYear(pDate)) Mod 100
If days < k Then
GetWeekOfYear = 0
ElseIf days = k Then
GetWeekOfYear = 1
Else
days = days - k + 1
GetWeekOfYear = -Int(-days / 7)
End If
End If
End Function
mazoolagh
دوشنبه 28 مهر 1404, 10:21 صبح
Persian date calculations
Public Function AddYears(pDate As Long, Years As Long) As Long
' usage: use negative value for Years to Subtract
Dim pYear As Long, pMonth As Long, pDay As Long
pDay = GetDay(pDate)
pMonth = GetMonth(pDate)
pYear = GetYear(pDate) + Years
If pMonth = 12 And pDay = 30 And (Not IsLeapYear(pYear)) Then
pDay = 29
End If
AddYears = FromDateParts(pYear, pMonth, pDay)
End Function
Public Function AddMonths(pDate As Long, Months As Long) As Long
' usage: use negative value for Months to Subtract
Dim pYear As Long, pMonth As Long, pDay As Long
pDay = GetDay(pDate)
pMonth = GetMonth(pDate)
pYear = GetYear(pDate)
ValidateDateParts pYear, pMonth, pDay
pMonth = pMonth + (Months Mod 12)
pYear = pYear + (Months \ 12) + (pMonth \ 12)
pMonth = pMonth Mod 12
If pMonth < 1 Then
pMonth = pMonth + 12
pYear = pYear - 1
End If
If pDay > GetDaysInMonth(pYear, pMonth) Then
pDay = GetDaysInMonth(pYear, pMonth)
End If
AddMonths = FromDateParts(pYear, pMonth, pDay)
End Function
Public Function AddDays(pDate As Long, Days As Long) As Long
' usage: use negative value for Days to Subtract
Dim pYear As Long, pMonth As Long, pDay As Long
pDay = GetDay(pDate)
pMonth = GetMonth(pDate)
pYear = GetYear(pDate)
ValidateDateParts pYear, pMonth, pDay
Dim day_of_year As Long
day_of_year = GetDayOfYear(pDate) + Days
If day_of_year < 0 Then
Do Until day_of_year > 0
pYear = pYear - 1
day_of_year = day_of_year + GetDaysInYear(pYear)
Loop
Else
Do Until day_of_year <= GetDaysInYear(pYear)
day_of_year = day_of_year - GetDaysInYear(pYear)
pYear = pYear + 1
Loop
End If
If day_of_year < 187 Then
pMonth = (day_of_year \ 31) + 1
pDay = day_of_year Mod 31
Else
pMonth = (day_of_year - 186) \ 30 + 7
pDay = (day_of_year - 186) Mod 30
End If
If pDay = 0 Then
pMonth = pMonth - 1
If pMonth = 0 Then
pMonth = 12
pYear = pYear - 1
End If
pDay = GetDaysInMonth(pYear, pMonth)
End If
AddDays = FromDateParts(pYear, pMonth, pDay)
End Function
Public Function DiffDays(pDate1 As Long, pDate2 As Long) As Long
ValidateDateValue pDate1
ValidateDateValue pDate2
Dim gDate1 As Date, gdate2 As Date
gDate1 = Pr2Gr(pDate1)
gdate2 = Pr2Gr(pDate2)
DiffDays = DateDiff("d", gDate1, gdate2)
End Function
Public Function DiffWeeks(pDate1 As Long, pDate2 As Long) As Long
ValidateDateValue pDate1
ValidateDateValue pDate2
Dim gDate1 As Date, gdate2 As Date
gDate1 = Pr2Gr(pDate1)
gdate2 = Pr2Gr(pDate2)
DiffWeeks = DateDiff("ww", gDate1, gdate2, vbSaturday)
End Function
Public Function DiffDateSpan(pDate1 As Long, pDate2 As Long) As DateSpan
Dim TotalDays As Long
TotalDays = DiffDays(pDate1, pDate2)
Dim YYYY As Long, MM As Long
Dim dYears As Long, dMonths As Long, dDays As Long, xdays As Long
If TotalDays < 0 Then
MM = GetMonth(pDate2)
YYYY = GetYear(pDate2)
Else
MM = GetMonth(pDate1)
YYYY = GetYear(pDate1)
End If
dYears = 0
dMonths = 0
dDays = 0
xdays = Abs(TotalDays)
Do Until xdays < GetDaysInMonth(YYYY, MM)
xdays = xdays - GetDaysInMonth(YYYY, MM)
dMonths = dMonths + 1
If dMonths = 12 Then
dMonths = 0
dYears = dYears + 1
End If
MM = MM + 1
If MM > 12 Then
MM = 1
YYYY = YYYY + 1
End If
Loop
DiffDateSpan.Year = dYears
DiffDateSpan.Month = dMonths
DiffDateSpan.Day = xdays
DiffDateSpan.Sign = Sgn(TotalDays)
DiffDateSpan.TotalDays = TotalDays
End Function
mazoolagh
دوشنبه 28 مهر 1404, 10:23 صبح
Persian date formatting and spell
Public Function FormatDate(pDate As Long) As String
ValidateDateValue pDate
FormatDate = Format(pDate, "0000/00/00")
End Function
Public Function SpellDate(pDate As Long) As String
SpellDate = GetDay(pDate) & " " & GetMonthName(pDate) & " " & GetYear(pDate)
End Function
Public Function SpellFullDate(pDate As Long) As String
SpellFullDate = GetWeekDayName(pDate) & " " & SpellDate(pDate)
End Function
تا اینجا توابع PersianDateHelper بود که بتنهایی میتونه استفاده بشه،
از پست بعدی به کلاس PersianDate میپردازیم.
mazoolagh
دوشنبه 28 مهر 1404, 10:24 صبح
این کلاس تاریخ ایرانی رو بصورت یک آبجکت تعریف میکنه
و تقریبا همه متدهای اون از PersianDateHelper گرفته شده.
پراپرتی های این کلاس read-only و مقداردهی تاریخ فقط بامتدهای زیر مجاز هست:
FromDateValue
FromDateParts
FromGregorian
مقدار پیش فرض آبجکت تاریخ MinPrDate هست که در PersianDateHelper تعریف شده.
mazoolagh
دوشنبه 28 مهر 1404, 10:27 صبح
declaration / initialize
Option Compare Database
Option Explicit
Private mYYYY As Long
Private mMM As Long
Private mDD As Long
Private mValue As Long
Private mgDate As Date
Private Sub Class_Initialize()
InitializeData
Value = MinPrDate
Gregorian = MinGrDate
End Sub
mazoolagh
دوشنبه 28 مهر 1404, 18:49 عصر
properties
Public Property Get Year() As Long
Year = mYYYY
End Property
Private Property Let Year(vlu As Long)
mYYYY = vlu
End Property
Public Property Get Month() As Long
Month = mMM
End Property
Private Property Let Month(vlu As Long)
mMM = vlu
End Property
Public Property Get Day() As Long
Day = mDD
End Property
Private Property Let Day(vlu As Long)
mDD = vlu
End Property
Public Property Get Gregorian() As Date
Gregorian = mgDate
End Property
Private Property Let Gregorian(vlu As Date)
mgDate = DateSerial(VBA.Year(vlu), VBA.Month(vlu), VBA.Day(vlu))
End Property
Public Property Get Value() As Long
Value = mValue
End Property
Private Property Let Value(vlu As Long)
mValue = vlu
mYYYY = PersianDateHelper.GetYear(vlu)
mMM = PersianDateHelper.GetMonth(vlu)
mDD = PersianDateHelper.GetDay(vlu)
End Property
mazoolagh
دوشنبه 28 مهر 1404, 18:54 عصر
methods
Public Sub FromDateValue(pDate As Long)
Value = PersianDateHelper.FromDateValue(pDate)
mgDate = Pr2Gr(Value)
End Sub
Public Sub FromDateParts(YYYY As Long, MM As Long, DD As Long)
Value = PersianDateHelper.FromDateParts(YYYY, MM, DD)
mgDate = Pr2Gr(Value)
End Sub
Public Sub FromGregorian(gDate As Date)
Value = Gr2Pr(gDate)
mgDate = DateSerial(VBA.Year(gDate), VBA.Month(gDate), VBA.Day(gDate))
End Sub
Public Sub AddYears(Years As Long)
' usage: use negative value for Years to Subtract
Dim pDate As Long
pDate = PersianDateHelper.AddYears(Value, Years)
FromDateValue pDate
End Sub
Public Sub AddMonths(Months As Long)
' usage: use negative value for Months to Subtract
Dim pDate As Long
pDate = PersianDateHelper.AddMonths(Value, Months)
FromDateValue pDate
End Sub
Public Sub AddDays(Days As Long)
' usage: use negative value for Days to Subtract
Dim pDate As Long
pDate = PersianDateHelper.AddDays(Value, Days)
FromDateValue pDate
End Sub
Public Function GetDaysInYear() As Long
GetDaysInYear = PersianDateHelper.GetDaysInYear(Year)
End Function
Public Function GetDaysInMonth() As Long
GetDaysInMonth = PersianDateHelper.GetDaysInMonth(Year, Month)
End Function
Public Function GetDayOfYear()
GetDayOfYear = PersianDateHelper.GetDayOfYear(Value)
End Function
Public Function GetWeekOfYear(Optional Rule As WeekRule = WeekRule.FirstDay) As Long
GetWeekOfYear = PersianDateHelper.GetWeekOfYear(Value, Rule)
End Function
Public Function GetWeekDay() As Long
GetWeekDay = VBA.WeekDay(Gregorian, vbSaturday)
End Function
Public Function GetWeekDayName() As String
GetWeekDayName = WeekDayNames(GetWeekDay)
End Function
Public Function GetMonthName() As String
GetMonthName = MonthNames(Month)
End Function
Public Function ToString() As String
ToString = Format(Value, "0000/00/00")
End Function
Public Function SpellDate() As String
SpellDate = PersianDateHelper.SpellDate(Value)
End Function
Public Function SpellFullDate() As String
SpellFullDate = PersianDateHelper.SpellFullDate(Value)
End Function
vBulletin® v4.2.5, Copyright ©2000-1404, Jelsoft Enterprises Ltd.