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.
importcom.faridsolgi.persiandatemultiplatform.domain.PersianDateTime// Create a date onlyval persianDate =PersianDateTime(year =1402, month =7, day =1)
// Create a date with timeval 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 onlyval parsedDate =PersianDateTime.parse("1402/07/01")
// Parse date with timeval parsedDateTime =PersianDateTime.parse("1402-07-01 14:30:45")importcom.faridsolgi.persiandatemultiplatform.domain.PersianDateTimeimportkotlinx.datetime.TimeZone// Parse from epoch milliseconds (e.g., 1759323028800 = 01 Oct 2025)val timestamp =1759323028800Lval persianDateTime =PersianDateTime.parse(timestamp, TimeZone.currentSystemDefault())
println(persianDateTime.year) // 1404println(persianDateTime.month) // 7println(persianDateTime.day) // 9println(persianDateTime.hour) // 16// Accessing date componentsprintln(parsedDate.year) // 1402println(parsedDate.month) // 7println(parsedDate.day) // 1// Formatting a Persian date to stringval formatted = parsedDateTime.toString()
println(formatted) // Example: "1402-07-01 14:30:45"Convert LocalDate or LocalDateTime to a Persian date:
importcom.faridsolgi.persiandatemultiplatform.converter.toPersianDateTimeimportkotlinx.datetime.LocalDateimportkotlinx.datetime.LocalDateTimeval gregorianDate =LocalDate(2023, 9, 24)
val persianDate = gregorianDate.toPersianDateTime() // PersianDateTime instanceval gregorianDateTime =LocalDateTime(2023, 9, 24, 15, 20, 0)
val persianDateTime = gregorianDateTime.toPersianDateTime()Convert Persian date back to Gregorian:
importcom.faridsolgi.persiandatemultiplatform.converter.toGregorianval gregorian = persianDate.toLocalDate()
val gregorianWithTime = persianDate.toLocalDateTime()You can get the current Persian date for a specific time zone:
importkotlinx.datetime.Clockimportkotlinx.datetime.TimeZoneimportcom.faridsolgi.persiandatemultiplatform.converter.nowInTehranimportcom.faridsolgi.persiandatemultiplatform.converter.nowPersianDate// Get the current date in Tehran's timezoneval 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.
importcom.faridsolgi.persiandatemultiplatform.converter.plusimportcom.faridsolgi.persiandatemultiplatform.converter.minusimportkotlinx.datetime.DateTimeUnitimportkotlinx.datetime.DatePeriodval nextWeek = persianDate.plus(7, DateTimeUnit.DAY) // add 7 daysval yesterday = persianDate.minus(1, DateTimeUnit.DAY) // subtract 1 dayval nextMonth = persianDate.plus(DatePeriod(months =1)) // add one monthval lastYear = persianDate.minus(DatePeriod(years =1)) // subtract one yearimportcom.faridsolgi.persiandatemultiplatform.converter.isBeforeimportcom.faridsolgi.persiandatemultiplatform.converter.isAfterimportcom.faridsolgi.persiandatemultiplatform.converter.isBetweenif (persianDate.isBefore(nextWeek)) { /* ... */ }
if (persianDate.isAfter(yesterday)) { /* ... */ }
if (persianDate.isBetween(yesterday, nextWeek)) { /* ... */ }importcom.faridsolgi.persiandatemultiplatform.converter.isLeapimportcom.faridsolgi.persiandatemultiplatform.converter.monthLengthimportcom.faridsolgi.persiandatemultiplatform.converter.monthNameimportcom.faridsolgi.persiandatemultiplatform.converter.dayOfWeekNameprintln(persianDate.isLeap()) // true/falseprintln(persianDate.monthLength()) // 31, 30, or 29println(persianDate.persianMonth().displayName) // "مهر" (Mehr)println(persianDate.persianDayOfWeek().displayName) // "سهشنبه" (Tuesday)importcom.faridsolgi.persiandatemultiplatform.converter.toDateStringimportcom.faridsolgi.persiandatemultiplatform.converter.toTimeStringimportcom.faridsolgi.persiandatemultiplatform.converter.toDateTimeStringprintln(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:
importcom.faridsolgi.persiandatemultiplatform.converter.formatval 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.
importcom.faridsolgi.persiandatemultiplatform.domain.PersianDateTime// ✅ Valid dateval validDate =PersianDateTime(1402, 7, 15)
// ❌ Invalid monthtry {
PersianDateTime(1402, 13, 5)
} catch (e:IllegalArgumentException) {
println(e.message) // "ماه نامعتبر: 13"
}
// ❌ Invalid daytry {
PersianDateTime(1402, 7, 32)
} catch (e:IllegalArgumentException) {
println(e.message) // "روز نامعتبر: 32 برای ماه 7"
}
// ❌ Invalid timetry {
PersianDateTime(1402, 7, 15, 25, 0, 0)
} catch (e:IllegalArgumentException) {
println(e.message) // "ساعت نامعتبر: 25"
}
// ✅ Parsing a date stringval parsedDate =PersianDateTime.parse("1402/07/01 14:30:45")
// ❌ Parsing an invalid stringtry {
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.