diff options
| author | Arcterus <Arcterus@mail.com> | 2014-02-19 19:08:12 -0800 |
|---|---|---|
| committer | Arcterus <Arcterus@mail.com> | 2014-02-21 07:44:11 -0800 |
| commit | 66f93291ec62682b48ef2411295843a04cbfd8e5 (patch) | |
| tree | a1cc53bec3b432d5662a4489489d0418d8fa5266 /src/libtime | |
| parent | 2fa7d6b44fcc329e849f4dd43e11c6fdd43ebd76 (diff) | |
Move time out of extra (cc #8784)
Diffstat (limited to 'src/libtime')
| -rw-r--r-- | src/libtime/lib.rs | 1484 |
1 files changed, 1484 insertions, 0 deletions
diff --git a/src/libtime/lib.rs b/src/libtime/lib.rs new file mode 100644 index 00000000000..1f0a0686658 --- /dev/null +++ b/src/libtime/lib.rs @@ -0,0 +1,1484 @@ +// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[crate_id = "time#0.10-pre"]; +#[crate_type = "rlib"]; +#[crate_type = "dylib"]; +#[license = "MIT/ASL2"]; + +#[allow(missing_doc)]; + +extern crate serialize; + +use std::io::BufReader; +use std::libc; +use std::num; +use std::str; + +static NSEC_PER_SEC: i32 = 1_000_000_000_i32; + +mod rustrt { + use super::Tm; + + extern { + pub fn rust_tzset(); + pub fn rust_gmtime(sec: i64, nsec: i32, result: &mut Tm); + pub fn rust_localtime(sec: i64, nsec: i32, result: &mut Tm); + pub fn rust_timegm(tm: &Tm) -> i64; + pub fn rust_mktime(tm: &Tm) -> i64; + } +} + +#[cfg(unix, not(target_os = "macos"))] +mod imp { + use std::libc::{c_int, timespec}; + + // Apparently android provides this in some other library? + #[cfg(not(target_os = "android"))] + #[link(name = "rt")] + extern {} + + extern { + pub fn clock_gettime(clk_id: c_int, tp: *mut timespec) -> c_int; + } + +} +#[cfg(target_os = "macos")] +mod imp { + use std::libc::{timeval, timezone, c_int, mach_timebase_info}; + + extern { + pub fn gettimeofday(tp: *mut timeval, tzp: *mut timezone) -> c_int; + pub fn mach_absolute_time() -> u64; + pub fn mach_timebase_info(info: *mut mach_timebase_info) -> c_int; + } +} + +/// A record specifying a time value in seconds and nanoseconds. + + +#[deriving(Clone, DeepClone, Eq, Encodable, Decodable)] +pub struct Timespec { sec: i64, nsec: i32 } +/* + * Timespec assumes that pre-epoch Timespecs have negative sec and positive + * nsec fields. Darwin's and Linux's struct timespec functions handle pre- + * epoch timestamps using a "two steps back, one step forward" representation, + * though the man pages do not actually document this. For example, the time + * -1.2 seconds before the epoch is represented by `Timespec { sec: -2_i64, + * nsec: 800_000_000_i32 }`. + */ +impl Timespec { + pub fn new(sec: i64, nsec: i32) -> Timespec { + assert!(nsec >= 0 && nsec < NSEC_PER_SEC); + Timespec { sec: sec, nsec: nsec } + } +} + +impl Ord for Timespec { + fn lt(&self, other: &Timespec) -> bool { + self.sec < other.sec || + (self.sec == other.sec && self.nsec < other.nsec) + } +} + +/** + * Returns the current time as a `timespec` containing the seconds and + * nanoseconds since 1970-01-01T00:00:00Z. + */ +pub fn get_time() -> Timespec { + unsafe { + let (sec, nsec) = os_get_time(); + return Timespec::new(sec, nsec); + } + + #[cfg(windows)] + unsafe fn os_get_time() -> (i64, i32) { + static NANOSECONDS_FROM_1601_TO_1970: u64 = 11644473600000000; + + let mut time = libc::FILETIME { + dwLowDateTime: 0, + dwHighDateTime: 0, + }; + libc::GetSystemTimeAsFileTime(&mut time); + + // A FILETIME contains a 64-bit value representing the number of + // hectonanosecond (100-nanosecond) intervals since 1601-01-01T00:00:00Z. + // http://support.microsoft.com/kb/167296/en-us + let ns_since_1601 = ((time.dwHighDateTime as u64 << 32) | + (time.dwLowDateTime as u64 << 0)) / 10; + let ns_since_1970 = ns_since_1601 - NANOSECONDS_FROM_1601_TO_1970; + + ((ns_since_1970 / 1000000) as i64, + ((ns_since_1970 % 1000000) * 1000) as i32) + } + + #[cfg(target_os = "macos")] + unsafe fn os_get_time() -> (i64, i32) { + use std::ptr; + let mut tv = libc::timeval { tv_sec: 0, tv_usec: 0 }; + imp::gettimeofday(&mut tv, ptr::mut_null()); + (tv.tv_sec as i64, tv.tv_usec * 1000) + } + + #[cfg(not(target_os = "macos"), not(windows))] + unsafe fn os_get_time() -> (i64, i32) { + let mut tv = libc::timespec { tv_sec: 0, tv_nsec: 0 }; + imp::clock_gettime(libc::CLOCK_REALTIME, &mut tv); + (tv.tv_sec as i64, tv.tv_nsec as i32) + } +} + + +/** + * Returns the current value of a high-resolution performance counter + * in nanoseconds since an unspecified epoch. + */ +pub fn precise_time_ns() -> u64 { + return os_precise_time_ns(); + + #[cfg(windows)] + fn os_precise_time_ns() -> u64 { + let mut ticks_per_s = 0; + assert_eq!(unsafe { + libc::QueryPerformanceFrequency(&mut ticks_per_s) + }, 1); + let ticks_per_s = if ticks_per_s == 0 {1} else {ticks_per_s}; + let mut ticks = 0; + assert_eq!(unsafe { + libc::QueryPerformanceCounter(&mut ticks) + }, 1); + + return (ticks as u64 * 1000000000) / (ticks_per_s as u64); + } + + #[cfg(target_os = "macos")] + fn os_precise_time_ns() -> u64 { + let time = unsafe { imp::mach_absolute_time() }; + let mut info = libc::mach_timebase_info { numer: 0, denom: 0 }; + unsafe { imp::mach_timebase_info(&mut info); } + return time * ((info.numer / info.denom) as u64); + } + + #[cfg(not(windows), not(target_os = "macos"))] + fn os_precise_time_ns() -> u64 { + let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 }; + unsafe { + imp::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts); + } + return (ts.tv_sec as u64) * 1000000000 + (ts.tv_nsec as u64) + } +} + + +/** + * Returns the current value of a high-resolution performance counter + * in seconds since an unspecified epoch. + */ +pub fn precise_time_s() -> f64 { + return (precise_time_ns() as f64) / 1000000000.; +} + +pub fn tzset() { + unsafe { + rustrt::rust_tzset(); + } +} + +#[deriving(Clone, DeepClone, Eq, Encodable, Decodable)] +pub struct Tm { + tm_sec: i32, // seconds after the minute ~[0-60] + tm_min: i32, // minutes after the hour ~[0-59] + tm_hour: i32, // hours after midnight ~[0-23] + tm_mday: i32, // days of the month ~[1-31] + tm_mon: i32, // months since January ~[0-11] + tm_year: i32, // years since 1900 + tm_wday: i32, // days since Sunday ~[0-6] + tm_yday: i32, // days since January 1 ~[0-365] + tm_isdst: i32, // Daylight Savings Time flag + tm_gmtoff: i32, // offset from UTC in seconds + tm_zone: ~str, // timezone abbreviation + tm_nsec: i32, // nanoseconds +} + +pub fn empty_tm() -> Tm { + // 64 is the max size of the timezone buffer allocated on windows + // in rust_localtime. In glibc the max timezone size is supposedly 3. + let zone = str::with_capacity(64); + Tm { + tm_sec: 0_i32, + tm_min: 0_i32, + tm_hour: 0_i32, + tm_mday: 0_i32, + tm_mon: 0_i32, + tm_year: 0_i32, + tm_wday: 0_i32, + tm_yday: 0_i32, + tm_isdst: 0_i32, + tm_gmtoff: 0_i32, + tm_zone: zone, + tm_nsec: 0_i32, + } +} + +/// Returns the specified time in UTC +pub fn at_utc(clock: Timespec) -> Tm { + unsafe { + let Timespec { sec, nsec } = clock; + let mut tm = empty_tm(); + rustrt::rust_gmtime(sec, nsec, &mut tm); + tm + } +} + +/// Returns the current time in UTC +pub fn now_utc() -> Tm { + at_utc(get_time()) +} + +/// Returns the specified time in the local timezone +pub fn at(clock: Timespec) -> Tm { + unsafe { + let Timespec { sec, nsec } = clock; + let mut tm = empty_tm(); + rustrt::rust_localtime(sec, nsec, &mut tm); + tm + } +} + +/// Returns the current time in the local timezone +pub fn now() -> Tm { + at(get_time()) +} + + +impl Tm { + /// Convert time to the seconds from January 1, 1970 + pub fn to_timespec(&self) -> Timespec { + unsafe { + let sec = match self.tm_gmtoff { + 0_i32 => rustrt::rust_timegm(self), + _ => rustrt::rust_mktime(self) + }; + + Timespec::new(sec, self.tm_nsec) + } + } + + /// Convert time to the local timezone + pub fn to_local(&self) -> Tm { + at(self.to_timespec()) + } + + /// Convert time to the UTC + pub fn to_utc(&self) -> Tm { + at_utc(self.to_timespec()) + } + + /** + * Return a string of the current time in the form + * "Thu Jan 1 00:00:00 1970". + */ + pub fn ctime(&self) -> ~str { self.strftime("%c") } + + /// Formats the time according to the format string. + pub fn strftime(&self, format: &str) -> ~str { + strftime(format, self) + } + + /** + * Returns a time string formatted according to RFC 822. + * + * local: "Thu, 22 Mar 2012 07:53:18 PST" + * utc: "Thu, 22 Mar 2012 14:53:18 UTC" + */ + pub fn rfc822(&self) -> ~str { + if self.tm_gmtoff == 0_i32 { + self.strftime("%a, %d %b %Y %T GMT") + } else { + self.strftime("%a, %d %b %Y %T %Z") + } + } + + /** + * Returns a time string formatted according to RFC 822 with Zulu time. + * + * local: "Thu, 22 Mar 2012 07:53:18 -0700" + * utc: "Thu, 22 Mar 2012 14:53:18 -0000" + */ + pub fn rfc822z(&self) -> ~str { + self.strftime("%a, %d %b %Y %T %z") + } + + /** + * Returns a time string formatted according to ISO 8601. + * + * local: "2012-02-22T07:53:18-07:00" + * utc: "2012-02-22T14:53:18Z" + */ + pub fn rfc3339(&self) -> ~str { + if self.tm_gmtoff == 0_i32 { + self.strftime("%Y-%m-%dT%H:%M:%SZ") + } else { + let s = self.strftime("%Y-%m-%dT%H:%M:%S"); + let sign = if self.tm_gmtoff > 0_i32 { '+' } else { '-' }; + let mut m = num::abs(self.tm_gmtoff) / 60_i32; + let h = m / 60_i32; + m -= h * 60_i32; + s + format!("{}{:02d}:{:02d}", sign, h as int, m as int) + } + } +} + +/// Parses the time from the string according to the format string. +pub fn strptime(s: &str, format: &str) -> Result<Tm, ~str> { + fn match_str(s: &str, pos: uint, needle: &str) -> bool { + let mut i = pos; + for ch in needle.bytes() { + if s[i] != ch { + return false; + } + i += 1u; + } + return true; + } + + fn match_strs(ss: &str, pos: uint, strs: &[(~str, i32)]) + -> Option<(i32, uint)> { + let mut i = 0u; + let len = strs.len(); + while i < len { + match strs[i] { // can't use let due to let-pattern bugs + (ref needle, value) => { + if match_str(ss, pos, *needle) { + return Some((value, pos + needle.len())); + } + } + } + i += 1u; + } + + None + } + + fn match_digits(ss: &str, pos: uint, digits: uint, ws: bool) + -> Option<(i32, uint)> { + let mut pos = pos; + let len = ss.len(); + let mut value = 0_i32; + + let mut i = 0u; + while i < digits { + if pos >= len { + return None; + } + let range = ss.char_range_at(pos); + pos = range.next; + + match range.ch { + '0' .. '9' => { + value = value * 10_i32 + (range.ch as i32 - '0' as i32); + } + ' ' if ws => (), + _ => return None + } + i += 1u; + } + + Some((value, pos)) + } + + fn match_fractional_seconds(ss: &str, pos: uint) -> (i32, uint) { + let len = ss.len(); + let mut value = 0_i32; + let mut multiplier = NSEC_PER_SEC / 10; + let mut pos = pos; + + loop { + if pos >= len { + break; + } + let range = ss.char_range_at(pos); + + match range.ch { + '0' .. '9' => { + pos = range.next; + // This will drop digits after the nanoseconds place + let digit = range.ch as i32 - '0' as i32; + value += digit * multiplier; + multiplier /= 10; + } + _ => break + } + } + + (value, pos) + } + + fn match_digits_in_range(ss: &str, pos: uint, digits: uint, ws: bool, + min: i32, max: i32) -> Option<(i32, uint)> { + match match_digits(ss, pos, digits, ws) { + Some((val, pos)) if val >= min && val <= max => { + Some((val, pos)) + } + _ => None + } + } + + fn parse_char(s: &str, pos: uint, c: char) -> Result<uint, ~str> { + let range = s.char_range_at(pos); + + if c == range.ch { + Ok(range.next) + } else { + Err(format!("Expected {}, found {}", + str::from_char(c), + str::from_char(range.ch))) + } + } + + fn parse_type(s: &str, pos: uint, ch: char, tm: &mut Tm) + -> Result<uint, ~str> { + match ch { + 'A' => match match_strs(s, pos, [ + (~"Sunday", 0_i32), + (~"Monday", 1_i32), + (~"Tuesday", 2_i32), + (~"Wednesday", 3_i32), + (~"Thursday", 4_i32), + (~"Friday", 5_i32), + (~"Saturday", 6_i32) + ]) { + Some(item) => { let (v, pos) = item; tm.tm_wday = v; Ok(pos) } + None => Err(~"Invalid day") + }, + 'a' => match match_strs(s, pos, [ + (~"Sun", 0_i32), + (~"Mon", 1_i32), + (~"Tue", 2_i32), + (~"Wed", 3_i32), + (~"Thu", 4_i32), + (~"Fri", 5_i32), + (~"Sat", 6_i32) + ]) { + Some(item) => { let (v, pos) = item; tm.tm_wday = v; Ok(pos) } + None => Err(~"Invalid day") + }, + 'B' => match match_strs(s, pos, [ + (~"January", 0_i32), + (~"February", 1_i32), + (~"March", 2_i32), + (~"April", 3_i32), + (~"May", 4_i32), + (~"June", 5_i32), + (~"July", 6_i32), + (~"August", 7_i32), + (~"September", 8_i32), + (~"October", 9_i32), + (~"November", 10_i32), + (~"December", 11_i32) + ]) { + Some(item) => { let (v, pos) = item; tm.tm_mon = v; Ok(pos) } + None => Err(~"Invalid month") + }, + 'b' | 'h' => match match_strs(s, pos, [ + (~"Jan", 0_i32), + (~"Feb", 1_i32), + (~"Mar", 2_i32), + (~"Apr", 3_i32), + (~"May", 4_i32), + (~"Jun", 5_i32), + (~"Jul", 6_i32), + (~"Aug", 7_i32), + (~"Sep", 8_i32), + (~"Oct", 9_i32), + (~"Nov", 10_i32), + (~"Dec", 11_i32) + ]) { + Some(item) => { let (v, pos) = item; tm.tm_mon = v; Ok(pos) } + None => Err(~"Invalid month") + }, + 'C' => match match_digits_in_range(s, pos, 2u, false, 0_i32, + 99_i32) { + Some(item) => { + let (v, pos) = item; + tm.tm_year += (v * 100_i32) - 1900_i32; + Ok(pos) + } + None => Err(~"Invalid year") + }, + 'c' => { + parse_type(s, pos, 'a', &mut *tm) + .and_then(|pos| parse_char(s, pos, ' ')) + .and_then(|pos| parse_type(s, pos, 'b', &mut *tm)) + .and_then(|pos| parse_char(s, pos, ' ')) + .and_then(|pos| parse_type(s, pos, 'e', &mut *tm)) + .and_then(|pos| parse_char(s, pos, ' ')) + .and_then(|pos| parse_type(s, pos, 'T', &mut *tm)) + .and_then(|pos| parse_char(s, pos, ' ')) + .and_then(|pos| parse_type(s, pos, 'Y', &mut *tm)) + } + 'D' | 'x' => { + parse_type(s, pos, 'm', &mut *tm) + .and_then(|pos| parse_char(s, pos, '/')) + .and_then(|pos| parse_type(s, pos, 'd', &mut *tm)) + .and_then(|pos| parse_char(s, pos, '/')) + .and_then(|pos| parse_type(s, pos, 'y', &mut *tm)) + } + 'd' => match match_digits_in_range(s, pos, 2u, false, 1_i32, + 31_i32) { + Some(item) => { let (v, pos) = item; tm.tm_mday = v; Ok(pos) } + None => Err(~"Invalid day of the month") + }, + 'e' => match match_digits_in_range(s, pos, 2u, true, 1_i32, + 31_i32) { + Some(item) => { let (v, pos) = item; tm.tm_mday = v; Ok(pos) } + None => Err(~"Invalid day of the month") + }, + 'f' => { + let (val, pos) = match_fractional_seconds(s, pos); + tm.tm_nsec = val; + Ok(pos) + } + 'F' => { + parse_type(s, pos, 'Y', &mut *tm) + .and_then(|pos| parse_char(s, pos, '-')) + .and_then(|pos| parse_type(s, pos, 'm', &mut *tm)) + .and_then(|pos| parse_char(s, pos, '-')) + .and_then(|pos| parse_type(s, pos, 'd', &mut *tm)) + } + 'H' => { + match match_digits_in_range(s, pos, 2u, false, 0_i32, 23_i32) { + Some(item) => { let (v, pos) = item; tm.tm_hour = v; Ok(pos) } + None => Err(~"Invalid hour") + } + } + 'I' => { + match match_digits_in_range(s, pos, 2u, false, 1_i32, 12_i32) { + Some(item) => { + let (v, pos) = item; + tm.tm_hour = if v == 12_i32 { 0_i32 } else { v }; + Ok(pos) + } + None => Err(~"Invalid hour") + } + } + 'j' => { + match match_digits_in_range(s, pos, 3u, false, 1_i32, 366_i32) { + Some(item) => { + let (v, pos) = item; + tm.tm_yday = v - 1_i32; + Ok(pos) + } + None => Err(~"Invalid day of year") + } + } + 'k' => { + match match_digits_in_range(s, pos, 2u, true, 0_i32, 23_i32) { + Some(item) => { let (v, pos) = item; tm.tm_hour = v; Ok(pos) } + None => Err(~"Invalid hour") + } + } + 'l' => { + match match_digits_in_range(s, pos, 2u, true, 1_i32, 12_i32) { + Some(item) => { + let (v, pos) = item; + tm.tm_hour = if v == 12_i32 { 0_i32 } else { v }; + Ok(pos) + } + None => Err(~"Invalid hour") + } + } + 'M' => { + match match_digits_in_range(s, pos, 2u, false, 0_i32, 59_i32) { + Some(item) => { let (v, pos) = item; tm.tm_min = v; Ok(pos) } + None => Err(~"Invalid minute") + } + } + 'm' => { + match match_digits_in_range(s, pos, 2u, false, 1_i32, 12_i32) { + Some(item) => { + let (v, pos) = item; + tm.tm_mon = v - 1_i32; + Ok(pos) + } + None => Err(~"Invalid month") + } + } + 'n' => parse_char(s, pos, '\n'), + 'P' => match match_strs(s, pos, + [(~"am", 0_i32), (~"pm", 12_i32)]) { + + Some(item) => { let (v, pos) = item; tm.tm_hour += v; Ok(pos) } + None => Err(~"Invalid hour") + }, + 'p' => match match_strs(s, pos, + [(~"AM", 0_i32), (~"PM", 12_i32)]) { + + Some(item) => { let (v, pos) = item; tm.tm_hour += v; Ok(pos) } + None => Err(~"Invalid hour") + }, + 'R' => { + parse_type(s, pos, 'H', &mut *tm) + .and_then(|pos| parse_char(s, pos, ':')) + .and_then(|pos| parse_type(s, pos, 'M', &mut *tm)) + } + 'r' => { + parse_type(s, pos, 'I', &mut *tm) + .and_then(|pos| parse_char(s, pos, ':')) + .and_then(|pos| parse_type(s, pos, 'M', &mut *tm)) + .and_then(|pos| parse_char(s, pos, ':')) + .and_then(|pos| parse_type(s, pos, 'S', &mut *tm)) + .and_then(|pos| parse_char(s, pos, ' ')) + .and_then(|pos| parse_type(s, pos, 'p', &mut *tm)) + } + 'S' => { + match match_digits_in_range(s, pos, 2u, false, 0_i32, 60_i32) { + Some(item) => { + let (v, pos) = item; + tm.tm_sec = v; + Ok(pos) + } + None => Err(~"Invalid second") + } + } + //'s' {} + 'T' | 'X' => { + parse_type(s, pos, 'H', &mut *tm) + .and_then(|pos| parse_char(s, pos, ':')) + .and_then(|pos| parse_type(s, pos, 'M', &mut *tm)) + .and_then(|pos| parse_char(s, pos, ':')) + .and_then(|pos| parse_type(s, pos, 'S', &mut *tm)) + } + 't' => parse_char(s, pos, '\t'), + 'u' => { + match match_digits_in_range(s, pos, 1u, false, 1_i32, 7_i32) { + Some(item) => { + let (v, pos) = item; + tm.tm_wday = if v == 7 { 0 } else { v }; + Ok(pos) + } + None => Err(~"Invalid day of week") + } + } + 'v' => { + parse_type(s, pos, 'e', &mut *tm) + .and_then(|pos| parse_char(s, pos, '-')) + .and_then(|pos| parse_type(s, pos, 'b', &mut *tm)) + .and_then(|pos| parse_char(s, pos, '-')) + .and_then(|pos| parse_type(s, pos, 'Y', &mut *tm)) + } + //'W' {} + 'w' => { + match match_digits_in_range(s, pos, 1u, false, 0_i32, 6_i32) { + Some(item) => { let (v, pos) = item; tm.tm_wday = v; Ok(pos) } + None => Err(~"Invalid day of week") + } + } + 'Y' => { + match match_digits(s, pos, 4u, false) { + Some(item) => { + let (v, pos) = item; + tm.tm_year = v - 1900_i32; + Ok(pos) + } + None => Err(~"Invalid year") + } + } + 'y' => { + match match_digits_in_range(s, pos, 2u, false, 0_i32, 99_i32) { + Some(item) => { + let (v, pos) = item; + tm.tm_year = v; + Ok(pos) + } + None => Err(~"Invalid year") + } + } + 'Z' => { + if match_str(s, pos, "UTC") || match_str(s, pos, "GMT") { + tm.tm_gmtoff = 0_i32; + tm.tm_zone = ~"UTC"; + Ok(pos + 3u) + } else { + // It's odd, but to maintain compatibility with c's + // strptime we ignore the timezone. + let mut pos = pos; + let len = s.len(); + while pos < len { + let range = s.char_range_at(pos); + pos = range.next; + if range.ch == ' ' { break; } + } + + Ok(pos) + } + } + 'z' => { + let range = s.char_range_at(pos); + + if range.ch == '+' || range.ch == '-' { + match match_digits(s, range.next, 4u, false) { + Some(item) => { + let (v, pos) = item; + if v == 0_i32 { + tm.tm_gmtoff = 0_i32; + tm.tm_zone = ~"UTC"; + } + + Ok(pos) + } + None => Err(~"Invalid zone offset") + } + } else { + Err(~"Invalid zone offset") + } + } + '%' => parse_char(s, pos, '%'), + ch => { + Err(format!("unknown formatting type: {}", str::from_char(ch))) + } + } + } + + let mut rdr = BufReader::new(format.as_bytes()); + let mut tm = Tm { + tm_sec: 0_i32, + tm_min: 0_i32, + tm_hour: 0_i32, + tm_mday: 0_i32, + tm_mon: 0_i32, + tm_year: 0_i32, + tm_wday: 0_i32, + tm_yday: 0_i32, + tm_isdst: 0_i32, + tm_gmtoff: 0_i32, + tm_zone: ~"", + tm_nsec: 0_i32, + }; + let mut pos = 0u; + let len = s.len(); + let mut result = Err(~"Invalid time"); + + while pos < len { + let range = s.char_range_at(pos); + let ch = range.ch; + let next = range.next; + + let mut buf = [0]; + let c = match rdr.read(buf) { + Ok(..) => buf[0] as char, + Err(..) => break + }; + match c { + '%' => { + let ch = match rdr.read(buf) { + Ok(..) => buf[0] as char, + Err(..) => break + }; + match parse_type(s, pos, ch, &mut tm) { + Ok(next) => pos = next, + Err(e) => { result = Err(e); break; } + } + }, + c => { + if c != ch { break } + pos = next; + } + } + } + + if pos == len && rdr.tell().unwrap() == format.len() as u64 { + Ok(Tm { + tm_sec: tm.tm_sec, + tm_min: tm.tm_min, + tm_hour: tm.tm_hour, + tm_mday: tm.tm_mday, + tm_mon: tm.tm_mon, + tm_year: tm.tm_year, + tm_wday: tm.tm_wday, + tm_yday: tm.tm_yday, + tm_isdst: tm.tm_isdst, + tm_gmtoff: tm.tm_gmtoff, + tm_zone: tm.tm_zone.clone(), + tm_nsec: tm.tm_nsec, + }) + } else { result } +} + +/// Formats the time according to the format string. +pub fn strftime(format: &str, tm: &Tm) -> ~str { + fn days_in_year(year: int) -> i32 { + if (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)) { + 366 /* Days in a leap year */ + } else { + 365 /* Days in a non-leap year */ + } + } + + fn iso_week_days(yday: i32, wday: i32) -> int { + /* The number of days from the first day of the first ISO week of this + * year to the year day YDAY with week day WDAY. + * ISO weeks start on Monday. The first ISO week has the year's first + * Thursday. + * YDAY may be as small as yday_minimum. + */ + let yday: int = yday as int; + let wday: int = wday as int; + let iso_week_start_wday: int = 1; /* Monday */ + let iso_week1_wday: int = 4; /* Thursday */ + let yday_minimum: int = 366; + /* Add enough to the first operand of % to make it nonnegative. */ + let big_enough_multiple_of_7: int = (yday_minimum / 7 + 2) * 7; + + yday - (yday - wday + iso_week1_wday + big_enough_multiple_of_7) % 7 + + iso_week1_wday - iso_week_start_wday + } + + fn iso_week(ch:char, tm: &Tm) -> ~str { + let mut year: int = tm.tm_year as int + 1900; + let mut days: int = iso_week_days (tm.tm_yday, tm.tm_wday); + + if days < 0 { + /* This ISO week belongs to the previous year. */ + year -= 1; + days = iso_week_days (tm.tm_yday + (days_in_year(year)), tm.tm_wday); + } else { + let d: int = iso_week_days (tm.tm_yday - (days_in_year(year)), + tm.tm_wday); + if 0 <= d { + /* This ISO week belongs to the next year. */ + year += 1; + days = d; + } + } + + match ch { + 'G' => format!("{}", year), + 'g' => format!("{:02d}", (year % 100 + 100) % 100), + 'V' => format!("{:02d}", days / 7 + 1), + _ => ~"" + } + } + + fn parse_type(ch: char, tm: &Tm) -> ~str { + let die = || format!("strftime: can't understand this format {} ", ch); + match ch { + 'A' => match tm.tm_wday as int { + 0 => ~"Sunday", + 1 => ~"Monday", + 2 => ~"Tuesday", + 3 => ~"Wednesday", + 4 => ~"Thursday", + 5 => ~"Friday", + 6 => ~"Saturday", + _ => die() + }, + 'a' => match tm.tm_wday as int { + 0 => ~"Sun", + 1 => ~"Mon", + 2 => ~"Tue", + 3 => ~"Wed", + 4 => ~"Thu", + 5 => ~"Fri", + 6 => ~"Sat", + _ => die() + }, + 'B' => match tm.tm_mon as int { + 0 => ~"January", + 1 => ~"February", + 2 => ~"March", + 3 => ~"April", + 4 => ~"May", + 5 => ~"June", + 6 => ~"July", + 7 => ~"August", + 8 => ~"September", + 9 => ~"October", + 10 => ~"November", + 11 => ~"December", + _ => die() + }, + 'b' | 'h' => match tm.tm_mon as int { + 0 => ~"Jan", + 1 => ~"Feb", + 2 => ~"Mar", + 3 => ~"Apr", + 4 => ~"May", + 5 => ~"Jun", + 6 => ~"Jul", + 7 => ~"Aug", + 8 => ~"Sep", + 9 => ~"Oct", + 10 => ~"Nov", + 11 => ~"Dec", + _ => die() + }, + 'C' => format!("{:02d}", (tm.tm_year as int + 1900) / 100), + 'c' => { + format!("{} {} {} {} {}", + parse_type('a', tm), + parse_type('b', tm), + parse_type('e', tm), + parse_type('T', tm), + parse_type('Y', tm)) + } + 'D' | 'x' => { + format!("{}/{}/{}", + parse_type('m', tm), + parse_type('d', tm), + parse_type('y', tm)) + } + 'd' => format!("{:02d}", tm.tm_mday), + 'e' => format!("{:2d}", tm.tm_mday), + 'f' => format!("{:09d}", tm.tm_nsec), + 'F' => { + format!("{}-{}-{}", + parse_type('Y', tm), + parse_type('m', tm), + parse_type('d', tm)) + } + 'G' => iso_week('G', tm), + 'g' => iso_week('g', tm), + 'H' => format!("{:02d}", tm.tm_hour), + 'I' => { + let mut h = tm.tm_hour; + if h == 0 { h = 12 } + if h > 12 { h -= 12 } + format!("{:02d}", h) + } + 'j' => format!("{:03d}", tm.tm_yday + 1), + 'k' => format!("{:2d}", tm.tm_hour), + 'l' => { + let mut h = tm.tm_hour; + if h == 0 { h = 12 } + if h > 12 { h -= 12 } + format!("{:2d}", h) + } + 'M' => format!("{:02d}", tm.tm_min), + 'm' => format!("{:02d}", tm.tm_mon + 1), + 'n' => ~"\n", + 'P' => if (tm.tm_hour as int) < 12 { ~"am" } else { ~"pm" }, + 'p' => if (tm.tm_hour as int) < 12 { ~"AM" } else { ~"PM" }, + 'R' => { + format!("{}:{}", + parse_type('H', tm), + parse_type('M', tm)) + } + 'r' => { + format!("{}:{}:{} {}", + parse_type('I', tm), + parse_type('M', tm), + parse_type('S', tm), + parse_type('p', tm)) + } + 'S' => format!("{:02d}", tm.tm_sec), + 's' => format!("{}", tm.to_timespec().sec), + 'T' | 'X' => { + format!("{}:{}:{}", + parse_type('H', tm), + parse_type('M', tm), + parse_type('S', tm)) + } + 't' => ~"\t", + 'U' => format!("{:02d}", (tm.tm_yday - tm.tm_wday + 7) / 7), + 'u' => { + let i = tm.tm_wday as int; + (if i == 0 { 7 } else { i }).to_str() + } + 'V' => iso_week('V', tm), + 'v' => { + format!("{}-{}-{}", + parse_type('e', tm), + parse_type('b', tm), + parse_type('Y', tm)) + } + 'W' => format!("{:02d}", (tm.tm_yday - (tm.tm_wday - 1 + 7) % 7 + 7) + / 7), + 'w' => (tm.tm_wday as int).to_str(), + 'Y' => (tm.tm_year as int + 1900).to_str(), + 'y' => format!("{:02d}", (tm.tm_year as int + 1900) % 100), + 'Z' => tm.tm_zone.clone(), + 'z' => { + let sign = if tm.tm_gmtoff > 0_i32 { '+' } else { '-' }; + let mut m = num::abs(tm.tm_gmtoff) / 60_i32; + let h = m / 60_i32; + m -= h * 60_i32; + format!("{}{:02d}{:02d}", sign, h, m) + } + '+' => tm.rfc3339(), + '%' => ~"%", + _ => die() + } + } + + let mut buf = ~[]; + + let mut rdr = BufReader::new(format.as_bytes()); + loop { + let mut b = [0]; + let ch = match rdr.read(b) { + Ok(..) => b[0], + Err(..) => break, + }; + match ch as char { + '%' => { + rdr.read(b).unwrap(); + let s = parse_type(b[0] as char, tm); + buf.push_all(s.as_bytes()); + } + ch => buf.push(ch as u8) + } + } + + str::from_utf8_owned(buf).unwrap() +} + +#[cfg(test)] +mod tests { + use super::{Timespec, get_time, precise_time_ns, precise_time_s, tzset, + at_utc, at, strptime}; + + use std::f64; + use std::result::{Err, Ok}; + + #[cfg(windows)] + fn set_time_zone() { + use std::libc; + // Windows crt doesn't see any environment variable set by + // `SetEnvironmentVariable`, which `os::setenv` internally uses. + // It is why we use `putenv` here. + extern { + fn _putenv(envstring: *libc::c_char) -> libc::c_int; + } + + unsafe { + // Windows does not understand "America/Los_Angeles". + // PST+08 may look wrong, but not! "PST" indicates + // the name of timezone. "+08" means UTC = local + 08. + "TZ=PST+08".with_c_str(|env| { + _putenv(env); + }) + } + tzset(); + } + #[cfg(not(windows))] + fn set_time_zone() { + use std::os; + os::setenv("TZ", "America/Los_Angeles"); + tzset(); + } + + fn test_get_time() { + static SOME_RECENT_DATE: i64 = 1325376000i64; // 2012-01-01T00:00:00Z + static SOME_FUTURE_DATE: i64 = 1577836800i64; // 2020-01-01T00:00:00Z + + let tv1 = get_time(); + debug!("tv1={:?} sec + {:?} nsec", tv1.sec as uint, tv1.nsec as uint); + + assert!(tv1.sec > SOME_RECENT_DATE); + assert!(tv1.nsec < 1000000000i32); + + let tv2 = get_time(); + debug!("tv2={:?} sec + {:?} nsec", tv2.sec as uint, tv2.nsec as uint); + + assert!(tv2.sec >= tv1.sec); + assert!(tv2.sec < SOME_FUTURE_DATE); + assert!(tv2.nsec < 1000000000i32); + if tv2.sec == tv1.sec { + assert!(tv2.nsec >= tv1.nsec); + } + } + + fn test_precise_time() { + let s0 = precise_time_s(); + debug!("s0={} sec", f64::to_str_digits(s0, 9u)); + assert!(s0 > 0.); + + let ns0 = precise_time_ns(); + let ns1 = precise_time_ns(); + debug!("ns0={:?} ns", ns0); + debug!("ns1={:?} ns", ns1); + assert!(ns1 >= ns0); + + let ns2 = precise_time_ns(); + debug!("ns2={:?} ns", ns2); + assert!(ns2 >= ns1); + } + + fn test_at_utc() { + set_time_zone(); + + let time = Timespec::new(1234567890, 54321); + let utc = at_utc(time); + + assert_eq!(utc.tm_sec, 30_i32); + assert_eq!(utc.tm_min, 31_i32); + assert_eq!(utc.tm_hour, 23_i32); + assert_eq!(utc.tm_mday, 13_i32); + assert_eq!(utc.tm_mon, 1_i32); + assert_eq!(utc.tm_year, 109_i32); + assert_eq!(utc.tm_wday, 5_i32); + assert_eq!(utc.tm_yday, 43_i32); + assert_eq!(utc.tm_isdst, 0_i32); + assert_eq!(utc.tm_gmtoff, 0_i32); + assert_eq!(utc.tm_zone, ~"UTC"); + assert_eq!(utc.tm_nsec, 54321_i32); + } + + fn test_at() { + set_time_zone(); + + let time = Timespec::new(1234567890, 54321); + let local = at(time); + + error!("time_at: {:?}", local); + + assert_eq!(local.tm_sec, 30_i32); + assert_eq!(local.tm_min, 31_i32); + assert_eq!(local.tm_hour, 15_i32); + assert_eq!(local.tm_mday, 13_i32); + assert_eq!(local.tm_mon, 1_i32); + assert_eq!(local.tm_year, 109_i32); + assert_eq!(local.tm_wday, 5_i32); + assert_eq!(local.tm_yday, 43_i32); + assert_eq!(local.tm_isdst, 0_i32); + assert_eq!(local.tm_gmtoff, -28800_i32); + + // FIXME (#2350): We should probably standardize on the timezone + // abbreviation. + let zone = &local.tm_zone; + assert!(*zone == ~"PST" || *zone == ~"Pacific Standard Time"); + + assert_eq!(local.tm_nsec, 54321_i32); + } + + fn test_to_timespec() { + set_time_zone(); + + let time = Timespec::new(1234567890, 54321); + let utc = at_utc(time); + + assert_eq!(utc.to_timespec(), time); + assert_eq!(utc.to_local().to_timespec(), time); + } + + fn test_conversions() { + set_time_zone(); + + let time = Timespec::new(1234567890, 54321); + let utc = at_utc(time); + let local = at(time); + + assert!(local.to_local() == local); + assert!(local.to_utc() == utc); + assert!(local.to_utc().to_local() == local); + assert!(utc.to_utc() == utc); + assert!(utc.to_local() == local); + assert!(utc.to_local().to_utc() == utc); + } + + fn test_strptime() { + set_time_zone(); + + match strptime("", "") { + Ok(ref tm) => { + assert!(tm.tm_sec == 0_i32); + assert!(tm.tm_min == 0_i32); + assert!(tm.tm_hour == 0_i32); + assert!(tm.tm_mday == 0_i32); + assert!(tm.tm_mon == 0_i32); + assert!(tm.tm_year == 0_i32); + assert!(tm.tm_wday == 0_i32); + assert!(tm.tm_isdst == 0_i32); + assert!(tm.tm_gmtoff == 0_i32); + assert!(tm.tm_zone == ~""); + assert!(tm.tm_nsec == 0_i32); + } + Err(_) => () + } + + let format = "%a %b %e %T.%f %Y"; + assert_eq!(strptime("", format), Err(~"Invalid time")); + assert!(strptime("Fri Feb 13 15:31:30", format) + == Err(~"Invalid time")); + + match strptime("Fri Feb 13 15:31:30.01234 2009", format) { + Err(e) => fail!(e), + Ok(ref tm) => { + assert!(tm.tm_sec == 30_i32); + assert!(tm.tm_min == 31_i32); + assert!(tm.tm_hour == 15_i32); + assert!(tm.tm_mday == 13_i32); + assert!(tm.tm_mon == 1_i32); + assert!(tm.tm_year == 109_i32); + assert!(tm.tm_wday == 5_i32); + assert!(tm.tm_yday == 0_i32); + assert!(tm.tm_isdst == 0_i32); + assert!(tm.tm_gmtoff == 0_i32); + assert!(tm.tm_zone == ~""); + assert!(tm.tm_nsec == 12340000_i32); + } + } + + fn test(s: &str, format: &str) -> bool { + match strptime(s, format) { + Ok(ref tm) => tm.strftime(format) == s.to_owned(), + Err(e) => fail!(e) + } + } + + let days = [ + ~"Sunday", + ~"Monday", + ~"Tuesday", + ~"Wednesday", + ~"Thursday", + ~"Friday", + ~"Saturday" + ]; + for day in days.iter() { + assert!(test(*day, "%A")); + } + + let days = [ + ~"Sun", + ~"Mon", + ~"Tue", + ~"Wed", + ~"Thu", + ~"Fri", + ~"Sat" + ]; + for day in days.iter() { + assert!(test(*day, "%a")); + } + + let months = [ + ~"January", + ~"February", + ~"March", + ~"April", + ~"May", + ~"June", + ~"July", + ~"August", + ~"September", + ~"October", + ~"November", + ~"December" + ]; + for day in months.iter() { + assert!(test(*day, "%B")); + } + + let months = [ + ~"Jan", + ~"Feb", + ~"Mar", + ~"Apr", + ~"May", + ~"Jun", + ~"Jul", + ~"Aug", + ~"Sep", + ~"Oct", + ~"Nov", + ~"Dec" + ]; + for day in months.iter() { + assert!(test(*day, "%b")); + } + + assert!(test("19", "%C")); + assert!(test("Fri Feb 13 23:31:30 2009", "%c")); + assert!(test("02/13/09", "%D")); + assert!(test("03", "%d")); + assert!(test("13", "%d")); + assert!(test(" 3", "%e")); + assert!(test("13", "%e")); + assert!(test("2009-02-13", "%F")); + assert!(test("03", "%H")); + assert!(test("13", "%H")); + assert!(test("03", "%I")); // FIXME (#2350): flesh out + assert!(test("11", "%I")); // FIXME (#2350): flesh out + assert!(test("044", "%j")); + assert!(test(" 3", "%k")); + assert!(test("13", "%k")); + assert!(test(" 1", "%l")); + assert!(test("11", "%l")); + assert!(test("03", "%M")); + assert!(test("13", "%M")); + assert!(test("\n", "%n")); + assert!(test("am", "%P")); + assert!(test("pm", "%P")); + assert!(test("AM", "%p")); + assert!(test("PM", "%p")); + assert!(test("23:31", "%R")); + assert!(test("11:31:30 AM", "%r")); + assert!(test("11:31:30 PM", "%r")); + assert!(test("03", "%S")); + assert!(test("13", "%S")); + assert!(test("15:31:30", "%T")); + assert!(test("\t", "%t")); + assert!(test("1", "%u")); + assert!(test("7", "%u")); + assert!(test("13-Feb-2009", "%v")); + assert!(test("0", "%w")); + assert!(test("6", "%w")); + assert!(test("2009", "%Y")); + assert!(test("09", "%y")); + assert!(strptime("UTC", "%Z").unwrap().tm_zone == + ~"UTC"); + assert!(strptime("PST", "%Z").unwrap().tm_zone == + ~""); + assert!(strptime("-0000", "%z").unwrap().tm_gmtoff == + 0); + assert!(strptime("-0800", "%z").unwrap().tm_gmtoff == + 0); + assert!(test("%", "%%")); + + // Test for #7256 + assert_eq!(strptime("360", "%Y-%m-%d"), Err(~"Invalid year")) + } + + fn test_ctime() { + set_time_zone(); + + let time = Timespec::new(1234567890, 54321); + let utc = at_utc(time); + let local = at(time); + + error!("test_ctime: {:?} {:?}", utc.ctime(), local.ctime()); + + assert_eq!(utc.ctime(), ~"Fri Feb 13 23:31:30 2009"); + assert_eq!(local.ctime(), ~"Fri Feb 13 15:31:30 2009"); + } + + fn test_strftime() { + set_time_zone(); + + let time = Timespec::new(1234567890, 54321); + let utc = at_utc(time); + let local = at(time); + + assert_eq!(local.strftime(""), ~""); + assert_eq!(local.strftime("%A"), ~"Friday"); + assert_eq!(local.strftime("%a"), ~"Fri"); + assert_eq!(local.strftime("%B"), ~"February"); + assert_eq!(local.strftime("%b"), ~"Feb"); + assert_eq!(local.strftime("%C"), ~"20"); + assert_eq!(local.strftime("%c"), ~"Fri Feb 13 15:31:30 2009"); + assert_eq!(local.strftime("%D"), ~"02/13/09"); + assert_eq!(local.strftime("%d"), ~"13"); + assert_eq!(local.strftime("%e"), ~"13"); + assert_eq!(local.strftime("%f"), ~"000054321"); + assert_eq!(local.strftime("%F"), ~"2009-02-13"); + assert_eq!(local.strftime("%G"), ~"2009"); + assert_eq!(local.strftime("%g"), ~"09"); + assert_eq!(local.strftime("%H"), ~"15"); + assert_eq!(local.strftime("%I"), ~"03"); + assert_eq!(local.strftime("%j"), ~"044"); + assert_eq!(local.strftime("%k"), ~"15"); + assert_eq!(local.strftime("%l"), ~" 3"); + assert_eq!(local.strftime("%M"), ~"31"); + assert_eq!(local.strftime("%m"), ~"02"); + assert_eq!(local.strftime("%n"), ~"\n"); + assert_eq!(local.strftime("%P"), ~"pm"); + assert_eq!(local.strftime("%p"), ~"PM"); + assert_eq!(local.strftime("%R"), ~"15:31"); + assert_eq!(local.strftime("%r"), ~"03:31:30 PM"); + assert_eq!(local.strftime("%S"), ~"30"); + assert_eq!(local.strftime("%s"), ~"1234567890"); + assert_eq!(local.strftime("%T"), ~"15:31:30"); + assert_eq!(local.strftime("%t"), ~"\t"); + assert_eq!(local.strftime("%U"), ~"06"); + assert_eq!(local.strftime("%u"), ~"5"); + assert_eq!(local.strftime("%V"), ~"07"); + assert_eq!(local.strftime("%v"), ~"13-Feb-2009"); + assert_eq!(local.strftime("%W"), ~"06"); + assert_eq!(local.strftime("%w"), ~"5"); + assert_eq!(local.strftime("%X"), ~"15:31:30"); // FIXME (#2350): support locale + assert_eq!(local.strftime("%x"), ~"02/13/09"); // FIXME (#2350): support locale + assert_eq!(local.strftime("%Y"), ~"2009"); + assert_eq!(local.strftime("%y"), ~"09"); + assert_eq!(local.strftime("%+"), ~"2009-02-13T15:31:30-08:00"); + + // FIXME (#2350): We should probably standardize on the timezone + // abbreviation. + let zone = local.strftime("%Z"); + assert!(zone == ~"PST" || zone == ~"Pacific Standard Time"); + + assert_eq!(local.strftime("%z"), ~"-0800"); + assert_eq!(local.strftime("%%"), ~"%"); + + // FIXME (#2350): We should probably standardize on the timezone + // abbreviation. + let rfc822 = local.rfc822(); + let prefix = ~"Fri, 13 Feb 2009 15:31:30 "; + assert!(rfc822 == prefix + "PST" || rfc822 == prefix + "Pacific Standard Time"); + + assert_eq!(local.ctime(), ~"Fri Feb 13 15:31:30 2009"); + assert_eq!(local.rfc822z(), ~"Fri, 13 Feb 2009 15:31:30 -0800"); + assert_eq!(local.rfc3339(), ~"2009-02-13T15:31:30-08:00"); + + assert_eq!(utc.ctime(), ~"Fri Feb 13 23:31:30 2009"); + assert_eq!(utc.rfc822(), ~"Fri, 13 Feb 2009 23:31:30 GMT"); + assert_eq!(utc.rfc822z(), ~"Fri, 13 Feb 2009 23:31:30 -0000"); + assert_eq!(utc.rfc3339(), ~"2009-02-13T23:31:30Z"); + } + + fn test_timespec_eq_ord() { + let a = &Timespec::new(-2, 1); + let b = &Timespec::new(-1, 2); + let c = &Timespec::new(1, 2); + let d = &Timespec::new(2, 1); + let e = &Timespec::new(2, 1); + + assert!(d.eq(e)); + assert!(c.ne(e)); + + assert!(a.lt(b)); + assert!(b.lt(c)); + assert!(c.lt(d)); + + assert!(a.le(b)); + assert!(b.le(c)); + assert!(c.le(d)); + assert!(d.le(e)); + assert!(e.le(d)); + + assert!(b.ge(a)); + assert!(c.ge(b)); + assert!(d.ge(c)); + assert!(e.ge(d)); + assert!(d.ge(e)); + + assert!(b.gt(a)); + assert!(c.gt(b)); + assert!(d.gt(c)); + } + + #[test] + #[ignore(cfg(target_os = "android"))] // FIXME #10958 + fn run_tests() { + // The tests race on tzset. So instead of having many independent + // tests, we will just call the functions now. + test_get_time(); + test_precise_time(); + test_at_utc(); + test_at(); + test_to_timespec(); + test_conversions(); + test_strptime(); + test_ctime(); + test_strftime(); + test_timespec_eq_ord(); + } +} |
