World calendars
Dates in the world's other calendars, each way and with month names: Hebrew, Persian, Ethiopian, Coptic, Indian national, Thai Buddhist, Japanese eras (令和8年) and Taiwan's Minguo; and the Jewish holidays of a year.
Version 1.0.0 · 3 functions · 4 crates
Functions
What use world_calendars.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use calendars_convert, calendars_to_gregorian from world_calendars (Quillo 0.6).
calendars_convert(date: str, calendar: str) → anyor an errorA Gregorian date ("2026-10-29", or "today") in calendar: "hebrew", "persian", "ethiopian", "coptic", "indian", "buddhist", "japanese" or "roc". { calendar, era, year, month, month_code, month_name, day, text }; month counts the months of that year (a Hebrew leap year has 13), month_code names it for good ("M05L" is Adar I). Japanese dates are written as on forms, "令和8年10月29日".
calendars_to_gregorian(calendar: str, year: int, month: str, day: int) → stror an errorThe Gregorian date ("2026-04-02") of a date in calendar: month is a number, a code such as "M05L", or a name ("Nisan", "Farvardin"). For Japanese dates pass "japanese:reiwa" (or heisei, showa...) as the calendar.
calendars_jewish_holidays(year: int) → listor an errorThe Jewish holidays that begin in Gregorian year, each on its first full day (they start at sunset the evening before): a list of { name, date, hebrew_date } from Rosh Hashanah to Tisha B'Av.
Get it
$ quillo install world_calendars # world_calendars.qwer into this folder$ quillo install world_calendars --example # and its example, world_calendars.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/world_calendars.qwer$ curl -O https://quillolang.org/plugins/files/world_calendars.qweThen, in your program, all of it or only what you call:
use world_calendars.qweruse calendars_convert, calendars_to_gregorian from world_calendars // Quillo 0.6: only theseIt uses these crates, which cargo fetches the first time the plugin is built:
icu_calendar = "=1.5.2"tinystr = "0.7"chrono = { version = "0.4", default-features = false, features = ["std", "clock"] }serde_json = { version = "1", features = ["preserve_order"] }Example
// An international events site: each event's date in the calendars its guests keep, Japanese era dates for forms, and the Jewish holidays to plan around.//// quillo run world_calendars.qwe then open http://localhost:8080/datesuse "world_calendars.qwer"let DAY = "2026-10-29"route GET /dates { { hebrew: calendars_convert(DAY, "hebrew")?, persian: calendars_convert(DAY, "persian")?.text, ethiopian: calendars_convert(DAY, "ethiopian")?.text, coptic: calendars_convert(DAY, "coptic")?.text, indian: calendars_convert(DAY, "indian")?.text, thai: calendars_convert(DAY, "buddhist")?.text, japanese: calendars_convert(DAY, "japanese")?.text, taiwan: calendars_convert(DAY, "roc")?.text, nowruz_1405: calendars_to_gregorian("persian", 1405, "Farvardin", 1)?, heisei_start: calendars_to_gregorian("japanese:heisei", 1, "1", 8)?, enkutatash: calendars_to_gregorian("ethiopian", 2019, "Meskerem", 1)?, not_a_month: calendars_to_gregorian("hebrew", 5787, "Smarch", 1).error, }}route GET /jewish-holidays/:year(int) = calendars_jewish_holidays(year)?$ quillo run world_calendars.qwe$ curl 'localhost:8080/dates'$ curl 'localhost:8080/jewish-holidays/2026'Source
//! Dates in the world's other calendars: Hebrew, Persian (Solar Hijri), Ethiopian, Coptic, Indian national, Thai Buddhist, Japanese eras (令和8年) and Taiwan's Minguo, each way, with month names; and the Jewish holidays of a year.//! version: 1.0.0//!//! [dependencies]//! icu_calendar = "=1.5.2"//! tinystr = "0.7"//! chrono = { version = "0.4", default-features = false, features = ["std", "clock"] }//! serde_json = { version = "1", features = ["preserve_order"] }use chrono::{Datelike, NaiveDate};use icu_calendar::types::{Era, MonthCode};use icu_calendar::{AnyCalendar, AnyCalendarKind, Date};use serde_json::{json, Value};const HEBREW: [(&str, &str); 13] = [("M01", "Tishrei"), ("M02", "Cheshvan"), ("M03", "Kislev"), ("M04", "Tevet"), ("M05", "Shevat"), ("M05L", "Adar I"), ("M06", "Adar"), ("M07", "Nisan"), ("M08", "Iyar"), ("M09", "Sivan"), ("M10", "Tammuz"), ("M11", "Av"), ("M12", "Elul")];const PERSIAN: [&str; 12] = ["Farvardin", "Ordibehesht", "Khordad", "Tir", "Mordad", "Shahrivar", "Mehr", "Aban", "Azar", "Dey", "Bahman", "Esfand"];const ETHIOPIAN: [&str; 13] = ["Meskerem", "Tikimt", "Hidar", "Tahsas", "Tir", "Yekatit", "Megabit", "Miyazya", "Ginbot", "Sene", "Hamle", "Nehasse", "Pagume"];const COPTIC: [&str; 13] = ["Thout", "Paopi", "Hathor", "Koiak", "Tobi", "Meshir", "Paremhat", "Parmouti", "Pashons", "Paoni", "Epip", "Mesori", "Pi Kogi Enavot"];const INDIAN: [&str; 12] = ["Chaitra", "Vaishakha", "Jyeshtha", "Ashadha", "Shravana", "Bhadrapada", "Ashvin", "Kartika", "Agrahayana", "Pausha", "Magha", "Phalguna"];const GREGORIAN: [&str; 12] = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];const JP_ERAS: [(&str, &str, &str); 5] = [("meiji", "明治", "Meiji"), ("taisho", "大正", "Taishō"), ("showa", "昭和", "Shōwa"), ("heisei", "平成", "Heisei"), ("reiwa", "令和", "Reiwa")];fn kind(name: &str) -> Result<(AnyCalendarKind, &'static str, &'static str), String> { // (kind, its name here, the era a year is counted in) Ok(match name.trim().to_lowercase().as_str() { "hebrew" | "jewish" => (AnyCalendarKind::Hebrew, "hebrew", "am"), "persian" | "solar_hijri" | "jalali" => (AnyCalendarKind::Persian, "persian", "ah"), "ethiopian" => (AnyCalendarKind::Ethiopian, "ethiopian", "incar"), "coptic" => (AnyCalendarKind::Coptic, "coptic", "ad"), "indian" | "saka" => (AnyCalendarKind::Indian, "indian", "saka"), "buddhist" | "thai" => (AnyCalendarKind::Buddhist, "buddhist", "be"), "japanese" => (AnyCalendarKind::Japanese, "japanese", ""), "roc" | "minguo" | "taiwan" => (AnyCalendarKind::Roc, "roc", "roc"), other => return Err(format!("`{}` is not a calendar here: hebrew, persian, ethiopian, coptic, indian, buddhist, japanese or roc", other)), })}fn parse(date: &str) -> Result<NaiveDate, String> { let t = date.trim(); if t.is_empty() || t == "today" { return Ok(chrono::Utc::now().date_naive()) } NaiveDate::parse_from_str(t.get(..10).unwrap_or(t), "%Y-%m-%d").map_err(|_| format!("`{}` is not a date (2026-10-29)", date))}fn month_name(cal: &str, code: &str, ordinal: u32) -> String { let n = code.get(1..3).and_then(|m| m.parse::<usize>().ok()).unwrap_or(ordinal as usize).max(1); match cal { "hebrew" => HEBREW.iter().find(|(c, _)| *c == code).map(|x| x.1.to_string()).unwrap_or_default(), "persian" => PERSIAN.get(n - 1).copied().unwrap_or("").to_string(), "ethiopian" => ETHIOPIAN.get(n - 1).copied().unwrap_or("").to_string(), "coptic" => COPTIC.get(n - 1).copied().unwrap_or("").to_string(), "indian" => INDIAN.get(n - 1).copied().unwrap_or("").to_string(), _ => GREGORIAN.get(n - 1).copied().unwrap_or("").to_string(), }}fn convert(d: NaiveDate, calendar: &str) -> Result<Value, String> { let (k, cal, _) = kind(calendar)?; let iso = Date::try_new_iso_date(d.year(), d.month() as u8, d.day() as u8).map_err(|e| format!("{}: {:?}", d, e))?; let c = iso.to_any().to_calendar(AnyCalendar::new(k)); let y = c.year(); let m = c.month(); let code = m.code.0.to_string(); let day = c.day_of_month().0; let name = month_name(cal, &code, m.ordinal); let era = y.era.0.to_string(); let mut v = json!({ "calendar": cal, "era": era, "year": y.number, "month": m.ordinal, "month_code": code, "month_name": name, "day": day, "text": format!("{} {} {}", day, name, y.number), }); if cal == "japanese" { let (zh, en) = JP_ERAS.iter().find(|(c, _, _)| *c == era).map(|(_, z, e)| (*z, *e)).unwrap_or(("", "")); let yr = if y.number == 1 { "元".to_string() } else { y.number.to_string() }; v["text"] = json!(format!("{}{}年{}月{}日", zh, yr, m.ordinal, day)); v["era_name"] = json!(en); } if cal == "roc" { v["text"] = json!(format!("民國{}年{}月{}日", y.number, m.ordinal, day)) } if cal == "buddhist" { v["text"] = json!(format!("{} {} B.E. {}", day, name, y.number)) } Ok(v)}/// A Gregorian date (`"2026-10-29"`, or `"today"`) in `calendar`: `"hebrew"`, `"persian"`, `"ethiopian"`, `"coptic"`, `"indian"`, `"buddhist"`, `"japanese"` or `"roc"`./// `{ calendar, era, year, month, month_code, month_name, day, text }`; `month` counts the months of that year (a Hebrew leap year has 13), `month_code` names it for good (`"M05L"` is Adar I). Japanese dates are written as on forms, "令和8年10月29日".pub fn calendars_convert(date: &str, calendar: &str) -> Result<Value, String> { convert(parse(date)?, calendar)}/// The Gregorian date (`"2026-04-02"`) of a date in `calendar`: `month` is a number, a code such as `"M05L"`, or a name (`"Nisan"`, `"Farvardin"`). For Japanese dates pass `"japanese:reiwa"` (or `heisei`, `showa`...) as the calendar.pub fn calendars_to_gregorian(calendar: &str, year: i64, month: &str, day: i64) -> Result<String, String> { let (cal_name, jp_era) = match calendar.split_once(':') { Some((c, e)) => (c, Some(e.trim().to_lowercase())), None => (calendar, None) }; let (k, cal, default_era) = kind(cal_name)?; let era = match (cal, jp_era) { ("japanese", Some(e)) => JP_ERAS.iter().find(|(c, _, en)| *c == e || en.to_lowercase() == e).map(|x| x.0.to_string()).ok_or_else(|| format!("`{}` is not a Japanese era", e))?, ("japanese", None) => return Err("give the era for Japanese dates: \"japanese:reiwa\"".into()), _ => default_era.to_string(), }; let m = month.trim(); let code = if let Ok(n) = m.parse::<u32>() { format!("M{:02}", n) } else if m.len() >= 3 && m.starts_with('M') && m[1..3].chars().all(|c| c.is_ascii_digit()) { m.to_string() } else { let names: Vec<(String, String)> = match cal { "hebrew" => HEBREW.iter().map(|(c, n)| (c.to_string(), n.to_string())).collect(), "persian" => PERSIAN.iter().enumerate().map(|(i, n)| (format!("M{:02}", i + 1), n.to_string())).collect(), "ethiopian" => ETHIOPIAN.iter().enumerate().map(|(i, n)| (format!("M{:02}", i + 1), n.to_string())).collect(), "coptic" => COPTIC.iter().enumerate().map(|(i, n)| (format!("M{:02}", i + 1), n.to_string())).collect(), "indian" => INDIAN.iter().enumerate().map(|(i, n)| (format!("M{:02}", i + 1), n.to_string())).collect(), _ => GREGORIAN.iter().enumerate().map(|(i, n)| (format!("M{:02}", i + 1), n.to_string())).collect(), }; names.iter().find(|(_, n)| n.eq_ignore_ascii_case(m)).map(|(c, _)| c.clone()).ok_or_else(|| format!("`{}` is not a month of the {} calendar", m, cal))? }; if !(1..=31).contains(&day) { return Err(format!("day {} is out of range", day)) } let era_t: tinystr::TinyStr16 = era.parse().map_err(|_| "a bad era".to_string())?; let code_t: tinystr::TinyStr4 = code.parse().map_err(|_| format!("`{}` is not a month code", code))?; let d = Date::try_new_from_codes(Era(era_t), year as i32, MonthCode(code_t), day as u8, AnyCalendar::new(k)) .map_err(|e| format!("{} {} {} is not a {} date: {:?}", day, month, year, cal, e))?; let iso = d.to_iso(); Ok(format!("{:04}-{:02}-{:02}", iso.year().number, iso.month().ordinal, iso.day_of_month().0))}/// The Jewish holidays that begin in Gregorian `year`, each on its first full day (they start at sunset the evening before): a list of `{ name, date, hebrew_date }` from Rosh Hashanah to Tisha B'Av.pub fn calendars_jewish_holidays(year: i64) -> Result<Vec<Value>, String> { if !(1900..=2200).contains(&year) { return Err("years 1900 to 2200".into()) } let days: [(&str, &str, i64); 11] = [ ("Rosh Hashanah", "M01", 1), ("Yom Kippur", "M01", 10), ("Sukkot", "M01", 15), ("Shemini Atzeret", "M01", 22), ("Simchat Torah", "M01", 23), ("Hanukkah", "M03", 25), ("Tu BiShvat", "M05", 15), ("Purim", "M06", 14), ("Passover", "M07", 15), ("Shavuot", "M09", 6), ("Tisha B'Av", "M11", 9), ]; let mut out = Vec::new(); for hy in [year + 3760, year + 3761] { for (name, code, d) in days.iter() { let g = calendars_to_gregorian("hebrew", hy, code, *d)?; if g.starts_with(&format!("{:04}-", year)) { let mname = HEBREW.iter().find(|(c, _)| c == code).map(|x| x.1).unwrap_or(""); out.push(json!({ "name": name, "date": g, "hebrew_date": format!("{} {} {}", d, mname, hy) })); } } } out.sort_by(|a, b| a["date"].as_str().cmp(&b["date"].as_str())); Ok(out)}Related
hijri.qwer · 1.0.0Islamic (Hijri) dates: a Gregorian date in Umm al-Qura, civil or observational reckoning, written in English and Arabic; a Hijri date back to the Gregorian; a month's first and last days; and the year's holy days.7 views · 7 downloadsChinese calendar chinese_calendar.qwer · 1.0.0The Chinese lunisolar calendar: a date's lunar year, month and day in Chinese, leap months included, the year's stem and branch, zodiac animal and element, Chinese New Year, lunar dates back to Gregorian, and the 24 solar terms.8 views · 7 downloadsPublic holidays holidays.qwer · 1.0.0Public holidays of 27 countries for any year from 1970 to 2099, computed rather than listed: Easter and its feasts, "the last Monday of May", Japan's equinoxes, with the day each is taken when it falls on a weekend.8 views · 11 downloadsFluent translations fluent.qwer · 1.0.0Project Fluent translations (.ftl): messages formatted by locale with each language's plural forms, selectors on gender or anything else, terms and attributes, fallback from one locale to the next, and translation files checked and compared.8 views · 8 downloadsLanguages languages.qwer · 1.0.0ISO 639 language codes and names in English and in the language itself, two- and three-letter codes read, and the best of your translations picked from Accept-Language.7 views · 10 downloadsBusiness days business_days.qwer · 1.0.0Working days for due dates, delivery estimates and SLAs: add them, count them between dates, skip weekends and holidays, with US, UK, French and German public holidays built in.6 views · 5 downloadsThread 0 posts
On World calendars: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.