user2974739
user2974739

Reputation: 639

Calculate time from an array and convert to time zone

I have an array bed_times with Time instances in UTC format:

bed_times = [
  Time.utc(2015, 12, 10,  5, 58, 24),
  Time.utc(2015, 12,  9,  3, 35, 28),
  Time.utc(2015, 12,  8,  6, 32, 26),
  Time.utc(2015, 12,  7,  1, 43, 28),
  Time.utc(2015, 12,  5,  7, 49, 30),
  Time.utc(2015, 12, 04,  7,  2, 30)
]
#=> [2015-12-10 05:58:24 UTC,
#    2015-12-09 03:35:28 UTC,
#    2015-12-08 06:32:26 UTC,
#    2015-12-07 01:43:28 UTC,
#    2015-12-05 07:49:30 UTC,
#    2015-12-04 07:02:30 UTC]

I am trying to get the average bedtime, but I'm not getting the correct result

ave = Time.at(bed_times.map(&:to_f).inject(:+) / bed_times.size)

result is

2015-12-07 01:26:57 -0800

which is not correct. Also, I want to then convert the average time to a different time zone

I tried

Time.zone = 'Pacific Time (US & Canada)'
Time.zone.parse(ave.to_s)
2015-12-07 01:26:57 -0800

This is not correct either.

Upvotes: 3

Views: 771

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110725

As I understand, you are given an array arr that contains the UTC times of bedtimes in areas that are on PST time. You wish to compute the average bedtime.

Code

def avg_bedtime(arr)
  avg = Time.at(arr.reduce(0) do |t,s|
    lt = Time.parse(s).localtime("-08:00")
    t + Time.new(2000, 1, (lt.hour < 12) ? 2 : 1, lt.hour, lt.min, lt.sec, "-08:00").to_f
  end/arr.size)
  "%d:%d:%d" % [avg.hour, avg.min, avg.sec]
end

Example

arr = ["2015-12-10 08:58:24 UTC", "2015-12-09 03:35:28 UTC",
       "2015-12-08 06:32:26 UTC", "2015-12-07 01:43:28 UTC",
       "2015-12-05 07:49:30 UTC", "2015-12-04 07:02:30 UTC"]

I've changed the first time in this array to make it more interesting.

avg_bedtime(arr)
  #=> "21:56:57"

Explanation

Let's begin by converting these strings to time objects:

utc = arr.map { |s| Time.parse(s) }
  #=> [2015-12-10 08:58:24 UTC, 2015-12-09 03:35:28 UTC, 2015-12-08 06:32:26 UTC,
  #    2015-12-07 01:43:28 UTC, 2015-12-05 07:49:30 UTC, 2015-12-04 07:02:30 UTC]

Recalling that PST is 8 hours later than UTC, we can use Time.localtime to convert to PST:

pst = utc.map { |t| t.localtime("-08:00") }
  #=> [2015-12-10 00:58:24 -0800, 2015-12-08 19:35:28 -0800,
  #    2015-12-07 22:32:26 -0800, 2015-12-06 17:43:28 -0800,
  #    2015-12-04 23:49:30 -0800, 2015-12-03 23:02:30 -0800] 

I will refer to bedtimes before noon to be "late" bedtimes and those later in the day to be "early" bedtimes. (This is of course arbitrary. If, for example, some of the individuals are shift workers, this could be a problem.) As you see, the first element of pst is a late bedtime and all others are early bedtimes.

I will now convert these time objects to time objects having the same time of day but a different date. Early bedtime objects will be assigned an arbitrary date (say, January 1, 2000) and late bedtime objects will be one day later (January 2, 2000):

adj = pst.map { |t| Time.new(2000, 1, (t.hour < 12) ? 2 : 1, t.hour, t.min, t.sec, "-08:00") }
  #=> [2000-01-02 00:58:24 -0800, 2000-01-01 19:35:28 -0800, 2000-01-01 22:32:26 -0800,
 #     2000-01-01 17:43:28 -0800, 2000-01-01 23:49:30 -0800, 2000-01-01 23:02:30 -0800]

We can now convert the time objects to seconds since the epoch:

secs = adj.map { |t| t.to_f }
  #=> [946803504.0, 946784128.0, 946794746.0, 946777408.0, 946799370.0, 946796550.0]

compute the average:

avg = secs.reduce(:+)/arr.size
  #=> 946792617.6666666

convert back to a time object for the PST zone:

tavg = Time.at(avg)
  #=> 2000-01-01 21:56:57 -0800 

and, lastly, extract the time of day:

"%d:%d:%d" % [tavg.hour, tavg.min, tavg.sec]
  # "21:56:57

Upvotes: 0

NickGnd
NickGnd

Reputation: 5197

You have to calculate the average on the gap from midnight.

A not elegant (but fast) solution could be:

# Keep only time    
bed_times.map! { |bt| Time.parse(bt.split(" ")[1]) }

# calculate the gap from 00:00:00
gap_from_midnight = bed_times.map do |bt|
  if bt > Time.parse("12:00:00")
    gap = (bt.to_f - Time.parse("24:00:00").to_f)
  else
    gap = (bt.to_f - Time.parse("00:00:00").to_f)
  end
  gap.to_i
end

# average in sec
avg_in_sec = gap_from_midnight.inject(:+) / bed_times.size

# average in UTC time zone 
avg = Time.at(avg_in_sec).utc # => 1970-01-01 05:26:57 UTC (result for bed_times array)

# average in PST time zone (see note)
avg_pst = Time.parse(avg.to_s).in_time_zone("Pacific Time (US & Canada)") # => Wed, 31 Dec 1969 21:26:57 PST -08:00 (result for bed_times array)

# Keep only time
avg_pst.strftime("%H:%M:%S") # => "21:26:57" (result for bed_times array)

With your bed_times array (with the values as a string)

bed_times = [
  "2015-12-10 05:58:24 UTC",
  "2015-12-09 03:35:28 UTC",
  "2015-12-08 06:32:26 UTC",
  "2015-12-07 01:43:28 UTC",
  "2015-12-05 07:49:30 UTC",
  "2015-12-04 07:02:30 UTC"
]

the average is :

  • 05:26:57 in UTC zone
  • 21:26:57 in PST zone

With another array like this

bed_times = [
  "2015-12-10 01:00:00 UTC",
  "2015-12-09 23:00:00 UTC",
  "2015-10-19 18:00:00 UTC",
]

the average is:

  • 22:00:00 in UTC zone
  • 14:00:00 in PST zone

note: .in_time_zone is a helper from ActiveSupport::TimeWithZone http://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html

Upvotes: 2

Related Questions