diff --git a/components/fpspreadsheet/fpsnumformatparser.pas b/components/fpspreadsheet/fpsnumformatparser.pas
index 778b382d8..f2b15c74d 100644
--- a/components/fpspreadsheet/fpsnumformatparser.pas
+++ b/components/fpspreadsheet/fpsnumformatparser.pas
@@ -147,6 +147,7 @@ type
procedure ClearAll;
function GetDateTimeCode(ASection: Integer): String;
function IsDateTimeFormat: Boolean;
+ function IsTimeFormat: Boolean;
procedure LimitDecimals;
procedure Localize;
@@ -1184,6 +1185,26 @@ begin
Result := false;
end;
+{ Returns true if the format elements contain only time, no date tokens. }
+function TsNumFormatParser.IsTimeFormat: Boolean;
+var
+ section: Integer;
+ elem: Integer;
+begin
+ Result := false;
+ for section := 0 to High(FSections) do
+ for elem := 0 to High(FSections[section].Elements) do
+ if FSections[section].Elements[elem].Token in [nftHour, nftMinute, nftSecond]
+ then begin
+ Result := true;
+ end else
+ if FSections[section].Elements[elem].Token in [nftYear, nftMonth, nftDay, nftExpChar, nftCurrSymbol]
+ then begin
+ Result := false;
+ exit;
+ end;
+end;
+
function TsNumFormatParser.IsTokenAt(AToken: TsNumFormatToken;
ASection, AIndex: Integer): Boolean;
begin
diff --git a/components/fpspreadsheet/fpsopendocument.pas b/components/fpspreadsheet/fpsopendocument.pas
index 3dd599760..97bb0316a 100755
--- a/components/fpspreadsheet/fpsopendocument.pas
+++ b/components/fpspreadsheet/fpsopendocument.pas
@@ -58,6 +58,9 @@ type
{ TsSpreadOpenDocNumFormatParser }
TsSpreadOpenDocNumFormatParser = class(TsNumFormatParser)
+ private
+ function BuildDateTimeXMLAsString(ASection: Integer; AIndent: String;
+ out AIsTimeOnly: Boolean): String;
protected
function BuildXMLAsStringFromSection(ASection: Integer;
AIndent,AFormatName: String): String;
@@ -312,6 +315,104 @@ end;
{ TsSpreadOpenDocNumFormatParser }
+function TsSpreadOpenDocNumFormatParser.BuildDateTimeXMLAsString(ASection: Integer;
+ AIndent: String; out AIsTimeOnly: boolean): String;
+var
+ el: Integer;
+ s: String;
+ prevToken: TsNumFormatToken;
+begin
+ Result := '';
+ AIsTimeOnly := true;
+ with FSections[ASection] do begin
+ el := 0;
+ while el < Length(Elements) do begin
+ case Elements[el].Token of
+ nftYear:
+ begin
+ prevToken := Elements[el].Token;
+ AIsTimeOnly := false;
+ s := IfThen(Elements[el].IntValue > 2, 'number:style="long" ', '');
+ Result := Result + AIndent +
+ ' ' + LineEnding;
+ end;
+
+ nftMonth:
+ begin
+ prevToken := Elements[el].Token;
+ AIsTimeOnly := false;
+ case Elements[el].IntValue of
+ 1: s := '';
+ 2: s := 'number:style="long"';
+ 3: s := 'number:textual="true"';
+ 4: s := 'number:style="long" number:textual="true"';
+ end;
+ Result := result + AIndent +
+ ' ' + LineEnding;
+ end;
+
+ nftDay:
+ begin
+ prevToken := Elements[el].Token;
+ AIsTimeOnly := false;
+ case Elements[el].IntValue of
+ 1: s := 'day ';
+ 2: s := 'day number:style="long"';
+ 3: s := 'day number:textual="true"';
+ 4: s := 'day number:style="long" number:textual="true"';
+ 5: s := 'day-of-week ';
+ 6: s := 'day-of-week number:style="long"';
+ end;
+ Result := Result + AIndent +
+ ' ' + LineEnding;
+ end;
+
+ nftHour, nftMinute, nftSecond:
+ begin
+ prevToken := Elements[el].Token;
+ case Elements[el].Token of
+ nftHour : s := 'hours ';
+ nftMinute: s := 'minutes ';
+ nftSecond: s := 'seconds ';
+ end;
+ s := s + IfThen(abs(Elements[el].IntValue) = 1, '', 'number:style="long" ');
+ if Elements[el].IntValue < 0 then
+ s := s + 'number:truncate-on-overflow="false" ';
+ Result := Result + AIndent +
+ ' ' + LineEnding;
+ end;
+
+ nftMilliseconds:
+ begin
+ // ???
+ end;
+
+ nftDateTimeSep, nftText, nftEscaped, nftSpace:
+ begin
+ if Elements[el].TextValue = ' ' then
+ s := ''
+ else begin
+ s := Elements[el].TextValue;
+ if (s = '/') then begin
+ if prevToken in [nftYear, nftMonth, nftDay] then
+ s := FWorkbook.FormatSettings.DateSeparator
+ else
+ s := FWorkbook.FormatSettings.TimeSeparator;
+ end;
+ end;
+ Result := Result + AIndent +
+ ' ' + s + '' + LineEnding;
+ end;
+
+ nftAMPM:
+ Result := Result + AIndent +
+ ' ' + LineEnding;
+ end;
+ inc(el);
+ end;
+ end;
+end;
+
function TsSpreadOpenDocNumFormatParser.BuildXMLAsString(AIndent,
AFormatName: String): String;
var
@@ -340,6 +441,7 @@ var
clr: TsColorvalue;
el: Integer;
s: String;
+ isTimeOnly: Boolean;
begin
Result := '';
@@ -528,6 +630,23 @@ begin
end; // while
Result := Result + sStyleMap + AIndent + '' + LineEnding;
end;
+
+ // date/time
+ nftYear, nftMonth, nftDay, nftHour, nftMinute, nftSecond:
+ begin
+ s := BuildDateTimeXMLAsString(ASection, AIndent, isTimeOnly);
+ if isTimeOnly then
+ Result := Result + AIndent +
+ '' + LineEnding +
+ s + AIndent +
+ '' + LineEnding
+ else
+ Result := Result + AIndent +
+ '' + LineEnding +
+ s + AIndent +
+ '' + LineEnding;
+ exit;
+ end;
end;
inc(el);
end;
@@ -3035,20 +3154,36 @@ end;
*******************************************************************}
procedure TsSpreadOpenDocWriter.WriteDateTime(AStream: TStream;
const ARow, ACol: Cardinal; const AValue: TDateTime; ACell: PCell);
+const
+ FMT: array[boolean] of string = (ISO8601FormatExtended, ISO8601FormatTimeOnly);
+ DT: array[boolean] of string = ('date', 'time');
+ // Index "boolean" is to be understood as "isTimeOnly"
var
- lStyle: string = '';
+ lStyle: string;
+ strValue: String;
+ displayStr: String;
lIndex: Integer;
+ isTimeOnly: Boolean;
begin
if ACell^.UsedFormattingFields <> [] then begin
lIndex := FindFormattingInList(ACell);
- lStyle := ' table:style-name="ce' + IntToStr(lIndex) + '" ';
+ lStyle := 'table:style-name="ce' + IntToStr(lIndex) + '" ';
end else
lStyle := '';
// The row should already be the correct one
- FCellContent :=
- ' ' + LineEnding +
- ' ' + LineEnding;
+
+ // We have to distinguish between time-only values and values that contain date parts.
+ isTimeOnly := IsTimeFormat(ACell^.NumberFormat) or IsTimeFormat(ACell^.NumberFormatStr);
+ strValue := FormatDateTime(FMT[isTimeOnly], AValue);
+ displayStr := FormatDateTime(ACell^.NumberFormatStr, AValue);
+
+ FCellContent := Format(
+ ' ' + LineEnding +
+ ' %s ' + LineEnding +
+ ' ' + LineEnding, [
+ DT[isTimeOnly], DT[isTimeOnly], strValue, lStyle, displayStr
+ ]);
end;
{
diff --git a/components/fpspreadsheet/fpsutils.pas b/components/fpspreadsheet/fpsutils.pas
index c17a4c592..d47f8c515 100644
--- a/components/fpspreadsheet/fpsutils.pas
+++ b/components/fpspreadsheet/fpsutils.pas
@@ -29,6 +29,8 @@ const
ISO8601Format='yyyymmdd"T"hhmmss';
// Extended ISO 8601 date/time format, used in e.g. ODF/opendocument
ISO8601FormatExtended='yyyy"-"mm"-"dd"T"hh":"mm":"ss';
+ // ISO 8601 time-only format, used in ODF/opendocument
+ ISO8601FormatTimeOnly='"PT"hh"H"nn"M"ss"S"';
// Endianess helper functions
function WordToLE(AValue: Word): Word;
@@ -71,6 +73,8 @@ function IfThen(ACondition: Boolean; AValue1,AValue2: TsNumberFormat): TsNumberF
function IsCurrencyFormat(AFormat: TsNumberFormat): Boolean;
function IsDateTimeFormat(AFormat: TsNumberFormat): Boolean; overload;
function IsDateTimeFormat(AFormatStr: String): Boolean; overload;
+function IsTimeFormat(AFormat: TsNumberFormat): Boolean; overload;
+function IsTimeFormat(AFormatStr: String): Boolean; overload;
function BuildCurrencyFormatString(ADialect: TsNumFormatDialect;
ANumberFormat: TsNumberFormat; const AFormatSettings: TFormatSettings;
@@ -579,6 +583,24 @@ begin
end;
end;
+function IsTimeFormat(AFormat: TsNumberFormat): boolean;
+begin
+ Result := AFormat in [nfShortTime, nfLongTime, nfShortTimeAM, nfLongTimeAM,
+ nfTimeInterval];
+end;
+
+function IsTimeFormat(AFormatStr: String): Boolean;
+var
+ parser: TsNumFormatParser;
+begin
+ parser := TsNumFormatParser.Create(nil, AFormatStr);
+ try
+ Result := parser.IsTimeFormat;
+ finally
+ parser.Free;
+ end;
+end;
+
{ Builds a date/time format string from the numberformat code. If the format code
is nfFmtDateTime the given AFormatString is used. AFormatString can use the
abbreviations "dm" (for "d/mmm"), "my" (for "mmm/yy"), "ms" (for "mm:ss")