How can I insert in a database the number of days of the year and at the same time insert in the same record the month, day of the month, and the day of the week?
This is my table:
tabela/coluna.Dias_ano(registo 1...365)
Year:=StrToInt(ano.Text);
diasano.Text:= IntToStr( DaysInAYear(Year) );
diasAno| Mes |diames |dia semana |
1 | janeiro | 1 |Segunda |
2 | janeiro | 2 | Terça |
...
365 | Dezembro | 31 | Segunda
Probably I'm missing the question but in case I'm not, you can find what you need in "DateUtils.pas". It has functions like "DayOfTheYear", "MonthOfTheYear", "DayOfTheMonth", "DayOfTheWeek" and many more. I think you're gonna store them in different fields, but there might be a probability that you don't need to store them at all; the database you're using might supply similar functionality, in that case you can construct your queries to supply the filtering/ordering you need.
edit: code for the 3rd comment below;
procedure TForm1.Button1Click(Sender: TObject);
var
Year, DaysInYear: Word;
FirstDay, i: Integer;
begin
Year := StrToInt(ano.Text);
DaysInYear := DaysInAYear(Year);
diasano.Text := IntToStr(DaysInYear);
FirstDay := Trunc(EncodeDate(Year, 1, 1));
for i := FirstDay to FirstDay + DaysInYear - 1 do begin
Planeamento.Append;
Planeamento.FieldByName('diasAno').Value := DayOfTheYear(i);
Planeamento.FieldByName('Month').Value := LongMonthNames[MonthOfTheYear(i)];
Planeamento.FieldByName('DayOfMonth').Value := DayOfTheMonth(i);
Planeamento.FieldByName('DayOfWeek').Value := LongDayNames[DayOfTheWeek(i)];
Planeamento.Post;
end;
end;
edit: With calculated fields;
For the below example the table has five columns instead of four. Let's name the first column 'Date'. This column is the only column to store data and will hold the Date (as per ldsandon's answer, since by storing the date instead of day-number, you won't have to keep track of what table represents what year, and calculations will be simpler).
The other four columns are exactly the same as in the question, except that they all are "calculated fields".
procedure TForm1.Button1Click(Sender: TObject);
var
Year, DaysInYear: Word;
FirstDay, i: Integer;
begin
Year := StrToInt(ano.Text);
DaysInYear := DaysInAYear(Year);
diasano.Text := IntToStr(DaysInYear);
FirstDay := Trunc(EncodeDate(Year, 1, 1));
for i := FirstDay to FirstDay + DaysInYear - 1 do begin
Planeamento.Append;
Planeamento.FieldByName('Date').Value := i;
Planeamento.Post;
end;
end;
procedure TForm1.PlaneamentoCalcFields(DataSet: TDataSet);
var
Date: TDateTime;
begin
Date := DataSet.FieldByName('Date').AsDateTime;
DataSet.FieldByName('diasAno').AsInteger := DayOfTheYear(Date);
DataSet.FieldByName('Month').AsString := LongMonthNames[MonthOfTheYear(Date)];
DataSet.FieldByName('DayOfMonth').AsInteger := DayOfTheMonth(Date);
DataSet.FieldByName('DayOfWeek').AsString := LongDayNames[DayOfTheWeek(Date)];
end;
You could simply calculate what value has 1/1/<year>, than check if <year> is leap or not and then with a simple for loop calculate the TDateTime value for each day (just add the day number to the January 1st value), extract the info you need with the DateUtils functions and write them to a record for each day.
But I would advise you agains such a solution. Those all are informations already encoded into a datetime value. I would simply store each item with its date, the whole calendar can be easily built client side when needed (and only the needed parts), without having to store it wholly in the database.
Related
I'm using two Month calendar on my form. I select a date on each one and I want to calculate the number of days between these two days. I only code I have is
procedure TForm1.Button1Click(Sender: TObject);
Var
N, m: TDate;
d: Real;
l,k:String;
begin
N := (MonthCalendar1.Date);
m := MonthCalendar2.Date;
L := formatdatetime('dd', N);
K:=formatdatetime('dd',M);
d := StrToFloat(L)-StrToFloat(K);
ShowMessage(FloatToStr(d));
end;
You can use DaysBetween() from System.DateUtils to obtain the difference, in days, between two TDateTime values.
See Docs: http://docwiki.embarcadero.com/Libraries/Rio/en/System.DateUtils.DaysBetween
Edit, see code:
uses System.DateUtils;
[..]
procedure TForm1.Button1Click(Sender: TObject);
var
NumDays: Integer;
begin
NumDays := DaysBetween(MonthCalendar1.Date, MonthCalendar2.Date);
ShowMessage('Days between selected dates: ' + NumDays.ToString);
end;
You don't need a MonthCalendar to do this. The part of a TDateTime to the RHS of the decimal point is effectively a "day number" so if you substract one of those from another, you find the "days between". Apply a Trunc to both to discard the time-of-day parts. See http://docwiki.embarcadero.com/Libraries/Rio/en/System.TDateTime for definition of TDateTime.
So, for example, given
var
D1,
D2 : TDateTime;
DaysBetween : Integer;
and D1 and D2 are two TDateTimes (that you could enter if you wish using a TMonthCalendar), then
DaysBetween := Trunc(D2) - Trunc(D1);
The call to the built-in Trunc function discards the fractional, time-of-day part of a TDateTime value (that is, the partr to the right of the decimal point), so that 23:59 on one day and 00:01 on the next are calculated to be one day apart. This may, or may not, be the result you want, depending on your application, which is why I have suggested calculating it yourself rather than using the built-in DaysBetween function.
Delphi.
I need to convert a datetime string to a TDateTime type. The code I use:
...
var
fs : TFormatSettings;
dt: TDateTime;
begin
fs := TFormatSettings.Create;
fs.DateSeparator := '-';
fs.TimeSeparator := ':';
fs.ShortDateFormat := 'dd-mmm-yy';
fs.ShortTimeFormat := 'hh:nn:ss';
dt := StrToDateTime(Timestamp, fs);
...
The string is like this: Timestamp := '26-Feb-16 08:30:00'
I get only convert error messages
EConvertError, '26-Feb-16 08:30:28' is not a valid date and time
If I manually enter a timestamp of format 'yyyy/mm/dd hh:nn:ss' and make ShortDateFormat := 'yyyy/mm/dd'; and ShortTimeFormat := 'hh:nn:ss'; I have no problems...
I don't know what I'm missing? Anyone have a clue?
StrToDateTime does not support formats that specify the month as a name, either short or long form.
You will have to parse your text using some other method. Frankly, this format is very easy to parse.
Split the input on the space character to get date and time parts.
Split the date part on - to get day, month and year parts. Search through the 12 short month names to find a match.
Split the time part on : to find hours, minutes and seconds. Or use StrToTime.
Or you could take the input and replace short month names with month numbers and use StrToDateTime with the mm month format.
When I need to increase date 1/1/2016 by 7 days, I can simply do:
IncDay(myDate, 7); // Result 8/1/2016
What do I do, if I need to ignore some days (e.g. Saturdays), so the Result is 10/1/2016 ?
Apparently, based on the somewhat cryptic comments, you wish to increment a date by a number of days, excluding Saturday. You can do that by making use of the the DayOfTheWeek function in DateUtils. This will tell you which day of the week a specified date falls on.
So your function is something like this:
function IncExcludingSaturday(FromDate: TDateTime; IncDays: Integer): TDateTime;
begin
Assert(IncDays >= 0);
Result := FromDate;
if DayOfTheWeek(Result) = DaySaturday then
Result := IncDay(Result);
while IncDays > 0 do
begin
Result := IncDay(Result);
if DayOfTheWeek(Result) = DaySaturday then
Result := IncDay(Result);
dec(IncDays);
end;
end;
This is a rather crude way to achieve your goal. You can find more interesting ideas here: AddBusinessDays and GetBusinessDays
Now, in the question you suggest that 7 days from 01/01/2016, excluding Saturdays, takes you to 09/01/2016. But that is surely wrong since that date is a Saturday. The correct answer is surely 10/01/2016 which is a Sunday. In other words we need to skip over two Saturdays, on the 2nd and the 9th.
This adds a day to the calculation for each Saturday found in the ANumberOfDays range:
{.$DEFINE UNCLEAR_WHAT_YOU_R_ASKING}
function IncDayIgnoringSaturdays(const AValue: TDateTime; const ANumberOfDays: Integer = 1): TDateTime;
var
i, j: Integer;
begin
i := ANumberOfDays;
j := 0;
Result := AValue;
while i > 0 do begin
Result := IncDay(Result);
if DayOfTheWeek(Result) = DaySaturday then
Inc(j);
Dec(i);
end;
Result := IncDay(Result, j);
{$IFDEF UNCLEAR_WHAT_YOU_R_ASKING}
if DayOfTheWeek(Result) = DaySaturday then
Result := IncDay(Result);
{$ENDIF}
end;
begin
WriteLn(DateTimeToStr(IncDayIgnoringSaturdays(StrToDateTime('1/1/2016'), 7)));
WriteLn(DateTimeToStr(IncDayIgnoringSaturdays(StrToDateTime('1/1/2016'), 14)));
ReadLn;
end.
EDIT
The above may return a date on Saturday or not, depending on the UNCLEAR_WHAT_YOU_R_ASKING conditional define.
You've been given a couple of answers for the special case of excluding a single specific weekday. This is not particularly flexible.
You could try for implementing a function that takes a set of weekdays that are 'valid' as one of its parameters. The number of days to increment the date by is approximately AIncByDays * 7 / NoOfDaysInSet. But it gets rather tricky adjusting the result correctly for valid/invalid weekdays 'near' the start date. Even after all this complexity, you still wouldn't have a way to deal with special dates, like public holidays.
Fortunately there's different approach that's much simpler to implement and far more flexible. It's only drawback is that it's inefficient for 'large' increments.
The general approach is to increment 1 day at a time.
And on each increment check the validity of the new date.
Only if the new date is valid, reduce the increment counter by 1.
Repeat the above in a loop until the increment counter is reduced to 0.
The following uses a callback function to check the validity of each date.
type
TValidDateFunc = function (ADate: TDateTime): Boolean;
function IncValidDays(AStartDate: TDateTime; AIncBy: Integer; AIsValid: TValidDateFunc): TDateTime;
var
LIncDirection: Integer;
begin
// Support dec using negative AIncBy
if AIncBy >= 0 then
LIncDirection := 1
else
LIncDirection := -1;
Result := AStartDate;
while (AIncBy <> 0) do
begin
IncDay(Result, LIncDirection);
if (AIsValid(Result)) then
Dec(AIncBy, LIncDirection);
end;
end;
Now you can simply write whatever function you desire to determine a valid date and use it in the above function. E.g.
function DateNotSaturday(ADate: TDateTime): Boolean;
begin
Result := (DayOfTheWeek(ADate) <> DaySaturday);
end;
NewDate := IncValidDays(SomeDate, 10, DateNotSaturday);
Note that it now becomes quite easy to write a function that uses only work days that aren't public holidays. E.g.
function IsWorkDay(ADate: TDateTime): Boolean;
var
LDay, LMonth, LYear: Word;
begin
DecodeDate(ADate, LYear, LMonth, LDay);
Result := True;
Result := Result and (DayOfTheWeek(ADate) <> DaySaturday);
Result := Result and (DayOfTheWeek(ADate) <> DaySunday);
Result := Result and ((LDay <> 1) or (LMonth <> 1)); //Excludes New Years day.
...
end;
The biggest advantage of this approach is that you don't have to deal with the risk of 'double-ignoring' a date because it's both a weekend day and a public holiday.
NOTE: In recent versions of Delphi you could replace the callback function with an anonymous method.
Determine the day of the week using DayOfTheWeek function. Now you know when will be next Saturday and whether it will get inside your period. If your period is larger than one week, then you can multiple number of Saturdays in your period by a number of full weeks. If your period is larger than 7 weeks, then you will have to add one more day for each 7 weeks.
I need the RcordCount of a TADOTable before opening it. So I run this query:
ADOQuery1.SQL.Text := ‘SELECT Count(*) AS Record_Count FROM Table1’;
ADOQuery1.Open;
But this takes 9.7s (average of 10 runs) on my PC for a table consisting of over 5 million records. This time seems nothing compared to even touching a table with this size, but I think this kind of data should be stored somewhere in the .mdb file. Is there any other way to get RecordCount of a table directly from the file?
I'm using Delphi XE-5 and the file is in Microsoft Access 2003 format.
No, SELECT Count(*) is the only and the correct way to do it.
But: almost 10s for this query is awfully slow, especially if this is on your local PC.
Does the table have a Primary Key? If yes, what data type is it?
I created a simple table with an Autonumber Primary Key and inserted 5 million records.
SELECT COUNT(*) FROM tBig takes less than 1/10 seconds to execute.
Well, I searched a little more and found it. You can read the number of records of a table directly from the schema:
function ReadRecordCountFromSchema(const Connection: TADOConnection;
const TableName: string): Integer;
var
CardinalityField,
NameField: TField;
DataSet: TADODataSet;
begin
DataSet := TADODataSet.Create(nil);
Result := -1;
try
Connection.OpenSchema(siStatistics, EmptyParam, EmptyParam, DataSet);
CardinalityField := DataSet.FieldByName('CARDINALITY'); { do not localize }
NameField := DataSet.FieldByName('TABLE_NAME'); { do not localize }
while not DataSet.EOF do
begin
if CompareText(NameField.AsString, TableName) = 0 then
begin
Result := CardinalityField.AsInteger;
Break;
end;
DataSet.Next;
end;
finally
DataSet.Free;
end;
end;
Actually TADOConnection.GetTableNames gave me the idea.
Edit:
As Andre451 mentioned, it's better to pass TableName to OpenSchema as a restriction:
function ReadRecordCountFromSchema(const Connection: TADOConnection;
const TableName: string): Integer;
var
DataSet: TADODataSet;
begin
DataSet := TADODataSet.Create(nil);
Result := -1;
try
Connection.OpenSchema(siStatistics,
VarArrayOf([Unassigned, Unassigned, TableName]),
EmptyParam, DataSet);
if DataSet.RecordCount > 0 then
Result := DataSet.FieldByName('CARDINALITY').AsInteger;
finally
DataSet.Free;
end;
end;
I agree with Andre451. The only other thing I would say is that you should specify the field in the Count statement instead of using the *.
SELECT COUNT(PrimaryKey) FROM tBig; would do it.
I'm basically coding some sort of table where for column tags I have some numbers and for row tags I have some strings which contain such numbers separated by commas.
I'm taking all the row tags from a TString named minterms_essentials and the column tags from one named minterms.
First I must tag the created 2 dimensions array. And then, if any string from the rows contains certain letter, I must place an 'x' in the proper column.
I've wrote this Delphi code but I'm getting access violation so far...
SetLength(tabla, minterms_essentials.Count+1,minterms.Count+1);
for i := 0 to minterms.Count-1 do
begin
tabla[0,i+1] := IntToStr(BinToInt(minterms[i]));
end;
for i := 0 to minterms_essentials.Count-1 do
begin
tabla[i+1,0] := minterms_essentials[i];
end;
for i := 1 to minterms_essentials.Count-1 do
begin
for g := 1 to minterms.Count-1 do
begin
ss := tabla[g,0].Split([',']);
for s in ss do
begin
if s = tabla[0,g] then
begin
tabla[g,i] := 'x';
end;
end;
end;
end;
Is there any better and correct way to do this?
Look at this:
first dimension is defined by minterms_essentials
SetLength(tabla, minterms_essentials.Count+1,minterms.Count+1);
but here you are using minterms to index first dimension of array:
for g := 1 to minterms.Count-1 do
begin
ss := tabla[g,0].Split([',']);
P.S. Have you still not turned on range check?