PersianDateMultiplatform is a Kotlin Multiplatform library for working with the Persian (Jalali/Shamsi) calendar across Android, iOS, Desktop (JVM), and Web (Kotlin/Wasm). The library provides utilities for conversion, formatting, and manipulation of Persian dates, with support for leap years, month and weekday names, and integration into Compose Multiplatform projects.
- Persian Date Conversion: Convert between Gregorian and Persian date representations.
- Date Formatting: Format Persian date/time objects to strings (date, time, date-time).
- Leap Year Calculations: Accurately determine leap years in the Jalali calendar.
- Month/Weekday Names: Get Persian month and weekday names.
- Multiplatform Support: Use in Android, iOS, Desktop (JVM), and Web (Kotlin/Wasm).
- DSL for Formatting: Easy-to-use builder for custom date/time formats.
- Integration Samples: Compose Multiplatform project structure for real-world apps.
- Kotlin Multiplatform Projects (Common Main)
The library is published to Maven Central.
The library is compatible with the Kotlin Standard Library not lower than 2.1.20.
If you target Android devices running below API 26, you need to use Android Gradle plugin 4.0 or newer and enable core library desugaring.
Add the dependency to your commonMain source set in your build.gradle.kts:
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
// Add kotlinx-datetime first
implementation("org.jetbrains.kotlinx:kotlinx-datetime:<version>")
// Then your PersianDateTime library
implementation("io.github.faridsolgi:persianDateTime:<version>")
}
}
}
}- Android Native Projects
For Android-native projects, use the dedicated Android artifact:
dependencies {
// Add kotlinx-datetime first
implementation("org.jetbrains.kotlinx:kotlinx-datetime:<version>")
// Then the PersianDateTime Android artifact
implementation("io.github.faridsolgi:persianDateTime:<version>")
}You can also get the library from Maven Central: PersianDateTime on Maven Central or clone and include the library module directly.
If you also add the kotlinx-serialization library to your project,
PersianDateTime supports @Serializable.
import com.faridsolgi.persiandatemultiplatform.domain.PersianDateTime
// Create a date only
val persianDate = PersianDateTime(year = 1402, month = 7, day = 1)
// Create a date with time
val persianDateTime = PersianDateTime(
year = 1402,
month = 7,
day = 1,
hour = 14,
minute = 30,
second = 45
) /**
* Parse PersianDateTime from a string.
*
* Supported formats:
* - "yyyy/MM/dd"
* - "yyyy-MM-dd"
* - "yyyy/MM/dd HH:mm"
* - "yyyy-MM-dd HH:mm"
* - "yyyy/MM/dd HH:mm:ss"
* - "yyyy-MM-dd HH:mm:ss"
* - "yyyy-MM-ddTHH:mm:ss"
*
* @param input The date string to parse.
* @return A valid [PersianDateTime] instance.
* @throws IllegalArgumentException if the input format is invalid.
*/
// Parse date only
val parsedDate = PersianDateTime.parse("1402/07/01")
// Parse date with time
val parsedDateTime = PersianDateTime.parse("1402-07-01 14:30:45")import com.faridsolgi.persiandatemultiplatform.domain.PersianDateTime
import kotlinx.datetime.TimeZone
// Parse from epoch milliseconds (e.g., 1759323028800 = 01 Oct 2025)
val timestamp = 1759323028800L
val persianDateTime = PersianDateTime.parse(timestamp, TimeZone.currentSystemDefault())
println(persianDateTime.year) // 1404
println(persianDateTime.month) // 7
println(persianDateTime.day) // 9
println(persianDateTime.hour) // 16// Accessing date components
println(parsedDate.year) // 1402
println(parsedDate.month) // 7
println(parsedDate.day) // 1
// Formatting a Persian date to string
val formatted = parsedDateTime.toString()
println(formatted) // Example: "1402-07-01 14:30:45"Convert LocalDate or LocalDateTime to a Persian date:
import com.faridsolgi.persiandatemultiplatform.converter.toPersianDateTime
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
val gregorianDate = LocalDate(2023, 9, 24)
val persianDate = gregorianDate.toPersianDateTime() // PersianDateTime instance
val gregorianDateTime = LocalDateTime(2023, 9, 24, 15, 20, 0)
val persianDateTime = gregorianDateTime.toPersianDateTime()Convert Persian date back to Gregorian:
import com.faridsolgi.persiandatemultiplatform.converter.toGregorian
val gregorian = persianDate.toLocalDate()
val gregorianWithTime = persianDate.toLocalDateTime()You can get the current Persian date for a specific time zone:
import kotlinx.datetime.Clock
import kotlinx.datetime.TimeZone
import com.faridsolgi.persiandatemultiplatform.converter.nowInTehran
import com.faridsolgi.persiandatemultiplatform.converter.nowPersianDate
// Get the current date in Tehran's timezone
val nowInTehran = Clock.System.nowInTehran
println("Current Persian date in Tehran: ${nowInTehran.toDateString()}")
// Get the current date for a different time zone, e.g., "Europe/Paris"
val nowInParis = Clock.System.nowPersianDate(TimeZone.of("Europe/Paris"))
println("Current Persian date in Paris: ${nowInParis.toDateString()}")You can perform date arithmetic on PersianDateTime using plus and minus with DateTimeUnit or DatePeriod.
import com.faridsolgi.persiandatemultiplatform.converter.plus
import com.faridsolgi.persiandatemultiplatform.converter.minus
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.DatePeriod
val nextWeek = persianDate.plus(7, DateTimeUnit.DAY) // add 7 days
val yesterday = persianDate.minus(1, DateTimeUnit.DAY) // subtract 1 day
val nextMonth = persianDate.plus(DatePeriod(months = 1)) // add one month
val lastYear = persianDate.minus(DatePeriod(years = 1)) // subtract one yearimport com.faridsolgi.persiandatemultiplatform.converter.isBefore
import com.faridsolgi.persiandatemultiplatform.converter.isAfter
import com.faridsolgi.persiandatemultiplatform.converter.isBetween
if (persianDate.isBefore(nextWeek)) { /* ... */ }
if (persianDate.isAfter(yesterday)) { /* ... */ }
if (persianDate.isBetween(yesterday, nextWeek)) { /* ... */ }import com.faridsolgi.persiandatemultiplatform.converter.isLeap
import com.faridsolgi.persiandatemultiplatform.converter.monthLength
import com.faridsolgi.persiandatemultiplatform.converter.monthName
import com.faridsolgi.persiandatemultiplatform.converter.dayOfWeekName
println(persianDate.isLeap()) // true/false
println(persianDate.monthLength()) // 31, 30, or 29
println(persianDate.persianMonth().displayName) // "مهر" (Mehr)
println(persianDate.persianDayOfWeek().displayName) // "سهشنبه" (Tuesday)import com.faridsolgi.persiandatemultiplatform.converter.toDateString
import com.faridsolgi.persiandatemultiplatform.converter.toTimeString
import com.faridsolgi.persiandatemultiplatform.converter.toDateTimeString
println(persianDate.toDateString()) // "1402/07/02"
println(persianDate.toTimeString()) // "00:00:00"
println(persianDate.toDateTimeString()) // "1402/07/02 00:00:00"The formatting DSL supports full date/time patterns:
import com.faridsolgi.persiandatemultiplatform.converter.format
val custom = persianDate.format {
day()
char('/')
month()
char('/')
year()
char(' ')
hour12()
char(':')
minute()
amPm()
}
println(custom) // "02/07/1402 03:45ب.ظ"| Function | Description | Example Output |
|---|---|---|
year(pad) |
Year with optional padding (default 4) | 1402 |
month(pad) |
Month number with optional padding (2) | 07 |
day(pad) |
Day of month with optional padding (2) | 02 |
hour24(pad) |
Hour in 24-hour format (00–23) | 14 |
hour12(pad) |
Hour in 12-hour format (01–12) | 02 |
minute(pad) |
Minute with optional padding (2) | 45 |
second(pad) |
Second with optional padding (2) | 09 |
amPm() |
AM/PM marker | ب.ظ,ق.ظ |
char(c) |
Literal character | / |
monthName() |
Persian month name | مهر |
dayOfWeekName() |
Persian weekday name | سهشنبه |
All PersianDateTime instances are automatically validated. If you try to create a date or time
that is invalid, the library will throw an IllegalArgumentException. Users do not need to
call any validator—it happens internally.
- Month: Must be between 1 and 12.
- Day: Must be valid for the given month and year (including leap years for month 12).
- Time: Hours must be 0–23, minutes 0–59, seconds 0–59.
import com.faridsolgi.persiandatemultiplatform.domain.PersianDateTime
// ✅ Valid date
val validDate = PersianDateTime(1402, 7, 15)
// ❌ Invalid month
try {
PersianDateTime(1402, 13, 5)
} catch (e: IllegalArgumentException) {
println(e.message) // "ماه نامعتبر: 13"
}
// ❌ Invalid day
try {
PersianDateTime(1402, 7, 32)
} catch (e: IllegalArgumentException) {
println(e.message) // "روز نامعتبر: 32 برای ماه 7"
}
// ❌ Invalid time
try {
PersianDateTime(1402, 7, 15, 25, 0, 0)
} catch (e: IllegalArgumentException) {
println(e.message) // "ساعت نامعتبر: 25"
}
// ✅ Parsing a date string
val parsedDate = PersianDateTime.parse("1402/07/01 14:30:45")
// ❌ Parsing an invalid string
try {
PersianDateTime.parse("1402/13/01")
} catch (e: IllegalArgumentException) {
println(e.message) // "ماه نامعتبر: 13"
}
⚠️ Tip: Always catchIllegalArgumentExceptionwhen accepting user input or parsing strings to safely handle invalid dates.
MIT License
Copyright (c) 2024 Farid Solgi
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.