在两个日期之间生成一个随机日期

215

如何生成一个随机日期,该日期必须介于两个给定日期之间?

函数签名应该像这样:

random_date("1/1/2008 1:30 PM", "1/1/2009 4:50 AM", 0.34)
                   ^                       ^          ^

            date generated has  date generated has  a random number
            to be after this    to be before this

它将返回一个日期,例如:2/4/2008 7:20 PM


目前问题的表述并不清楚,无法确定您是想要随机日期还是时间。您的示例表明您正在寻找一个时间。如果必须在两个日期之间,您可能需要修改到目前为止给出的答案以适应您的需求,并排除结束和开始时间。最后,在大多数答案中,例如被接受的答案,由于截断为整数,代码输出一个不包括结束时间的日期时间。要生成一个可能包含结束时间的时间,请将代码更改为 ptime = stime + prop * (etime - stime) + 0.5 - tortal
是的,可能这个问题涉及到插值,每个发现它的人都想要一个随机日期 :) - Tomasz Gandor
31个回答

199

将两个字符串转换为时间戳(以您选择的分辨率,例如毫秒、秒、小时、天等),用较晚的时间戳减去较早的时间戳,将您的随机数(假设它在范围[0,1]内分布)乘以该差异,然后再加上之前的时间戳。 将时间戳转换回日期字符串,就可以得到该范围内的随机时间。

Python 示例(输出与您指定的格式几乎相同,除了 0填充 - 这要归咎于美国时间格式约定):

import random
import time
    
def str_time_prop(start, end, time_format, prop):
    """Get a time at a proportion of a range of two formatted times.

    start and end should be strings specifying times formatted in the
    given format (strftime-style), giving an interval [start, end].
    prop specifies how a proportion of the interval to be taken after
    start.  The returned time will be in the specified format.
    """

    stime = time.mktime(time.strptime(start, time_format))
    etime = time.mktime(time.strptime(end, time_format))

    ptime = stime + prop * (etime - stime)

    return time.strftime(time_format, time.localtime(ptime))


def random_date(start, end, prop):
    return str_time_prop(start, end, '%m/%d/%Y %I:%M %p', prop)
    
print(random_date("1/1/2008 1:30 PM", "1/1/2009 4:50 AM", random.random()))

这不支持1900年之前的日期。 - user3064538

158
from random import randrange
from datetime import timedelta

def random_date(start, end):
    """
    This function will return a random datetime between two datetime 
    objects.
    """
    delta = end - start
    int_delta = (delta.days * 24 * 60 * 60) + delta.seconds
    random_second = randrange(int_delta)
    return start + timedelta(seconds=random_second)

精度为秒。如果需要,可以将其提高到微秒级别,或者减少到半小时的级别。只需更改最后一行的计算即可。

运行示例:

from datetime import datetime

d1 = datetime.strptime('1/1/2008 1:30 PM', '%m/%d/%Y %I:%M %p')
d2 = datetime.strptime('1/1/2009 4:50 AM', '%m/%d/%Y %I:%M %p')

print(random_date(d1, d2))

输出:

2008-12-04 01:50:17

3
在这种情况下使用 start 变量是完全正确的。我在代码中唯一看到的问题是从结果 delta 中使用 seconds 属性。它不能返回整个时间间隔中的总秒数;相反,它只是来自“时间”组件的秒数(介于0和60之间的数字);timedelta 对象有一个 total_seconds 方法,应该使用它来代替。 - emyller
8
我使用 (delta.days * 24 * 60 * 60) + delta.seconds 来获得总秒数。在2009年回答这个问题时,Python 2.7 中的 total_seconds() 方法还不存在。如果你正在使用 Python 2.7,则应该使用该方法,但目前的代码也可以正常工作。 - nosklo
我之前不知道在2.7-版本中不存在这种方法。我刚刚检查了一下,发现timedelta对象基本上由天数和秒数组成,所以你是对的。 :-) - emyller
1
@emyller:仅为完整起见,timedelta对象由天、秒和微秒组成。上面随机日期生成代码的精度高达秒级,但可以根据我在答案中提到的进行更改。 - nosklo

123

更新的回答

使用Faker甚至更加简单。

安装

pip install faker

使用方法:

from faker import Faker
fake = Faker()

fake.date_between(start_date='today', end_date='+30y')
# datetime.date(2025, 3, 12)

fake.date_time_between(start_date='-30y', end_date='now')
# datetime.datetime(2007, 2, 28, 11, 28, 16)

# Or if you need a more specific date boundaries, provide the start 
# and end dates explicitly.
import datetime
start_date = datetime.date(year=2015, month=1, day=1)
fake.date_between(start_date=start_date, end_date='+30y')

旧回答

使用雷达非常简单。

安装

pip install radar

用法

import datetime

import radar 

# Generate random datetime (parsing dates from str values)
radar.random_datetime(start='2000-05-24', stop='2013-05-24T23:59:59')

# Generate random datetime from datetime.datetime values
radar.random_datetime(
    start = datetime.datetime(year=2000, month=5, day=24),
    stop = datetime.datetime(year=2013, month=5, day=24)
)

# Just render some random datetime. If no range is given, start defaults to 
# 1970-01-01 and stop defaults to datetime.datetime.now()
radar.random_datetime()

3
为建议使用faker模块点赞。我曾经使用它来生成个人资料,但没有使用日期工具。在测试时,faker是一个非常好的模块。 - Gahan
1
即使需要安装它,使用一个令人难以置信的库也值得点赞。这将把实现的复杂度基本上降至4行。 - Blairg23
我来晚了,但我真的很喜欢Faker。不过有没有办法将start_date指定为“2015-01-01”而不是“-3y”?我查看了他们的文档,但没有找到。我觉得他们选择“-3y”格式很奇怪。 - KubiK888
1
@KubiK888:好的,请查看我的更新回答。您应该明确提供start_date。 - Artur Barseghyan
@amchugh89 如果有人在编写严肃的东西,你的答案对于许多边缘情况都是不正确的,而仅仅使用一个库则a)容易 b)已被深思熟虑。 - user3064538
显示剩余4条评论

103
一个小版本。
import datetime
import random


def random_date(start, end):
    """Generate a random datetime between `start` and `end`"""
    return start + datetime.timedelta(
        # Get a random amount of seconds between `start` and `end`
        seconds=random.randint(0, int((end - start).total_seconds())),
    )

请注意,startend参数都应该是datetime对象。如果您有字符串,请将其转换为datetime对象也很容易。其他答案指出了一些方法。


29

这是一种不同的方法 - 它有点起作用..

from random import randint
import datetime

date=datetime.date(randint(2005,2025), randint(1,12),randint(1,28))

更好的方法

startdate=datetime.date(YYYY,MM,DD)
date=startdate+datetime.timedelta(randint(1,365))

2
第一种方法永远不会选择以29日、30日或31日结尾的日期,而您的第二种方法没有考虑闰年,当一年有366天时,即如果startdate + 1年经过闰年的12月31日,这段代码将永远不会选择完全一年后的同一日期。这两种方法只允许您指定一个起始日期和未来多少年,而问题是要求指定两个日期,我认为这是一个更有用的API。 - user3064538

25

自 Python 3 开始,timedelta 支持与浮点数的乘法运算,因此现在您可以执行以下操作:

import random
random_date = start + (end - start) * random.random()

假设startend的类型均为datetime.datetime。例如,要在接下来的一天内生成随机日期时间:

import random
from datetime import datetime, timedelta

start = datetime.now()
end = start + timedelta(days=1)
random_date = start + (end - start) * random.random()

精彩:非常紧凑的解决方案 - Domenico Spidy Tamburro

6
为了提供一个基于pandas的解决方案,我使用以下内容:
import pandas as pd
import numpy as np

def random_date(start, end, position=None):
    start, end = pd.Timestamp(start), pd.Timestamp(end)
    delta = (end - start).total_seconds()
    if position is None:
        offset = np.random.uniform(0., delta)
    else:
        offset = position * delta
    offset = pd.offsets.Second(offset)
    t = start + offset
    return t

我喜欢它,因为它具有很好的 pd.Timestamp 特性,可以让我在其中输入不同的内容和格式。考虑以下几个例子...

您的签名。

>>> random_date(start="1/1/2008 1:30 PM", end="1/1/2009 4:50 AM", position=0.34)
Timestamp('2008-05-04 21:06:48', tz=None)

随机位置。

>>> random_date(start="1/1/2008 1:30 PM", end="1/1/2009 4:50 AM")
Timestamp('2008-10-21 05:30:10', tz=None)

不同格式。
>>> random_date('2008-01-01 13:30', '2009-01-01 4:50')
Timestamp('2008-11-18 17:20:19', tz=None)

直接传递pandas/datetime对象。

>>> random_date(pd.datetime.now(), pd.datetime.now() + pd.offsets.Hour(3))
Timestamp('2014-03-06 14:51:16.035965', tz=None)

你如何优雅地创建一个随机日期时间序列(即,不需要为每个元素迭代函数)? - dmvianna
也许可以修改该函数以生成“delta”值数组,并一次性将它们映射到时间戳。不过,个人而言,我更喜欢像这样做:pd.Series([5] * 10, [random_date('2014-01-01', '2014-01-30') for i in range(10)]) - metakermit

4
将日期转换为时间戳并使用这些时间戳调用random.randint,然后再将随机生成的时间戳转换回日期。
from datetime import datetime
import random

def random_date(first_date, second_date):
    first_timestamp = int(first_date.timestamp())
    second_timestamp = int(second_date.timestamp())
    random_timestamp = random.randint(first_timestamp, second_timestamp)
    return datetime.fromtimestamp(random_timestamp)

然后你可以像这样使用它。
from datetime import datetime

d1 = datetime.strptime("1/1/2018 1:30 PM", "%m/%d/%Y %I:%M %p")
d2 = datetime.strptime("1/1/2019 4:50 AM", "%m/%d/%Y %I:%M %p")

random_date(d1, d2)

random_date(d2, d1)  # ValueError because the first date comes after the second date

如果您关心时区,建议直接使用来自Faker库的date_time_between_dates函数,正如另一个回答所建议的那样,这里我从这里借鉴了代码。

4
# needed to create data for 1000 fictitious employees for testing code 
# code relating to randomly assigning forenames, surnames, and genders
# has been removed as not germaine to the question asked above but FYI
# genders were randomly assigned, forenames/surnames were web scrapped,
# there is no accounting for leap years, and the data stored in mySQL
   
import random 
from datetime import datetime
from datetime import timedelta

for employee in range(1000):
    # assign a random date of birth (employees are aged between sixteen and sixty five)
    dlt = random.randint(365*16, 365*65)
    dob = datetime.today() - timedelta(days=dlt)
    # assign a random date of hire sometime between sixteenth birthday and today
    doh = datetime.today() - timedelta(days=random.randint(0, dlt-365*16))
    print("born {} hired {}".format(dob.strftime("%d-%m-%y"), doh.strftime("%d-%m-%y")))

简单易懂,不需要转换时间戳。如果你需要精确的“日期”而非“日期时间”,这个方法非常有用。 - Tomasz Gandor

3

以下是对标题字面意思的回答,而不是针对问题的主体:

import time
import datetime
import random

def date_to_timestamp(d) :
  return int(time.mktime(d.timetuple()))

def randomDate(start, end):
  """Get a random date between two dates"""

  stime = date_to_timestamp(start)
  etime = date_to_timestamp(end)

  ptime = stime + random.random() * (etime - stime)

  return datetime.date.fromtimestamp(ptime)

这段代码基于被接受的答案,但有所改动。

你可以将倒数第二行改为 ptime = random.randint(stime, etime),这样更准确一些,因为 randint 生成的是一个包含在内的范围。 - user3064538

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接