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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! # Beats
//!
//! Swatch internet time (.beats) crate for rust.
//!

extern crate chrono;

use std::fmt;
use std::cmp::Ordering;
use chrono::prelude::*;

/// Struct for representing a .beat
#[derive(Debug)]
pub struct Beat {
    trunc: f64,
    fract: f64
}

impl Beat {
    /// Create a Beat for the current time
    ///
    /// # Example
    /// ~~~
    /// use beats::Beat;
    ///
    /// let now = Beat::now();
    /// println!("It is currently: {}", now);
    /// ~~~
    pub fn now() -> Beat {
        Beat::from(Utc::now())
    }
}

impl fmt::Display for Beat {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "@{:07.3}", self.trunc + self.fract)
    }
}

impl<Tz: TimeZone> From<DateTime<Tz>> for Beat {
    fn from(time: DateTime<Tz>) -> Beat {
        calculate_beats(time)
    }
}

impl PartialEq for Beat {
    fn eq(&self, other: &Beat) -> bool {
        self.trunc == other.trunc && self.fract == other.fract
    }
}

impl PartialOrd for Beat {
    fn partial_cmp(&self, other: &Beat) -> Option<Ordering> {
        let my_time = &self.trunc + &self.fract;
        let other_time = &other.trunc + &other.fract;
        match (my_time <= other_time, my_time >= other_time) {
            (false, false) => None,
            (false, true) => Some(Ordering::Greater),
            (true, false) => Some(Ordering::Less),
            (true, true) => Some(Ordering::Equal),
        }
    }
}

fn calculate_beats<T: TimeZone>(time: DateTime<T>) -> Beat {
    // Correct the timezone to UTC because beats are the same time everywhere
    let time = time.with_timezone(&Utc);
    let offset = time.second() + ((time.minute() * 60) + ((time.hour() + 1) * 3600));
    let beats = offset as f64 / 86.4;
    let trunc = beats.trunc();
    let fract = beats.fract();

    wrap(Beat { trunc, fract })
}

fn wrap(beat: Beat) -> Beat {
    let trunc = if beat.trunc >= 1000.0 {
        beat.trunc - 1000.0
    } else {
        beat.trunc
    };

    Beat { trunc, fract: beat.fract }
}

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

    #[test]
    fn check_wrap() {
        let beat = Beat {
            trunc: 1000.0,
            fract: 0.001
        };

        assert_eq!(wrap(beat), Beat {trunc: 0.0, fract: 0.001});
    }

    #[test]
    fn from_time() {
        assert_eq!(Beat::from(Utc::now()), Beat::now());
        assert_eq!(Beat::from(Local::now()), Beat::now());
    }

    #[test]
    fn ordering() {
        let beat1 = Beat {
            trunc: 123.0,
            fract: 0.456
        };

        let beat2 = Beat {
            trunc: 456.0,
            fract: 0.123
        };

        assert!(beat1 < beat2);
        assert!(beat2 > beat1);
    }

    #[test]
    fn display() {
        let beat_nopad = Beat {
            trunc: 123.0,
            fract: 0.123
        };

        let beat_leftpad = Beat {
            trunc: 23.0,
            fract: 0.123
        };

        let beat_rightpad = Beat {
            trunc: 123.0,
            fract: 0.12
        };

        let beat_bothpad = Beat {
            trunc: 23.0,
            fract: 0.12
        };

        assert_eq!(format!("{}", beat_nopad), "@123.123");
        assert_eq!(format!("{}", beat_leftpad), "@023.123");
        assert_eq!(format!("{}", beat_rightpad), "@123.120");
        assert_eq!(format!("{}", beat_bothpad), "@023.120");
    }
}