const ISLAMIC_EPOCH: i64 = 1_948_440;
#[must_use]
pub fn gregorian_to_jdn(year: i64, month: i64, day: i64) -> i64 {
let a = (14 - month) / 12;
let y = year + 4800 - a;
let m = month + 12 * a - 3;
day + (153 * m + 2) / 5 + 365 * y + y / 4 - y / 100 + y / 400 - 32045
}
#[must_use]
pub fn jdn_to_gregorian(jdn: i64) -> (i64, i64, i64) {
let a = jdn + 32044;
let b = (4 * a + 3) / 146097;
let c = a - 146097 * b / 4;
let d = (4 * c + 3) / 1461;
let e = c - 1461 * d / 4;
let m = (5 * e + 2) / 153;
let day = e - (153 * m + 2) / 5 + 1;
let month = m + 3 - 12 * (m / 10);
let year = 100 * b + d - 4800 + m / 10;
(year, month, day)
}
#[must_use]
pub fn islamic_to_jdn(year: i64, month: i64, day: i64) -> i64 {
let before = 29 * (month - 1) + month / 2;
day + before + (year - 1) * 354 + (3 + 11 * year) / 30 + ISLAMIC_EPOCH - 1
}
#[must_use]
pub fn jdn_to_islamic(jdn: i64) -> (i64, i64, i64) {
let mut year = (30 * (jdn - ISLAMIC_EPOCH) + 10646) / 10631;
if year < 1 {
year = 1;
}
while islamic_to_jdn(year, 1, 1) > jdn {
year -= 1;
}
while islamic_to_jdn(year + 1, 1, 1) <= jdn {
year += 1;
}
let mut month = 1;
while month < 12 && islamic_to_jdn(year, month + 1, 1) <= jdn {
month += 1;
}
let day = jdn - islamic_to_jdn(year, month, 1) + 1;
(year, month, day)
}
const PERSIAN_EPOCH: i64 = 1_948_321;
#[must_use]
pub fn persian_to_jdn(year: i64, month: i64, day: i64) -> i64 {
let epbase = if year >= 0 { year - 474 } else { year - 473 };
let epyear = 474 + epbase.rem_euclid(2820);
let month_days = if month <= 7 {
(month - 1) * 31
} else {
(month - 1) * 30 + 6
};
day + month_days
+ (epyear * 682 - 110) / 2816
+ (epyear - 1) * 365
+ epbase.div_euclid(2820) * 1_029_983
+ (PERSIAN_EPOCH - 1)
}
#[must_use]
pub fn jdn_to_persian(jdn: i64) -> (i64, i64, i64) {
let mut year = jdn_to_gregorian(jdn).0 - 621;
while persian_to_jdn(year, 1, 1) > jdn {
year -= 1;
}
while persian_to_jdn(year + 1, 1, 1) <= jdn {
year += 1;
}
let mut month = 1;
while month < 12 && persian_to_jdn(year, month + 1, 1) <= jdn {
month += 1;
}
let day = jdn - persian_to_jdn(year, month, 1) + 1;
(year, month, day)
}
#[must_use]
pub fn gregorian_to_persian(year: i64, month: i64, day: i64) -> (i64, i64, i64) {
jdn_to_persian(gregorian_to_jdn(year, month, day))
}
#[must_use]
pub fn persian_to_gregorian(year: i64, month: i64, day: i64) -> (i64, i64, i64) {
jdn_to_gregorian(persian_to_jdn(year, month, day))
}
#[must_use]
pub fn gregorian_to_islamic(year: i64, month: i64, day: i64) -> (i64, i64, i64) {
jdn_to_islamic(gregorian_to_jdn(year, month, day))
}
#[must_use]
pub fn islamic_to_gregorian(year: i64, month: i64, day: i64) -> (i64, i64, i64) {
jdn_to_gregorian(islamic_to_jdn(year, month, day))
}
const HEBREW_EPOCH_RD: i64 = -1_373_427;
const RD_TO_JDN: i64 = 1_721_425;
fn hebrew_leap(year: i64) -> bool {
(7 * year + 1).rem_euclid(19) < 7
}
fn hebrew_year_months(year: i64) -> i64 {
if hebrew_leap(year) {
13
} else {
12
}
}
fn hebrew_elapsed_days(year: i64) -> i64 {
let months = (235 * year - 234).div_euclid(19);
let parts = 12084 + 13753 * months;
let day = 29 * months + parts.div_euclid(25920);
if (3 * (day + 1)).rem_euclid(7) < 3 {
day + 1
} else {
day
}
}
fn hebrew_new_year_rd(year: i64) -> i64 {
let correction = {
let (a, b, c) = (
hebrew_elapsed_days(year - 1),
hebrew_elapsed_days(year),
hebrew_elapsed_days(year + 1),
);
if c - b == 356 {
2
} else if b - a == 382 {
1
} else {
0
}
};
HEBREW_EPOCH_RD + hebrew_elapsed_days(year) + correction
}
fn hebrew_year_days(year: i64) -> i64 {
hebrew_new_year_rd(year + 1) - hebrew_new_year_rd(year)
}
fn hebrew_month_days(year: i64, month: i64) -> i64 {
match month {
2 | 4 | 6 | 10 | 13 => 29,
8 => {
if matches!(hebrew_year_days(year), 355 | 385) {
30
} else {
29
}
} 9 => {
if matches!(hebrew_year_days(year), 353 | 383) {
29
} else {
30
}
} 12 => {
if hebrew_leap(year) {
30
} else {
29
}
} _ => 30, }
}
fn hebrew_to_rd(year: i64, month: i64, day: i64) -> i64 {
let mut rd = hebrew_new_year_rd(year) + day - 1;
if month < 7 {
for m in 7..=hebrew_year_months(year) {
rd += hebrew_month_days(year, m);
}
for m in 1..month {
rd += hebrew_month_days(year, m);
}
} else {
for m in 7..month {
rd += hebrew_month_days(year, m);
}
}
rd
}
#[must_use]
pub fn hebrew_to_jdn(year: i64, month: i64, day: i64) -> i64 {
hebrew_to_rd(year, month, day) + RD_TO_JDN
}
#[must_use]
pub fn jdn_to_hebrew(jdn: i64) -> (i64, i64, i64) {
let rd = jdn - RD_TO_JDN;
let mut year = (rd - HEBREW_EPOCH_RD) * 98496 / 35_975_351 + 1;
while hebrew_new_year_rd(year + 1) <= rd {
year += 1;
}
while hebrew_new_year_rd(year) > rd {
year -= 1;
}
let start = if rd < hebrew_to_rd(year, 1, 1) { 7 } else { 1 };
let mut month = start;
while rd > hebrew_to_rd(year, month, hebrew_month_days(year, month)) {
month += 1;
}
let day = rd - hebrew_to_rd(year, month, 1) + 1;
(year, month, day)
}
#[must_use]
pub fn gregorian_to_hebrew(year: i64, month: i64, day: i64) -> (i64, i64, i64) {
jdn_to_hebrew(gregorian_to_jdn(year, month, day))
}
#[must_use]
pub fn hebrew_to_gregorian(year: i64, month: i64, day: i64) -> (i64, i64, i64) {
jdn_to_gregorian(hebrew_to_jdn(year, month, day))
}
const CHINESE_FIRST_YEAR: i64 = 1900;
const CHINESE_LAST_YEAR: i64 = 2099;
const CHINESE_EPOCH_JDN: i64 = 2_415_051; #[rustfmt::skip]
const CHINESE_YEAR_INFO: [u32; 200] = [
0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5d0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0,
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b5a0, 0x195a6,
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0,
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,
0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0,
0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06aa0, 0x1a6c4, 0x0aae0,
0x092e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4,
0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0,
0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160,
0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252,
];
fn cn_info(year: i64) -> Option<u32> {
if (CHINESE_FIRST_YEAR..=CHINESE_LAST_YEAR).contains(&year) {
Some(CHINESE_YEAR_INFO[(year - CHINESE_FIRST_YEAR) as usize])
} else {
None
}
}
fn cn_month_days(info: u32, month: i64) -> i64 {
((info >> (16 - month)) & 1) as i64 + 29
}
fn cn_leap_days(info: u32) -> i64 {
((info >> 16) & 1) as i64 + 29
}
fn cn_year_days(info: u32) -> i64 {
let leap = (info % 16) as i64;
let mut sum = 0;
let mut m = 1;
while m <= 12 {
sum += cn_month_days(info, m);
if leap == m {
sum += cn_leap_days(info);
}
m += 1;
}
sum
}
#[must_use]
pub fn chinese_to_jdn(year: i64, month: i64, day: i64, leap: bool) -> Option<i64> {
let info = cn_info(year)?;
if leap && (info % 16) as i64 != month {
return None;
}
let mut jdn = CHINESE_EPOCH_JDN;
let mut y = CHINESE_FIRST_YEAR;
while y < year {
jdn += cn_year_days(cn_info(y)?);
y += 1;
}
let leap_m = (info % 16) as i64;
let mut m = 1;
while m <= 12 {
if m == month && !leap {
return Some(jdn + day - 1);
}
jdn += cn_month_days(info, m);
if leap_m == m {
if m == month && leap {
return Some(jdn + day - 1);
}
jdn += cn_leap_days(info);
}
m += 1;
}
None
}
#[must_use]
pub fn jdn_to_chinese(jdn: i64) -> Option<(i64, i64, i64, bool)> {
let mut offset = jdn - CHINESE_EPOCH_JDN;
if offset < 0 {
return None;
}
let mut year = CHINESE_FIRST_YEAR;
loop {
let yd = cn_year_days(cn_info(year)?);
if offset < yd {
break;
}
offset -= yd;
year += 1;
}
let info = cn_info(year)?;
let leap_m = (info % 16) as i64;
let mut m = 1;
while m <= 12 {
let d = cn_month_days(info, m);
if offset < d {
return Some((year, m, offset + 1, false));
}
offset -= d;
if leap_m == m {
let dl = cn_leap_days(info);
if offset < dl {
return Some((year, m, offset + 1, true));
}
offset -= dl;
}
m += 1;
}
None
}
#[must_use]
pub fn gregorian_to_chinese(year: i64, month: i64, day: i64) -> Option<(i64, i64, i64, bool)> {
jdn_to_chinese(gregorian_to_jdn(year, month, day))
}
#[must_use]
pub fn chinese_to_gregorian(
year: i64,
month: i64,
day: i64,
leap: bool,
) -> Option<(i64, i64, i64)> {
Some(jdn_to_gregorian(chinese_to_jdn(year, month, day, leap)?))
}
#[must_use]
pub fn japanese_era(year: i64, month: i64, day: i64) -> (&'static str, i64) {
const ERAS: [(i64, i64, i64, &str); 5] = [
(1868, 10, 23, "Meiji"),
(1912, 7, 30, "Taisho"),
(1926, 12, 25, "Showa"),
(1989, 1, 8, "Heisei"),
(2019, 5, 1, "Reiwa"),
];
for &(sy, sm, sd, name) in ERAS.iter().rev() {
if (year, month, day) >= (sy, sm, sd) {
return (name, year - sy + 1);
}
}
("CE", year)
}
#[must_use]
pub fn day_of_week(year: i64, month: i64, day: i64) -> u8 {
(gregorian_to_jdn(year, month, day).rem_euclid(7) + 1) as u8
}
#[must_use]
pub fn iso_week(year: i64, month: i64, day: i64) -> (i64, u8, u8) {
let jdn = gregorian_to_jdn(year, month, day);
let weekday = jdn.rem_euclid(7) + 1; let thursday = jdn - (weekday - 4);
let (iso_year, _, _) = jdn_to_gregorian(thursday);
let jan4 = gregorian_to_jdn(iso_year, 1, 4);
let jan4_weekday = jan4.rem_euclid(7) + 1;
let week1_monday = jan4 - (jan4_weekday - 1);
let week = ((jdn - week1_monday) / 7 + 1) as u8;
(iso_year, week, weekday as u8)
}