Skip to content

Instantly share code, notes, and snippets.

@senevoldsen
Last active November 5, 2017 13:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save senevoldsen/018ee3fbf91931de997ae7556dc0a73c to your computer and use it in GitHub Desktop.
Save senevoldsen/018ee3fbf91931de997ae7556dc0a73c to your computer and use it in GitHub Desktop.
Strftime for SQF
/*
Copyright 2017 Muzzleflash
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
Date to text module based on Python/C's strftime
------------- Before use:
[] call compile preProcessFile "datetime.sqf";
where "datetime.sqf" is the path to this file.
------------- Example of use:
--- Using the date command:
[date, "%d %B, %Y"] call MF_fnc_DT_FormatTime;
"14 August, 2035"
--- Manually pass date. Note seconds are supported by adding an additional element.
[[2035, 6, 11, 16, 12, 37], "%d %b %H:%M:%S"] call MF_fnc_DT_FormatTime;
"11 Jun 16:12:37"
--- 12 hour clock
[date, "%I:%M %p"] call MF_fnc_DT_FormatTime;
"01:37 PM"
--- Special format codes using %@.
Create military DTG in time zone Romeo (R)
for 13:37 on 14 August 2035
[date, "%@ZR"] call MF_fnc_DT_FormatTime;
"141337RAUG35"
Note: Benchmarking suggests about 0.3ms per call.
------------- Supported format flags
Note for more details format codes see the table here:
https://docs.python.org/2/library/datetime.html#strftime-strptime-behavior
Supported | Description
format
codes
YES | %a Weekday as locale’s abbreviated name.
YES | %A Weekday as locale’s full name.
YES | %w Weekday as a decimal number, where 0 is Sunday and 6 is Saturday.
YES | %d Day of the month as a zero-padded decimal number.
YES | %b Month as locale’s abbreviated name.
YES | %B Month as locale’s full name.
YES | %m Month as a zero-padded decimal number.
YES | %y Year without century as a zero-padded decimal number.
YES | %Y Year with century as a decimal number.
YES | %H Hour (24-hour clock) as a zero-padded decimal number.
YES | %I Hour (12-hour clock) as a zero-padded decimal number.
YES | %p Locale’s equivalent of either AM or PM.
YES | %M Minute as a zero-padded decimal number.
YES | %S Second as a zero-padded decimal number.
NO | %f Microsecond as a decimal number, zero-padded on the left.
NO | %z UTC offset in the form +HHMM or -HHMM (empty string if the the object is naive).
NO | %Z Time zone name (empty string if the object is naive).
YES | %j Day of the year as a zero-padded decimal number.
NO | %U Week number of the year (Sunday as the first day of the week) as a zero padded decimal number. All days in a new year preceding the first Sunday are considered to be in week 0.
NO | %W Week number of the year (Monday as the first day of the week) as a decimal number. All days in a new year preceding the first Monday are considered to be in week 0.
YES | %c Locale’s appropriate date and time representation.
YES | %x Locale’s appropriate date representation.
YES | %X Locale’s appropriate time representation.
YES | %% A literal '%' character.
--- Extra
YES | %@ZX Military Date Group.
! Important: X is an argument.
You can change that.
For example Z for ZULU: %@ZZ
Or J for Juliet: %@ZJ
[date, "%@ZZ"] call MF_fnc_DT_FormatTime;
"141337ZAUG35"
YES | %@O Ordinal
! Important: Must come right after number.
It will delete leading 0s.
[date, "%m%@O %B, %Y"] call MF_fnc_DT_FormatTime;
"2nd February, 2035"
*/
// ----- Settings
MF_fnc_DT_LOCALE_DATE_INTERNATIONAL = "%d/%m/%Y";
MF_fnc_DT_LOCALE_DATE_US = "%m/%d/%Y";
MF_fnc_DT_LOCALE_DATE = MF_fnc_DT_LOCALE_DATE_INTERNATIONAL;
MF_fnc_DT_LOCALE_TIME = "%H:%M:%S";
MF_fnc_DT_LOCALE_DATETIME = "%a %b %d %H:%M:%S %Y";
//---------------------------------------------------------------------------------
// ----------- Implementation
// Note we used dynamic scoping to get date components
// So do not overwrite those variables.
MF_fnc_DT_FormatTime = {
params ["_argDate", "_format"];
private _date = _argDate call BIS_fnc_fixDate;
_date params ["_year", "_month", "_day", "_hour", "_minute"];
// Add seconds if not supplied.
private _second = _argDate param [5, 0];
private _dayOfYear = [] call MF_fnc_DT_Strf_DayOfYear;
private _dayOfWeek = [] call MF_fnc_DT_Strf_DayOfWeekNum;
toString ([_format] call MF_fnc_DT_Strf_ProcessFormat);
};
MF_fnc_DT_Strf_ProcessFormat = {
params ["_formatStr"];
private _formatArr = toArray _formatStr;
private _output = [];
private _formatI = 0;
private _length = count _formatArr;
while {_formatI < _length} do {
private _char = _formatArr select _formatI;
if (_char != 37) then { // != %
_output pushBack _char;
_formatI = _formatI + 1;
} else {
private _formatCode = _formatArr select (_formatI+1);
private _idx = MF_fnc_DT_Strf_Formats find _formatCode;
_formatI = _formatI + 2;
if (_idx == -1) exitWith {
["MF_fnc_DT_FormatTime: Unknown format code '%1'", toString [_formatCode]] call BIS_fnc_Error;
_formatI = _length
};
(MF_fnc_DT_Strf_Handlers select _idx) params ["_handler", ["_hArgs", []]];
[_hArgs] call _handler;
};
};
_output;
};
/*
NOTE: There is a problem with the command date, e.g.:
setDate [2004, 12, 31, 23, 55], and then run date you get [2005,1,1,23,55]
So to use this properly for that period, you must manually construct the proper input
to MF_fnd_DT_FormatTime
*/
MF_fnc_DT_Strf_DayOfYear = {
private _floatDate = dateToNumber [_year, _month, _day, _hour, _minute];
ceil (linearConversion [0, 1, _floatDate, 0, 365, false]);
};
MF_fnc_DT_Strf_DayOfWeekNum = {
// Zeller's
private _zQ = _day;
private _zM = _month;
private _zY = _year;
if (_month <= 2) then {
_zY = _zY - 1;
_zM = _zM + 12;
};
private _zK = _zY mod 100;
private _zJ = floor (_zY / 100);
private _zDoW = (
_zQ +
(floor (13 * (_zM + 1) / 5)) +
_zK +
(floor (_zK / 4)) +
(floor (_zJ / 4)) -
(2*_zJ)
);
((_zDoW+5) mod 7) + 1
};
MF_fnc_DT_Strf_LiteralPercent = {
_output pushBack ((toArray "%") select 0);
};
MF_fnc_DT_Strf_DayOfWeekText = {
params [["_args", []]];
private _dayOfWeekTexts = [
"Invalid"
, "Monday"
, "Tuesday"
, "Wednesday"
, "Thursday"
, "Friday"
, "Saturday"
, "Sunday"
];
private _result = toArray (_dayOfWeekTexts select _dayOfWeek);
if (_args find "short" != -1) then {
_result resize 3;
};
_output append _result;
};
MF_fnc_DT_Strf_MonthText = {
params [["_args", []]];
private _monthTexts = [
"Invalid"
, "January"
, "February"
, "March"
, "April"
, "May"
, "June"
, "July"
, "August"
, "September"
, "October"
, "November"
, "December"
];
private _result = toArray (_monthTexts select _month);
if (_args find "short" != -1) then {
_result resize 3;
};
_output append _result
};
MF_fnc_DT_Strf_PushPadded = {
params ["_value", "_width", ["_padChar", "0"]];
// Convert to string
if (not (_value isEqualType "")) then {
_value = str _value;
};
_value = toArray _value;
// Convert to char code
if (_padChar isEqualType "") then {
_padChar = toArray _padChar select 0;
};
private _padding = _width - (count _value) max 0;
for "_i" from 1 to _padding do {
_output pushBack _padChar;
};
_output append _value;
};
MF_fnc_DT_Strf_DTG = {
private _timeZoneChar = _formatArr select _formatI;
// Skip the in-format character args
_formatI = _formatI + 1;
_output append (["%d%H%M"] call MF_fnc_DT_Strf_ProcessFormat);
_output pushBack _timeZoneChar;
// Capitalize abbreviated month
_output append (toArray toUpper toString (["%b%y"] call MF_fnc_DT_Strf_ProcessFormat));
};
MF_fnc_DT_Strf_Ordinal = {
private _tempEndI = (count _output) - 1;
// Examine _output skipping spaces
while {_tempEndI >= 0 and {(_output select _tempEndI) == 32}} do {
_tempEndI = _tempEndI - 1;
};
private _isDigit = {_this >= 48 && _this <= 57};
private _lastDigit = _output select _tempEndI;
if (not (_lastDigit call _isDigit)) exitWith {
["MF_fnc_DT_FormatTime: Ordinal must follow number, only blank space allowed between"] call BIS_fnc_Error;
};
// Keep going until no longer number
private _tempStartI = _tempEndI - 1;
while {_tempStartI >= 0 and {(_output select _tempStartI) call _isDigit}} do {
_tempStartI = _tempStartI - 1;
};
_tempStartI = _tempStartI + 1;
private _deleted = 0;
if (_tempStartI >= 0 && _tempStartI != _tempEndI) then {
// Forward delete 0's
while {(_output select _tempStartI) == 48} do {
_output deleteAt _tempStartI;
_deleted = _deleted + 1;
};
};
// Fix index
_tempEndI = _tempEndI - _deleted;
_lastDigit = _lastDigit - 48;
// Handle 11th to 13th
private _endsWithTh = (
(_lastDigit != 0 and _lastDigit > 3) or {
(_tempEndI > 0) and {
(_output select (_tempEndI-1) == 49) //Left of last is a one.
}
}
);
private _ending = if (_endsWithTh) then {
toArray "th"
} else {
toArray (["st", "nd", "rd"] select (_lastDigit-1))
};
_output append _ending
};
MF_fnc_DT_Strf_Special = {
private _specialMode = toString [_formatArr select _formatI];
// Skip _specialMode char
_formatI = _formatI + 1;
private _specialHandlerFound = false;
if (_specialMode isEqualTo "Z") then {
_specialHandlerFound = true;
[] call MF_fnc_DT_Strf_DTG;
};
if (_specialMode isEqualTo "O") then {
_specialHandlerFound = true;
[] call MF_fnc_DT_Strf_Ordinal;
};
if (not _specialHandlerFound) then {
["MF_fnc_DT_FormatTime: Unknown special code '%1'", _specialMode] call BIS_fnc_Error;
};
};
MF_fnc_DT_Strf_Formats = [];
MF_fnc_DT_Strf_Handlers = [];
MF_fnc_DT_Strf_AddHandler = {
params ["_letter", "_args", ["_handler", nil]];
if (isNil "_handler") then {
_handler = _args;
_args = [];
};
MF_fnc_DT_Strf_Formats pushBack ((toArray _letter) select 0);
MF_fnc_DT_Strf_Handlers pushBack [_handler, _args];
};
["a", {["short"] call MF_fnc_DT_Strf_DayOfWeekText;}] call MF_fnc_DT_Strf_AddHandler;
["A", {[] call MF_fnc_DT_Strf_DayOfWeekText;}] call MF_fnc_DT_Strf_AddHandler;
["w", {[(_dayOfWeek + 6) mod 7, 1, " "] call MF_fnc_DT_Strf_PushPadded;}] call MF_fnc_DT_Strf_AddHandler;
["d", {[_day, 2, "0"] call MF_fnc_DT_Strf_PushPadded;}] call MF_fnc_DT_Strf_AddHandler;
["b", ["short"], MF_fnc_DT_Strf_MonthText] call MF_fnc_DT_Strf_AddHandler;
["B", MF_fnc_DT_Strf_MonthText] call MF_fnc_DT_Strf_AddHandler;
["m", {[_month, 2, "0"] call MF_fnc_DT_Strf_PushPadded;}] call MF_fnc_DT_Strf_AddHandler;
["y", {[_year mod 100, 2, "0"] call MF_fnc_DT_Strf_PushPadded;}] call MF_fnc_DT_Strf_AddHandler;
["Y", {[_year, 0, " "] call MF_fnc_DT_Strf_PushPadded;}] call MF_fnc_DT_Strf_AddHandler;
["H", {[_hour, 2, "0"] call MF_fnc_DT_Strf_PushPadded;}] call MF_fnc_DT_Strf_AddHandler;
["I", {
private _adjustedHour = _hour mod 12;
if (_adjustedHour == 0) then {_adjustedHour = 12};
[_adjustedHour, 2, "0"] call MF_fnc_DT_Strf_PushPadded;
}] call MF_fnc_DT_Strf_AddHandler;
["p", {
private _text = if (_hour < 12) then {"AM"} else {"PM"};
[_text, 0, ""] call MF_fnc_DT_Strf_PushPadded;
}] call MF_fnc_DT_Strf_AddHandler;
["M", {[_minute, 2, "0"] call MF_fnc_DT_Strf_PushPadded;}] call MF_fnc_DT_Strf_AddHandler;
["S", {[_second, 2, "0"] call MF_fnc_DT_Strf_PushPadded;}] call MF_fnc_DT_Strf_AddHandler;
["j", {[_dayOfYear, 3, "0"] call MF_fnc_DT_Strf_PushPadded;}] call MF_fnc_DT_Strf_AddHandler;
["c", {_output append ([MF_fnc_DT_LOCALE_DATETIME] call MF_fnc_DT_Strf_ProcessFormat);}] call MF_fnc_DT_Strf_AddHandler;
["x", {_output append ([MF_fnc_DT_LOCALE_DATE] call MF_fnc_DT_Strf_ProcessFormat);}] call MF_fnc_DT_Strf_AddHandler;
["X", {_output append ([MF_fnc_DT_LOCALE_TIME] call MF_fnc_DT_Strf_ProcessFormat);}] call MF_fnc_DT_Strf_AddHandler;
["%", MF_fnc_DT_Strf_LiteralPercent] call MF_fnc_DT_Strf_AddHandler;
// Specials
["@", {[] call MF_fnc_DT_Strf_Special;}] call MF_fnc_DT_Strf_AddHandler;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment