/
snowflake.dart
151 lines (123 loc) · 5.34 KB
/
snowflake.dart
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/// A unique ID used to identify objects in the API.
///
/// {@template snowflake}
/// Snowflakes are generally unique across the API except in some cases where children share their
/// parent's IDs.
///
/// {@template snowflake_ordering}
/// Snowflakes are ordered first by their [timestamp], then by [workerId], [processId] and
/// [increment]. The last three fields are only used internally by Discord so the only ordering
/// visible through the API is by [timestamp].
/// {@endtemplate}
///
/// External references:
/// * Discord API Reference: https://discord.com/developers/docs/reference#snowflakes
/// {@endtemplate}
class Snowflake implements Comparable<Snowflake> {
/// A [DateTime] representing the start of the Discord epoch.
///
/// This is used as the epoch for [millisecondsSinceEpoch].
static final epoch = DateTime.utc(2015, 1, 1, 0, 0, 0);
/// The duration after which bulk delete operations are no longer valid.
static const bulkDeleteLimit = Duration(days: 14);
/// A snowflake with a value of 0.
static const zero = Snowflake(0);
/// The value of this snowflake.
///
/// While this is stored in a signed [int], Discord treats this as an unsigned value.
final int value;
/// The time at which this snowflake was created.
DateTime get timestamp => epoch.add(Duration(milliseconds: millisecondsSinceEpoch));
/// The number of milliseconds since the [epoch].
///
/// Discord uses a non-standard epoch for their snowflakes. As such,
/// [DateTime.fromMillisecondsSinceEpoch] will not work with this value. Users should instead use
/// the [timestamp] getter.
int get millisecondsSinceEpoch => value >> 22;
/// The internal worker ID for this snowflake.
///
/// {@template internal_field}
/// This is an internal field and has no practical application.
/// {@endtemplate}
int get workerId => (value & 0x3E0000) >> 17;
/// The internal process ID for this snowflake.
///
/// {@macro internal_field}
int get processId => (value & 0x1F000) >> 12;
/// The internal increment value for this snowflake.
///
/// {@macro internal_field}
int get increment => value & 0xFFF;
/// Whether this snowflake has a value of `0`.
bool get isZero => value == 0;
/// Create a new snowflake from an integer value.
///
/// {@macro snowflake}
const Snowflake(this.value);
/// Parse a string or integer value to a snowflake.
///
/// Both data types are accepted as Discord's Gateway can transmit Snowflakes as strings or integers when using the [GatewayPayloadFormat.etf] payload format.
///
/// The [value] must be an [int] or a [String] parsable by [int.parse].
///
/// {@macro snowflake}
// TODO: This method will fail once snowflakes become larger than 2^63.
// We need to parse the unsigned [value] into a signed [int].
factory Snowflake.parse(Object /* String | int */ value) {
assert(value is String || value is int);
if (value is! int) {
value = int.parse(value.toString());
}
return Snowflake(value);
}
/// Create a snowflake with a timestamp equal to the current time.
///
/// {@macro snowflake}
factory Snowflake.now() => Snowflake.fromDateTime(DateTime.now());
/// Create a snowflake with a timestamp equal to [dateTime].
///
/// [dateTime] must be a [DateTime] which is at the same moment as or after [epoch].
///
/// {@macro snowflake}
factory Snowflake.fromDateTime(DateTime dateTime) {
assert(
dateTime.isAfter(epoch) || dateTime.isAtSameMomentAs(epoch),
'Cannot create a Snowflake before the epoch.',
);
return Snowflake(dateTime.difference(epoch).inMilliseconds << 22);
}
/// Create a snowflake representing the oldest time at which bulk delete operations will work.
///
/// {@macro snowflake}
factory Snowflake.firstBulk() => Snowflake.fromDateTime(DateTime.now().subtract(bulkDeleteLimit));
/// Return `true` if this snowflake has a [timestamp] before [other]'s timestamp.
bool isBefore(Snowflake other) => timestamp.isBefore(other.timestamp);
/// Return `true` if this snowflake has a [timestamp] after [other]'s timestamp.
bool isAfter(Snowflake other) => timestamp.isAfter(other.timestamp);
/// Return `true` if this snowflake has a [timestamp] at the same time as [other]'s timestamp.
bool isAtSameMomentAs(Snowflake other) => timestamp.isAtSameMomentAs(other.timestamp);
/// Return a snowflake [duration] after this snowflake.
///
/// The returned snowflake has no [workerId], [processId] or [increment].
Snowflake operator +(Duration duration) => Snowflake.fromDateTime(timestamp.add(duration));
/// Return a snowflake [duration] before this snowflake.
///
/// The returned snowflake has no [workerId], [processId] or [increment].
Snowflake operator -(Duration duration) => Snowflake.fromDateTime(timestamp.subtract(duration));
@override
int compareTo(Snowflake other) => value.compareTo(other.value);
/// Whether this snowflake is before [other].
///
/// See [isBefore] for details.
bool operator <(Snowflake other) => isBefore(other);
/// Whether this snowflake is after [other].
///
/// See [isAfter] for details.
bool operator >(Snowflake other) => isAfter(other);
@override
bool operator ==(Object other) => identical(this, other) || (other is Snowflake && other.value == value);
@override
int get hashCode => value.hashCode;
@override
String toString() => value.toString();
}