1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
use earth_orbit::*;
use moon_phase::*;
use sun_transit::*;

/// Constructs a string representing the time in a geodate format
pub fn get_date(timestamp: i64, longitude: f64, use_solar_calendar: bool) -> String {
    let now = timestamp;
    let lon = longitude;

    let mut tom = now + 86400;
    let mut mid = get_midnight(now, lon);

    if mid > now {
      tom = now;
      mid = get_midnight(now - 86400, lon);
    }

    let n = 2 + (now / 86400 / 365) as usize;
    let k = if use_solar_calendar { 4 } else { 1 }; // Nb of events per year
    let mut seasonal_events = (1 * k .. n * k).map(|i| {
        // Approximate time of the next new year
        let new_year_timestamp = ((i / k) as f64 * 86400.0 * 365.25) as i64;

        // Arbitrary time half a year before that
        let mid_year_timestamp = new_year_timestamp - 180 * 86400;

        // Accurate time of the previous new year
        let new_year_timestamp = get_previous_december_solstice(mid_year_timestamp);

        match (i % k) + 4 - k {
            0 => get_next_march_equinox(new_year_timestamp),     // only if use_solar_calendar
            1 => get_next_june_solstice(new_year_timestamp),     // only if use_solar_calendar
            2 => get_next_september_equinox(new_year_timestamp), // only if use_solar_calendar
            3 => get_next_december_solstice(new_year_timestamp),
            _ => unreachable!()
        }
    });
    let mut next_seasonal_event = seasonal_events.next().unwrap();

    let m = n * 13;
    let mut new_moons = (0..m).map(|i| {
        // Lunations since the first new moon of January 2000
        let lunation_number = (i as f64) - 371.0;

        get_new_moon(lunation_number)
    });
    let mut new_moon = new_moons.next().unwrap();

    let mut d = 0;
    let mut m = 0;
    let mut y = 0;
    let mut t = get_midnight(0, lon);

    if t < 0 {
      t += 86400;
    }

    while t < mid - 2000 { // Mean solar day approximation
        d += 1;
        t += 86400;
        if use_solar_calendar {
            if next_seasonal_event < (t + 86400) { // New month
                next_seasonal_event = seasonal_events.next().unwrap();
                d = 0;
                m += 1;
                if m == 4 { // New year
                    m = 0;
                    y += 1;
                }
            }
        } else {
            if new_moon < (t + 86400) { // New month
                new_moon = new_moons.next().unwrap();
                d = 0;
                m += 1;
                if next_seasonal_event < (t + 86400) { // New year
                    next_seasonal_event = seasonal_events.next().unwrap();
                    m = 0;
                    y += 1;
                }
            }
        }
    }

    let e = (10000 * (now - mid)) / (get_midnight(tom, lon) - mid);
    let c = e / 100;
    let b = e % 100;

    format!("{:02}:{:02}:{:02}:{:02}:{:02}", y, m, d, c, b)
}

/// Constructs a string representing the time in a geodate format with a lunisolar calendar
pub fn get_lunisolar_date(timestamp: i64, longitude: f64) -> String {
    get_date(timestamp, longitude, false)
}

/// Constructs a string representing the time in a geodate format with a solar calendar
pub fn get_solar_date(timestamp: i64, longitude: f64) -> String {
    get_date(timestamp, longitude, true)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn get_solar_date_test() {
        // Stonehenge coordinates: 51.178844, -1.826189
        assert_eq!("44:02:00:15:42", get_solar_date(1403322675, -1.826189));
    }

    #[test]
    fn get_lunisolar_date_test() {
        assert_eq!("14:03:03:71:61", get_lunisolar_date(449947500, -2.7653));
    }
}