Stats calculation is now timezone-aware.

This commit is contained in:
Eugene Burmakin 2025-07-22 23:57:25 +02:00
parent 9803ccc6a8
commit bdcfb5eb62
4 changed files with 63 additions and 6 deletions

View file

@ -37,6 +37,16 @@ class Stat < ApplicationRecord
end
def calculate_daily_distances(monthly_points)
Stats::DailyDistanceQuery.new(monthly_points, timespan).call
Stats::DailyDistanceQuery.new(monthly_points, timespan, user_timezone).call
end
private
def user_timezone
# Future: Once user.timezone column exists, uncomment the line below
# user.timezone.presence || Time.zone.name
# For now, use application timezone
Time.zone.name
end
end

View file

@ -1,9 +1,10 @@
# frozen_string_literal: true
class Stats::DailyDistanceQuery
def initialize(monthly_points, timespan)
def initialize(monthly_points, timespan, user_timezone = nil)
@monthly_points = monthly_points
@timespan = timespan
@user_timezone = user_timezone || 'UTC'
end
def call
@ -15,22 +16,22 @@ class Stats::DailyDistanceQuery
private
attr_reader :monthly_points, :timespan
attr_reader :monthly_points, :timespan, :user_timezone
def daily_distances(monthly_points)
Stat.connection.select_all(<<-SQL.squish)
WITH points_with_distances AS (
SELECT
EXTRACT(day FROM to_timestamp(timestamp)) as day_of_month,
EXTRACT(day FROM (to_timestamp(timestamp) AT TIME ZONE 'UTC' AT TIME ZONE '#{user_timezone}')) as day_of_month,
CASE
WHEN LAG(lonlat) OVER (
PARTITION BY EXTRACT(day FROM to_timestamp(timestamp))
PARTITION BY EXTRACT(day FROM (to_timestamp(timestamp) AT TIME ZONE 'UTC' AT TIME ZONE '#{user_timezone}'))
ORDER BY timestamp
) IS NOT NULL THEN
ST_Distance(
lonlat::geography,
LAG(lonlat) OVER (
PARTITION BY EXTRACT(day FROM to_timestamp(timestamp))
PARTITION BY EXTRACT(day FROM (to_timestamp(timestamp) AT TIME ZONE 'UTC' AT TIME ZONE '#{user_timezone}'))
ORDER BY timestamp
)::geography
)

30
benchmark_stats.rb Normal file
View file

@ -0,0 +1,30 @@
require 'benchmark'
# Test the optimized stats calculation
data = Benchmark.measure do
user_id = 7
last_calculated_at = DateTime.new(1970, 1, 1)
time_diff = last_calculated_at.to_i..Time.current.to_i
timestamps = Point.where(user_id:, timestamp: time_diff).pluck(:timestamp).uniq
months = timestamps.group_by do |timestamp|
time = Time.zone.at(timestamp)
[time.year, time.month]
end.keys
months.each do |year, month|
Stats::CalculateMonth.new(user_id, year, month).call
end
end
puts "Stats calculation benchmark:"
puts "User Time: #{data.utime}s"
puts "System Time: #{data.stime}s"
puts "Total Time: #{data.real}s"
# @real=28.869485000148416,
# @stime=2.4980050000000027,
# @total=20.303141999999976,
# @utime=17.805136999999974>

View file

@ -0,0 +1,16 @@
# Example migration for adding per-user timezone support
# To use: rename this file to remove .example and run rails db:migrate
class AddTimezoneToUsers < ActiveRecord::Migration[7.1]
def change
add_column :users, :timezone, :string, default: 'UTC'
add_index :users, :timezone
# Populate existing users with application timezone
reversible do |dir|
dir.up do
User.update_all(timezone: Time.zone.name)
end
end
end
end