How can I get a random time between Now and some previous time point (eg. 1 hour ago)?
I'm trying to make an extension method that allows me to create a random time between Now and some user requested historical time point in the form of a TimeSpan.
F开发者_如何学运维or example : a random time between now and 1 hour ago.
So, I came up with the following Random(..)
extension method.
I thought to make the random seed NOT static, but if i call this method a LOT and QUICKLY (eg. in a loop), then I thought the seed (which is based on time) isn't really random for really fast calls. is that true? (it seems to be, when i check my results)
public static DateTimeOffset Random(this DateTimeOffset value, TimeSpan timeSpan)
{
var random = new Random();
DateTimeOffset minDateTime = value.Subtract(timeSpan);
int range = ((DateTime.Today - minDateTime)).Hours;
return minDateTime.AddHours(random.Next(range));
}
As others have said, the problem is that new Random()
uses the current time to form the seed, and you're getting the same one lots of times.
Basically you want to create relatively few instances. As Random
isn't thread-safe, you need ThreadStatic
or ThreadLocal<T>
- the latter is new to .NET 4.0. Here's a sample StaticRandom
class (using .NET 4.0) which lets you use the Instance
property to get a valid instance for this thread. Note that on type initialization, a counter is set from the current time. This is then used for successive seeds.
using System;
using System.Threading;
public static class StaticRandom
{
private static int seed;
private static ThreadLocal<Random> threadLocal = new ThreadLocal<Random>
(() => new Random(Interlocked.Increment(ref seed)));
static StaticRandom()
{
seed = Environment.TickCount;
}
public static Random Instance { get { return threadLocal.Value; } }
}
Then you can just use StaticRandom.Instance
whenever you need an instance of Random
.
Now to get back to the original question, it's not entirely clear what your current extension method is doing. Why are you using DateTime.Today
at all? I suspect you want something like:
public static DateTimeOffset Random(this DateTimeOffset value, TimeSpan timeSpan)
{
double seconds = timeSpan.TotalSeconds * StaticRandom.Instance.NextDouble();
// Alternatively: return value.AddSeconds(-seconds);
TimeSpan span = TimeSpan.FromSeconds(seconds);
return value - span;
}
However, that will give you a completely random time - it's almost bound to be part way through a millisecond, for instance. Is that okay, or do you effectively want it to be an exact number of seconds (or minutes, or hours) based on the original timespan?
Use a Random
object that's created/initialised once and not every time the method is called. Another option is to pass a Random
instance into the method when you call it.
You could also create overloads that allow you to do either of the above options:
public static DateTimeOffset Random(this DateTimeOffset value, TimeSpan timeSpan)
{
if (_threadStaticRng == null)
_threadStaticRng = new Random();
return value.Random(timeSpan, _threadStaticRng);
}
public static DateTimeOffset Random(
this DateTimeOffset value, TimeSpan timeSpan, Random rng)
{
DateTimeOffset minDateTime = value.Subtract(timeSpan);
int range = ((DateTime.Today - minDateTime)).Hours;
return minDateTime.AddHours(rng.Next(range));
}
[ThreadStatic]
private static Random _threadStaticRng;
You have to seed the Random number generator. A good practice would be to do the following:
var random = new Random((int)DateTime.Now.Ticks);
This should make it more random for you.
You could also create the Random generator as a static class variable so you don't have to instantiate it every time.
精彩评论