|
1 | 1 | #[cfg(not(feature = "std"))] |
2 | 2 | use crate::alloc_prelude::*; |
| 3 | +#[cfg(feature = "std")] |
| 4 | +use crate::OffsetDateTime; |
3 | 5 | use crate::{ |
4 | 6 | format::{parse, ParseError, ParseResult, ParsedItems}, |
5 | 7 | DeferredFormat, Duration, |
@@ -197,6 +199,35 @@ impl UtcOffset { |
197 | 199 | pub(crate) const fn as_duration(self) -> Duration { |
198 | 200 | Duration::seconds(self.seconds as i64) |
199 | 201 | } |
| 202 | + |
| 203 | + /// Obtain the system's UTC offset at a known moment in time. If the offset |
| 204 | + /// cannot be determined, UTC is returned. |
| 205 | + /// |
| 206 | + /// ```rust,no_run |
| 207 | + /// # use time::{UtcOffset, OffsetDateTime}; |
| 208 | + /// let unix_epoch = OffsetDateTime::unix_epoch(); |
| 209 | + /// let local_offset = UtcOffset::local_offset_at(unix_epoch); |
| 210 | + /// println!("{}", local_offset.format("%z")); |
| 211 | + /// ``` |
| 212 | + #[inline(always)] |
| 213 | + #[cfg(feature = "std")] |
| 214 | + pub fn local_offset_at(datetime: OffsetDateTime) -> Self { |
| 215 | + try_local_offset_at(datetime).unwrap_or(Self::UTC) |
| 216 | + } |
| 217 | + |
| 218 | + /// Obtain the system's current UTC offset. If the offset cannot be |
| 219 | + /// determined, UTC is returned. |
| 220 | + /// |
| 221 | + /// ```rust,no_run |
| 222 | + /// # use time::UtcOffset; |
| 223 | + /// let local_offset = UtcOffset::current_local_offset(); |
| 224 | + /// println!("{}", local_offset.format("%z")); |
| 225 | + /// ``` |
| 226 | + #[inline(always)] |
| 227 | + #[cfg(feature = "std")] |
| 228 | + pub fn current_local_offset() -> Self { |
| 229 | + OffsetDateTime::now_local().offset() |
| 230 | + } |
200 | 231 | } |
201 | 232 |
|
202 | 233 | /// Methods that allow parsing and formatting the `UtcOffset`. |
@@ -256,6 +287,171 @@ impl Display for UtcOffset { |
256 | 287 | } |
257 | 288 | } |
258 | 289 |
|
| 290 | +/// Attempt to obtain the system's UTC offset. If the offset cannot be |
| 291 | +/// determined, `None` is returned. |
| 292 | +#[cfg(feature = "std")] |
| 293 | +#[allow(clippy::too_many_lines)] // |
| 294 | +fn try_local_offset_at(datetime: OffsetDateTime) -> Option<UtcOffset> { |
| 295 | + use core::{convert::TryInto, mem}; |
| 296 | + |
| 297 | + #[cfg(target_family = "unix")] |
| 298 | + { |
| 299 | + /// Convert the given Unix timestamp to a `libc::tm`. Returns `None` on |
| 300 | + /// any error. |
| 301 | + fn timestamp_to_tm(timestamp: i64) -> Option<libc::tm> { |
| 302 | + extern "C" { |
| 303 | + fn tzset(); |
| 304 | + } |
| 305 | + |
| 306 | + let timestamp = timestamp.try_into().ok()?; |
| 307 | + |
| 308 | + // Safety: Plain old data. |
| 309 | + #[allow(unsafe_code)] |
| 310 | + let mut tm = unsafe { mem::zeroed() }; |
| 311 | + |
| 312 | + // Update timezone information from system. `localtime_r` does not |
| 313 | + // do this for us. |
| 314 | + // |
| 315 | + // Safety: tzset is thread-safe. |
| 316 | + #[allow(unsafe_code)] |
| 317 | + unsafe { |
| 318 | + tzset(); |
| 319 | + } |
| 320 | + |
| 321 | + // Safety: We are calling a system API, which mutates the `tm` |
| 322 | + // variable. If a null pointer is returned, an error occurred. |
| 323 | + #[allow(unsafe_code)] |
| 324 | + let tm_ptr = unsafe { libc::localtime_r(×tamp, &mut tm) }; |
| 325 | + |
| 326 | + if tm_ptr.is_null() { |
| 327 | + None |
| 328 | + } else { |
| 329 | + Some(tm) |
| 330 | + } |
| 331 | + } |
| 332 | + |
| 333 | + let tm = timestamp_to_tm(datetime.timestamp())?; |
| 334 | + |
| 335 | + // `tm_gmtoff` extension |
| 336 | + #[cfg(not(target_os = "solaris"))] |
| 337 | + { |
| 338 | + tm.tm_gmtoff.try_into().ok().map(UtcOffset::seconds) |
| 339 | + } |
| 340 | + |
| 341 | + // No `tm_gmtoff` extension |
| 342 | + #[cfg(target_os = "solaris")] |
| 343 | + { |
| 344 | + use crate::Date; |
| 345 | + use core::convert::TryFrom; |
| 346 | + |
| 347 | + let mut tm = tm; |
| 348 | + if tm.tm_sec == 60 { |
| 349 | + // Leap seconds are not currently supported. |
| 350 | + tm.tm_sec = 59; |
| 351 | + } |
| 352 | + |
| 353 | + let local_timestamp = |
| 354 | + Date::try_from_yo(1900 + tm.tm_year, u16::try_from(tm.tm_yday).ok()? + 1) |
| 355 | + .ok()? |
| 356 | + .try_with_hms( |
| 357 | + tm.tm_hour.try_into().ok()?, |
| 358 | + tm.tm_min.try_into().ok()?, |
| 359 | + tm.tm_sec.try_into().ok()?, |
| 360 | + ) |
| 361 | + .ok()? |
| 362 | + .assume_utc() |
| 363 | + .timestamp(); |
| 364 | + |
| 365 | + (local_timestamp - datetime.timestamp()) |
| 366 | + .try_into() |
| 367 | + .ok() |
| 368 | + .map(UtcOffset::seconds) |
| 369 | + } |
| 370 | + } |
| 371 | + |
| 372 | + #[cfg(target_family = "windows")] |
| 373 | + { |
| 374 | + use crate::offset; |
| 375 | + use winapi::{ |
| 376 | + shared::minwindef::FILETIME, |
| 377 | + um::{ |
| 378 | + minwinbase::SYSTEMTIME, |
| 379 | + timezoneapi::{SystemTimeToFileTime, SystemTimeToTzSpecificLocalTime}, |
| 380 | + }, |
| 381 | + }; |
| 382 | + |
| 383 | + /// Convert a `SYSTEMTIME` to a `FILETIME`. Returns `None` if any error |
| 384 | + /// occurred. |
| 385 | + fn systemtime_to_filetime(systime: &SYSTEMTIME) -> Option<FILETIME> { |
| 386 | + // Safety: We only read `ft` if it is properly initialized. |
| 387 | + #[allow(unsafe_code, deprecated)] |
| 388 | + let mut ft = unsafe { mem::uninitialized() }; |
| 389 | + |
| 390 | + // Safety: `SystemTimeToFileTime` is thread-safe. |
| 391 | + #[allow(unsafe_code)] |
| 392 | + { |
| 393 | + if 0 == unsafe { SystemTimeToFileTime(systime, &mut ft) } { |
| 394 | + // failed |
| 395 | + None |
| 396 | + } else { |
| 397 | + Some(ft) |
| 398 | + } |
| 399 | + } |
| 400 | + } |
| 401 | + |
| 402 | + /// Convert a `FILETIME` to an `i64`, representing a number of seconds. |
| 403 | + fn filetime_to_secs(filetime: &FILETIME) -> i64 { |
| 404 | + /// FILETIME represents 100-nanosecond intervals |
| 405 | + const FT_TO_SECS: i64 = 10_000_000; |
| 406 | + ((filetime.dwHighDateTime as i64) << 32 | filetime.dwLowDateTime as i64) / FT_TO_SECS |
| 407 | + } |
| 408 | + |
| 409 | + /// Convert an `OffsetDateTime` to a `SYSTEMTIME`. |
| 410 | + fn offset_to_systemtime(datetime: OffsetDateTime) -> SYSTEMTIME { |
| 411 | + let (month, day_of_month) = datetime.to_offset(offset!(UTC)).month_day(); |
| 412 | + SYSTEMTIME { |
| 413 | + wYear: datetime.year() as u16, |
| 414 | + wMonth: month as u16, |
| 415 | + wDay: day_of_month as u16, |
| 416 | + wDayOfWeek: 0, // ignored |
| 417 | + wHour: datetime.hour() as u16, |
| 418 | + wMinute: datetime.minute() as u16, |
| 419 | + wSecond: datetime.second() as u16, |
| 420 | + wMilliseconds: datetime.millisecond(), |
| 421 | + } |
| 422 | + } |
| 423 | + |
| 424 | + // This function falls back to UTC if any system call fails. |
| 425 | + let systime_utc = offset_to_systemtime(datetime.to_offset(offset!(UTC))); |
| 426 | + |
| 427 | + // Safety: `local_time` is only read if it is properly initialized, and |
| 428 | + // `SystemTimeToTzSpecificLocalTime` is thread-safe. |
| 429 | + #[allow(unsafe_code)] |
| 430 | + let systime_local = unsafe { |
| 431 | + #[allow(deprecated)] |
| 432 | + let mut local_time = mem::uninitialized(); |
| 433 | + if 0 == SystemTimeToTzSpecificLocalTime( |
| 434 | + core::ptr::null(), // use system's current timezone |
| 435 | + &systime_utc, |
| 436 | + &mut local_time, |
| 437 | + ) { |
| 438 | + // call failed |
| 439 | + return None; |
| 440 | + } else { |
| 441 | + local_time |
| 442 | + } |
| 443 | + }; |
| 444 | + |
| 445 | + // Convert SYSTEMTIMEs to FILETIMEs so we can perform arithmetic on them. |
| 446 | + let ft_system = systemtime_to_filetime(&systime_utc)?; |
| 447 | + let ft_local = systemtime_to_filetime(&systime_local)?; |
| 448 | + |
| 449 | + let diff_secs = filetime_to_secs(&ft_local) - filetime_to_secs(&ft_system); |
| 450 | + |
| 451 | + diff_secs.try_into().ok().map(UtcOffset::seconds) |
| 452 | + } |
| 453 | +} |
| 454 | + |
259 | 455 | #[cfg(test)] |
260 | 456 | mod test { |
261 | 457 | use super::*; |
|
0 commit comments