summaryrefslogtreecommitdiff
path: root/indra/llcommon/llcond.cpp
blob: d5362a48fc374b2e2fcf14c520203b9e7b07ba4f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**
 * @file   llcond.cpp
 * @author Nat Goodspeed
 * @date   2019-07-17
 * @brief  Implementation for llcond.
 * 
 * $LicenseInfo:firstyear=2019&license=viewerlgpl$
 * Copyright (c) 2019, Linden Research, Inc.
 * $/LicenseInfo$
 */

// Precompiled header
#include "linden_common.h"
// associated header
#include "llcond.h"
// STL headers
// std headers
// external library headers
// other Linden headers

namespace // anonymous
{

// See comments in LLCond::convert(const LLDate&) below
std::time_t compute_lldate_epoch()
{
    LLDate lldate_epoch;
    std::tm tm;
    // It should be noted that calling LLDate::split() to write directly
    // into a std::tm struct depends on S32 being a typedef for int in
    // stdtypes.h: split() takes S32*, whereas tm fields are documented to
    // be int. If you get compile errors here, somebody changed the
    // definition of S32. You'll have to declare six S32 variables,
    // split() into them, then assign them into the relevant tm fields.
    if (! lldate_epoch.split(&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
                             &tm.tm_hour, &tm.tm_min, &tm.tm_sec))
    {
        // Theoretically split() could return false. In that case, we
        // don't have valid data, so we can't compute offset, so skip the
        // rest of this.
        return 0;
    }

    tm.tm_isdst = 0;
    std::time_t lldate_epoch_time = std::mktime(&tm);
    if (lldate_epoch_time == -1)
    {
        // Theoretically mktime() could return -1, meaning that the contents
        // of the passed std::tm cannot be represented as a time_t. (Worrisome
        // if LLDate's epoch happened to be exactly 1 tick before
        // std::time_t's epoch...)
        // In the error case, assume offset 0.
        return 0;
    }

    // But if we got this far, lldate_epoch_time is the time_t we want.
    return lldate_epoch_time;
}

} // anonymous namespace

// convert LLDate to a chrono::time_point
std::chrono::system_clock::time_point LLCond::convert(const LLDate& lldate)
{
    // std::chrono::system_clock's epoch MAY be the Unix epoch, namely
    // midnight UTC on 1970-01-01, in fact it probably is. But until C++20,
    // system_clock does not guarantee that. Unfortunately time_t doesn't
    // specify its epoch either, other than to note that it "almost always" is
    // the Unix epoch (https://en.cppreference.com/w/cpp/chrono/c/time_t).
    // LLDate, being based on apr_time_t, does guarantee 1970-01-01T00:00 UTC.
    // http://apr.apache.org/docs/apr/1.5/group__apr__time.html#gadb4bde16055748190eae190c55aa02bb

    // The easy, efficient conversion would be
    // std::chrono::system_clock::from_time_t(std::time_t(LLDate::secondsSinceEpoch())).
    // But that assumes that both time_t and system_clock have the same epoch
    // as LLDate -- an assumption that will work until it unexpectedly doesn't.

    // It would be more formally correct to break out the year, month, day,
    // hour, minute, second (UTC) using LLDate::split() and recombine them
    // into std::time_t using std::mktime(). However, both split() and
    // mktime() have integer second granularity, whereas callers of
    // wait_until() are very likely to be interested in sub-second precision.
    // In that sense std::chrono::system_clock::from_time_t() is still
    // preferred.

    // So use the split() / mktime() mechanism to determine the numeric value
    // of the LLDate / apr_time_t epoch as expressed in time_t. (We assume
    // that the epoch offset can be expressed as integer seconds, per split()
    // and mktime(), which seems plausible.)

    // n.b. A function-static variable is initialized only once in a
    // thread-safe way.
    static std::time_t lldate_epoch_time = compute_lldate_epoch();

    // LLDate::secondsSinceEpoch() gets us, of course, how long it has
    // been since lldate_epoch_time. So adding lldate_epoch_time should
    // give us the correct time_t representation of a given LLDate even if
    // time_t's epoch differs from LLDate's.
    // We don't have to worry about the relative epochs of time_t and
    // system_clock because from_time_t() takes care of that!
    return std::chrono::system_clock::from_time_t(lldate_epoch_time +
                                                  lldate.secondsSinceEpoch());
}

// convert F32Milliseconds to a chrono::duration
std::chrono::milliseconds LLCond::convert(F32Milliseconds)
{
    // extract the F32 milliseconds from F32Milliseconds, construct
    // std::chrono::milliseconds from that value
    return std::chrono::milliseconds(timeout_duration.value());
}