1/* 2 * This file is part of gitg 3 * 4 * Copyright (C) 2013 - Jesse van den Kieboom 5 * 6 * gitg is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * gitg is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with gitg. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20namespace Gitg 21{ 22 23public errordomain DateError 24{ 25 INVALID_FORMAT 26} 27 28public class Date : Object, Initable 29{ 30 private static Regex s_rfc2822; 31 private static Regex s_iso8601; 32 private static Regex s_internal; 33 34 private static Settings? s_gnome_interface_settings; 35 private static bool s_tried_gnome_interface_settings; 36 37 private static string?[] s_months = new string?[] { 38 null, 39 "Jan", 40 "Feb", 41 "Mar", 42 "Apr", 43 "May", 44 "Jun", 45 "Jul", 46 "Aug", 47 "Sep", 48 "Oct", 49 "Nov", 50 "Dec" 51 }; 52 53 static construct 54 { 55 try 56 { 57 58 s_iso8601 = new Regex(@"^ 59 (?<year>[0-9]{4}) 60 (?: 61 [-.]?(?: 62 (?<month>[0-9]{2}) 63 (?: 64 [-.]?(?<day>[0-9]{2}) 65 )? 66 | 67 W(?<week>[0-9]{2}) 68 (?: 69 [-.]?(?<weekday>[0-9]) 70 )? 71 ) 72 (?: 73 [T ](?<hour>[0-9]{2}) 74 (?: 75 :? 76 (?<minute>[0-9]{2}) 77 (?: 78 :? 79 (?<seconds>[0-9]{2}) 80 (?<tz> 81 (?<tzutc>Z) | 82 [+-](?<tzhour>[0-9]{2}) 83 (?: 84 :? 85 (?<tzminute>[0-9]{2}) 86 )? 87 )? 88 )? 89 )? 90 )? 91 )? 92 $$", RegexCompileFlags.EXTENDED); 93 94 s_rfc2822 = new Regex(@"^ 95 (?: 96 [\\s]*(?<dayofweek>Mon|Tue|Wed|Thu|Fri|Sat|Sun) 97 , 98 )? 99 [\\s]*(?<day>[0-9]{1,2}) 100 [\\s]+ 101 (?<month>Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) 102 [\\s]+ 103 (?<year>[0-9]{4}) 104 [\\s]+ 105 (?<hour>[0-9]{2}) 106 : 107 (?<minute>[0-9]{2}) 108 (?: 109 : 110 (?<seconds>[0-9]{2}) 111 )? 112 [\\s]+ 113 (?<tz> 114 [+-] 115 (?<tzhour>[0-9]{2}) 116 (?<tzminute>[0-9]{2}) 117 ) 118 $$", RegexCompileFlags.EXTENDED); 119 120 s_internal = new Regex(@"^ 121 @? 122 (?<timestamp>[0-9]+) 123 [ ](?<tz> 124 [+-](?<tzhour>[0-9]{2}) 125 (?: 126 :? 127 (?<tzminute>[0-9]{2})? 128 ) 129 ) 130 $$", RegexCompileFlags.EXTENDED); 131 132 } 133 catch (Error e) 134 { 135 warning(@"Failed to compile date regex: $(e.message)"); 136 } 137 } 138 139 private static bool fetch_and_set_int(MatchInfo info, string name, ref int retval) 140 { 141 string? val = info.fetch_named(name); 142 143 if (val == null) 144 { 145 return false; 146 } 147 148 retval = int.parse(val); 149 return true; 150 } 151 152 private static bool fetch_and_set_double(MatchInfo info, string name, ref double retval) 153 { 154 string? val = info.fetch_named(name); 155 156 if (val == null) 157 { 158 return false; 159 } 160 161 retval = double.parse(val); 162 return true; 163 } 164 165 private static DateTime parse_internal(MatchInfo info) throws Error 166 { 167 string? timestamp = info.fetch_named("timestamp"); 168 int64 unixt = int64.parse(timestamp); 169 170 string? tzs = info.fetch_named("tz"); 171 172 if (tzs != null) 173 { 174 var ret = new DateTime.from_unix_utc(unixt); 175 return ret.to_timezone(new TimeZone(tzs)); 176 } 177 else 178 { 179 return new DateTime.from_unix_local(unixt); 180 } 181 } 182 183 private static DateTime parse_iso8601(MatchInfo info) throws Error 184 { 185 TimeZone tz = new TimeZone.utc(); 186 187 int year = 0; 188 int month = 1; 189 int day = 1; 190 int hour = 0; 191 int minute = 0; 192 double seconds = 0.0; 193 194 fetch_and_set_int(info, "year", ref year); 195 fetch_and_set_int(info, "month", ref month); 196 fetch_and_set_int(info, "day", ref day); 197 fetch_and_set_int(info, "hour", ref hour); 198 fetch_and_set_int(info, "minute", ref minute); 199 fetch_and_set_double(info, "seconds", ref seconds); 200 201 string? tzs = info.fetch_named("tz"); 202 203 if (tzs != null) 204 { 205 tz = new TimeZone(tzs); 206 } 207 else 208 { 209 tz = new TimeZone.local(); 210 } 211 212 return new DateTime(tz, year, month, day, hour, minute, seconds); 213 } 214 215 private static DateTime parse_rfc2822(MatchInfo info) throws Error 216 { 217 TimeZone tz; 218 int year = 0; 219 int month = 0; 220 int day = 1; 221 int hour = 0; 222 int minute = 0; 223 double seconds = 0; 224 225 fetch_and_set_int(info, "year", ref year); 226 227 string? monthstr = info.fetch_named("month"); 228 229 for (int i = 0; i < s_months.length; ++i) 230 { 231 if (s_months[i] != null && s_months[i] == monthstr) 232 { 233 month = i; 234 break; 235 } 236 } 237 238 if (month == 0) 239 { 240 throw new DateError.INVALID_FORMAT("Invalid month specified"); 241 } 242 243 fetch_and_set_int(info, "day", ref day); 244 fetch_and_set_int(info, "hour", ref hour); 245 fetch_and_set_int(info, "minute", ref minute); 246 fetch_and_set_double(info, "seconds", ref seconds); 247 248 string? tzs = info.fetch_named("tz"); 249 250 if (tzs != null) 251 { 252 tz = new TimeZone(tzs); 253 } 254 else 255 { 256 tz = new TimeZone.local(); 257 } 258 259 return new DateTime(tz, year, month, day, hour, minute, seconds); 260 } 261 262 private DateTime d_datetime; 263 264 public string date_string 265 { 266 get; construct set; 267 } 268 269 public DateTime date 270 { 271 get { return d_datetime; } 272 } 273 274 public bool init(Cancellable? cancellable = null) throws Error 275 { 276 MatchInfo info; 277 278 if (s_internal.match(date_string, 0, out info)) 279 { 280 d_datetime = parse_internal(info); 281 282 return true; 283 } 284 285 if (s_iso8601.match(date_string, 0, out info)) 286 { 287 d_datetime = parse_iso8601(info); 288 289 return true; 290 } 291 292 if (s_rfc2822.match(date_string, 0, out info)) 293 { 294 d_datetime = parse_rfc2822(info); 295 296 return true; 297 } 298 299 throw new DateError.INVALID_FORMAT("Invalid date format"); 300 } 301 302 public Date(string date) throws Error 303 { 304 Object(date_string: date); 305 ((Initable)this).init(null); 306 } 307 308 private bool is_24h 309 { 310 get 311 { 312 if (s_gnome_interface_settings == null && !s_tried_gnome_interface_settings) 313 { 314 var source = SettingsSchemaSource.get_default(); 315 316 s_tried_gnome_interface_settings = true; 317 318 var schema_id = "org.gnome.desktop.interface"; 319 320 if (source != null && source.lookup(schema_id, true) != null) 321 { 322 s_gnome_interface_settings = new Settings(schema_id); 323 } 324 } 325 326 if (s_gnome_interface_settings == null) 327 { 328 return false; 329 } 330 331 return s_gnome_interface_settings.get_enum("clock-format") == GDesktop.ClockFormat.24H; 332 } 333 } 334 335 public string for_display() 336 { 337 var dt = d_datetime; 338 TimeSpan t = (new DateTime.now_local()).difference(dt); 339 340 if (t < TimeSpan.MINUTE * 29.5) 341 { 342 int rounded_minutes = (int) Math.round((float) t / TimeSpan.MINUTE); 343 344 if (rounded_minutes == 0) 345 { 346 return _("Now"); 347 } 348 else 349 { 350 return ngettext("A minute ago", "%d minutes ago", rounded_minutes).printf(rounded_minutes); 351 } 352 } 353 else if (t < TimeSpan.MINUTE * 45) 354 { 355 return _("Half an hour ago"); 356 } 357 else if (t < TimeSpan.HOUR * 23.5) 358 { 359 int rounded_hours = (int) Math.round((float) t / TimeSpan.HOUR); 360 return ngettext("An hour ago", "%d hours ago", rounded_hours).printf(rounded_hours); 361 } 362 else if (t < TimeSpan.DAY * 7) 363 { 364 int rounded_days = (int) Math.round((float) t / TimeSpan.DAY); 365 return ngettext("A day ago", "%d days ago", rounded_days).printf(rounded_days); 366 } 367 else if (dt.get_year() == new DateTime.now_local().get_year()) 368 { 369 if (is_24h) 370 { 371 /* Translators: this is a strftime type date format which is 372 used when the date is in the current year and uses a 24 hour 373 clock.*/ 374 return dt.format(_("%b %e, %H∶%M")); 375 } 376 else 377 { 378 /* Translators: this is a strftime type date format which is 379 used when the date is in the current year and uses a 12 hour 380 clock.*/ 381 return dt.format(_("%b %e, %I∶%M %p")); 382 } 383 } 384 else 385 { 386 if (is_24h) 387 { 388 /* Translators: this is a strftime type date format which is 389 used when the date is not in the current year and uses a 24 390 hour clock.*/ 391 return dt.format(_("%b %e %Y, %H∶%M")); 392 } 393 else 394 { 395 /* Translators: this is a strftime type date format which is 396 used when the date is not in the current year and uses a 12 397 hour clock.*/ 398 return dt.format(_("%b %e %Y, %I∶%M %p")); 399 } 400 } 401 } 402 403 public Date.for_date_time(DateTime dt) 404 { 405 d_datetime = dt; 406 } 407 408 public static DateTime parse(string date) throws Error 409 { 410 return (new Date(date)).date; 411 } 412} 413 414} 415 416// ex: ts=4 noet 417