8
8
* A representations of IMAP's INTERNALDATE field.
10
* INTERNALDATE's format is
12
* dd-Mon-yyyy hh:mm:ss +hhmm
14
* Note that Mon is the standard ''English'' three-letter abbreviation.
10
16
* See [[http://tools.ietf.org/html/rfc3501#section-2.3.3]]
13
19
public class Geary.Imap.InternalDate : Geary.MessageData.AbstractMessageData, Geary.Imap.MessageData,
14
20
Gee.Hashable<InternalDate>, Gee.Comparable<InternalDate> {
21
// see get_en_us_mon() for explanation
22
private const string[] EN_US_MON = {
23
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
26
private const string[] EN_US_MON_DOWN = {
27
"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"
15
30
public DateTime value { get; private set; }
16
31
public time_t as_time_t { get; private set; }
32
public string? original { get; private set; default = null; }
18
public InternalDate(string internaldate) throws ImapError {
19
as_time_t = GMime.utils_header_decode_date(internaldate, null);
21
throw new ImapError.PARSE_ERROR("Unable to parse \"%s\": not INTERNALDATE format",
25
value = new DateTime.from_unix_local(as_time_t);
34
private InternalDate(string original, DateTime datetime) {
35
this.original = original;
37
as_time_t = Time.datetime_to_time_t(datetime);
28
40
public InternalDate.from_date_time(DateTime datetime) throws ImapError {
42
as_time_t = Time.datetime_to_time_t(datetime);
45
public static InternalDate decode(string internaldate) throws ImapError {
46
if (String.is_empty(internaldate))
47
throw new ImapError.PARSE_ERROR("Invalid INTERNALDATE: empty string");
49
if (internaldate.length > 64)
50
throw new ImapError.PARSE_ERROR("Invalid INTERNALDATE: too long (%d)", internaldate.length);
52
// Alas, GMime.utils_header_decode_date() is too forgiving for our needs, so do it manually
53
int day, year, hour, min, sec;
56
int count = internaldate.scanf("%d-%3s-%d %d:%d:%d %5s", out day, mon, out year, out hour,
57
out min, out sec, tz);
59
throw new ImapError.PARSE_ERROR("Invalid INTERNALDATE \"%s\": too few fields (%d)", internaldate, count);
61
// check numerical ranges; this does not verify this is an actual date, DateTime will do
62
// that (and round upward, which has to be accepted)
63
if (!Numeric.int_in_range_inclusive(day, 1, 31)
64
|| !Numeric.int_in_range_inclusive(hour, 0, 23)
65
|| !Numeric.int_in_range_inclusive(min, 0, 59)
66
|| !Numeric.int_in_range_inclusive(sec, 0, 59)
68
throw new ImapError.PARSE_ERROR("Invalid INTERNALDATE \"%s\": bad numerical range", internaldate);
71
// check month (this catches localization problems)
73
string mon_down = ((string) mon).down();
74
for (int ctr = 0; ctr < EN_US_MON_DOWN.length; ctr++) {
75
if (mon_down == EN_US_MON_DOWN[ctr]) {
83
throw new ImapError.PARSE_ERROR("Invalid INTERNALDATE \"%s\": bad month", internaldate);
85
// TODO: verify timezone
87
// assemble into DateTime, which validates the time as well (this is why we want to keep
88
// original around, for other reasons) ... month is 1-based in DateTime
89
DateTime datetime = new DateTime(new TimeZone((string) tz), year, month + 1, day, hour, min,
92
return new InternalDate(internaldate, datetime);
51
114
* @see serialize_for_search
53
116
public string serialize() {
54
return value.format("%d-%b-%Y %H:%M:%S %z");
117
return original ?? value.format("%d-%%s-%Y %H:%M:%S %z").printf(get_en_us_mon());
58
* Returns the {@link InternalDate}'s string representation for a SEARCH function.
121
* Returns the {@link InternalDate}'s string representation for a SEARCH command.
60
123
* SEARCH does not respect time or timezone, so drop when sending it. See
61
124
* [[http://tools.ietf.org/html/rfc3501#section-6.4.4]]
65
129
public string serialize_for_search() {
66
return value.format("%d-%b-%Y");
130
return value.format("%d-%%s-%Y").printf(get_en_us_mon());
134
* Because IMAP's INTERNALDATE strings are ''never'' localized (as best as I can gather), so
135
* need to use en_US appreviated month names, as that's the only value in INTERNALDATE that is
136
* in a language and not a numeric value.
138
private string get_en_us_mon() {
139
// month is 1-based inside of DateTime
140
int mon = (value.get_month() - 1).clamp(0, EN_US_MON.length - 1);
142
return EN_US_MON[mon];
69
145
public uint hash() {