رفتن به مطلب

Parsidate -کامل ترین کتابخانه کار با تاریخ و ساعت شمسی در اکوسیستم Rust

تقویم شمسی به‌عنوان یک استاندارد رسمی در ایران و برخی کشورهای خاور میانه، جایگاه ویژه‌ای در کاربردهای روزمره و حرفه‌ای دارد. با این حال، در اکوسیستم Rust، ابزارهای محدودی برای پشتیبانی از این تقویم وجود داشت. کتابخانه‌های موجود یا به زبان‌های دیگری توسعه یافته بودند که با نیازهای بهینه‌سازی Rust همخوانی نداشتند، یا فاقد به‌روزرسانی و پشتیبانی کافی بودند و تمام نیاز های برنامه نویسان را پوشش نمیدادند به همین دلیل تصمیم گرفتم برای رفع نیاز خودم و سایر برنامه نویسان این پکیج رو طراحی و منتشر کنم که تا این لحظه کامل ترین و جامع ترین پکیج کار با تاریخ و ساعت شمسی برای اکوسیستم Rust میباشد.

قابلیت‌ها پایه

  • تبدیل دوطرفه تاریخ: توابع from_gregorian و to_gregorian تاریخ ساعت را از میلادی به شمسی و از شمسی به میلادی تبدیل میکنند.

  • تاریخ روز: تابع today تاریخ و ساعت فعلی(امروز) را برمیگرداند

  • تشخیص سال کبیسه: تابع is_persian_leap_year سال‌های کبیسه شمسی را شناسایی میکند.

  • تشخیص روز هفته: تابع weekday که نام روز هفته را بازمی‌گرداند و همچنین توابع دیگر که شماره روز هفته و روزهای عادی را به شما میدهند.

  • فرمت و قالب بندی: تابع format تاریخ و ساعت شمسی را به تاریخ های کوتاه و بلند و ایزو نمایش میدهد و همچنین تابع format_strftime تاریخ را به فرمت سفارشی و دلخواه شما به نمایش در میاورد.

  • تجزیه (parse): تابع parse تاریخ و ساعت ورودی را به فرمت مد نظر شما تجزیه میکند.

  • محاسبه:توابع add_days , add_months , add_years امکان جمع و تفریق روز ها ماه ها و سالها را به شما میدهد.

  • تفاوت تاریخ و ساعت: تابع days_betweenتعداد روزهای بین دو تاریخ شمسی را محاسبه میکند.

  • اعتبار سنجی:تابع is_valid بررسی میکند که آیا تاریخ و ساعت فعلی یک تاریخ معتبر را مطابق قوانین تقویم فارسی و محدوده پشتیبانی شده این کتابخانه نشان می دهد یا خیر.

  • مدیریت خطا: استفاده از DateError و ParseErrorKind برای مدیریت انواع خطاها.

  • فصل ها: در نسخه 1.5.0 قابلیت پشتیبانی از فصل ها اضافه شده است.

  • هفته در سال: در نسخه 1.6.0 قابلیت تعیین اینکه یک تاریخ در کدام هفته ی سال میباشد اضافه شده است.

  • پشتیبان از Serde: افزودن پکیج serde به پروژه قابلیت های serialization/deserialization آن در پارسی دیت را به شما میدهد.

  • توابع کمکی(Helpers): به کمک توابع with_year, with_month, with_day میتوانید اولین و آخرین روز ماه و سال را بدست آورید یا تاریخ های سفارشی مورد نظرتان را ایجاد کنید.

همچنین پارسی دیت از محدوده تاریخ 1/1/1 شمسی تا 12/12/999 شمسی پشتیبانی میکند.

نحوه عملکرد

Parsidate از کتابخانه استاندارد chrono برای مدیریت تاریخ‌های میلادی استفاده می‌کند و با پیاده‌سازی الگوریتم‌های دقیق، عملیات کار با تاریخ را بصورت کاملا صحیح انجام میدهد.

نمونه‌ای از استفاده از این کتابخانه به شرح زیر است:

use chrono::{NaiveDate, NaiveDateTime, Duration};
use parsidate::{ParsiDate, ParsiDateTime, DateError}; // Import both

// --- ParsiDate Usage (Date only) ---
// Create a ParsiDate (validates on creation)
let pd = ParsiDate::new(1403, 5, 2).unwrap(); // 2 Mordad 1403
assert_eq!(pd.year(), 1403);
assert_eq!(pd.month(), 5); // 5 = Mordad
assert_eq!(pd.day(), 2);

// Check validity
assert!(pd.is_valid());
let invalid_date_res = ParsiDate::new(1404, 12, 30); // 1404 is not leap
assert_eq!(invalid_date_res, Err(DateError::InvalidDate));

// Gregorian to Persian Date
let g_date = NaiveDate::from_ymd_opt(2024, 7, 23).unwrap();
let pd_from_g = ParsiDate::from_gregorian(g_date).unwrap();
assert_eq!(pd_from_g, pd);

// Persian Date to Gregorian
let g_date_conv = pd.to_gregorian().unwrap();
assert_eq!(g_date_conv, g_date);

// Formatting Date
assert_eq!(pd.format("%Y-%m-%d is a %A"), "1403-05-02 is a سه‌شنبه");
assert_eq!(pd.format("%d %B %Y"), "02 مرداد 1403");
assert_eq!(pd.to_string(), "1403/05/02"); // Default Display

// Parsing Date
let parsed_short = ParsiDate::parse("1403/05/02", "%Y/%m/%d").unwrap();
assert_eq!(parsed_short, pd);

// Date Arithmetic
let next_day_date = pd.add_days(1).unwrap();
assert_eq!(next_day_date, ParsiDate::new(1403, 5, 3).unwrap());

// Get Today's Date
match ParsiDate::today() {
    Ok(today) => println!("Today's Persian date: {}", today.format("long")),
    Err(e) => eprintln!("Error getting today's date: {}", e),
}


// --- ParsiDateTime Usage (Date and Time) ---
// Create a ParsiDateTime (validates date and time)
let pdt = ParsiDateTime::new(1403, 5, 2, 15, 30, 45).unwrap();
assert_eq!(pdt.year(), 1403);
assert_eq!(pdt.hour(), 15);
assert_eq!(pdt.minute(), 30);
assert_eq!(pdt.second(), 45);
assert_eq!(pdt.date(), pd); // Access the ParsiDate part

// Invalid time creation
let invalid_time_res = ParsiDateTime::new(1403, 5, 2, 24, 0, 0);
assert_eq!(invalid_time_res, Err(DateError::InvalidTime));

// Gregorian DateTime to Persian DateTime
let g_dt = NaiveDate::from_ymd_opt(2024, 7, 23).unwrap().and_hms_opt(15, 30, 45).unwrap();
let pdt_from_g = ParsiDateTime::from_gregorian(g_dt).unwrap();
assert_eq!(pdt_from_g, pdt);

// Persian DateTime to Gregorian DateTime
let g_dt_conv = pdt.to_gregorian().unwrap();
assert_eq!(g_dt_conv, g_dt);

// Formatting DateTime
assert_eq!(pdt.format("%Y/%m/%d %H:%M:%S"), "1403/05/02 15:30:45");
assert_eq!(pdt.format("%A %d %B ساعت %T"), "سه‌شنبه 02 مرداد ساعت 15:30:45");
assert_eq!(pdt.to_string(), "1403/05/02 15:30:45"); // Default Display

// Parsing DateTime
let parsed_dt = ParsiDateTime::parse("1403/05/02 15:30:45", "%Y/%m/%d %H:%M:%S").unwrap();
assert_eq!(parsed_dt, pdt);
let parsed_dt_t = ParsiDateTime::parse("1403-05-02T15:30:45", "%Y-%m-%dT%T").unwrap();
assert_eq!(parsed_dt_t, pdt);

// DateTime Arithmetic with Duration
let next_hour = pdt.add_duration(Duration::hours(1)).unwrap();
assert_eq!(next_hour, ParsiDateTime::new(1403, 5, 2, 16, 30, 45).unwrap());
let prev_minute_rollover = pdt.sub_duration(Duration::minutes(31)).unwrap();
assert_eq!(prev_minute_rollover, ParsiDateTime::new(1403, 5, 2, 14, 59, 45).unwrap());
// Using operators
assert_eq!(pdt + Duration::seconds(15), Ok(ParsiDateTime::new(1403, 5, 2, 15, 31, 0).unwrap()));

// DateTime Arithmetic with days/months/years (preserves time)
let next_day_dt = pdt.add_days(1).unwrap();
assert_eq!(next_day_dt, ParsiDateTime::new(1403, 5, 3, 15, 30, 45).unwrap());
let next_month_dt = pdt.add_months(1).unwrap();
assert_eq!(next_month_dt, ParsiDateTime::new(1403, 6, 2, 15, 30, 45).unwrap());

// Modifying time components
let pdt_morning = pdt.with_hour(9).unwrap();
assert_eq!(pdt_morning.hour(), 9);
let pdt_start_of_minute = pdt.with_second(0).unwrap();
assert_eq!(pdt_start_of_minute.second(), 0);

// Get Current DateTime
match ParsiDateTime::now() {
    Ok(now) => println!("Current Persian DateTime: {}", now),
    Err(e) => eprintln!("Error getting current DateTime: {}", e),
}

در صورتی که به serialization/deserialization نیاز دارید پکیج serde را به پروژه خود اضافه کنید:

// --- Serde (Requires 'serde' feature) ---
#[cfg(feature = "serde")]
{
    // Make sure serde_json is a dev-dependency or added normally
    // use serde_json;

    // --- ParsiDate ---
    let pd_serde = ParsiDate::new(1403, 5, 2).unwrap();
    let json_pd = serde_json::to_string(&pd_serde).unwrap();
    println!("Serialized ParsiDate: {}", json_pd); // Output: {"year":1403,"month":5,"day":2}

    let deserialized_pd: ParsiDate = serde_json::from_str(&json_pd).unwrap();
    assert_eq!(deserialized_pd, pd_serde);
    assert!(deserialized_pd.is_valid());

    // Note: Default deserialization doesn't validate logical correctness for ParsiDate.
    let json_invalid_pd = r#"{"year":1404,"month":12,"day":30}"#; // Logically invalid
    let deserialized_invalid_pd: ParsiDate = serde_json::from_str(json_invalid_pd).unwrap();
    assert!(!deserialized_invalid_pd.is_valid());

    // --- ParsiDateTime ---
    let pdt_serde = ParsiDateTime::new(1403, 5, 2, 10, 20, 30).unwrap();
    let json_pdt = serde_json::to_string(&pdt_serde).unwrap();
    // Note the nested structure
    println!("Serialized ParsiDateTime: {}", json_pdt); // Output: {"date":{"year":1403,"month":5,"day":2},"hour":10,"minute":20,"second":30}

    let deserialized_pdt: ParsiDateTime = serde_json::from_str(&json_pdt).unwrap();
    assert_eq!(deserialized_pdt, pdt_serde);
    assert!(deserialized_pdt.is_valid());

    // Deserialization doesn't validate logical correctness for ParsiDateTime either.
    let json_invalid_pdt = r#"{"date":{"year":1403,"month":5,"day":2},"hour":25,"minute":0,"second":0}"#; // Invalid hour
    let deserialized_invalid_pdt: ParsiDateTime = serde_json::from_str(json_invalid_pdt).unwrap();
    assert!(!deserialized_invalid_pdt.is_valid()); // is_valid() check is needed
    assert_eq!(deserialized_invalid_pdt.hour(), 25); // Field gets populated
}

مشخصات فنی

  • وابستگی‌ها: تنها وابستگی اجباری این کتابخانه، chrono است که برای مدیریت تاریخ‌های میلادی به کار می‌رود و بصورت اختیاری از کتابخانه serde نیز پشتیبانی میکند.

  • الگوریتم تبدیل: مبتنی بر فرمول‌های استاندارد تبدیل تقویم میلادی به شمسی، با پشتیبانی از محاسبات سال‌های کبیسه.

  • رابط برنامه‌نویسی (API): توابع این پکیج با استفاده از نوع بازگشتی Result، مدیریت خطاها را به‌صورت ایمن تضمین می‌کنند.

نحوه نصب و استفاده

برای نصب این کتابخانه میتوانید از دستور زیر استفاده کنید(روش پیشنهادی):

cargo add parsidate

یا برای افزودن Parsidate به پروژه خود، کافی است خط زیر را به فایل Cargo.toml اضافه کنید:

[dependencies]
parsidate = "1.6.0"
chrono = "0.4"

اگر به serde نیاز دارید:

[dependencies]
parsidate = { version = "1.6.0", features = ["serde"] }
chrono = "0.4"
serde = { version = "1.0", features = ["derive"] } # Required for derive

دسترسی

صفحه ی parsidate در crates.io :

https://crates.io/crates/parsidate

صفحه ی parsidate در lib.rs:

https://lib.rs/crates/parsidate

صفحه ی مستندات parsidate در docs.rs :

https://docs.rs/parsidate

WiKi:

https://github.com/jalalvandi/ParsiDate/wiki

آخرین تغییرات:

https://github.com/jalalvandi/ParsiDate/blob/master/CHANGELOG.md


توضیحات: آخرین بروزرسانی :‌ 1.6.0 - 26 فروردین 1404

بازخورد کاربر

دیدگاه‌های پیشنهاد شده

هیچ دیدگاهی برای نمایش وجود دارد.

دیدگاه خود را ارسال کنید

از استفاده از کلمات رکیک و خلاف قوانین و غیر مرتبط با موضوع خودداری کنید ...
توجه: مطلب ارسالی شما پس از تایید مدیریت برای همه قابل رویت خواهد بود.

مهمان
افزودن دیدگاه...