我正在制作一个为用户提供折扣的应用程序,其中一个折扣是在他的生日那天给的。由于很难仅在生日那天使用折扣,因此我们决定在整个星期(包括两个周末)给予折扣。
好吧,所以我们需要一个方法来接收birthdayDate
和todayDate
并返回true或false来告知日期是否在生日范围内。
一切都很好,直到您开始查看临近年份的日期为止。
如果您的生日是2018年12月31日,则可以在2019年1月6日使用折扣,如果您的生日是2019年1月1日,则可以在2018年12月28日使用折扣。
因此,仅查看当前年份的生日是不够的,并且由于每年的星期几在每年更改,因此您今年生日的最后一个星期五将不是下一年的同一天。
有一种优雅的方法可以轻松找到此范围并创建返回true或false的方法?
真正的问题是找到哪个生日与生日相关,因为您可以在上一个生日之后到下一个生日之前获得折扣。
原始代码是用PHP编写的,但是由于它是一个算法问题,我可以接受任何语言的答案
| Today | Birth | Output |
|-------------------|-------------------|:------:|
| February 15, 2019 | February 22, 2000 | true |
| February 24, 2019 | February 22, 2000 | true |
| February 25, 2019 | February 22, 2000 | false |
| December 28, 2018 | January 03, 2000 | true |
| December 27, 2018 | January 03, 2000 | false |
| December 27, 2019 | January 03, 2000 | true |
| January 01, 2019 | January 03, 2000 | true |
| January 01, 2019 | December 31, 2000 | true |
| January 01, 2019 | December 28, 2000 | false |
答案 0 :(得分:4)
这是一些使用我的Noda Time库的C#代码,它们通过了您指定的所有测试。
基本思想很简单:
三个月的选择有些随意;它只是用来解决您刚刚生日的情况。 (有可能超过10天且少于355天都是可以的。我只觉得容易推断3个月。)
using System;
using NodaTime;
using NodaTime.Text;
class Test
{
static void Main()
{
RunTest("2019-02-15", "2000-02-22", true);
RunTest("2019-02-24", "2000-02-22", true);
RunTest("2019-02-25", "2000-02-22", false);
RunTest("2018-12-28", "2000-01-03", true);
RunTest("2019-01-01", "2000-01-03", true);
RunTest("2018-12-27", "2000-01-03", false);
RunTest("2019-12-27", "2000-01-03", true);
}
static void RunTest(string todayText, string birthdayText, bool expectedResult)
{
var pattern = LocalDatePattern.Iso;
RunTest(pattern.Parse(todayText).Value,
pattern.Parse(birthdayText).Value,
expectedResult);
}
static void RunTest(LocalDate today, LocalDate birthday, bool expectedResult)
{
// Work out "the birthday that comes after 3 months ago".
// That can be:
// - A recent birthday before the closest birthday discount period,
// in which case the *next* birthday will be after the current closest
// discount period
// - A birthday *in* the current closest birthday discount period
// - A birthday *after* the current closest birthday discount period,
// in which case the *previous* birthday was before the current
// closest discount period
LocalDate threeMonthsAgo = today.PlusMonths(-3);
int ageThreeMonthsAgo = Period.Between(birthday, threeMonthsAgo).Years;
// Note: this will use Feb 28th for a Feb 29th birthday in a non leap year.
LocalDate relevantBirthday = birthday.PlusYears(ageThreeMonthsAgo + 1);
// Find the strictly-previous Friday to start the discount interval
LocalDate discountStart = relevantBirthday.With(DateAdjusters.Previous(IsoDayOfWeek.Friday));
LocalDate discountEndInclusive = discountStart.PlusDays(9);
DateInterval discountInterval = new DateInterval(discountStart, discountEndInclusive);
bool actualResult = discountInterval.Contains(today);
Console.WriteLine($"{today} / {birthday} / {(actualResult == expectedResult ? "PASS" : "FAIL")}");
}
}
答案 1 :(得分:2)
建议解决方案(使用JS和时间戳)
// Helper constants
const day = 1000 * 60 * 60 * 24
const friday = 5
const $input = document.getElementById('datepicker')
const $result = document.getElementById('result')
const $tests = document.getElementById('tests')
// Generate the period based on birthday and now (optional)
function getPeriod(birthday, today) {
const now = new Date(today || new Date())
// reset to start at 00:00
now.setHours(0)
now.setMinutes(0)
now.setMilliseconds(0)
// Available beggining date
const begin = new Date(now - (10 * day))
birthday = new Date(birthday)
// fix birthday year
birthday.setFullYear(begin.getFullYear())
// if it already passed, jump to next year
if (birthday < begin)
birthday.setFullYear(birthday.getFullYear() + 1)
// Start date
const start = new Date(birthday)
// if the birthday is already on friday, jump to previous friday (3th condition)
if(start.getDay() === friday)
start.setTime(start.getTime() - (day * 7))
// go to the last friday
while (start.getDay() !== friday)
start.setTime(start.getTime() - day)
// return found date + 10 days
return [start, new Date(start.getTime() + (10 * day)-1)]
}
function calculatePeriod() {
const birthday = $input.value
const [begin, end] = getPeriod(birthday)
$result.innerHTML = begin.toString() + '<br>' + end.toString()
}
const testCases = [
['February 15, 2019', 'February 22, 2000'],
['February 24, 2019', 'February 22, 2000'],
['February 25, 2019', 'February 22, 2000'],
['December 28, 2018', 'January 03, 2000'],
['December 27, 2018', 'January 03, 2000'],
['December 27, 2019', 'January 03, 2000'],
['January 01, 2019 ', 'January 03, 2000'],
['January 01, 2019 ', 'December 31, 2000'],
['January 01, 2019 ', 'December 28, 2000'],
]
testCases.map(([now, birthday]) => {
const [begin, end] = getPeriod(birthday, now)
$tests.innerHTML += `BIRTH: ${birthday}<br>NOW: ${now}<br>BEGIN: ${begin}<br>END : ${end}<br><br>`
})
<h3>Select an date</h3>
<input type="date" id="datepicker" value="2019-01-01"/>
<button onclick="calculatePeriod()">Calculate</button>
<p>Result: <pre id="result">...</pre></p>
<hr />
<h3>Tests</h3>
<pre id="tests"></pre>
答案 2 :(得分:1)
Jon Skeet提供了相同的解决方案,但在PHP中使用Carbon。
use Carbon\Carbon;
class UserBirthdayTest extends TestCase
{
/**
* Setup this test
*/
public function setUp()
{
parent::setUp();
}
/**
* Test create methods on basic CRUDs controllers
*/
public function testCreate()
{
$this->validate("2019-02-15", "2000-02-22", true);
$this->validate("2019-02-24", "2000-02-22", true);
$this->validate("2019-02-25", "2000-02-22", false);
$this->validate("2018-12-28", "2000-01-03", true);
$this->validate("2019-01-01", "2000-01-03", true);
$this->validate("2018-12-27", "2000-01-03", false);
$this->validate("2019-12-27", "2000-01-03", true);
}
/**
* @param \PHPUnit\Framework\TestResult $today
* @param $birthday
* @param $expectedResult
* @return \PHPUnit\Framework\TestResult|void
*/
public function validate($today, $birthday, $expectedResult)
{
$today = new Carbon($today);
$birthday = new Carbon($birthday);
// closest discount period
$threeMonthsAgo = (new Carbon($today))->subMonth(3);
$ageThreeMonthsAgo = $birthday->diffInYears($threeMonthsAgo);
// Note: this will use Feb 28th for a Feb 29th birthday in a non leap year.
$relevantBirthday = $birthday->addYears($ageThreeMonthsAgo + 1);
// Find the strictly-previous Friday to start the discount interval
$discountStart = Carbon::createFromTimestamp(strtotime('last friday', $relevantBirthday->timestamp));
$discountEndInclusive = (new Carbon($discountStart))->addDays(9);
$actualResult = $today->greaterThanOrEqualTo($discountStart) && $today->lessThanOrEqualTo($discountEndInclusive); // between($discountStart, $discountEndInclusive, false);
$this->assertEquals($expectedResult, $actualResult);
}
}