1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Added some UTC and ISO8601 methods to the Time class. Also clarified some of its comments and added unit tests

This commit is contained in:
jules 2016-02-17 14:41:44 +00:00
parent d6f02c9ba4
commit 3fdf969ea2
2 changed files with 312 additions and 98 deletions

View file

@ -28,57 +28,98 @@
namespace TimeHelpers
{
static struct tm millisToLocal (const int64 millis) noexcept
static struct tm millisecondsToTM (const int64 jdm) noexcept
{
struct tm result;
const int64 seconds = millis / 1000;
if (seconds < 86400LL || seconds >= 2145916800LL)
{
// use extended maths for dates beyond 1970 to 2037..
const int timeZoneAdjustment = 31536000 - (int) (Time (1971, 0, 1, 0, 0).toMilliseconds() / 1000);
const int64 jdm = seconds + timeZoneAdjustment + 210866803200LL;
const int days = (int) (jdm / 86400LL);
const int a = 32044 + days;
const int b = (4 * a + 3) / 146097;
const int c = a - (b * 146097) / 4;
const int d = (4 * c + 3) / 1461;
const int e = c - (d * 1461) / 4;
const int m = (5 * e + 2) / 153;
const int days = (int) (jdm / 86400LL);
const int a = 32044 + days;
const int b = (4 * a + 3) / 146097;
const int c = a - (b * 146097) / 4;
const int d = (4 * c + 3) / 1461;
const int e = c - (d * 1461) / 4;
const int m = (5 * e + 2) / 153;
result.tm_mday = e - (153 * m + 2) / 5 + 1;
result.tm_mon = m + 2 - 12 * (m / 10);
result.tm_year = b * 100 + d - 6700 + (m / 10);
result.tm_wday = (days + 1) % 7;
result.tm_yday = -1;
result.tm_mday = e - (153 * m + 2) / 5 + 1;
result.tm_mon = m + 2 - 12 * (m / 10);
result.tm_year = b * 100 + d - 6700 + (m / 10);
result.tm_wday = (days + 1) % 7;
result.tm_yday = -1;
int t = (int) (jdm % 86400LL);
result.tm_hour = t / 3600;
t %= 3600;
result.tm_min = t / 60;
result.tm_sec = t % 60;
result.tm_isdst = -1;
}
else
{
time_t now = static_cast<time_t> (seconds);
#if JUCE_WINDOWS && JUCE_MINGW
return *localtime (&now);
#elif JUCE_WINDOWS
if (now >= 0 && now <= 0x793406fff)
localtime_s (&result, &now);
else
zerostruct (result);
#else
localtime_r (&now, &result); // more thread-safe
#endif
}
int t = (int) (jdm % 86400LL);
result.tm_hour = t / 3600;
t %= 3600;
result.tm_min = t / 60;
result.tm_sec = t % 60;
result.tm_isdst = -1;
return result;
}
static bool isBeyond1970to2030Range (const int64 seconds)
{
return seconds < 86400LL || seconds >= 2145916800LL;
}
static struct tm millisToLocal (const int64 millis) noexcept
{
const int64 seconds = millis / 1000;
if (isBeyond1970to2030Range (seconds))
{
const int timeZoneAdjustment = 31536000 - (int) (Time (1971, 0, 1, 0, 0).toMilliseconds() / 1000);
return millisecondsToTM (seconds + timeZoneAdjustment + 210866803200LL);
}
struct tm result;
time_t now = static_cast<time_t> (seconds);
#if JUCE_WINDOWS && JUCE_MINGW
return *localtime (&now);
#elif JUCE_WINDOWS
if (now >= 0 && now <= 0x793406fff)
localtime_s (&result, &now);
else
zerostruct (result);
#else
localtime_r (&now, &result); // more thread-safe
#endif
return result;
}
static struct tm millisToUTC (const int64 millis) noexcept
{
const int64 seconds = millis / 1000;
if (isBeyond1970to2030Range (seconds))
return millisecondsToTM (seconds + 210866803200LL);
struct tm result;
time_t now = static_cast<time_t> (seconds);
#if JUCE_WINDOWS && JUCE_MINGW
return *gmtime (&now);
#elif JUCE_WINDOWS
if (now >= 0 && now <= 0x793406fff)
gmtime_s (&result, &now);
else
zerostruct (result);
#else
gmtime_r (&now, &result); // more thread-safe
#endif
return result;
}
static int getUTCOffsetSeconds (const int64 millis) noexcept
{
struct tm utc = millisToUTC (millis);
utc.tm_isdst = -1; // Treat this UTC time as local to find the offset
return (int) ((millis / 1000) - (int64) mktime (&utc));
}
static int extendedModulo (const int64 value, const int modulo) noexcept
{
return (int) (value >= 0 ? (value % modulo)
@ -388,6 +429,120 @@ String Time::getTimeZone() const noexcept
return zone[0].substring (0, 3);
}
int Time::getUTCOffsetSeconds() const noexcept
{
return TimeHelpers::getUTCOffsetSeconds (millisSinceEpoch);
}
String Time::getUTCOffsetString (bool includeSemiColon) const
{
if (int seconds = getUTCOffsetSeconds())
{
const int minutes = seconds / 60;
return String::formatted (includeSemiColon ? "%+03d:%02d"
: "%+03d%02d",
minutes / 60,
minutes % 60);
}
return "Z";
}
String Time::toISO8601 (bool includeDividerCharacters) const
{
return String::formatted (includeDividerCharacters ? "%04d-%02d-%02dT%02d:%02d:%02d:%03d"
: "%04d%02d%02dT%02d%02d%02d%03d",
getYear(),
getMonth() + 1,
getDayOfMonth(),
getHours(),
getMinutes(),
getSeconds(),
getMilliseconds())
+ getUTCOffsetString (includeDividerCharacters);
}
static int parseFixedSizeIntAndSkip (String::CharPointerType& t, int numChars, char charToSkip) noexcept
{
int n = 0;
for (int i = numChars; --i >= 0;)
{
const int digit = (int) (*t - '0');
if (! isPositiveAndBelow (digit, 10))
return -1;
++t;
n = n * 10 + digit;
}
if (charToSkip != 0 && *t == (juce_wchar) charToSkip)
++t;
return n;
}
Time Time::fromISO8601 (StringRef iso) noexcept
{
String::CharPointerType t = iso.text;
const int year = parseFixedSizeIntAndSkip (t, 4, '-');
if (year < 0)
return Time();
const int month = parseFixedSizeIntAndSkip (t, 2, '-');
if (month < 0)
return Time();
const int day = parseFixedSizeIntAndSkip (t, 2, 0);
if (day < 0)
return Time();
int hours = 0, minutes = 0, seconds = 0, milliseconds = 0;
if (*t == 'T')
{
++t;
hours = parseFixedSizeIntAndSkip (t, 2, ':');
if (hours < 0)
return Time();
minutes = parseFixedSizeIntAndSkip (t, 2, ':');
if (minutes < 0)
return Time();
seconds = parseFixedSizeIntAndSkip (t, 2, ':');
if (seconds < 0)
return Time();
milliseconds = jmax (0, parseFixedSizeIntAndSkip (t, 3, 0));
}
Time result (year, month - 1, day, hours, minutes, seconds, milliseconds, false);
const bool negative = *t == '-';
if (negative || *t == '+')
{
++t;
const int offsetHours = parseFixedSizeIntAndSkip (t, 2, ':');
if (offsetHours < 0)
return Time();
const int offsetMinutes = parseFixedSizeIntAndSkip (t, 2, 0);
if (offsetMinutes < 0)
return Time();
const int offsetMs = (offsetHours * 60 + offsetMinutes) * 60 * 1000;
result.millisSinceEpoch += negative ? offsetMs : -offsetMs; // NB: this seems backwards but is correct!
}
return result;
}
String Time::getMonthName (const bool threeLetterVersion) const
{
return getMonthName (getMonth(), threeLetterVersion);
@ -463,3 +618,54 @@ Time Time::getCompilationDate()
timeTokens[0].getIntValue(),
timeTokens[1].getIntValue());
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
class TimeTests : public UnitTest
{
public:
TimeTests() : UnitTest ("Time") {}
void runTest() override
{
beginTest ("Time");
Time t = Time::getCurrentTime();
expect (t > Time());
Thread::sleep (15);
expect (Time::getCurrentTime() > t);
expect (t.getTimeZone().isNotEmpty());
expect (t.getUTCOffsetString (true) == "Z" || t.getUTCOffsetString (true).length() == 6);
expect (t.getUTCOffsetString (false) == "Z" || t.getUTCOffsetString (false).length() == 5);
DBG (t.getUTCOffsetSeconds());
DBG (t.getUTCOffsetString (true));
DBG (t.toISO8601 (true));
DBG (Time::fromISO8601 (t.toISO8601 (true)).toISO8601 (true));
DBG (t.toISO8601 (false));
expect (Time::fromISO8601 (t.toISO8601 (true)) == t);
expect (Time::fromISO8601 (t.toISO8601 (false)) == t);
expect (Time::fromISO8601 ("2016-02-16") == Time (2016, 1, 16, 0, 0, 0, 0, false));
expect (Time::fromISO8601 ("20160216") == Time (2016, 1, 16, 0, 0, 0, 0, false));
expect (Time::fromISO8601 ("2016-02-16T15:03:57+00:00") == Time (2016, 1, 16, 15, 3, 57, 0, false));
expect (Time::fromISO8601 ("20160216T150357+0000") == Time (2016, 1, 16, 15, 3, 57, 0, false));
expect (Time::fromISO8601 ("2016-02-16T15:03:57:999+00:00") == Time (2016, 1, 16, 15, 3, 57, 999, false));
expect (Time::fromISO8601 ("20160216T150357999+0000") == Time (2016, 1, 16, 15, 3, 57, 999, false));
expect (Time::fromISO8601 ("2016-02-16T15:03:57:999Z") == Time (2016, 1, 16, 15, 3, 57, 999, false));
expect (Time::fromISO8601 ("20160216T150357999Z") == Time (2016, 1, 16, 15, 3, 57, 999, false));
expect (Time::fromISO8601 ("2016-02-16T15:03:57:999-02:30") == Time (2016, 1, 16, 17, 33, 57, 999, false));
expect (Time::fromISO8601 ("20160216T150357999-0230") == Time (2016, 1, 16, 17, 33, 57, 999, false));
}
};
static TimeTests timeTests;
#endif

View file

@ -44,7 +44,7 @@ public:
//==============================================================================
/** Creates a Time object.
This default constructor creates a time of 1st January 1970, (which is
This default constructor creates a time of midnight Jan 1st 1970 UTC, (which is
represented internally as 0ms).
To create a time object representing the current time, use getCurrentTime().
@ -55,19 +55,16 @@ public:
/** Creates a time based on a number of milliseconds.
The internal millisecond count is set to 0 (1st January 1970). To create a
time object set to the current time, use getCurrentTime().
To create a time object set to the current time, use getCurrentTime().
@param millisecondsSinceEpoch the number of milliseconds since the unix
'epoch' (midnight Jan 1st 1970).
'epoch' (midnight Jan 1st 1970 UTC).
@see getCurrentTime, currentTimeMillis
*/
explicit Time (int64 millisecondsSinceEpoch) noexcept;
/** Creates a time from a set of date components.
The timezone is assumed to be whatever the system is using as its locale.
@param year the year, in 4-digit format, e.g. 2004
@param month the month, in the range 0 to 11
@param day the day of the month, in the range 1 to 31
@ -75,8 +72,8 @@ public:
@param minutes minutes 0 to 59
@param seconds seconds 0 to 59
@param milliseconds milliseconds 0 to 999
@param useLocalTime if true, encode using the current machine's local time; if
false, it will always work in GMT.
@param useLocalTime if true, assume input is in this machine's local timezone
if false, assume input is in UTC.
*/
Time (int year,
int month,
@ -107,82 +104,71 @@ public:
static Time JUCE_CALLTYPE getCurrentTime() noexcept;
/** Returns the time as a number of milliseconds.
@returns the number of milliseconds this Time object represents, since
midnight jan 1st 1970.
midnight Jan 1st 1970 UTC.
@see getMilliseconds
*/
int64 toMilliseconds() const noexcept { return millisSinceEpoch; }
/** Returns the year.
/** Returns the year (in this machine's local timezone).
A 4-digit format is used, e.g. 2004.
*/
int getYear() const noexcept;
/** Returns the number of the month.
/** Returns the number of the month (in this machine's local timezone).
The value returned is in the range 0 to 11.
@see getMonthName
*/
int getMonth() const noexcept;
/** Returns the name of the month.
/** Returns the name of the month (in this machine's local timezone).
@param threeLetterVersion if true, it'll be a 3-letter abbreviation, e.g. "Jan"; if false
it'll return the long form, e.g. "January"
@see getMonth
*/
String getMonthName (bool threeLetterVersion) const;
/** Returns the day of the month.
/** Returns the day of the month (in this machine's local timezone).
The value returned is in the range 1 to 31.
*/
int getDayOfMonth() const noexcept;
/** Returns the number of the day of the week.
/** Returns the number of the day of the week (in this machine's local timezone).
The value returned is in the range 0 to 6 (0 = sunday, 1 = monday, etc).
*/
int getDayOfWeek() const noexcept;
/** Returns the number of the day of the year.
/** Returns the number of the day of the year (in this machine's local timezone).
The value returned is in the range 0 to 365.
*/
int getDayOfYear() const noexcept;
/** Returns the name of the weekday.
/** Returns the name of the weekday (in this machine's local timezone).
@param threeLetterVersion if true, it'll return a 3-letter abbreviation, e.g. "Tue"; if
false, it'll return the full version, e.g. "Tuesday".
*/
String getWeekdayName (bool threeLetterVersion) const;
/** Returns the number of hours since midnight.
/** Returns the number of hours since midnight (in this machine's local timezone).
This is in 24-hour clock format, in the range 0 to 23.
@see getHoursInAmPmFormat, isAfternoon
*/
int getHours() const noexcept;
/** Returns true if the time is in the afternoon.
So it returns true for "PM", false for "AM".
/** Returns true if the time is in the afternoon (in this machine's local timezone).
@returns true for "PM", false for "AM".
@see getHoursInAmPmFormat, getHours
*/
bool isAfternoon() const noexcept;
/** Returns the hours in 12-hour clock format.
/** Returns the hours in 12-hour clock format (in this machine's local timezone).
This will return a value 1 to 12 - use isAfternoon() to find out
whether this is in the afternoon or morning.
@see getHours, isAfternoon
*/
int getHoursInAmPmFormat() const noexcept;
/** Returns the number of minutes, 0 to 59. */
/** Returns the number of minutes, 0 to 59 (in this machine's local timezone). */
int getMinutes() const noexcept;
/** Returns the number of seconds, 0 to 59. */
@ -200,11 +186,21 @@ public:
/** Returns true if the local timezone uses a daylight saving correction. */
bool isDaylightSavingTime() const noexcept;
//==============================================================================
/** Returns a 3-character string to indicate the local timezone. */
String getTimeZone() const noexcept;
/** Returns the local timezone offset from UTC in seconds. */
int getUTCOffsetSeconds() const noexcept;
/** Returns a string to indicate the offset of the local timezone from UTC.
@returns "+XX:XX", "-XX:XX" or "Z"
@param includeDividerCharacters whether to include or omit the ":" divider in the string
*/
String getUTCOffsetString (bool includeDividerCharacters) const;
//==============================================================================
/** Quick way of getting a string version of a date and time.
/** Returns a string version of this date and time, using this machine's local timezone.
For a more powerful way of formatting the date and time, see the formatted() method.
@ -227,33 +223,45 @@ public:
looking it up, these are the escape codes that strftime uses (other codes might
work on some platforms and not others, but these are the common ones):
%a is replaced by the locale's abbreviated weekday name.
%A is replaced by the locale's full weekday name.
%b is replaced by the locale's abbreviated month name.
%B is replaced by the locale's full month name.
%c is replaced by the locale's appropriate date and time representation.
%d is replaced by the day of the month as a decimal number [01,31].
%H is replaced by the hour (24-hour clock) as a decimal number [00,23].
%I is replaced by the hour (12-hour clock) as a decimal number [01,12].
%j is replaced by the day of the year as a decimal number [001,366].
%m is replaced by the month as a decimal number [01,12].
%M is replaced by the minute as a decimal number [00,59].
%p is replaced by the locale's equivalent of either a.m. or p.m.
%S is replaced by the second as a decimal number [00,61].
%U is replaced by the week number of the year (Sunday as the first day of the week) as a decimal number [00,53].
%w is replaced by the weekday as a decimal number [0,6], with 0 representing Sunday.
%W is replaced by the week number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0.
%x is replaced by the locale's appropriate date representation.
%X is replaced by the locale's appropriate time representation.
%y is replaced by the year without century as a decimal number [00,99].
%Y is replaced by the year with century as a decimal number.
%Z is replaced by the timezone name or abbreviation, or by no bytes if no timezone information exists.
%% is replaced by %.
- %a is replaced by the locale's abbreviated weekday name.
- %A is replaced by the locale's full weekday name.
- %b is replaced by the locale's abbreviated month name.
- %B is replaced by the locale's full month name.
- %c is replaced by the locale's appropriate date and time representation.
- %d is replaced by the day of the month as a decimal number [01,31].
- %H is replaced by the hour (24-hour clock) as a decimal number [00,23].
- %I is replaced by the hour (12-hour clock) as a decimal number [01,12].
- %j is replaced by the day of the year as a decimal number [001,366].
- %m is replaced by the month as a decimal number [01,12].
- %M is replaced by the minute as a decimal number [00,59].
- %p is replaced by the locale's equivalent of either a.m. or p.m.
- %S is replaced by the second as a decimal number [00,61].
- %U is replaced by the week number of the year (Sunday as the first day of the week) as a decimal number [00,53].
- %w is replaced by the weekday as a decimal number [0,6], with 0 representing Sunday.
- %W is replaced by the week number of the year (Monday as the first day of the week) as a decimal number [00,53]. All days in a new year preceding the first Monday are considered to be in week 0.
- %x is replaced by the locale's appropriate date representation.
- %X is replaced by the locale's appropriate time representation.
- %y is replaced by the year without century as a decimal number [00,99].
- %Y is replaced by the year with century as a decimal number.
- %Z is replaced by the timezone name or abbreviation, or by no bytes if no timezone information exists.
- %% is replaced by %.
@see toString
*/
String formatted (const String& format) const;
//==============================================================================
/** Returns a fully described string of this date and time in ISO-8601 format
(using the local timezone).
@param includeDividerCharacters whether to include or omit the "-" and ":"
dividers in the string
*/
String toISO8601 (bool includeDividerCharacters) const;
/** Parses an ISO-8601 string and returns it as a Time. */
static Time fromISO8601 (StringRef iso8601) noexcept;
//==============================================================================
/** Adds a RelativeTime to this time. */
Time& operator+= (RelativeTime delta) noexcept;
@ -290,7 +298,7 @@ public:
/** Returns the current system time.
Returns the number of milliseconds since midnight jan 1st 1970.
Returns the number of milliseconds since midnight Jan 1st 1970 UTC.
Should be accurate to within a few millisecs, depending on platform,
hardware, etc.