F# generator of daterange? - f#

I'm attempting to write a function that generates a list of DateTimes using the generator syntax:
let dateRange =
let endDate = System.DateTime.Parse("6/1/2010")
let startDate = System.DateTime.Parse("3/1/2010")
seq {
for date in startDate..endDate do
if MyDateClass.IsBusinessDay(date) then yield date
}
but the generator ('seq') block does not parse correctly. It wants a timespan. While the generator syntax seems perfect for what I want to do, it's rather non-intuitive for anything but two numbers.
Is it possible to use the generator syntax to create a DateTime range?
is there a better way to think about how to create the range than I wrote (i.e. the 'in' clause)

If TimeSpan had a static Zero property, then you could do something like startDate .. TimeSpan(1,0,0,0) .. endDate. Even though it doesn't, you can create a wrapper that will do the same thing:
open System
type TimeSpanWrapper = { timeSpan : TimeSpan } with
static member (+)(d:DateTime, tw) = d + tw.timeSpan
static member Zero = { timeSpan = TimeSpan(0L) }
let dateRange =
let endDate = System.DateTime.Parse("6/1/2010")
let startDate = System.DateTime.Parse("5/1/2010")
let oneDay = { timeSpan = System.TimeSpan(1,0,0,0) }
seq {
for date in startDate .. oneDay .. endDate do
if MyDateClass.IsBusinessDay(date) then yield date
}

The arithemetic difference between two DateTime objects in .NET is always a TimeSpan, that's your first problem. And if you had a TimeSpan, it wouldn't implement IEnumerable<>, so can't be used as a sequence. You can write your own sequence expression, though:
let rec dates (fromDate:System.DateTime) (toDate:System.DateTime) = seq {
if fromDate <= toDate then
yield fromDate
yield! dates (fromDate.AddDays(1.0)) toDate
}
You use it to create a sequence with all the dates in range, and then filter the result:
let result = dates startDate endDate |> Seq.filter (fun dt -> IsBusinessDate(dt))

Related

How to compare two string values within a threshold value in swift?

I am trying to write a code where I have got two time[hh:min] data(String type). Need to just compare but the challenge is my code undergones some validations before returning the final values. so the assertion fails sometimes stating expected value is [17:04] but actual is [17:05]. Is there any way where we can use concept of Threshold that upto few minutes (say 2 mins) the comparison will still be valid?
Step one is do not store a thing as something that it is not. If these are times, they should be stored as times. Strings are for representation to the users; underlying storage is for reality.
So now let's store our times as date components:
let t1 = DateComponents(hour:17, minute:4)
let t2 = DateComponents(hour:17, minute:5)
Now it's easy to find out how far apart they are:
let cal = Calendar(identifier: .gregorian)
if let d1 = cal.date(from: t1),
let d2 = cal.date(from: t2) {
let diff = abs(d1.timeIntervalSince(d2))
// and now decide what to do
}
You first need to seprate your string to an array, and then you can compare.
/* That two arrays are A1 and A2 */
let minute1 = Int(A1[0])*60+Int(A1[1])
let minute2 = Int(A2[0])*60+Int(A2[1])
This may help you. I think that #Sweeper did not understand that it is a time, not a date.
You can convert your string to minutes, subtract one from another and check if the absolute value is less than the threshold:
extension String {
var time24hToMinutes: Int? {
guard count == 5, let hours = Int(prefix(2)), let minutes = Int(suffix(2)), Array(self)[2] == ":" else { return nil }
return hours * 60 + minutes
}
func time24hCompare(to other: String, threshold: Int = 2) -> Bool {
guard let lhs = time24hToMinutes, let rhs = other.time24hToMinutes else { return false }
return abs(lhs-rhs) < threshold
}
}
Testing:
"17:02".time24hCompare(to: "17:04") // false
"17:03".time24hCompare(to: "17:04") // true
"17:04".time24hCompare(to: "17:04") // true
"17:05".time24hCompare(to: "17:04") // true
"17:06".time24hCompare(to: "17:04") // false

splitting date/time field to separate date and time

I have a date/time field (i.e. 2018-04-24 10:00:00) that I want to split into separate date and time. I have the following functions, but it does not work with uib-datepicker since I'm splitting a date/time field like a string:
function returnDate(date) {
var apptDate = date.split(' ')[0];
return apptDate;
}
function returnTime(date) {
var apptTime = date.split(' ')[1].substring(0,5);
var hours24 = parseInt(apptTime.substring(0, 2),10);
var hours = ((hours24 + 11) % 12) + 1;
var amPm = hours24 > 11 ? 'pm' : 'am';
var minutes = apptTime.substring(2);
return hours + minutes + ' ' + amPm;
}
I've also tried to use getDate, getFullYear, getMonth, etc. but I keep getting a TypeError with getDate.
Can someone provide some guidance on this date issue? Thanks!
Because between date and time it has a space, so you can get date and time separately by this way.
Method 1 : Split string
string date_time = "2018-04-24 10:00:00";
string[] words = date_time.Split(' ');//Split string
string date = words[0];//date = 1st object (before space)
string time = words[1];//time= 2nd object (after space)
Method 2 : Using Regular expression
string date_time = "2018-04-24 10:00:00";
string _date = "";
string _time = "";
Regex date = new Regex(#"([0-9-]+)\s");
Match match_date = date.Match(date_time);
Regex time = new Regex(#"\s([0-9:]+)");
Match match_time = time.Match(date_time);
//Date
if (match_date.Success)
{
_date = match_date.Value;
Console.WriteLine(_date);
}
//Time
if (match_time.Success)
{
_time = match_time.Value.Replace(" ","");
Console.WriteLine(_time);
}
have you tried new Date('2018-04-24 10:00:00') and then geting month year etc afterwords from a date object?

Previous working date in F#

Quite a beginner question: given a DateTime, I want to find the previous non-weekend date in F#. I thus created this:
open System
module Date =
let isWeekEnd (date:DateTime) =
((date.DayOfWeek = DayOfWeek.Saturday) || (date.DayOfWeek = DayOfWeek.Sunday))
let getPreviousWorkDay date =
while isWeekEnd date do
let date = date.AddDays -1.0
date
However, it seems I violate the fact that date is immutable. I hence wonder how to do this with a while loop and why not with a sequence by retrieving up to 3 days back and filtering the one which is nonweekend, finally taking the first...
If you want to use that while loop approach you need to declare date as mutable:
let getPreviousWorkDay d =
let mutable date = d
while isWeekEnd date do
date <- date.AddDays -1.0
date
Otherwise you can use recursion:
let rec getPreviousWorkDay date =
let prevDate = date.AddDays -1.0
if isWeekEnd prevDate then getPreviousWorkDay prevDate
else prevDate
Note that if what you want is really to go one day before and then if it's a weekend go further back, your function will stay in case of sending a weekday. But you can solve it by passing an already subtracted day:
let getPreviousWorkDay (date:DateTime) =
let rec loop date =
if isWeekEnd date then loop (date.AddDays -1.0)
else date
loop (date.AddDays -1.0)
The very nature of using a while loop means that you're going to be mutating something. If you don't want to do this and would rather go with a more functional solution then you could use a match expression:
module Date =
let getPreviousWorkDay (date:DateTime) =
match date.DayOfWeek with
| DayOfWeek.Monday -> date.AddDays -3.0
| DayOfWeek.Sunday -> date.AddDays -2.0
| _ -> date.AddDays -1.0
Here we don't need to mutate anything.

Relative date parsing

How to parse relative datetime in GO?
Example of relative dates:
today at 9:17 AM
yesterday at 9:58 PM
Saturday at 9:44 PM
Wednesday at 11:01 AM
So format is DAY (in the past) at TIME. I tried next example:
const longForm = "Monday at 3:04 PM"
t, _ := time.Parse(longForm, "Saturday at 3:50 PM")
fmt.Println(t)
demo
Time is parsed correctly, but day/date is ignored...
Expanding on my comment:
Just Monday without further date reference is meaningless in the eyes of the parser, so it is discarded. Which Monday? The parser is strict, not fuzzy. Assuming Monday refers to the current week is not something that such a parser can do. You will not to write your own more sophisticated parser for that.
So it would have to be along these lines - one function that converts a relative fuzzy day to a real date, and replaces that in the original expression, and another one that parses the whole thing:
const dateFormat = "2006-01-02"
const longForm = "2006-01-02 at 3:04 PM"
func parseFuzzyDate(fuzzyTime string) (time.Time, error) {
formattedTime, err := parseDayAndReplaceIt(fuzzyTime)
if err != nil {
return nil, err
}
return time.Parse(longForm, formattedTime)
}
and the second function gets the fuzzy time, finds the day, parses it and returns. I'm not going to implement it, just write in comments what should be done:
func parseDayAndReplaceIt(fuzzyTime string) (string, error) {
// 1. Extract the day
// 2. Parse weekday names to relative time
// 3. if it's not a weekday name, parse things like "tomorrow" "yesterday"
// 4. replace the day string in the original fuzzyTime with a formatted date that the parser can understand
// 5. return the formatted date
}
I tweaked something that I wrote a while back and consolidated it into this example code:
func lastDateOf(targetDay time.Weekday, timeOfDay time.Time) time.Time {
const oneDay = 24 * time.Hour
var dayIndex time.Duration
//dayIndex -= oneDay
for {
if time.Now().Add(dayIndex).Weekday() == targetDay {
y, m, d := time.Now().Add(dayIndex).Date()
return timeOfDay.AddDate(y, int(m)-1, d-1)
}
dayIndex -= oneDay
}
}
It returns the date, relative to now, of the previous targetDay, added to timeOfDay, assuming that timeOfDay consists of hours, minutes and seconds, and the zero time values for year, month and day it will give you a suitable answer.
It's not very flexible but I believe it suits your example reasonably well. Although it doesn't address relative terms like "tomorrow", "yesterday" or "next Saturday".
runnable version in the playground.
Custom parser:
func RelativeDateParse(s string) (time.Time, error) {
for n := 0; n < 7; n++ {
day := time.Now().AddDate(0, 0, -n)
dayName := day.Format("Monday")
switch n {
case 0:
dayName = "today"
case 1:
dayName = "yesterday"
}
s = strings.Replace(s, dayName + " at", day.Format("2006-01-02"), -1)
}
return time.Parse("2006-01-02 3:04 PM", s)
}
demo

How to categorize over units of measure?

The problem is simple, I wish to do some calculations on some travel expenses which include both expenses in DKK and JPY. Thus I've found a nice way to model currency so I am able to convert back and forth:
[<Measure>] type JPY
[<Measure>] type DKK
type CurrencyRate<[<Measure>]'u, [<Measure>]'v> =
{ Rate: decimal<'u/'v>; Date: System.DateTime}
let sep10 = System.DateTime(2015,9,10)
let DKK_TO_JPY : CurrencyRate<JPY,DKK> =
{ Rate = (1773.65m<JPY> / 100m<DKK>); Date = sep10}
let JPY_TO_DKK : CurrencyRate<DKK,JPY> =
{ Rate = (5.36m<DKK> / 100.0m<JPY>); Date=sep10 }
I proceed to model expenses as a record type
type Expense<[<Measure>] 'a> = {
name: string
quantity: int
amount: decimal<'a>
}
and here I have an example list of expenses:
let travel_expenses = [
{ name = "flight tickets"; quantity = 1; amount = 5000m<DKK> }
{ name = "shinkansen ->"; quantity = 1; amount = 10000m<JPY> }
{ name = "shinkansen <-"; quantity = 1; amount = 10000m<JPY> }
]
And this is where the show stops... F# doesn't like that list, and complaints that all of the list should be DKK, -which of course makes sense.
Then I thought that there must be some smart way to make a discriminated union of my units of measures to put them in a category, and then I attempted with:
[<Measure>] type Currency = JPY | DKK
But this is not possible and results in The kind of the type specified by its attributes does not match the kind implied by its definition.
The solution I've come up with so far is very redundant, and I feel that it makes the unit of measure quite pointless.
type Money =
| DKK of decimal<DKK>
| JPY of decimal<JPY>
type Expense = {
name: string
quantity: int
amount: Money
}
let travel_expenses = [
{ name = "flight tickets"; quantity = 1; amount = DKK(5000m<DKK>) }
{ name = "shinkansen ->"; quantity = 1; amount = JPY(10000m<JPY>) }
{ name = "shinkansen <-"; quantity = 1; amount = JPY(10000m<JPY>) }
]
Is there a good way of working with these units of measures as categories? like for example
[<Measure>] Length = Meter | Feet
[<Measure>] Currency = JPY | DKK | USD
or should I remodel my problem and maybe not use units of measure?
Regarding the first question no, you can't but I think you don't need units of measures for that problem as you state in your second question.
Think how do you plan to get those records at runtime (user input, from a db, from a file, ...) and remember units of measures are a compile-time features, erased at runtime. Unless those records are always hardcoded, which will make your program useless.
My feeling is that you need to deal at run-time with those currencies and makes more sense to treat them as data.
Try for instance adding a field to Expense called currency:
type Expense = {
name: string
quantity: int
amount: decimal
currency: Currency
}
then
type CurrencyRate = {
currencyFrom: Currency
currencyTo: Currency
rate: decimal
date: System.DateTime}
As an alternative to Gustavo's accepted answer, If you still want to prevent anybody and any function accidentally summing JPY with DKK amounts, you can keep your idea of discriminated union like so :
let sep10 = System.DateTime(2015,9,10)
type Money =
| DKK of decimal
| JPY of decimal
type Expense = {
name: string
quantity: int
amount: Money
date : System.DateTime
}
type RatesTime = { JPY_TO_DKK : decimal ; DKK_TO_JPY : decimal ; Date : System.DateTime}
let rates_sep10Tosep12 = [
{ JPY_TO_DKK = 1773.65m ; DKK_TO_JPY = 5.36m ; Date = sep10}
{ JPY_TO_DKK = 1779.42m ; DKK_TO_JPY = 5.31m ; Date = sep10.AddDays(1.0)}
{ JPY_TO_DKK = 1776.07m ; DKK_TO_JPY = 5.33m ; Date = sep10.AddDays(2.0)}
]
let travel_expenses = [
{ name = "flight tickets"; quantity = 1; amount = DKK 5000m; date =sep10 }
{ name = "shinkansen ->"; quantity = 1; amount = JPY 10000m; date = sep10.AddDays(1.0)}
{ name = "shinkansen <-"; quantity = 1; amount = JPY 10000m ; date = sep10.AddDays(2.0)}
]
let IN_DKK (rt : RatesTime list) (e : Expense) =
let {name= _ ;quantity = _ ;amount = a ;date = d} = e
match a with
|DKK x -> x
|JPY y ->
let rtOfDate = List.tryFind (fun (x:RatesTime) -> x.Date = d) rt
match rtOfDate with
| Some r -> y * r.JPY_TO_DKK
| None -> failwith "no rate for period %A" d
let total_expenses_IN_DKK =
travel_expenses
|> List.fold(fun acc e -> (IN_DKK rates_sep10Tosep12 e) + acc) 0m
Even better would be to make function IN_DKK as a member of type Expense and put a restriction (private,...) on the field "amount".
Your initial idea of units of measure makes sense to prevent summing different currencies but unfortunately it does not prevent from converting from one to another and back to the first currency. And since your rates are not inverse (r * r' <> 1 as your data shows), unit of measure for currencies are dangerous and error prone. Note : I did not take into account the field "quantity" in my snippet.

Resources