Skip to content

Instantly share code, notes, and snippets.

@MajidArabi
Last active May 18, 2025 10:23
Show Gist options
  • Save MajidArabi/9b30dba7959ffe5309421ce2abaa6463 to your computer and use it in GitHub Desktop.
Save MajidArabi/9b30dba7959ffe5309421ce2abaa6463 to your computer and use it in GitHub Desktop.
Convert Gregorian date to Persian date and vice versa For Kotlin multiplatform projects.
/**
* Kotlin multiplatform Gregorian date to
* Persian date and vice versa convertor
* @author majid-arabi
* Created 12/10/24 at 11:37 AM
*/
import kotlinx.datetime.Clock
import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone
import kotlinx.datetime.isoDayNumber
import kotlinx.datetime.number
import kotlinx.datetime.toLocalDateTime
class JalaliCalendar {
var year: Int
private set
var month: Int
private set
var day: Int
private set
constructor() {
val current = now()
this.year = current.year
this.month = current.month
this.day = current.day
}
constructor(
year: Int,
month: Int,
day: Int
) {
this.year = year
this.month = month
this.day = day
}
constructor(
date: LocalDate
) {
val date = toJalali(date)
this.year = date.year
this.month = date.month
this.day = date.day
}
/**
* Converts a Gregorian date to a Jalali date.
* @param date The Gregorian [LocalDate].
* @return A [JalaliCalendar] of Jalali year, month, and day.
*/
fun toJalali(year: Int, month: Int, day: Int): JalaliCalendar {
val g_d_m: IntArray = intArrayOf(0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334)
var gy2: Int = if (month > 2) (year + 1) else year
var days: Int =
355666 + (365 * year) + ((gy2 + 3) / 4).toInt() - ((gy2 + 99) / 100).toInt() + ((gy2 + 399) / 400).toInt() + day + g_d_m[month - 1]
var jy: Int = -1595 + (33 * (days / 12053).toInt())
days %= 12053
jy += 4 * (days / 1461).toInt()
days %= 1461
if (days > 365) {
jy += ((days - 1) / 365).toInt()
days = (days - 1) % 365
}
var jm: Int
var jd: Int
if (days < 186) {
jm = 1 + (days / 31).toInt()
jd = 1 + (days % 31)
} else {
jm = 7 + ((days - 186) / 30).toInt()
jd = 1 + ((days - 186) % 30)
}
return JalaliCalendar(jy, jm, jd)
}
/**
* Converts a Gregorian date to a Jalali date.
* @param date The Gregorian [LocalDate].
* @return A [JalaliCalendar] of Jalali year, month, and day.
*/
fun toJalali(
date: LocalDate
): JalaliCalendar = toJalali(
year = date.year,
day = date.dayOfMonth,
month = date.month.number
)
/**
* Converts a Jalali date to a Gregorian date.
* @param year Jalali year.
* @param month Jalali month.
* @param day Jalali day.
* @return A Gregorian [LocalDate].
*/
fun toGregorian(year: Int, month: Int, day: Int): LocalDate {
val jy1: Int = year + 1595
var days: Int =
-355668 + (365 * jy1) + ((jy1 / 33).toInt() * 8) + (((jy1 % 33) + 3) / 4).toInt() + day + (if (month < 7) ((month - 1) * 31) else (((month - 7) * 30) + 186))
var gy: Int = 400 * (days / 146097).toInt()
days %= 146097
if (days > 36524) {
gy += 100 * (--days / 36524).toInt()
days %= 36524
if (days >= 365) days++
}
gy += 4 * (days / 1461).toInt()
days %= 1461
if (days > 365) {
gy += ((days - 1) / 365).toInt()
days = (days - 1) % 365
}
var gd: Int = days + 1
val sal_a: IntArray = intArrayOf(
0,
31,
if ((gy % 4 == 0 && gy % 100 != 0) || (gy % 400 == 0)) 29 else 28,
31,
30,
31,
30,
31,
31,
30,
31,
30,
31
)
var gm = 0
while (gm < 13 && gd > sal_a[gm]) gd -= sal_a[gm++]
return LocalDate(gy, gm, gd)
}
/**
* Converts a Jalali date to a Gregorian date.
* @return A Gregorian [LocalDate].
*/
fun toGregorian(): LocalDate = toGregorian(
year = this.year,
month = this.month,
day = this.day
)
/**
* Gets the current Jalali date.
* @return A [JalaliCalendar] of Jalali year, month, and day.
*/
private fun now(): JalaliCalendar {
val timeZone = TimeZone.currentSystemDefault()
val localDateTime = Clock.System.now().toLocalDateTime(timeZone)
return toJalali(localDateTime.date)
}
/**
* Determines if a given Jalali year is a leap year.
* @param year Jalali year.
* @return True if the year is a leap year, false otherwise.
*/
fun isLeap(year: Int): Boolean = getLeapFactor(year) == 0
/**
* Determines if a given Jalali year is a leap year.
* @return True if the year is a leap year, false otherwise.
*/
fun isLeap(): Boolean = isLeap(this.year)
/**
* Gets the name of a Jalali month in Persian.
* @param month Jalali month (1-based).
* @return The Persian name of the month.
*/
fun getMonthName(
month: Int
): String = when (month) {
1 -> "فروردین"
2 -> "اردیبهشت"
3 -> "خرداد"
4 -> "تیر"
5 -> "مرداد"
6 -> "شهریور"
7 -> "مهر"
8 -> "آبان"
9 -> "آذر"
10 -> "دی"
11 -> "بهمن"
12 -> "اسفند"
else -> "نامعلوم"
}
/**
* Gets the name of a Jalali month in Persian.
* @return The Persian name of the month.
*/
fun getMonthName(): String = getMonthName(this.month)
/**
* Gets the length of a Jalali year (365 or 366 days).
* @param year Jalali year.
* @return The number of days in the year.
*/
fun getYearLength(year: Int): Int {
return if (isLeap(year)) 366 else 365
}
/**
* Gets the length of a Jalali year (365 or 366 days).
* @return The number of days in the year.
*/
fun getYearLength(): Int = getYearLength(this.year)
/**
* Gets the length of a specific Jalali month.
* @param year Jalali year.
* @param month Jalali month (1-based).
* @return The number of days in the month.
*/
fun getMonthLength(
year: Int,
month: Int
): Int = when {
month < 7 -> 31
month < 12 -> 30
month == 12 -> if (isLeap(year)) 30 else 29
else -> 0
}
/**
* Gets the length of a specific Jalali month.
* @param month Jalali month (1-based).
* @return The number of days in the month.
*/
fun getMonthLength(month: Int): Int = getMonthLength(this.year, month)
/**
* Gets the length of a specific Jalali month.
* @return The number of days in the month.
*/
fun getMonthLength(): Int = getMonthLength(this.year, this.month)
fun getDayOfWeek(): Int = toGregorian().dayOfWeek.isoDayNumber % 7 + 1
/**
* Gets the Persian name of the day of the week for a given Gregorian date.
* @param date A Gregorian [LocalDate].
* @return The Persian name of the day of the week.
*/
fun getDayOfWeekName(): String = when (getDayOfWeek()) {
1 -> "یک‌شنبه"
2 -> "دوشنبه"
3 -> "سه‌شنبه"
4 -> "چهارشنبه"
5 -> "پنجشنبه"
6 -> "جمعه"
7 -> "شنبه"
else -> "نامعلوم"
}
private fun getLeapFactor(jalaliYear: Int): Int {
val breaks = intArrayOf(
-61, 9, 38, 199, 426, 686, 756, 818, 1111, 1181, 1210,
1635, 2060, 2097, 2192, 2262, 2324, 2394, 2456, 3178
)
var jp = breaks[0]
for (j in 1..19) {
val jm = breaks[j]
val jump = jm - jp
if (jalaliYear < jm) {
var n = jalaliYear - jp
if ((jump - n) < 6)
n = n - jump + (jump + 4) / 33 * 33
var leap = ((n + 1) % 33 - 1) % 4
if (leap == -1)
leap = 4
return leap
}
jp = jm
}
return 0
}
override fun toString(): String {
return "$year/$month/$day"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment