Triki z PowerShellem #13 – Prędkość czasuOstatni dzień roku to dobry moment na różne dziwne przemyślenia na temat czasu. Kiedyś z tej okazji wymyśliłem zupełnie nowy system kalendarza, a dzisiaj przypomniałem sobie o niezupełnie poważnym pomyśle autorstwa Rega, ze zbliżonej dziedziny.
Chodzi o oryginalną wielkość "fizyczną": prędkość czasu. Ma ona ilościowo mierzyć znany wszystkim fakt, że wrażenie upływającego czasu jest różne w różnych sytuacjach. Gdy w rzeczywistości upłynął pewien czas
, a nam wydawało się, że minął raczej czas
, to odczuwana przez nas prędkość czasu
wynosi
.
Dla przykładu: na nudnym wykładzie mogło minąć ledwie 15 minut, podczas gdy nam wydaje się, że siedzimy na nim już godzinę; wówczas
. Z kolei grając przez trzy godziny możemy mieć wrażenie, że upłynęło tylko 30 minut - wtedy
. Jak widać
, zatem teoria zgadza się tutaj z intuicją, co pozwala wnioskować, że wielkość jest dobrze zdefiniowana :)
Pozostaje jeszcze kwestia jej pomiaru. W swoim oryginalnym artykule (który w tajemniczy sposób zniknął z sieci) Reg zaproponował, że można by go przeprowadzić za pomocą odpowiedniego programu. Miałby on działać w tle, losować pewien interwał czasu i po jego upływie pytać użytkownika, ile czasu według niego upłynęło od ostatniego pytania. Z dokładnością do małych błędów pomiarowych (np. czasu potrzebnego na wpisanie odpowiedzi) metoda wydaje się dobra - czemu więc by jej nie zaimplementować? :]
To właśnie uczyniłem, posługując się oczywiście swoim ulubionym narzędziem na takie okazje, czyli PowerShellem. Pewnym problemem było to, jak w skrypcie konsolowym postarać się o mierzenie czasu w sposób, który byłby niezauważalny dla użytkownika. Da się to jednak łatwo zrobić, posługując się... wbudowanym w system Harmonogramem zadań (Task Scheduler). Istnieje bowiem do niego dobrze udokumentowane, ładne i przejrzyste COM-owe API (dostępne od Visty wzwyż), którego użycie w PowerShellu wygląda zresztą kilka razy lepiej niż w C++ ;P
Wynikowy skrypt wygląda więc następująco:
# Stałe
$TASK_NAME = "TimeSpeed_Task" # Nazwa zadania w Harmonogramie
# Pobieramy obiekt harmonogramu zadań
$ts = New-Object -ComObject Schedule.Service
$ts.Connect($null, $null, $null, $null)
$tf = $ts.GetFolder($(New-Object String @([char]92, 1))) # "\"
if ($minutes -eq 0)
{
# Pierwsze uruchomienie
$task = $ts.NewTask(0)
$task.Data = ""
$continue = "T"
}
else
{
# Kolejne uruchomienia
$task = $tf.GetTask($TASK_NAME).Definition
# Pytamy użytkownika i liczymy prędkość czasu
$userMinutes = Read-Host -Prompt "Liczba minut od ostatniego razu"
$d_tau = $minutes / $userMinutes
if (-not [String]::IsNullOrEmpty($task.Data))
{
$timeData = $task.Data.Split(',')
$totalTime = [int]$timeData[0]
$totalUserTime = [int]$timeData[1]
}
$totalTime += $minutes
$totalUserTime += $userMinutes
$tau = $totalTime / $totalUserTime
$task.Data = $totalTime.ToString() + "," + $totalUserTime.ToString()
# Wyświetlamy aktualne wyniki
"Rzeczywista liczba minut: " + $minutes | Out-Host
"Chwilowa prędkość czasu: " + $d_tau | Out-Host
"Całkowita prędkość czasu: " + $tau | Out-Host
# Pytamy użytkownika, czy chce kontynuować
$continue = Read-Host -Prompt "Wykonać następny pomiar (T/n)?"
}
if ($continue -ine "n")
{
# Losujemy nowy interwał czasowy (od 5 do 60 minut, co 5)
$rand = New-Object System.Random
$nextMinutes = ($rand.Next(0, 12) + 1) * 5
# Dodajemy trigger dla zadania (czas: za X minut)
$task.Triggers.Clear()
$trigger = $task.Triggers.Create(1) # 1 - trigger na określony czas
$nextTime = [DateTime]::Now.AddMinutes($nextMinutes)
$trigger.StartBoundary = $nextTime.ToString("o")
# Dodajemy akcję (uruchomienie skryptu)
$task.Actions.Clear()
$action = $task.Actions.Create(0) # 0 - uruchomienie polecenia
$action.Path = "powershell"
$action.Arguments = "-Command . '" + $MyInvocation.MyCommand.Path
$action.Arguments += "' -minutes " + $nextMinutes.ToString()
# Dodajemy zadanie
# (6 - stwórz lub uaktualnij)
# (3 - zadanie uruchamiane tylko przy zalogowanym użytkowniku)
$tf.RegisterTaskDefinition($TASK_NAME, $task, 6, $null, $null, 3) | Out-Null
}
else
{
# Koniec - usuwamy zdarzenie
try { $tf.DeleteTask($TASK_NAME, 0) } catch { }
}
Przy swoim pierwszym uruchomieniu tworzy on nowe zadanie w Harmonogramie, które polega na jego ponownym odpaleniu po upływie losowej liczby minut (5, 10, 15, itd. aż do maks. 60 minut). Wtedy użytkownik jest pytany o to, ile czasu według niego upłynęło, a następnie wyświetlają się częściowe wyniki pomiaru. Wówczas można zdecydować o jego kontynuowaniu lub zakończeniu. (Zawsze można też ręcznie usunąć zadanie z Harmonogramu, rzecz jasna).

I to w sumie tyle. Można już przystąpić do badań określających dokładnie, jak bardzo dłuży nam się czas do północy :))