ruby
38 lines · 8 steps
Converting timestamps across time zones with TZInfo
A module that parses ISO 8601 strings, shifts them between named zones via TZInfo, and formats the result back with a correct offset.
Explained by
highlit
1require "time"
2require "tzinfo"
3
4module TimeConverter
5 module_function
6
7 def convert(iso_string, to_zone:, from_zone: nil)
8 parsed = Time.iso8601(iso_string)
9
10 if parsed.utc_offset.zero? && from_zone && !iso_string.match?(/[Zz]|[+-]\d{2}:?\d{2}\z/)
11 source = TZInfo::Timezone.get(from_zone)
12 parsed = source.local_to_utc(parsed) { |periods| periods.first }
13 end
14
15 target = TZInfo::Timezone.get(to_zone)
16 localized = target.utc_to_local(parsed.utc)
17 offset = target.observed_utc_offset(localized)
18
19 Time.new(
20 localized.year, localized.month, localized.day,
21 localized.hour, localized.min, localized.sec, offset
22 )
23 end
24
25 def to_iso(time, zone:)
26 tz = TZInfo::Timezone.get(zone)
27 period = tz.period_for_utc(time.utc)
28 local = tz.utc_to_local(time.utc)
29 sign = period.observed_utc_offset.negative? ? "-" : "+"
30 abs = period.observed_utc_offset.abs
31 format(
32 "%sT%s%s%02d:%02d",
33 local.strftime("%Y-%m-%d"),
34 local.strftime("%H:%M:%S"),
35 sign, abs / 3600, (abs % 3600) / 60
36 )
37 end
38end
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A naive timestamp with no offset is ambiguous, so you must attach a source zone before converting.
- 2Round-tripping through UTC is the reliable pivot for moving a moment between named zones.
- 3DST means an offset must be observed for a specific instant, not assumed constant for a zone.
Related explainers
ruby
class ProgressBar BAR_WIDTH = 40 def initialize(total, io: $stdout)
Building a terminal progress bar in Ruby
state tracking
terminal output
time estimation
Intermediate
8 steps
ruby
class Product < ApplicationRecord scope :search, ->(query) { return all if query.blank?
Building a multi-term search scope in Rails
arel
scopes
full-text search
Advanced
7 steps
rust
use std::time::Duration; #[derive(Debug, PartialEq)] pub enum ParseDurationError {
Parsing duration strings safely in Rust
parsing
error-handling
checked-arithmetic
Intermediate
8 steps
go
package logparse import ( "bufio"
Splitting multi-line logs with a Scanner
parsing
streaming
bufio
Intermediate
9 steps
ruby
module Rack class MaintenanceMode RETRY_AFTER = 3600
A Rack maintenance-mode middleware in Rails
middleware
rack
http-status
Intermediate
8 steps
ruby
class ImageNormalizer ORIENTATION_TRANSFORMS = { 1 => ->(img) {}, 2 => ->(img) { img.flop },
Correcting EXIF orientation in Ruby
lookup-table
lambdas
image-processing
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/converting-timestamps-across-time-zones-with-tzinfo-explained-ruby-9dd9/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.