summaryrefslogtreecommitdiff
path: root/meap/meap-code/ch9
diff options
context:
space:
mode:
Diffstat (limited to 'meap/meap-code/ch9')
-rwxr-xr-xmeap/meap-code/ch9/Vagrantfile29
-rwxr-xr-xmeap/meap-code/ch9/ch9-clock0/Cargo.toml10
-rwxr-xr-xmeap/meap-code/ch9/ch9-clock0/src/main.rs8
-rwxr-xr-xmeap/meap-code/ch9/ch9-clock1/Cargo.toml11
-rwxr-xr-xmeap/meap-code/ch9/ch9-clock1/src/main.rs53
-rwxr-xr-xmeap/meap-code/ch9/ch9-clock2/Cargo.toml19
-rwxr-xr-xmeap/meap-code/ch9/ch9-clock2/src/main.rs132
-rwxr-xr-xmeap/meap-code/ch9/ch9-clock3/Cargo.toml19
-rwxr-xr-xmeap/meap-code/ch9/ch9-clock3/Dockerfile10
-rwxr-xr-xmeap/meap-code/ch9/ch9-clock3/src/main.rs333
-rwxr-xr-xmeap/meap-code/ch9/ch9-sao-paulo/Cargo.toml8
-rwxr-xr-xmeap/meap-code/ch9/ch9-sao-paulo/src/main.rs18
12 files changed, 650 insertions, 0 deletions
diff --git a/meap/meap-code/ch9/Vagrantfile b/meap/meap-code/ch9/Vagrantfile
new file mode 100755
index 0000000..8273611
--- /dev/null
+++ b/meap/meap-code/ch9/Vagrantfile
@@ -0,0 +1,29 @@
+VAGRANTFILE_VERSION = "2"
+
+$provision_external = <<SCRIPT
+sudo apt-get -qq update
+sudo apt-get install -q -y curl
+
+curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain nightly
+SCRIPT
+
+# $provision_internal = <<SCRIPT
+# cd ~
+# mkdir code
+# sudo mount -t vboxsf code ./code
+# SCRIPT
+
+
+Vagrant.configure(VAGRANTFILE_VERSION) do |config|
+
+ config.vm.provider "virtualbox" do |v|
+ v.name = "rust_in_action_ch09"
+ v.linked_clone = true
+ end
+
+ config.vm.box = "bento/ubuntu-17.04-i386"
+ config.vm.synced_folder ".", "/ch09/"
+ config.vm.post_up_message = "Hope that you're enjoying Rust in Action!\n-TS McNamara"
+ config.vm.provision "shell", inline: $provision_external
+ #config.vm.provision "shell", inline: $provision_internal
+end \ No newline at end of file
diff --git a/meap/meap-code/ch9/ch9-clock0/Cargo.toml b/meap/meap-code/ch9/ch9-clock0/Cargo.toml
new file mode 100755
index 0000000..1117955
--- /dev/null
+++ b/meap/meap-code/ch9/ch9-clock0/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "clock"
+version = "0.1.0"
+authors = ["Tim McNamara <code@timmcnamara.co.nz>"]
+
+[dependencies]
+chrono = "0.4"
+
+[profile.release]
+lto = true
diff --git a/meap/meap-code/ch9/ch9-clock0/src/main.rs b/meap/meap-code/ch9/ch9-clock0/src/main.rs
new file mode 100755
index 0000000..abdf9a6
--- /dev/null
+++ b/meap/meap-code/ch9/ch9-clock0/src/main.rs
@@ -0,0 +1,8 @@
+extern crate chrono;
+
+use chrono::{Local}; // timezone type
+
+fn main() {
+ let now = Local::now();
+ println!("{}", now);
+}
diff --git a/meap/meap-code/ch9/ch9-clock1/Cargo.toml b/meap/meap-code/ch9/ch9-clock1/Cargo.toml
new file mode 100755
index 0000000..34d4928
--- /dev/null
+++ b/meap/meap-code/ch9/ch9-clock1/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "clock"
+version = "0.1.1"
+authors = ["Tim McNamara <code@timmcnamara.co.nz>"]
+
+[dependencies]
+chrono = "0.4"
+clap = "2"
+
+[profile.release]
+lto = true
diff --git a/meap/meap-code/ch9/ch9-clock1/src/main.rs b/meap/meap-code/ch9/ch9-clock1/src/main.rs
new file mode 100755
index 0000000..7f3c941
--- /dev/null
+++ b/meap/meap-code/ch9/ch9-clock1/src/main.rs
@@ -0,0 +1,53 @@
+extern crate chrono;
+extern crate clap;
+
+use clap::{App,Arg};
+use chrono::{DateTime}; // date type
+use chrono::{Local}; // timezone types
+
+struct Clock;
+
+impl Clock {
+ fn get() -> DateTime<Local> {
+ Local::now()
+ }
+
+ fn set() -> ! {
+ unimplemented!()
+ }
+}
+
+fn main() {
+ let app = App::new("clock")
+ .version("0.1")
+ .about("Gets and sets (aspirationally) the time.")
+ .arg(Arg::with_name("action")
+ .takes_value(true)
+ .possible_values(&["get", "set"])
+ .default_value("get"))
+ .arg(Arg::with_name("std")
+ .short("s")
+ .long("use-standard")
+ .takes_value(true)
+ .possible_values(&["rfc2822", "rfc3339", "timestamp"])
+ .default_value("rfc3339"))
+ .arg(Arg::with_name("datetime")
+ .help("When <action> is 'set', apply <datetime>. Otherwise, ignore."));
+
+ let args = app.get_matches();
+
+ let action = args.value_of("std").unwrap(); // default_value() has been supplied,
+ let std = args.value_of("std").unwrap(); // so it's safe to use .unwrap()
+
+ if action == "set" {
+ unimplemented!() // break early
+ }
+
+ let now = Clock::get();
+ match std {
+ "timestamp" => println!("{}", now.timestamp()),
+ "rfc2822" => println!("{}", now.to_rfc2822()),
+ "rfc3339" => println!("{}", now.to_rfc3339()),
+ _ => unreachable!(),
+ }
+}
diff --git a/meap/meap-code/ch9/ch9-clock2/Cargo.toml b/meap/meap-code/ch9/ch9-clock2/Cargo.toml
new file mode 100755
index 0000000..04cf8df
--- /dev/null
+++ b/meap/meap-code/ch9/ch9-clock2/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "clock"
+version = "0.1.2"
+authors = ["Tim McNamara <code@timmcnamara.co.nz>"]
+
+[dependencies]
+byteorder = "1"
+chrono = "0.4"
+clap = "2.32"
+
+[target.'cfg(windows)'.dependencies]
+winapi = "0.2"
+kernel32-sys = "0.2"
+
+[target.'cfg(not(windows))'.dependencies]
+libc = "0.2"
+
+[profile.release]
+lto = true
diff --git a/meap/meap-code/ch9/ch9-clock2/src/main.rs b/meap/meap-code/ch9/ch9-clock2/src/main.rs
new file mode 100755
index 0000000..9b6968b
--- /dev/null
+++ b/meap/meap-code/ch9/ch9-clock2/src/main.rs
@@ -0,0 +1,132 @@
+extern crate chrono;
+extern crate clap;
+#[cfg(windows)]
+extern crate kernel32;
+#[cfg(not(windows))]
+extern crate libc;
+#[cfg(windows)]
+extern crate winapi;
+
+use chrono::DateTime; // date type
+use chrono::Weekday;
+use chrono::{Datelike, Timelike}; // traits that provides .year(), .month(), .second() ... methods
+use chrono::{FixedOffset, Local, NaiveDateTime, Utc}; // timezone types
+use clap::{App, Arg};
+use std::mem::zeroed;
+use std::thread::sleep;
+struct Clock;
+
+impl Clock {
+ fn get() -> DateTime<Local> {
+ Local::now()
+ }
+
+ #[cfg(windows)]
+ fn set<Tz: TimeZone>(t: DateTime<Tz>) -> () {
+ use chrono::Weekday;
+ use kernel32::SetSystemTime;
+ use winapi::{SYSTEMTIME, WORD};
+
+ let t = t.with_timezone(&Local);
+
+ let mut systime: SYSTEMTIME = unsafe { zeroed() };
+
+ let dow = match t.weekday() {
+ Weekday::Mon => 1,
+ Weekday::Tue => 2,
+ Weekday::Wed => 3,
+ Weekday::Thu => 4,
+ Weekday::Fri => 5,
+ Weekday::Sat => 6,
+ Weekday::Sun => 0,
+ };
+
+ let mut ns = t.nanosecond();
+ let is_leap_second = ns > 1_000_000_000;
+
+ if is_leap_second {
+ ns -= 1_000_000_000;
+ }
+
+ systime.wYear = t.year() as WORD;
+ systime.wMonth = t.month() as WORD;
+ systime.wDayOfWeek = dow as WORD;
+ systime.wDay = t.day() as WORD;
+ systime.wHour = t.hour() as WORD;
+ systime.wMinute = t.minute() as WORD;
+ systime.wSecond = t.second() as WORD;
+ systime.wMilliseconds = (ns / 1_000_000) as WORD;
+
+ let systime_ptr = &systime as *const SYSTEMTIME; // as *mut SYSTEMTIME; // convert to a pointer, then change its mutability
+
+ unsafe {
+ SetSystemTime(systime_ptr); // giving a pointer to a value to something outside of Rust's control is unsafe
+ }
+ }
+
+ #[cfg(not(windows))]
+ fn set<Tz: TimeZone>(t: DateTime<Tz>) -> () {
+ use libc::settimeofday;
+ use libc::{suseconds_t, time_t, timeval, timezone};
+
+ let t = t.with_timezone(&Local); // variable names indicate the data's progression
+ let mut u: timeval = unsafe { zeroed() }; // through the function
+
+ u.tv_sec = t.timestamp() as time_t;
+ u.tv_usec = t.timestamp_subsec_micros() as suseconds_t;
+
+ unsafe {
+ let mock_tz: *const timezone = std::ptr::null();
+ settimeofday(&u as *const timeval, mock_tz);
+ }
+ }
+}
+
+fn main() {
+ let app = App::new("clock")
+ .version("0.1.2")
+ .about("Gets and (aspirationally) sets the time.")
+ .after_help("Note: UNIX timestamps are parsed as whole seconds since 1st January 1970 0:00:00 UTC. For more accuracy, use another format.")
+ .arg(Arg::with_name("action")
+ .takes_value(true)
+ .possible_values(&["get", "set"])
+ .default_value("get"))
+ .arg(Arg::with_name("std")
+ .short("s")
+ .long("use-standard")
+ .takes_value(true)
+ .possible_values(&["rfc2822", "rfc3339", "timestamp"])
+ .default_value("rfc3339"))
+ .arg(Arg::with_name("datetime")
+ .help("When <action> is 'set', apply <datetime>. Otherwise, ignore."));
+
+ let args = app.get_matches();
+
+ let action = args.value_of("std").unwrap(); // default_value() has been supplied,
+ let std = args.value_of("std").unwrap(); // so it's safe to use .unwrap()
+
+ if action == "set" {
+ let t_ = args.value_of("datetime").unwrap();
+
+ let parser = match std {
+ "rfc2822" => DateTime::parse_from_rfc2822,
+ "rfc3339" => DateTime::parse_from_rfc3339,
+ //"rfc3339" => |secs:| NaiveDateTime,
+ _ => unimplemented!(),
+ };
+
+ let err_msg = format!("Unable to parse {} according to {}", t_, std);
+ let t = parser(t_).expect(&err_msg);
+
+ Clock::set(t)
+ }
+
+ let now = Clock::get();
+
+ match std {
+ "timestamp" => println!("{}", now.timestamp()),
+ "rfc2822" => println!("{}", now.to_rfc2822()),
+ "rfc3339" => println!("{}", now.to_rfc3339()),
+ _ => unreachable!(),
+ }
+}
diff --git a/meap/meap-code/ch9/ch9-clock3/Cargo.toml b/meap/meap-code/ch9/ch9-clock3/Cargo.toml
new file mode 100755
index 0000000..c0cb2d9
--- /dev/null
+++ b/meap/meap-code/ch9/ch9-clock3/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "clock"
+version = "0.1.3"
+authors = ["Tim McNamara <code@timmcnamara.co.nz>"]
+
+[dependencies]
+byteorder = "1"
+chrono = "0.4"
+clap = "2.32"
+
+[target.'cfg(windows)'.dependencies]
+winapi = "0.2"
+kernel32-sys = "0.2"
+
+[target.'cfg(not(windows))'.dependencies]
+libc = "0.2"
+
+[profile.release]
+lto = true
diff --git a/meap/meap-code/ch9/ch9-clock3/Dockerfile b/meap/meap-code/ch9/ch9-clock3/Dockerfile
new file mode 100755
index 0000000..36055d0
--- /dev/null
+++ b/meap/meap-code/ch9/ch9-clock3/Dockerfile
@@ -0,0 +1,10 @@
+FROM rust:latest
+
+WORKDIR /opt/clock
+COPY . .
+
+# docker run --rm --user "$(id -u)":"$(id -g)" -v "$PWD":/opt/clock -w /opt/clock rust:latest cargo build --release
+
+# RUN cargo build
+
+# CMD ["clock"]
diff --git a/meap/meap-code/ch9/ch9-clock3/src/main.rs b/meap/meap-code/ch9/ch9-clock3/src/main.rs
new file mode 100755
index 0000000..ad3ee9f
--- /dev/null
+++ b/meap/meap-code/ch9/ch9-clock3/src/main.rs
@@ -0,0 +1,333 @@
+extern crate byteorder;
+extern crate chrono;
+extern crate clap;
+#[cfg(not(windows))] extern crate libc;
+#[cfg(windows)] extern crate kernel32;
+#[cfg(windows)] extern crate winapi;
+
+use byteorder::{BigEndian, ReadBytesExt};
+use chrono::DateTime;
+use chrono::Duration as ChronoDuration;
+use chrono::{Datelike, TimeZone, Timelike};
+use chrono::{Local, Utc};
+use clap::{App, Arg};
+use std::mem::zeroed;
+use std::net::UdpSocket;
+use std::time::Duration;
+
+const NTP_MESSAGE_LENGTH: usize = 48; // 12*4 bytes
+const NTP_TO_UNIX_SECONDS: i64 = 2_208_988_800;
+const LOCAL_ADDR: &'static str = "0.0.0.0:12300";
+
+#[derive(Default, Debug, Copy, Clone)]
+struct NTPTimestamp {
+ seconds: u32,
+ fraction: u32,
+}
+
+struct NTPMessage {
+ data: [u8; NTP_MESSAGE_LENGTH],
+}
+
+#[derive(Debug)]
+struct NTPResult {
+ t1: DateTime<Utc>,
+ t2: DateTime<Utc>,
+ t3: DateTime<Utc>,
+ t4: DateTime<Utc>,
+}
+
+impl NTPResult {
+ fn offset(&self) -> i64 {
+ let duration = (self.t2 - self.t1) + (self.t3 - self.t4);
+ duration.num_milliseconds() / 2
+ }
+
+ fn delay(&self) -> i64 {
+ let duration = (self.t4 - self.t1) - (self.t3 - self.t2);
+ duration.num_milliseconds()
+ }
+}
+
+impl From<NTPTimestamp> for DateTime<Utc> {
+ fn from(ntp: NTPTimestamp) -> Self {
+ let secs = ntp.seconds as i64 - NTP_TO_UNIX_SECONDS;
+ let mut nanos = ntp.fraction as f64;
+ nanos *= 1e9;
+ nanos /= 2_f64.powi(32);
+
+ Utc.timestamp(secs, nanos as u32)
+ }
+}
+
+impl From<DateTime<Utc>> for NTPTimestamp {
+ fn from(utc: DateTime<Utc>) -> Self {
+ let secs = utc.timestamp() + NTP_TO_UNIX_SECONDS;
+ let mut fraction = utc.nanosecond() as f64;
+ fraction *= 2_f64.powi(32);
+ fraction /= 1e9;
+
+ NTPTimestamp {
+ seconds: secs as u32,
+ fraction: fraction as u32,
+ }
+ }
+}
+
+impl NTPMessage {
+ fn new() -> Self {
+ NTPMessage {
+ data: [0; NTP_MESSAGE_LENGTH],
+ }
+ }
+
+ fn client() -> Self {
+ const VERSION: u8 = 3;
+ const MODE: u8 = 3;
+
+ let mut msg = NTPMessage::new();
+
+ // the first byte of every NTP message contains three fields,
+ // but we only need to set two of them
+ msg.data[0] |= VERSION << 3;
+ msg.data[0] |= MODE;
+ msg
+ }
+
+ fn parse_timestamp(&self, i: usize) -> Result<NTPTimestamp, std::io::Error> {
+ let mut reader = &self.data[i..i + 8];
+ let seconds = reader.read_u32::<BigEndian>()?;
+ let fraction = reader.read_u32::<BigEndian>()?;
+
+ Ok(NTPTimestamp {
+ seconds: seconds,
+ fraction: fraction,
+ })
+ }
+
+ fn rx_time(&self) -> Result<NTPTimestamp, std::io::Error> {
+ self.parse_timestamp(32)
+ }
+
+ fn tx_time(&self) -> Result<NTPTimestamp, std::io::Error> {
+ self.parse_timestamp(40)
+ }
+}
+
+fn weighted_mean(values: &[f64], weights: &[f64]) -> f64 {
+ let mut result = 0.0;
+ let mut sum_of_weights = 0.0;
+
+ for (v, w) in values.iter().zip(weights) {
+ result += v * w;
+ sum_of_weights += w;
+ }
+
+ result / sum_of_weights
+}
+
+fn ntp_roundtrip(host: &str, port: u16) -> Result<NTPResult, std::io::Error> {
+ let destination = format!("{}:{}", host, port);
+ let timeout = Duration::from_secs(1);
+
+ let request = NTPMessage::client();
+ let mut response = NTPMessage::new();
+
+ let message = request.data;
+
+ let udp = UdpSocket::bind(LOCAL_ADDR)?;
+ udp.connect(&destination).expect("unable to connect");
+
+ let t1 = Utc::now();
+ udp.send(&message)?;
+ udp.set_read_timeout(Some(timeout))?;
+ udp.recv_from(&mut response.data)?;
+ let t4 = Utc::now();
+
+ let t2: DateTime<Utc> = response.rx_time().unwrap().into();
+ let t3: DateTime<Utc> = response.tx_time().unwrap().into();
+
+ Ok(NTPResult {
+ t1: t1,
+ t2: t2,
+ t3: t3,
+ t4: t4,
+ })
+}
+
+fn check_time() -> Result<f64, std::io::Error> {
+ const NTP_PORT: u16 = 123;
+
+ let servers = [
+ "time.nist.gov",
+ "time.apple.com",
+ "time.euro.apple.com",
+ "time.google.com",
+ "time2.google.com",
+ //"time.windows.com",
+ ];
+
+ let mut times = Vec::with_capacity(servers.len());
+
+ for &server in servers.iter() {
+ print!("{} =>", server);
+
+ let calc = ntp_roundtrip(&server, NTP_PORT);
+
+ match calc {
+ Ok(time) => {
+ println!(" {}ms away from local system time", time.offset());
+ times.push(time);
+ }
+ Err(_) => println!(" ? [response took too long]"),
+ };
+ }
+
+ let mut offsets = Vec::with_capacity(servers.len());
+ let mut offset_weights = Vec::with_capacity(servers.len());
+
+ for time in &times {
+ let offset = time.offset() as f64;
+ let delay = time.delay() as f64;
+
+ let weight = 1_000_000.0 / (delay * delay); // <3>
+ if weight.is_finite() {
+ offsets.push(offset);
+ offset_weights.push(weight);
+ }
+ }
+
+ let avg_offset = weighted_mean(&offsets, &offset_weights);
+
+ Ok(avg_offset)
+}
+
+struct Clock;
+
+impl Clock {
+ fn get() -> DateTime<Local> {
+ Local::now()
+ }
+
+ #[cfg(windows)]
+ fn set<Tz: TimeZone>(t: DateTime<Tz>) -> () {
+ use chrono::Weekday;
+ use kernel32::SetSystemTime;
+ use winapi::{SYSTEMTIME, WORD};
+
+ let t = t.with_timezone(&Local);
+
+ let mut systime: SYSTEMTIME = unsafe { zeroed() };
+
+ let dow = match t.weekday() {
+ Weekday::Mon => 1,
+ Weekday::Tue => 2,
+ Weekday::Wed => 3,
+ Weekday::Thu => 4,
+ Weekday::Fri => 5,
+ Weekday::Sat => 6,
+ Weekday::Sun => 0,
+ };
+
+ let mut ns = t.nanosecond();
+ let is_leap_second = ns > 1_000_000_000;
+
+ if is_leap_second {
+ ns -= 1_000_000_000;
+ }
+
+ systime.wYear = t.year() as WORD;
+ systime.wMonth = t.month() as WORD;
+ systime.wDayOfWeek = dow as WORD;
+ systime.wDay = t.day() as WORD;
+ systime.wHour = t.hour() as WORD;
+ systime.wMinute = t.minute() as WORD;
+ systime.wSecond = t.second() as WORD;
+ systime.wMilliseconds = (ns / 1_000_000) as WORD;
+
+ let systime_ptr = &systime as *const SYSTEMTIME; // as *mut SYSTEMTIME; // convert to a pointer, then change its mutability
+
+ unsafe {
+ SetSystemTime(systime_ptr); // giving a pointer to a value to something outside of Rust's control is unsafe
+ }
+ }
+
+ #[cfg(not(windows))]
+ fn set<Tz: TimeZone>(t: DateTime<Tz>) -> () {
+ use libc::settimeofday;
+ use libc::{suseconds_t, time_t, timeval, timezone};
+
+ let t = t.with_timezone(&Local); // variable names indicate the data's progression
+ let mut u: timeval = unsafe { zeroed() }; // through the function
+
+ u.tv_sec = t.timestamp() as time_t;
+ u.tv_usec = t.timestamp_subsec_micros() as suseconds_t;
+
+ unsafe {
+ let mock_tz: *const timezone = std::ptr::null();
+ settimeofday(&u as *const timeval, mock_tz);
+ }
+ }
+}
+
+fn main() {
+ let app = App::new("clock")
+ .version("0.1.3")
+ .about("Gets and sets the time.")
+ .after_help("Note: UNIX timestamps are parsed as whole seconds since 1st January 1970 0:00:00 UTC. For more accuracy, use another format.")
+ .arg(Arg::with_name("action")
+ .takes_value(true)
+ .possible_values(&["get", "set", "check-ntp"])
+ .default_value("get"))
+ .arg(Arg::with_name("std")
+ .short("s")
+ .long("use-standard")
+ .takes_value(true)
+ .possible_values(&["rfc2822", "rfc3339", "timestamp"])
+ .default_value("rfc3339"))
+ .arg(Arg::with_name("datetime")
+ .help("When <action> is 'set', apply <datetime>. Otherwise, ignore."));
+
+ let args = app.get_matches();
+
+ let action = args.value_of("action").unwrap(); // default_value() has been supplied,
+ let std = args.value_of("std").unwrap(); // so it's safe to use .unwrap()
+
+ if action == "set" {
+ let t_ = args.value_of("datetime").unwrap();
+
+ let parser = match std {
+ "rfc2822" => DateTime::parse_from_rfc2822,
+ "rfc3339" => DateTime::parse_from_rfc3339,
+ _ => unimplemented!(),
+ };
+
+ let err_msg = format!("Unable to parse {} according to {}", t_, std);
+ let t = parser(t_).expect(&err_msg);
+
+ Clock::set(t);
+ } else if action == "check-ntp" {
+ let offset = check_time().unwrap() as isize;
+ let adjust_ms = offset.signum() * offset.abs().min(200) / 5;
+ let adjust_ms_ = ChronoDuration::milliseconds(adjust_ms as i64);
+ let now: DateTime<Utc> = Utc::now() + adjust_ms_;
+ Clock::set(now);
+ }
+
+ let maybe_error = std::io::Error::last_os_error();
+ let os_error_code = &maybe_error.raw_os_error();
+ match os_error_code {
+ Some(0) => (),
+ None => (),
+ _ => eprintln!("Unable to set the time: {:?}", maybe_error),
+ }
+
+ let now = Clock::get();
+
+ match std {
+ "timestamp" => println!("{}", now.timestamp()),
+ "rfc2822" => println!("{}", now.to_rfc2822()),
+ "rfc3339" => println!("{}", now.to_rfc3339()),
+ _ => unreachable!(),
+ }
+}
diff --git a/meap/meap-code/ch9/ch9-sao-paulo/Cargo.toml b/meap/meap-code/ch9/ch9-sao-paulo/Cargo.toml
new file mode 100755
index 0000000..57ebe1c
--- /dev/null
+++ b/meap/meap-code/ch9/ch9-sao-paulo/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "ch9-sao-paulo"
+version = "0.1.0"
+authors = ["Tim McNamara <code@timmcnamara.co.nz>"]
+
+[dependencies]
+chrono = "0.4"
+chrono-tz = "0.4" \ No newline at end of file
diff --git a/meap/meap-code/ch9/ch9-sao-paulo/src/main.rs b/meap/meap-code/ch9/ch9-sao-paulo/src/main.rs
new file mode 100755
index 0000000..e26c49a
--- /dev/null
+++ b/meap/meap-code/ch9/ch9-sao-paulo/src/main.rs
@@ -0,0 +1,18 @@
+extern crate chrono;
+extern crate chrono_tz;
+
+use chrono::prelude::*;
+use chrono::{Duration};
+use chrono_tz::America::Sao_Paulo;
+
+fn main() {
+ let t = Utc.timestamp(1431648000, 0);
+ assert_eq!(t.to_string(), "2015-05-15 00:00:00 UTC");
+ println!("{:?}", t);
+
+
+ let sp1 = Sao_Paulo.ymd(1996, 2, 11).and_hms(23, 59, 59);
+ let sp2 = Sao_Paulo.ymd(1996, 2, 11).and_hms(23, 59, 59) + Duration::seconds(1);
+ println!("{} {}", sp1.to_string(), sp1.timestamp());
+ println!("{} {}", sp2.to_string(), sp2.timestamp(), );
+} \ No newline at end of file