Calculate Days Between Two Dates - delphi

Please help me find days between two dates.
I have two objects TDBDateEdit date1 and date2.
procedure Torder_form.date2Click(Sender: TObject);
var d3: TDateTime;
begin
d3:=date2.date - date1.date;
showmessage(datetostr(d3));
end.
I put to date1 = 07.10.2015
to date2 - 15.11.2015
Result must be: 39
But program gives me result: 07.02.1900
I found DaysBetween function. and I changed my codes like that
procedure Torder_form.date2Click(Sender: TObject);
var d3: TDateTime;
begin
d3:=DaysBetween(date2.date,date1.date);
showmessage(datetostr(d3));
end.
But programs says Result: 07.02.1900

You're trying to store a non-date value (the number of days between two dates) in a TDateTime value. Since you don't want a date, use a double instead, and interpret it as a double:
var
DaysDiff: Double;
begin
DaysDiff := Date2.Date - Date1.Date;
ShowMessage(FloatToStr(DaysDiff));
end;
Better yet, use the functions in DateUtils to do the work for you. If you need just whole days, use DaysBetween:
var
DaysDiff: Integer;
begin
DaysDiff := DaysBetween(Date2.Date, Date1.Date);
ShowMessage(IntToStr(DaysDiff));
end;
If you need fractional (partial) days, use DaySpan:
var
DaysDiff: Double;
begin
DaysDiff := DaySpan(Date2.Date, Date1.Date);
ShowMessage(FloatToStr(DaysDiff));
end;

I had the same problem in one of my projects. but after a little search in Delphi help, I find out Delphi has a rich set of functions on dates. Anyway you can use from 'DaysBetween' function to solve your problem. My code was something like this:
procedure TForm1.btnResultClick(Sender: TObject);
var
FirstDate, SecondDate: TDateTime;
format:TFormatSettings;
intDays: Integer;
begin
format:= TFormatSettings.Create();
format.ShortDateFormat := 'yyyy/mm/dd';
FirstDate := StrToDate(eFirstDate.Text,format);
SecondDate := StrToDate(eSecondDate.Text,format);
intDays:= DaysBetween(FirstDate,SecondDate);
eFinalDate.Text:= intToStr(intDays);
end;

Related

delphi get hour&minutes difference in specific format

Was playing with dateutils and did some experimenting.
procedure TForm1.Button1Click(Sender: TObject);
var
fromDate, toDate : TDateTime;
begin
fromDate := cxDateEdit1.Date ;
toDate := cxDateEdit2.Date ;
Label1.Caption := 'Hour difference '+IntToStr(HoursBetween(toDate, fromDate))+' hours';
Label2.Caption := 'Minute difference '+IntToStr(MinutesBetween(toDate, fromDate))+' minutes';
end;
How can I get a time difference result in a label caption like hh/mm (example 01:05) ???
A TDateTime is intended to be used with absolute dates and times. Instead you might consider TTimeSpan from the System.TimeSpan unit.
uses
System.TimeSpan;
....
var
d1, d2: TDateTime;
Span: TTimeSpan;
str: string;
....
d1 := ...;
d2 := ...;
Span := TTimeSpan.Subtract(d2, d1);
str := Format('%.2d:%.2d', [Span.Hours, Span.Minutes]));
This assumes that the span is less than a day. But then the format of your output seems to build in that very assumption.
Whether or not this is really any better than simply subtracting two date time values I am not so sure.
SysUtils.FormatDateTime has many useful TDateTime to string conversions:
Label3.Caption := 'Time difference [hh:mm] '+FormatDateTime('hh:nn',toDate-fromDate);
As an alternative, use the result from MinutesBetween:
var
minutes: Integer;
...
minutes := MinutesBetween(toDate,FromDate);
Label3.Caption :=
'Time difference [hh:mm] '+Format('%.2d:%.2d',[minutes div 60,minutes mod 60]);

Where is the TdxDBTreeListColumn.OnFilterStringUnformat analog in TcxGridDBBandedColumn?

We use the DevExpress ExpressQuantumGrid v3 (TdxDBGrid) and ExpressQuantumGrid Suite v12 (TcxGrid) in our application. With the TdxDBGrid, we use the TdxDBTreeListColumn.OnFilterStringFormat and OnFilterStringUnformat events to allow us to filter using string representations of values of the underlying datatype associated with the column. For example, we may have time periods stored in milliseconds, but displayed in HH:MM:SS format.
But I'm stuck with how do do this with the TcxGrid. While I can use the TcxGridDBBandedColumn.OnGetFilterDisplayText as an analog for TdxDBTreeListColumn.OnFilterStringFormat, I'm stuck with how to implement the functionality provided by TdxDBTreeListColumn.OnFilterStringUnformat, to ensure I can convert from the display value specified by the user to the value stored in the underlying dataset.
How is this functionality achieved with the TcxGrid?
I'm not sure I've understand your question 100%. I unsure what you mean by
I'm stuck with how to implement the functionality provided by TdxDBTreeListColumn.OnFilterStringUnformat, to ensure I can convert from the display value specified by the user to the value stored in the underlying dataset.
First of all I've made a small example:
Added a new TdxMemtable with a Date Field in it, linked it to a tcxGrid and i've added some random data to it:
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
BeginOfYear: TDateTime;
begin
Randomize;
dxMemData1.Active := true;
dxMemData1.DisableControls;
BeginOfYear := EncodeDate(2015, 1, 1);
for i := 0 to 500 do
dxMemData1.AppendRecord([i, Random(Trunc(Date - BeginOfYear)) + BeginOfYear]);
dxMemData1.EnableControls;
end;
Then I've given the Column an OnGetFilterDisplayText event:
procedure TForm1.cxGrid1DBTableView1Field2GetFilterDisplayText(Sender: TcxCustomGridTableItem; const AValue: Variant; var ADisplayText: string);
begin
if VarIsType(AValue, varDate) then
ADisplayText := FormatDateTime(FormatSettings.LongDateFormat, AValue);
end;
And it gives me the result I wanted:
with no OnGetFilterDisplayText event:
And with a OnGetFilterDisplayText event:
As you can see I've formated the text in the Filter box with out modifying the internal data.
So the last thing is to display the data in the wanted format by adding an OnGetDataText to the colum :
procedure TForm1.cxGrid1DBTableView1Field1GetDataText(Sender: TcxCustomGridTableItem; ARecordIndex: Integer; var AText: string);
var
aDateTime: TDateTime;
begin
if TryStrToDate(AText, aDateTime) then
AText := FormatDateTime(FormatSettings.LongDateFormat, aDateTime);
end;
And here you have the result :
After:
By doing it this way you keep your data in your dataset in your internal format but displays it to the user diffrent.
in order for showing you how to get the original data value and the datavalue on the Screen I've added two tcxEdit and a AfterScrollEcent to mu dataset:
procedure TMainForm.gridDBTableView1FocusedRecordChanged(Sender: TcxCustomGridTableView; APrevFocusedRecord, AFocusedRecord: TcxCustomGridRecord; ANewItemRecordFocusingChanged: Boolean);
var
Index: Integer;
begin
if AFocusedRecord = nil then
exit;
Index := gridDBTableView1time_field.Index;
cxTextEdit1.Text := AFocusedRecord.Values[Index];
cxTextEdit2.Text := AFocusedRecord.DisplayTexts[Index];
end;
Here is the result :
So far we have out data displayed the way we wanted it, and filtering from the headder is posible, but ig you from the choose custom filtering you'll get an error.
In order for making that work you need to create a TcxFilterComboBoxHelper descendant?
type
TmyFilterComboBoxHelper = class(TcxFilterComboBoxHelper)
private
class function TryLongDateFormatToDate(const S: string; out Value: TDateTime): Boolean;
class function TryStringToMilliseconds(const S: string; out Value: Int64): Boolean;
public
class procedure GetFilterValue(AEdit: TcxCustomEdit; AEditProperties: TcxCustomEditProperties; var V: Variant; var S: TCaption); override;
end;
The complete code can be found here:
http://pastebin.com/A1NRNg2J

DateTimeToUnix in UTC?

I need UTC variants of the functions DateTimeToUnix and UnixToDateTime, so a Chinese customer is able to interact with the server in Germany. Both sides should be able to exchange Unix timestamps (in UTC, without DST) and be able to communicate through this way.
In a bugreport of HeidiSQL , users discussed that DateTimeToUnix and UnixToDateTime do not care about the time zone, and there I have found following code:
function DateTimeToUTC(dt: TDateTime): Int64;
var
tzi: TTimeZoneInformation;
begin
Result := DateTimeToUnix(dt);
GetTimeZoneInformation(tzi);
Result := Result + tzi.Bias * 60;
end;
MSDN explains twi.Bias as follows:
All translations between UTC time and local time are based on the following formula:
UTC = local time + bias
The bias is the difference, in minutes, between UTC time and local time.
This sounds logical, but since I was unsure if the code above was correct, I made following program to check it:
// A date in summer time (DST)
Memo1.Lines.add('1401494400'); // 31 May 2014 00:00:00 GMT according to http://www.epochconverter.com/
Memo1.Lines.add(inttostr(DateTimeToUnixUTC(StrToDate('31.05.2014'))));
// A date in winter time
Memo1.Lines.add('567302400'); // 24 Dec 1987 00:00:00 GMT according to http://www.epochconverter.com/
Memo1.Lines.add(inttostr(DateTimeToUnixUTC(StrToDate('24.12.1987'))));
The output in Germany (GMT+1+DST) is currently:
1401494400
1401490800
567302400
567298800
I expected the output being:
1401494400
1401494400
567302400
567302400
What am I doing wrong?
PS: For this project I am bound to Delphi 6.
You have already found DateTimeToUnix and UnixToDateTime. So that part of the conversion is taken care of.
All you need to do now is convert between local and UTC time. You can do that using DateUtils.TTimeZone class. Specifically DateUtils.TTimeZone.ToUniversalTime and DateUtils.TTimeZone.ToLocalTime.
These four functions give you all that you need.
I think I have found some solutions for my question. All 3 solutions gave the same output, but I will try to find out which one is best and I will test it on several machines with different locales.
Solution #1 using TzSpecificLocalTimeToSystemTime and SystemTimeToTzSpecificLocalTime works fine, but requires Windows XP and above:
(Source: https://stackoverflow.com/a/15567777/3544341 , modified)
// Statically binds Windows API functions instead of calling them dynamically.
// Requires Windows XP for the compiled application to run.
{.$DEFINE USE_NEW_WINDOWS_API}
{$IFDEF USE_NEW_WINDOWS_API}
function SystemTimeToTzSpecificLocalTime(lpTimeZoneInformation: PTimeZoneInformation; var lpUniversalTime,lpLocalTime: TSystemTime): BOOL; stdcall; external kernel32 name 'SystemTimeToTzSpecificLocalTime';
{$ELSE}
function SystemTimeToTzSpecificLocalTime(lpTimeZoneInformation: PTimeZoneInformation; var lpUniversalTime,lpLocalTime: TSystemTime): BOOL; stdcall;
var
h: HModule;
f: function(lpTimeZoneInformation: PTimeZoneInformation; var lpUniversalTime,lpLocalTime: TSystemTime): BOOL; stdcall;
begin
h := LoadLibrary(kernel32);
if h = 0 then RaiseLastOSError;
#f := GetProcAddress(h, 'SystemTimeToTzSpecificLocalTime');
if #f = nil then RaiseLastOSError;
result := f(lpTimeZoneInformation, lpUniversalTime, lpLocalTime);
end;
{$ENDIF}
{$IFDEF USE_NEW_WINDOWS_API}
function TzSpecificLocalTimeToSystemTime(lpTimeZoneInformation: PTimeZoneInformation; var lpLocalTime, lpUniversalTime: TSystemTime): BOOL; stdcall; external kernel32 name 'TzSpecificLocalTimeToSystemTime';
{$ELSE}
function TzSpecificLocalTimeToSystemTime(lpTimeZoneInformation: PTimeZoneInformation; var lpLocalTime, lpUniversalTime: TSystemTime): BOOL; stdcall;
var
h: HModule;
f: function(lpTimeZoneInformation: PTimeZoneInformation; var lpLocalTime, lpUniversalTime: TSystemTime): BOOL; stdcall;
begin
h := LoadLibrary(kernel32);
if h = 0 then RaiseLastOSError;
#f := GetProcAddress(h, 'TzSpecificLocalTimeToSystemTime');
if #f = nil then RaiseLastOSError;
result := f(lpTimeZoneInformation, lpLocalTime, lpUniversalTime);
end;
{$ENDIF}
function UTCToLocalDateTime_WinXP(d: TDateTime): TDateTime;
var
TZI: TTimeZoneInformation;
LocalTime, UniversalTime: TSystemTime;
begin
GetTimeZoneInformation(tzi);
DateTimeToSystemTime(d,UniversalTime);
SystemTimeToTzSpecificLocalTime(#tzi,UniversalTime,LocalTime);
Result := SystemTimeToDateTime(LocalTime);
end;
function LocalDateTimeToUTC_WinXP(d: TDateTime): TDateTime;
var
TZI: TTimeZoneInformation;
LocalTime, UniversalTime: TSystemTime;
begin
GetTimeZoneInformation(tzi);
DateTimeToSystemTime(d,LocalTime);
TzSpecificLocalTimeToSystemTime(#tzi,LocalTime,UniversalTime);
Result := SystemTimeToDateTime(UniversalTime);
end;
Solution #2 as workaround for older operating systems does also work fine:
(Source: http://www.delphipraxis.net/299286-post4.html )
uses DateUtils;
function GetDateTimeForBiasSystemTime(GivenDateTime: TSystemTime; GivenYear: integer): TDateTime;
var
Year, Month, Day: word;
Hour, Minute, Second, MilliSecond: word;
begin
GivenDateTime.wYear := GivenYear;
while not TryEncodeDayOfWeekInMonth(GivenDateTime.wYear, GivenDateTime.wMonth, GivenDateTime.wDay, GivenDateTime.wDayOfWeek, Result) do
Dec(GivenDateTime.wDay);
DecodeDateTime(Result, Year, Month, Day, Hour, Minute, Second, MilliSecond);
Result := EncodeDateTime(Year, Month, Day, GivenDateTime.wHour, GivenDateTime.wMinute, GivenDateTime.wSecond, GivenDateTime.wMilliseconds);
end;
function GetBiasForDate(GivenDateTime: TDateTime): integer;
var
tzi: TIME_ZONE_INFORMATION;
begin
GetTimeZoneInformation(tzi);
if (GivenDateTime < GetDateTimeForBiasSystemTime(tzi.StandardDate, YearOf(GivenDateTime))) and
(GivenDateTime >= GetDateTimeForBiasSystemTime(tzi.DaylightDate, YearOf(GivenDateTime))) then
Result := (tzi.Bias + tzi.DaylightBias) * -1
else
Result := (tzi.Bias + tzi.StandardBias) * -1;
end;
function UTCToLocalDateTime_OldWin(aUTC: TDateTime): TDateTime;
begin
Result := IncMinute(aUTC, GetBiasForDate(aUTC));
end;
function LocalDateTimeToUTC_OldWin(aLocal: TDateTime): TDateTime;
begin
Result := IncMinute(aLocal, GetBiasForDate(aLocal) * -1);
end;
Solution #3 using TTimeZone for users of newer versions of Delphi, does give the same results as the codes above:
(Solution by David Heffernan, alas not possible in my current project, because I am bound to Delphi 6)
uses DateUtils;
{$IF Declared(TTimeZone)}
function UTCToLocalDateTime_XE(aUTC: TDateTime): TDateTime;
begin
result := TTimeZone.Local.ToLocalTime(aUTC);
end;
function LocalDateTimeToUTC_XE(aLocal: TDateTime): TDateTime;
begin
result := TTimeZone.Local.ToUniversalTime(aLocal);
end;
{$IFEND}
Now we can put all 3 solutions together! :-)
function UTCToLocalDateTime(aUTC: TDateTime): TDateTime;
begin
{$IF Declared(UTCToLocalDateTime_XE)}
result := UTCToLocalDateTime_XE(aUTC);
{$ELSE}
{$IFDEF USE_NEW_WINDOWS_API}
result := UTCToLocalDateTime_WinXP(aUTC);
{$ELSE}
try
result := UTCToLocalDateTime_WinXP(aUTC);
except
on E: EOSError do
begin
// Workaround for Windows versions older than Windows XP
result := UTCToLocalDateTime_OldWin(aUTC);
end
else raise;
end;
{$ENDIF}
{$IFEND}
end;
function LocalDateTimeToUTC(aLocal: TDateTime): TDateTime;
begin
{$IF Declared(LocalDateTimeToUTC_XE)}
result := LocalDateTimeToUTC_XE(aLocal);
{$ELSE}
{$IFDEF USE_NEW_WINDOWS_API}
result := LocalDateTimeToUTC_WinXP(aLocal);
{$ELSE}
try
result := LocalDateTimeToUTC_WinXP(aLocal);
except
on E: EOSError do
begin
// Workaround for Windows versions older than Windows XP
result := LocalDateTimeToUTC_OldWin(aLocal);
end
else raise;
end;
{$ENDIF}
{$IFEND}
end;
An easy method to get the current UTC unix timestamp is
function NowUTC: TDateTime;
var
st: TSystemTime;
begin
GetSystemTime(st);
result := EncodeDateTime(st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
end;
function CurrentUnixUTCTimestamp: int64;
begin
result := DateTimeToUnix(NowUTC);
end;
DateTimeToUnix and UnixToDateTime have got a second argument now:
function DateTimeToUnix(const AValue: TDateTime; AInputIsUTC: Boolean): Int64;
function UnixToDateTime(const AValue: Int64; AReturnUTC: Boolean): TDateTime;
So, you can easily choose between UTC and local time.
Using kbmMW's TkbmMWDateTime class it is very easy as it is always timezone aware:
var
dt:TkbmMWDateTime;
unix:int64;
begin
dt:=TkbmMWDateTime.Now;
unix:=dt.UTCSinceEpoch;
end;
And it also goes the other way around. In fact there are many such epoch variations and time formats supported in TkbmMWDateTime.
I would recommend, if you any place need to exchange a string with date/time info in it, to use ISO8601 format. In kbmMW you do like this:
var
s:string;
begin
s:=TkbmMWDateTime.Now.ISO8601String;
...
end;
It also goes two ways.
You can read a bit more about kbmMW's DateTime handling here:
https://components4developers.blog/2018/05/25/kbmmw-features-3-datetime/
kbmMW is a toolbox that fully supports Delphi including all platforms.

Overload TFormatSettings and incompatible types

i have this procedure:
procedure Initialize(out FormatSettings: TFormatSettings);
const
LongDayNamesEx : array [1..7] of string = ('Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato', 'Domenica');
LongMonthNamesEx : array [1..12] of string = ('Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre');
begin
FormatSettings := TFormatSettings.Create;
with FormatSettings do
begin
LongDayNames := LongDayNamesEx;
LongMonthNames := LongMonthNamesEx;
end;
end;
And i get an error about incompatible types (E2008). How i can solve this problem? I don't want to use something as:
LongDayNames[1] := 'Lunedì';
LongDayNames[2] := 'Martedì';
...
LongDayNames[7] := 'Domenica';
LongMonthNames[1] := 'Gennaio';
LongMonthNames[2] := 'Febbraio';
...
LongMonthNames[12] := 'Dicembre';
if not stricly necessary.
Thanks for help.
You can do like this:
type
TDayNameArray = array[1..7] of string;
const
LongDayNamesEx: TDayNameArray = ('Måndag', 'Tisdag', 'Onsdag', 'Torsdag',
'Fredag', 'Lördag', 'Söndag');
var
fs: TFormatSettings;
begin
TDayNameArray(fs.LongDayNames) := LongDayNamesEx;
Andreas gave you a good answer to the direct question that you asked.
Taking a different approach, I think you could solve your problem more easily by passing the locale when you initlialise the object. For example:
FormatSettings := TFormatSettings.Create('it-IT');
for Italian. Then the system will fill out the locale specific settings, day names, month names etc.
Or perhaps you would use the overload that takes a locale ID is more appropriate. No matter, you surely get the idea.
To answer the question you asked directly, the obvious solution is to use a for loop. Combine a record helper and open array parameters to make it more easily called:
type
TTFormatSettingsHelper = record helper for TFormatSettings
procedure SetLongDayNames(const Values: array of string);
end;
procedure TTFormatSettingsHelper.SetLongDayNames(const Values: array of string);
var
Index: Integer;
Value: string;
begin
Assert(high(Values)-low(Values)
= high(Self.LongDayNames)-low(Self.LongDayNames));
Index := low(Self.LongDayNames);
for Value in Values do
begin
Self.LongDayNames[Index] := Value;
inc(Index);
end;
end;
And then to call this you simply write:
FormatSettings.SetLongDayNames(['Lunedì', 'Martedì', 'Mercoledì', 'Giovedì',
'Venerdì', 'Sabato', 'Domenica']);

First [Monday] of the month, Second [Thursday] of the month, etc. Delphi

Using Delphi, I need a function to evaluate the current date and see if it's, for example, the Third Sunday of the month, etc.
In pseudocode:
function IsFirst(const CurrentDateTime: TDateTime; const Day: Word): Boolean;
/// Day can be 1-7 (monday to sunday)
begin
Result:= ??
end;
Another function would be needed to calculate the Second, Third, Forth and Last of the month. DateUtils seems to have nothing like that. Any ideas?
This function is what you need:
function IsFirst(const DateTime: TDateTime; const Day: Word): Boolean;
begin
Result := (DayOfTheWeek(DateTime)=Day) and
InRange(DayOfTheMonth(DateTime), 1, 7);
end;
The equivalent function for the second occurrence is:
function IsSecond(const DateTime: TDateTime; const Day: Word): Boolean;
begin
Result := (DayOfTheWeek(DateTime)=Day) and
InRange(DayOfTheMonth(DateTime), 8, 14);
end;
I'm sure you can fill out the details for third, fourth and fifth. You may prefer to write a single general function like this:
function IsNth(const DateTime: TDateTime; const Day: Word;
const N: Integer): Boolean;
var
FirstDayOfWeek, LastDayOfWeek: Integer;
begin
LastDayOfWeek := N*7;
FirstDayOfWeek = LastDayOfWeek-6;
Result := (DayOfTheWeek(DateTime)=Day) and
InRange(DayOfTheMonth(DateTime), FirstDayOfWeek, LastDayOfWeek);
end;
This can be done using simple math.
Get the DayOfTheWeek and divide the DayOf by seven.

Resources