How to define an event that is triggered at certain DateTime - f#

My goal is to create an event which is triggered when certain date-time conditions are met - for example when the seconds value of DateTime.Now is changed (later it should be the month value)
I'm trying this:
type Timer() =
let timerEvent = new Event<System.DateTime>()
let cts = new System.Threading.CancellationTokenSource()
member this.onSecondTick =
let loop =
async {
while true do
let time = System.DateTime.Now
let tickDecrTime = time - System.TimeSpan(1)
if time.Second <> tickDecrTime.Second then
timerEvent.Trigger(time)
else
let offset = 200;
let sleepTime = 1000 - time.Millisecond - offset;
if sleepTime > 0 then do! Async.Sleep(sleepTime)
}
Async.Start(loop, cts.Token)
timerEvent.Publish
interface System.IDisposable with
member this.Dispose() =
printfn "Disposed"
cts.Cancel()
member this.Dispose() =( this:> System.IDisposable).Dispose()
If I'm not using the Async.Sleep in the else branch of async{} then during the execution with FSI the NET Host process processor time rises up to 20% on my core i5, which I believe shouldn't work like that. If I'm using Async.Sleep then sometimes it misses the seconds events depending on the offset milliseconds value which I'm subtracting from the overall milliseconds period to get the estimation of the time for which I could send to sleep the async process.
Is there a better way to trigger date-time related events than checking condition on DateTime.Now in the infinite loop?

Related

Formula To Aggregate Totals for Year

I have a spreadsheet that I use to keep track of climbing progress (snippet shown below). I have formulas and graphs that keep track of counts of specific grades over time, but I am having trouble with a formula to keep a running total (by year) of feet climbed. I intent to put this in another sheet.
Basically I would like a single cell that does something like ... if Sheet1!A:A begins with "21." and if Sheet1!E:E,"<>*%" (which means I actually completed the climb) then add the rows total climb length (Sheet1!J:J * Sheet1!I:I) to the running total for that year.
What is the best way to do this?
You can try using Apps Script and creating a script in order to manage your task.
So for example, you might want to take a look at the snippet below:
Code
function calculateTotal() {
let ss = SpreadsheetApp.getActive().getSheetByName('Sheet1');
let date = ss.getRange('A2:A').getDisplayValues();
let tries = ss.getRange('E2:E').getDisplayValues();
let lengths = ss.getRange('I2:I').getDisplayValues();
let total = 0;
for (let i =0; i<date.length; i++) {
if (date[i][0].toString().startsWith('21') != false && tries[i][0].toString().includes('%') == false) {
total = total+lengths[i][0];
}
}
ss.getRange('M2').setValue(total);
}
Explanation
The script above gathers all the values from the Sheet1 and loops through them. If the conditions check (the date should start with 21 and the E column does not contain %) then the corresponding length is added to the total; the total is then saved in the M2 cell in this case.
Further improvement
The advantage of using a script is that it is versatile and easier to manage. In this situation, you can make use of Apps Script's time-driven triggers; so assuming you plan on updating your spreadsheet every day at a specific time, you can create a trigger which will run right after it.
For example, the below function creates a trigger for the function above which will run every day at ~9.
function createTrigger() {
ScriptApp.newTrigger("calculateTotal")
.timeBased()
.atHour(9)
.everyDays(1)
.create();
}
Reference
Google Apps Script;
Apps Script Installable Triggers.
Thanks Ale13 ... using your example and adding a couple of things (also needed to parseInt totals) ...
function calculateTotal() {
let ss = SpreadsheetApp.getActive().getSheetByName('Sheet1');
let s7 = SpreadsheetApp.getActive().getSheetByName('Sheet7');
let date = ss.getRange('A2:A').getDisplayValues();
let type = ss.getRange('F2:F').getDisplayValues();
let tries = ss.getRange('E2:E').getDisplayValues();
let lengths = ss.getRange('I2:I').getDisplayValues();
let laps = ss.getRange('J2:J').getDisplayValues();
let btotal = 0;
let rtotal = 0;
for (let i =0; i<date.length; i++) {
if (date[i][0].toString().startsWith('21') != false && tries[i][0].toString().includes('%') == false) {
// Totals for Bouldering
if (type[i][0] == "B") {
btotal = btotal + parseInt(lengths[i][0]*laps[i][0]);
}
// Totals for Top Rope or Sport
else {
rtotal = rtotal + parseInt(lengths[i][0]*laps[i][0])
}
}
}
console.log("Roped total = " + rtotal)
console.log("Bouldering total = " + btotal)
s7.getRange('B2').setValue(rtotal);
s7.getRange('B3').setValue(btotal);
}

How do you apply a Combine operator only after the first message has been received?

In Combine, using only the built-in operators, is there a way to skip an operator on the first value but then apply that operator for all subsequent values?
Consider the following:
publisher
.debounce(...)
.sink(...)
In this arrangement, debounce will wait for the specified timeout to elapse before passing on the value to sink. However, there are many times when you only want debounce to kick-in after the first element. For example, if the user is trying to filter a list of contacts, it's very possible that they only enter one letter into a text field. If that's the case, the application should probably start filtering immediately, without having to wait for the debounce to timeout.
I'm aware of the Drop publishers, but I can't seem to find a combination of them that will perform more of a "skip" operation such that the sink receives every value, but the debounce is ignored on the first value.
Something like the following:
publisher
.if_first_element_passthrough_to_sink(...), else_debounce(...)
.sink(...)
Is something like this possible with the built-in operators?
Clarification
Some clarification since my original posting wasn't as clear as it should have been... The answer provided by Asperi below is very close, but ideally the first element in a sequence is always delivered, then debounce would kick in.
Imagine the user is typing the following:
A B C ... (pauses typing for a few seconds) ... D ... (pauses) ... E F G
What I would like is:
A, D and E are delivered immediately.
B C is coalesced into just C using debounce
F G is coalesced into just G using debounce
If I correctly understood your needs it can be achieved based on Concatenate as like the following (in pseudo-code):
let originalPublisher = ...
let publisher = Publishers.Concatenate(
prefix: originalPublisher.first(),
suffix: originalPublisher.debounce(for: 0.5, scheduler: RunLoop.main))
.eraseToAnyPublisher()
so, prefix just sends first element downstream from original publisher and finished, afterwards suffix just pass all following elements using debounce.
In your particular case of debounce, you might prefer the behavior of throttle. It sends the first element immediately, and then sends no more than one element per interval.
Anyway, can you do it with Combine built-ins? Yes, with some difficulty. Should you? Maybe…
Here's a marble diagram of your goal:
Each time a value goes into the kennyc-debouncer, it starts a timer (represented by a shaded region). If a value arrives while the timer is running, the kennyc-debouncer saves the value and restarts the timer. When the timer expires, if any values arrived while the timer was running, the kennyc-debouncer emits the latest value immediately.
The scan operator allows us to keep state that we mutate each time an input arrives. We need to send two kinds of inputs into scan: the outputs from the upstream publisher, and timer firings. So let's define a type for those inputs:
fileprivate enum DebounceEvent<Value> {
case value(Value)
case timerFired
}
What kind of state do we need inside our scan transform? We definitely need the scheduler, the interval, and the scheduler options, so that we can set timers.
We also need a PassthroughSubject we can use to turn timer firings into inputs to the scan operator.
We can't actually cancel and restart a timer, so instead, when the timer fires, we'll see whether it should have been restarted. If so, we'll start another timer. So we need to know whether the timer is running, and what output to send when the timer fires, and the restart time for the timer if restarting is necessary.
Since scan's output is the entire state value, we also need the state to include the output value to send downstream, if any.
Here's the state type:
fileprivate struct DebounceState<Value, S: Scheduler> {
let scheduler: S
let interval: S.SchedulerTimeType.Stride
let options: S.SchedulerOptions?
let subject = PassthroughSubject<Void, Never>()
enum TimerState {
case notRunning
case running(PendingOutput?)
struct PendingOutput {
var value: Value
var earliestDeliveryTime: S.SchedulerTimeType
}
}
var output: Value? = nil
var timerState: TimerState = .notRunning
}
Now let's look at how to actually use scan with some other operators to implement the kennyc version of debounce:
extension Publisher {
func kennycDebounce<S: Scheduler>(
for dueTime: S.SchedulerTimeType.Stride,
scheduler: S,
options: S.SchedulerOptions? = nil
) -> AnyPublisher<Output, Failure>
{
let initialState = DebounceState<Output, S>(
scheduler: scheduler,
interval: dueTime,
options: options)
let timerEvents = initialState.subject
.map { _ in DebounceEvent<Output>.timerFired }
.setFailureType(to: Failure.self)
return self
.map { DebounceEvent.value($0) }
.merge(with: timerEvents)
.scan(initialState) { $0.updated(with: $1) }
.compactMap { $0.output }
.eraseToAnyPublisher()
}
}
We start by constructing the initial state for the scan operator.
Then, we create a publisher that turns the Void outputs of the state's PassthroughSubject into .timerFired events.
Finally, we construct our full pipeline, which has four stages:
Turn the upstream outputs (from self) into .value events.
Merge the value events with the timer events.
Use scan to update the debouncing state with the value and timer events. The actual work is done in an updated(with:) method we'll add to DebounceState below.
Map the full state down to just the value we want to pass downstream, and discard nulls (which happen when upstream events get suppressed by debouncing).
All that's left is to write the updated(with:) method. It looks at each incoming event's type (value or timerFired) and the state of the timer to decide what the new state should be and, if necessary, set a new timer.
extension DebounceState {
func updated(with event: DebounceEvent<Value>) -> DebounceState<Value, S> {
var answer = self
switch (event, timerState) {
case (.value(let value), .notRunning):
answer.output = value
answer.timerState = .running(nil)
scheduler.schedule(after: scheduler.now.advanced(by: interval), tolerance: .zero, options: options) { [subject] in subject.send() }
case (.value(let value), .running(_)):
answer.output = nil
answer.timerState = .running(.init(value: value, earliestDeliveryTime: scheduler.now.advanced(by: interval)))
case (.timerFired, .running(nil)):
answer.output = nil
answer.timerState = .notRunning
case (.timerFired, .running(.some(let pendingOutput))):
let now = scheduler.now
if pendingOutput.earliestDeliveryTime <= now {
answer.output = pendingOutput.value
answer.timerState = .notRunning
} else {
answer.output = nil
scheduler.schedule(after: pendingOutput.earliestDeliveryTime, tolerance: .zero, options: options) { [subject] in subject.send() }
}
case (.timerFired, .notRunning):
// Impossible!
answer.output = nil
}
return answer
}
}
Does it work? Let's test it:
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
let subject = PassthroughSubject<String, Never>()
let q = DispatchQueue.main
let start = DispatchTime.now()
let cfStart = CFAbsoluteTimeGetCurrent()
q.asyncAfter(deadline: start + .milliseconds(100)) { subject.send("A") }
// A should be delivered at start + 100ms.
q.asyncAfter(deadline: start + .milliseconds(200)) { subject.send("B") }
q.asyncAfter(deadline: start + .milliseconds(300)) { subject.send("C") }
// C should be delivered at start + 800ms.
q.asyncAfter(deadline: start + .milliseconds(1100)) { subject.send("D") }
// D should be delivered at start + 1100ms.
q.asyncAfter(deadline: start + .milliseconds(1800)) { subject.send("E") }
// E should be delivered at start + 1800ms.
q.asyncAfter(deadline: start + .milliseconds(1900)) { subject.send("F") }
q.asyncAfter(deadline: start + .milliseconds(2000)) { subject.send("G") }
// G should be delivered at start + 2500ms.
let ticket = subject
.kennycDebounce(for: .milliseconds(500), scheduler: q)
.sink {
print("\($0) \(((CFAbsoluteTimeGetCurrent() - cfStart) * 1000).rounded())") }
Output:
A 107.0
C 847.0
D 1167.0
E 1915.0
G 2714.0
I'm not sure why the later events are so delayed. It could just be playground side effects.

Rounding a Duration to the nearest second based on desired precision

I recently started working with Dart, and was trying to format a countdown clock with numbers in a per-second precision.
When counting down time, there's often a precise-yet-imperfect way of representing the time - so if I started a Duration at 2 minutes, and asked to show the current time after one second has elapsed, it is almost guaranteed that the precision of the timer will report at 1:58:999999 (example), and if use Duration.inSeconds() to emit the value, it will be 118 (seconds) which is due to how the ~/ operator works, since it's rounding down to integers based on the Duration's microseconds.
If I render the value as a clock, I'll see the clock go from "2:00" to "1:58" after one second, and will end up displaying "0:00" twice, until the countdown is truly at 0:00:00.
As a human, this appears like the clock is skipping, so I figured since the delta is so small, I should round up to the nearest second, and that would be accurate enough for a countdown timer, and handle the slight imprecision measured in micro/milli-seconds to better serve the viewer.
I came up with this secondRounder approach:
Duration secondRounder(Duration duration) {
int roundedDuration;
if (duration.inMilliseconds > (duration.inSeconds * 1000)) {
roundedDuration = duration.inSeconds + 1;
} else {
roundedDuration = duration.inSeconds;
}
return new Duration(seconds: roundedDuration);
}
This can also be run in this DartPad: https://dartpad.dartlang.org/2a08161c5f889e018938316237c0e810
As I'm yet unfamiliar with all of the methods, I've read through a lot of the docs, and this is the best I've come up with so far. I think I was looking for a method that might looks like:
roundedDuration = duration.ceil(nearest: millisecond)
Is there a better way to go about solving this that I haven't figured out yet?
You can "add" your own method to Duration as an extension method:
extension RoundDurationExtension on Duration {
/// Rounds the time of this duration up to the nearest multiple of [to].
Duration ceil(Duration to) {
int us = this.inMicroseconds;
int toUs = to.inMicroseconds.abs(); // Ignore if [to] is negative.
int mod = us % toUs;
if (mod != 0) {
return Duration(microseconds: us - mod + toUs);
}
return this;
}
}
That should allow you to write myDuration = myDuration.ceil(Duration(seconds: 1)); and round the myDuration up to the nearest second.
The best solution according to the documentation is to use .toStringAsFixed() function
https://api.dart.dev/stable/2.4.0/dart-core/num/toStringAsFixed.html
Examples from the Documentation
1.toStringAsFixed(3); // 1.000
(4321.12345678).toStringAsFixed(3); // 4321.123
(4321.12345678).toStringAsFixed(5); // 4321.12346
123456789012345678901.toStringAsFixed(3); // 123456789012345683968.000
1000000000000000000000.toStringAsFixed(3); // 1e+21
5.25.toStringAsFixed(0); // 5
Another more flexible option can be...
You can use this function to roundup the time.
DateTime alignDateTime(DateTime dt, Duration alignment,
[bool roundUp = false]) {
assert(alignment >= Duration.zero);
if (alignment == Duration.zero) return dt;
final correction = Duration(
days: 0,
hours: alignment.inDays > 0
? dt.hour
: alignment.inHours > 0
? dt.hour % alignment.inHours
: 0,
minutes: alignment.inHours > 0
? dt.minute
: alignment.inMinutes > 0
? dt.minute % alignment.inMinutes
: 0,
seconds: alignment.inMinutes > 0
? dt.second
: alignment.inSeconds > 0
? dt.second % alignment.inSeconds
: 0,
milliseconds: alignment.inSeconds > 0
? dt.millisecond
: alignment.inMilliseconds > 0
? dt.millisecond % alignment.inMilliseconds
: 0,
microseconds: alignment.inMilliseconds > 0 ? dt.microsecond : 0);
if (correction == Duration.zero) return dt;
final corrected = dt.subtract(correction);
final result = roundUp ? corrected.add(alignment) : corrected;
return result;
}
and then use it the following way
void main() {
DateTime dt = DateTime.now();
var newDate = alignDateTime(dt,Duration(minutes:30));
print(dt); // prints 2022-01-07 15:35:56.288
print(newDate); // prints 2022-01-07 15:30:00.000
}

Timestamp pattern

Let's assume I have the following reminder timestamp
local reminder_timestamp = "2013-12-13T00:00:00+01:00"
And I'm using the below function to return time in UTC
local function makeTimeStamp(dateString)
local pattern = "(%d+)%-(%d+)%-(%d+)%a(%d+)%:(%d+)%:([%d%.]+)([Z%p])(%d%d)%:?(%d%d)"
local year, month, day, hour, minute, seconds, tzoffset, offsethour, offsetmin = dateString:match(pattern)
local timestamp = os.time( {year=year, month=month, day=day, hour=hour, min=minute, sec=seconds} )
local offset = 0
if ( tzoffset ) then
if ( tzoffset == "+" or tzoffset == "-" ) then -- we have a timezone!
offset = offsethour * 60 + offsetmin
if ( tzoffset == "-" ) then
offset = offset * -1
end
timestamp = timestamp + offset
end
end
return timestamp
end
What should be the pattern above to match the reminder timestamp I mentioned earlier?
You need to use Lua's string parsing capabilities. Try a few of the techniques mentioned in the following, and if you still have issues, post specifically what is not working:
Question about splitting string and saving in several variables
Question about extracting data from a string, very similar to yours (although problem domain is GPS coordinates instead of date/time)
Question about how to do pattern matching in Lua, several good examples and links to docs
Here is the answer and the function actually works fine
pattern = "(%d+)%-(%d+)%-(%d+)%a(%d+)%:(%d+)%:([%d%.]+)([Z%p])(%d%d)%:?(%d%d)"
reminder_timestamp = "2013-12-23T08:00:00+01:00"
local year, month, day, hour, minute, seconds, tzoffset, offsethour, offsetmin = reminder_timestamp:match(pattern)
Resource: http://www.lua.org/manual/5.1/manual.html#5.4.1

F# Silverlight RPC: pre-fill paginated data

Thanks to everybody who has helped over the past few months trying to help me get my silverlight / f# prototype up and running (started in the RC version of VS - Ugh). The last problem we are trying to solve is an RPC issue.
We need to have the ability to paginate RPC calls, such that the first page is requested and bound to the grid and displayed, while the otehr pages are prefilled in the background and concatenated together. I guess psuedo code would look like this:
let pageNo = 1
let page1Data = JsonRpc.getSomeData(pageNo)
let grid.datasource <- page1Data
let grid.suspendFiltering <- true
// run the remainder in background
let allData : list ref = page1Data ref
for pageNo in [2..totalPages]
allData := allData # JsonRpc.getSomeData(pageNo)
let grid.datasource <- allData
let grid.suspendFiltering <- true
I appologize for the code above, I tried to make it as F# like as possible (writing in this text window); another flaw is the need to use call backs to bind the data to grids etc.
The question approaches might be used to solve this problem and what is the most approriate?
hmm... something like this? (typing in browser so may contain errors):
module Loader
open System
open System.Threading
let totalPages = 20
// emulation of long-running data loading routine
let private loadPageData (page : int) =
async {
do! Async.Sleep(1000)
return List.replicate 5 page
}
// loader - notifies UI about new data via callback
let loadAsync (callback : System.Action<_>) =
let syncContext = SynchronizationContext.Current
let doLoad = async {
// load first page and immediately feed it to callback
let! page1Data = loadPageData 1
do! Async.SwitchToContext syncContext
callback.Invoke(ResizeArray<_>(page1Data))
// load remaining data in the background
do! Async.SwitchToThreadPool()
let allData = ResizeArray<_>(page1Data)
for page in 2..totalPages do
let! pageData = loadPageData page
allData.AddRange(pageData)
do! Async.SwitchToContext syncContext
callback.Invoke(allData)
}
Async.Start doLoad
On UI side it will appear like this (i.e. data - ListBox or some other control)
Loader.loadDataAsync(list => data.ItemSource = list)

Resources