Delphi 基于系统周开始的周数函数
Delphi week number function based on system start of week
DateUtils.WeekOfTheYear 函数很棒,但它使用 ISO 8601 标准定义一周。也就是说,一周被认为是从星期一开始到星期日结束。我需要一个类似的功能来根据系统设置确定周数作为一周的开始。或者至少对于像 MySQL 的星期函数那样的星期日或星期一开始。谁有这样的功能?
ISO-8601 在这些计算的规范中包含的不仅仅是一周的第一天。例如,还有确定一年中第一周的规则。
不清楚您正在寻找的是否是一个函数来复制 ISO-8601 计算,这些规则在其他方面完好无损,只是一周的第一天有所不同,或 WEEK() 函数 MySQL 的直接等价物,或其他仅类似(且未完全定义)的函数。
值得注意的是 MySQL WEEK() 函数接受一个不能确定任意日期标记的参数一周的开始,而是指示是否使用星期日或星期一以及更改确定计算结果的许多其他规则。
相比之下,Windows 本身的一周第一天的系统设置可以是用户希望的一周中的任何一天 - 周一、周二、周三、周四、周五、周六或周日。
我在下面提供的实现是一个简单的计算(有些人可能称之为天真),它只是 returns 一个基于周数的值 0..53从指定日期到该日期所在年份的年初之间经过的时间段或部分时间段。
对于包含指定日期的年份,1 月 1 日 所在的那一周被视为第 0 周。
因此,如果 1 月 1 日 发生在 星期日 并且 "start of week" 定义为 星期一 然后:
Sun, 01-Jan = Week 0
Mon, 02-Jan = Week 1
..
Sun, 08-Jan = Week 1
Mon, 09-Jan = Week 2
..
etc
实施
我将实现分为两个不同的部分。
第一个 (WeeksSince01Jan) 接受一个日期和一个参数,指示星期几作为计算中的第一天。
此参数采用 TSYSDayOfWeek 值 - 一个枚举排列,使得每一天的序数值对应于系统区域设置中使用的星期几的值。 (RTLDayOfWeek函数返回的值使用了不同的值,在这段代码中定义为TRTLDayOfWeek).
实现的第二部分提供了一个 LocaleWeeksSince01Jan,以演示获取为当前用户定义的一周第一天的语言环境。然后将其简单地传递给对 WeeksSince01Jan.
的调用
type
TSYSDayOfWeek = (sdMon, sdTue, sdWed, sdThu, sdFri, sdSat, sdSun);
TRTLDayOfWeek = 1..7; // Sun -> Sat
function WeeksSince01Jan(const aDate: TDateTime;
const aFirstDayOfWeek: TSYSDayOfWeek): Word;
const
LOCALE_DOW : array[TRTLDayOfWeek] of TSYSDayOfWeek = (sdSun, sdMon, sdTue, sdWed, sdThu, sdFri, sdSat);
var
y, m, d: Word;
dayOfYearStart: TSYSDayOfWeek;
dtYearStart: TDateTime;
dtStartOfFirstWeekInYear: TDateTime;
iAdjust: Integer;
begin
// Get the date for the first day of the year and determine which
// day of the week (Mon-Fri) that was
DecodeDate(aDate, y, m, d);
dtYearStart := EncodeDate(y, 1, 1);
dayOfYearStart := LOCALE_DOW[DayOfWeek(dtYearStart)];
// Week calculation is simply the number of 7 day periods
// elapsed since the start of the year to the specified date,
// adjusted to reflect any 'offset' to the specified first day of week.
iAdjust := Ord(dayOfYearStart) - Ord(aFirstDayOfWeek);
result := (((Trunc(aDate) + iAdjust) - Trunc(dtYearStart)) div 7);
end;
function LocaleWeeksSince01Jan(const aDate: TDateTime): Word;
var
localeValue: array[0..1] of Char;
firstDayOfWeek: TSYSDayOfWeek;
begin
// Get the system defined first day of the week
GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, localeValue, SizeOf(localeValue));
firstDayOfWeek := TSYSDayOfWeek(Ord(localeValue[0]) - Ord('0'));
result := WeeksSince01Jan(aDate, firstDayOfWeek);
end;
如果您有更复杂的规则来根据该周的天数等确定一年中的第 0 周或第 1 周,那么您将需要相应地修改此实现。在当前的实施中没有尝试满足此类需求。
用于测试
下面的代码可以作为测试输出的基础(使用系统定义的一周的第一天):
const
YEAR = 2012;
var
d: Integer;
dt: TDateTime;
wk: Word;
begin
List.Items.Clear;
dt := EncodeDate(YEAR, 1, 1) - 7;
for d := 1 to 380 do
begin
dt := dt + 1;
wk := LocaleWeeksSince01Jan(dt);
List.Items.Add(Format('%s, %s = week %d', [ShortDayNames[DayOfWeek(dt)],
DateToStr(dt),
wk]));
end;
其中 List 是对 TListbox 的引用。
更改 YEAR 的值以生成涵盖指定年份中所有日期 +/- 额外 7/8 天的结果范围,以说明结果的变化在前后年份的年底。
注意: 2012 年表明有可能在该年返回涵盖所有潜在结果的日期, 0..53.
如果您只对从星期日而不是星期一开始的一周感兴趣,您可以简单地从 DateTime
值中减去 1 天,然后再将其提供给 DateUtils.WeekOfTheYear
函数。
编辑:
回复 David Heffernan 评论:
Imagine what happens when you subtract 1 from January 1st
这取决于1月1日是哪一天
来自 Embarcadero 文档:http://docwiki.embarcadero.com/Libraries/XE8/en/System.DateUtils.WeekOfTheYear
AYear returns the year in which the specified week occurs. Note that
this may not be the same as the year of AValue. This is because the
first week of a year is defined as the first week with four or more
days in that year. This means that, if the first calendar day of the
year is a Friday, Saturday, or Sunday, then for the first three, two,
or one days of the calendar year, WeekOfTheYear returns the last week
of the previous year. Similarly, if the last calendar day of the year
is a Monday, Tuesday, or Wednesday, then for the last one, two, or
three days of the calendar year, WeekOfTheYear returns 1 (the first
week of the next calendar year).
因此,如果一周从星期日而不是星期一开始,那么这意味着一周的开始和结束日期只是向后移动了一天。
所以对于这种情况,最好使用带有附加变量参数的覆盖版本,该变量参数存储了本周所属的年份。
我将 Deltics 的优秀代码与 SilverWarior 的简单想法相结合,创建了一个处理系统周开始日的 WeekOfYear 函数。
type
TSYSDayOfWeek = (sdMon, sdTue, sdWed, sdThu, sdFri, sdSat, sdSun);
function LocaleWeekOfTheYear(dte: TDateTime): word;
var
localeValue: array[0..1] of Char;
firstDayOfWeek: TSYSDayOfWeek;
yearOld,yearNew: word;
dteNew: TDateTime;
begin
// Get the system defined first day of the week
GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, localeValue, SizeOf(localeValue));
firstDayOfWeek := TSYSDayOfWeek(Ord(localeValue[0]) - Ord('0'));
yearOld:= Year(dte);
if (firstDayOfWeek=sdSun) then
dteNew:= dte-1
else
dteNew:= dte+Ord(firstDayOfWeek);
yearNew:= Year(dteNew);
if (yearOld=yearNew) then
dte:= dteNew;
Result:= WeekOfTheYear(dte);
end;
为了使一周的第一天是星期六,我使用将现在日期的值加 2。
例如WeekOf(Now + 2)
:让第一天是星期六,
WeekOf(Now + 2)
: 使第一天是星期天,
WeekOf(Now + 0)
: 使第一天是星期一,
DateUtils.WeekOfTheYear 函数很棒,但它使用 ISO 8601 标准定义一周。也就是说,一周被认为是从星期一开始到星期日结束。我需要一个类似的功能来根据系统设置确定周数作为一周的开始。或者至少对于像 MySQL 的星期函数那样的星期日或星期一开始。谁有这样的功能?
ISO-8601 在这些计算的规范中包含的不仅仅是一周的第一天。例如,还有确定一年中第一周的规则。
不清楚您正在寻找的是否是一个函数来复制 ISO-8601 计算,这些规则在其他方面完好无损,只是一周的第一天有所不同,或 WEEK() 函数 MySQL 的直接等价物,或其他仅类似(且未完全定义)的函数。
值得注意的是 MySQL WEEK() 函数接受一个不能确定任意日期标记的参数一周的开始,而是指示是否使用星期日或星期一以及更改确定计算结果的许多其他规则。
相比之下,Windows 本身的一周第一天的系统设置可以是用户希望的一周中的任何一天 - 周一、周二、周三、周四、周五、周六或周日。
我在下面提供的实现是一个简单的计算(有些人可能称之为天真),它只是 returns 一个基于周数的值 0..53从指定日期到该日期所在年份的年初之间经过的时间段或部分时间段。
对于包含指定日期的年份,1 月 1 日 所在的那一周被视为第 0 周。
因此,如果 1 月 1 日 发生在 星期日 并且 "start of week" 定义为 星期一 然后:
Sun, 01-Jan = Week 0
Mon, 02-Jan = Week 1
..
Sun, 08-Jan = Week 1
Mon, 09-Jan = Week 2
..
etc
实施
我将实现分为两个不同的部分。
第一个 (WeeksSince01Jan) 接受一个日期和一个参数,指示星期几作为计算中的第一天。
此参数采用 TSYSDayOfWeek 值 - 一个枚举排列,使得每一天的序数值对应于系统区域设置中使用的星期几的值。 (RTLDayOfWeek函数返回的值使用了不同的值,在这段代码中定义为TRTLDayOfWeek).
实现的第二部分提供了一个 LocaleWeeksSince01Jan,以演示获取为当前用户定义的一周第一天的语言环境。然后将其简单地传递给对 WeeksSince01Jan.
的调用type
TSYSDayOfWeek = (sdMon, sdTue, sdWed, sdThu, sdFri, sdSat, sdSun);
TRTLDayOfWeek = 1..7; // Sun -> Sat
function WeeksSince01Jan(const aDate: TDateTime;
const aFirstDayOfWeek: TSYSDayOfWeek): Word;
const
LOCALE_DOW : array[TRTLDayOfWeek] of TSYSDayOfWeek = (sdSun, sdMon, sdTue, sdWed, sdThu, sdFri, sdSat);
var
y, m, d: Word;
dayOfYearStart: TSYSDayOfWeek;
dtYearStart: TDateTime;
dtStartOfFirstWeekInYear: TDateTime;
iAdjust: Integer;
begin
// Get the date for the first day of the year and determine which
// day of the week (Mon-Fri) that was
DecodeDate(aDate, y, m, d);
dtYearStart := EncodeDate(y, 1, 1);
dayOfYearStart := LOCALE_DOW[DayOfWeek(dtYearStart)];
// Week calculation is simply the number of 7 day periods
// elapsed since the start of the year to the specified date,
// adjusted to reflect any 'offset' to the specified first day of week.
iAdjust := Ord(dayOfYearStart) - Ord(aFirstDayOfWeek);
result := (((Trunc(aDate) + iAdjust) - Trunc(dtYearStart)) div 7);
end;
function LocaleWeeksSince01Jan(const aDate: TDateTime): Word;
var
localeValue: array[0..1] of Char;
firstDayOfWeek: TSYSDayOfWeek;
begin
// Get the system defined first day of the week
GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, localeValue, SizeOf(localeValue));
firstDayOfWeek := TSYSDayOfWeek(Ord(localeValue[0]) - Ord('0'));
result := WeeksSince01Jan(aDate, firstDayOfWeek);
end;
如果您有更复杂的规则来根据该周的天数等确定一年中的第 0 周或第 1 周,那么您将需要相应地修改此实现。在当前的实施中没有尝试满足此类需求。
用于测试
下面的代码可以作为测试输出的基础(使用系统定义的一周的第一天):
const
YEAR = 2012;
var
d: Integer;
dt: TDateTime;
wk: Word;
begin
List.Items.Clear;
dt := EncodeDate(YEAR, 1, 1) - 7;
for d := 1 to 380 do
begin
dt := dt + 1;
wk := LocaleWeeksSince01Jan(dt);
List.Items.Add(Format('%s, %s = week %d', [ShortDayNames[DayOfWeek(dt)],
DateToStr(dt),
wk]));
end;
其中 List 是对 TListbox 的引用。
更改 YEAR 的值以生成涵盖指定年份中所有日期 +/- 额外 7/8 天的结果范围,以说明结果的变化在前后年份的年底。
注意: 2012 年表明有可能在该年返回涵盖所有潜在结果的日期, 0..53.
如果您只对从星期日而不是星期一开始的一周感兴趣,您可以简单地从 DateTime
值中减去 1 天,然后再将其提供给 DateUtils.WeekOfTheYear
函数。
编辑: 回复 David Heffernan 评论:
Imagine what happens when you subtract 1 from January 1st
这取决于1月1日是哪一天
来自 Embarcadero 文档:http://docwiki.embarcadero.com/Libraries/XE8/en/System.DateUtils.WeekOfTheYear
AYear returns the year in which the specified week occurs. Note that this may not be the same as the year of AValue. This is because the first week of a year is defined as the first week with four or more days in that year. This means that, if the first calendar day of the year is a Friday, Saturday, or Sunday, then for the first three, two, or one days of the calendar year, WeekOfTheYear returns the last week of the previous year. Similarly, if the last calendar day of the year is a Monday, Tuesday, or Wednesday, then for the last one, two, or three days of the calendar year, WeekOfTheYear returns 1 (the first week of the next calendar year).
因此,如果一周从星期日而不是星期一开始,那么这意味着一周的开始和结束日期只是向后移动了一天。
所以对于这种情况,最好使用带有附加变量参数的覆盖版本,该变量参数存储了本周所属的年份。
我将 Deltics 的优秀代码与 SilverWarior 的简单想法相结合,创建了一个处理系统周开始日的 WeekOfYear 函数。
type
TSYSDayOfWeek = (sdMon, sdTue, sdWed, sdThu, sdFri, sdSat, sdSun);
function LocaleWeekOfTheYear(dte: TDateTime): word;
var
localeValue: array[0..1] of Char;
firstDayOfWeek: TSYSDayOfWeek;
yearOld,yearNew: word;
dteNew: TDateTime;
begin
// Get the system defined first day of the week
GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, localeValue, SizeOf(localeValue));
firstDayOfWeek := TSYSDayOfWeek(Ord(localeValue[0]) - Ord('0'));
yearOld:= Year(dte);
if (firstDayOfWeek=sdSun) then
dteNew:= dte-1
else
dteNew:= dte+Ord(firstDayOfWeek);
yearNew:= Year(dteNew);
if (yearOld=yearNew) then
dte:= dteNew;
Result:= WeekOfTheYear(dte);
end;
为了使一周的第一天是星期六,我使用将现在日期的值加 2。
例如WeekOf(Now + 2)
:让第一天是星期六,
WeekOf(Now + 2)
: 使第一天是星期天,
WeekOf(Now + 0)
: 使第一天是星期一,