git-date (empty) → 0.1
raw patch · 6 files changed
+1545/−0 lines, 6 filesdep +basedep +bytestringdep +timesetup-changed
Dependencies added: base, bytestring, time, utf8-string
Files
- COPYING +365/−0
- Data/Time/Git.hs +42/−0
- README +3/−0
- Setup.hs +3/−0
- ext/date.c +1092/−0
- git-date.cabal +40/−0
+ COPYING view
@@ -0,0 +1,365 @@+The date.c which forms the bulk of this library is ripped directly from+git, and so I have chosen to license the whole library the same as git,+with the contents of git's COPYING file reproduced below.++----------------------------------------++ Note that the only valid version of the GPL as far as this project+ is concerned is _this_ particular version of the license (ie v2, not+ v2.2 or v3.x or whatever), unless explicitly otherwise stated.++ HOWEVER, in order to allow a migration to GPLv3 if that seems like+ a good idea, I also ask that people involved with the project make+ their preferences known. In particular, if you trust me to make that+ decision, you might note so in your copyright message, ie something+ like++ This file is licensed under the GPL v2, or a later version+ at the discretion of Linus.++ might avoid issues. But we can also just decide to synchronize and+ contact all copyright holders on record if/when the occasion arises.++ Linus Torvalds++----------------------------------------++ GNU GENERAL PUBLIC LICENSE+ Version 2, June 1991++ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA+ Everyone is permitted to copy and distribute verbatim copies+ of this license document, but changing it is not allowed.++ Preamble++ The licenses for most software are designed to take away your+freedom to share and change it. By contrast, the GNU General Public+License is intended to guarantee your freedom to share and change free+software--to make sure the software is free for all its users. This+General Public License applies to most of the Free Software+Foundation's software and to any other program whose authors commit to+using it. (Some other Free Software Foundation software is covered by+the GNU Lesser General Public License instead.) You can apply it to+your programs, too.++ When we speak of free software, we are referring to freedom, not+price. Our General Public Licenses are designed to make sure that you+have the freedom to distribute copies of free software (and charge for+this service if you wish), that you receive source code or can get it+if you want it, that you can change the software or use pieces of it+in new free programs; and that you know you can do these things.++ To protect your rights, we need to make restrictions that forbid+anyone to deny you these rights or to ask you to surrender the rights.+These restrictions translate to certain responsibilities for you if you+distribute copies of the software, or if you modify it.++ For example, if you distribute copies of such a program, whether+gratis or for a fee, you must give the recipients all the rights that+you have. You must make sure that they, too, receive or can get the+source code. And you must show them these terms so they know their+rights.++ We protect your rights with two steps: (1) copyright the software, and+(2) offer you this license which gives you legal permission to copy,+distribute and/or modify the software.++ Also, for each author's protection and ours, we want to make certain+that everyone understands that there is no warranty for this free+software. If the software is modified by someone else and passed on, we+want its recipients to know that what they have is not the original, so+that any problems introduced by others will not reflect on the original+authors' reputations.++ Finally, any free program is threatened constantly by software+patents. We wish to avoid the danger that redistributors of a free+program will individually obtain patent licenses, in effect making the+program proprietary. To prevent this, we have made it clear that any+patent must be licensed for everyone's free use or not licensed at all.++ The precise terms and conditions for copying, distribution and+modification follow.++ GNU GENERAL PUBLIC LICENSE+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION++ 0. This License applies to any program or other work which contains+a notice placed by the copyright holder saying it may be distributed+under the terms of this General Public License. The "Program", below,+refers to any such program or work, and a "work based on the Program"+means either the Program or any derivative work under copyright law:+that is to say, a work containing the Program or a portion of it,+either verbatim or with modifications and/or translated into another+language. (Hereinafter, translation is included without limitation in+the term "modification".) Each licensee is addressed as "you".++Activities other than copying, distribution and modification are not+covered by this License; they are outside its scope. The act of+running the Program is not restricted, and the output from the Program+is covered only if its contents constitute a work based on the+Program (independent of having been made by running the Program).+Whether that is true depends on what the Program does.++ 1. You may copy and distribute verbatim copies of the Program's+source code as you receive it, in any medium, provided that you+conspicuously and appropriately publish on each copy an appropriate+copyright notice and disclaimer of warranty; keep intact all the+notices that refer to this License and to the absence of any warranty;+and give any other recipients of the Program a copy of this License+along with the Program.++You may charge a fee for the physical act of transferring a copy, and+you may at your option offer warranty protection in exchange for a fee.++ 2. You may modify your copy or copies of the Program or any portion+of it, thus forming a work based on the Program, and copy and+distribute such modifications or work under the terms of Section 1+above, provided that you also meet all of these conditions:++ a) You must cause the modified files to carry prominent notices+ stating that you changed the files and the date of any change.++ b) You must cause any work that you distribute or publish, that in+ whole or in part contains or is derived from the Program or any+ part thereof, to be licensed as a whole at no charge to all third+ parties under the terms of this License.++ c) If the modified program normally reads commands interactively+ when run, you must cause it, when started running for such+ interactive use in the most ordinary way, to print or display an+ announcement including an appropriate copyright notice and a+ notice that there is no warranty (or else, saying that you provide+ a warranty) and that users may redistribute the program under+ these conditions, and telling the user how to view a copy of this+ License. (Exception: if the Program itself is interactive but+ does not normally print such an announcement, your work based on+ the Program is not required to print an announcement.)++These requirements apply to the modified work as a whole. If+identifiable sections of that work are not derived from the Program,+and can be reasonably considered independent and separate works in+themselves, then this License, and its terms, do not apply to those+sections when you distribute them as separate works. But when you+distribute the same sections as part of a whole which is a work based+on the Program, the distribution of the whole must be on the terms of+this License, whose permissions for other licensees extend to the+entire whole, and thus to each and every part regardless of who wrote it.++Thus, it is not the intent of this section to claim rights or contest+your rights to work written entirely by you; rather, the intent is to+exercise the right to control the distribution of derivative or+collective works based on the Program.++In addition, mere aggregation of another work not based on the Program+with the Program (or with a work based on the Program) on a volume of+a storage or distribution medium does not bring the other work under+the scope of this License.++ 3. You may copy and distribute the Program (or a work based on it,+under Section 2) in object code or executable form under the terms of+Sections 1 and 2 above provided that you also do one of the following:++ a) Accompany it with the complete corresponding machine-readable+ source code, which must be distributed under the terms of Sections+ 1 and 2 above on a medium customarily used for software interchange; or,++ b) Accompany it with a written offer, valid for at least three+ years, to give any third party, for a charge no more than your+ cost of physically performing source distribution, a complete+ machine-readable copy of the corresponding source code, to be+ distributed under the terms of Sections 1 and 2 above on a medium+ customarily used for software interchange; or,++ c) Accompany it with the information you received as to the offer+ to distribute corresponding source code. (This alternative is+ allowed only for noncommercial distribution and only if you+ received the program in object code or executable form with such+ an offer, in accord with Subsection b above.)++The source code for a work means the preferred form of the work for+making modifications to it. For an executable work, complete source+code means all the source code for all modules it contains, plus any+associated interface definition files, plus the scripts used to+control compilation and installation of the executable. However, as a+special exception, the source code distributed need not include+anything that is normally distributed (in either source or binary+form) with the major components (compiler, kernel, and so on) of the+operating system on which the executable runs, unless that component+itself accompanies the executable.++If distribution of executable or object code is made by offering+access to copy from a designated place, then offering equivalent+access to copy the source code from the same place counts as+distribution of the source code, even though third parties are not+compelled to copy the source along with the object code.++ 4. You may not copy, modify, sublicense, or distribute the Program+except as expressly provided under this License. Any attempt+otherwise to copy, modify, sublicense or distribute the Program is+void, and will automatically terminate your rights under this License.+However, parties who have received copies, or rights, from you under+this License will not have their licenses terminated so long as such+parties remain in full compliance.++ 5. You are not required to accept this License, since you have not+signed it. However, nothing else grants you permission to modify or+distribute the Program or its derivative works. These actions are+prohibited by law if you do not accept this License. Therefore, by+modifying or distributing the Program (or any work based on the+Program), you indicate your acceptance of this License to do so, and+all its terms and conditions for copying, distributing or modifying+the Program or works based on it.++ 6. Each time you redistribute the Program (or any work based on the+Program), the recipient automatically receives a license from the+original licensor to copy, distribute or modify the Program subject to+these terms and conditions. You may not impose any further+restrictions on the recipients' exercise of the rights granted herein.+You are not responsible for enforcing compliance by third parties to+this License.++ 7. If, as a consequence of a court judgment or allegation of patent+infringement or for any other reason (not limited to patent issues),+conditions are imposed on you (whether by court order, agreement or+otherwise) that contradict the conditions of this License, they do not+excuse you from the conditions of this License. If you cannot+distribute so as to satisfy simultaneously your obligations under this+License and any other pertinent obligations, then as a consequence you+may not distribute the Program at all. For example, if a patent+license would not permit royalty-free redistribution of the Program by+all those who receive copies directly or indirectly through you, then+the only way you could satisfy both it and this License would be to+refrain entirely from distribution of the Program.++If any portion of this section is held invalid or unenforceable under+any particular circumstance, the balance of the section is intended to+apply and the section as a whole is intended to apply in other+circumstances.++It is not the purpose of this section to induce you to infringe any+patents or other property right claims or to contest validity of any+such claims; this section has the sole purpose of protecting the+integrity of the free software distribution system, which is+implemented by public license practices. Many people have made+generous contributions to the wide range of software distributed+through that system in reliance on consistent application of that+system; it is up to the author/donor to decide if he or she is willing+to distribute software through any other system and a licensee cannot+impose that choice.++This section is intended to make thoroughly clear what is believed to+be a consequence of the rest of this License.++ 8. If the distribution and/or use of the Program is restricted in+certain countries either by patents or by copyrighted interfaces, the+original copyright holder who places the Program under this License+may add an explicit geographical distribution limitation excluding+those countries, so that distribution is permitted only in or among+countries not thus excluded. In such case, this License incorporates+the limitation as if written in the body of this License.++ 9. The Free Software Foundation may publish revised and/or new versions+of the General Public License from time to time. Such new versions will+be similar in spirit to the present version, but may differ in detail to+address new problems or concerns.++Each version is given a distinguishing version number. If the Program+specifies a version number of this License which applies to it and "any+later version", you have the option of following the terms and conditions+either of that version or of any later version published by the Free+Software Foundation. If the Program does not specify a version number of+this License, you may choose any version ever published by the Free Software+Foundation.++ 10. If you wish to incorporate parts of the Program into other free+programs whose distribution conditions are different, write to the author+to ask for permission. For software which is copyrighted by the Free+Software Foundation, write to the Free Software Foundation; we sometimes+make exceptions for this. Our decision will be guided by the two goals+of preserving the free status of all derivatives of our free software and+of promoting the sharing and reuse of software generally.++ NO WARRANTY++ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,+REPAIR OR CORRECTION.++ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE+POSSIBILITY OF SUCH DAMAGES.++ END OF TERMS AND CONDITIONS++ How to Apply These Terms to Your New Programs++ If you develop a new program, and you want it to be of the greatest+possible use to the public, the best way to achieve this is to make it+free software which everyone can redistribute and change under these terms.++ To do so, attach the following notices to the program. It is safest+to attach them to the start of each source file to most effectively+convey the exclusion of warranty; and each file should have at least+the "copyright" line and a pointer to where the full notice is found.++ <one line to give the program's name and a brief idea of what it does.>+ Copyright (C) <year> <name of author>++ This program is free software; you can redistribute it and/or modify+ it under the terms of the GNU General Public License as published by+ the Free Software Foundation; either version 2 of the License, or+ (at your option) any later version.++ This program is distributed in the hope that it will be useful,+ but WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+ GNU General Public License for more details.++ You should have received a copy of the GNU General Public License along+ with this program; if not, write to the Free Software Foundation, Inc.,+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.++Also add information on how to contact you by electronic and paper mail.++If the program is interactive, make it output a short notice like this+when it starts in an interactive mode:++ Gnomovision version 69, Copyright (C) year name of author+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.+ This is free software, and you are welcome to redistribute it+ under certain conditions; type `show c' for details.++The hypothetical commands `show w' and `show c' should show the appropriate+parts of the General Public License. Of course, the commands you use may+be called something other than `show w' and `show c'; they could even be+mouse-clicks or menu items--whatever suits your program.++You should also get your employer (if you work as a programmer) or your+school, if any, to sign a "copyright disclaimer" for the program, if+necessary. Here is a sample; alter the names:++ Yoyodyne, Inc., hereby disclaims all copyright interest in the program+ `Gnomovision' (which makes passes at compilers) written by James Hacker.++ <signature of Ty Coon>, 1 April 1989+ Ty Coon, President of Vice++This General Public License does not permit incorporating your program into+proprietary programs. If your program is a subroutine library, you may+consider it more useful to permit linking proprietary applications with the+library. If this is what you want to do, use the GNU Lesser General+Public License instead of this License.
+ Data/Time/Git.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE ForeignFunctionInterface #-}++module Data.Time.Git (approxidate, approxidateUTC) where++import Foreign+import Foreign.C.Types+import Foreign.C.String++import Data.Time+import Data.Time.Clock.POSIX++import qualified Data.ByteString as BS+import qualified Data.ByteString.UTF8 as BS (fromString)++-- | Parse a date/time string and return Just a Unix timestamp.+-- Return Nothing if the string could not be interpreted.+-- If no timezone is in the string, the local timezone is used.+approxidate :: String -> Maybe Integer+approxidate = unsafePerformIO . io_approxidate_careful++-- | The same as approxidate, but returns a UTCTime instead of a raw+-- timestamp.+approxidateUTC :: String -> Maybe UTCTime+approxidateUTC input =+ fmap (posixSecondsToUTCTime . realToFrac) (approxidate input)++foreign import ccall unsafe "date.c approxidate_careful"+ c_approxidate_careful ::+ CString -> Ptr CInt ->+ IO CULong++io_approxidate_careful :: String -> IO (Maybe Integer)+io_approxidate_careful input =+ BS.useAsCString (BS.fromString input) (\c_input ->+ alloca (\c_error_ret -> do+ poke c_error_ret 0+ time <- c_approxidate_careful c_input c_error_ret+ err <- peek c_error_ret+ if err == 0 then return $ Just (toInteger time) else+ return Nothing+ )+ )
+ README view
@@ -0,0 +1,3 @@+This library implements a simple, pure binding onto the date parsing+code from git. Why? Because it was the only reasonable implementation+of a generic date parser I could find.
+ Setup.hs view
@@ -0,0 +1,3 @@+import Distribution.Simple++main = defaultMain
+ ext/date.c view
@@ -0,0 +1,1092 @@+/*+ * GIT - The information manager from hell+ *+ * Copyright (C) Linus Torvalds, 2005+ */++/* Add includes and some stuff from git to make this code independent+ * -- Stephen Paul Weber */++#include <time.h>+#include <sys/time.h>+#include <stdio.h>+#include <string.h>+#include <limits.h>+#include <ctype.h>+#include <stdlib.h>++#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))++enum date_mode {+ DATE_NORMAL = 0,+ DATE_RELATIVE,+ DATE_SHORT,+ DATE_LOCAL,+ DATE_ISO8601,+ DATE_RFC2822,+ DATE_RAW,+ DATE_UNKNOWN+};++/*+ * This is like mktime, but without normalization of tm_wday and tm_yday.+ */+static time_t tm_to_time_t(const struct tm *tm)+{+ static const int mdays[] = {+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334+ };+ int year = tm->tm_year - 70;+ int month = tm->tm_mon;+ int day = tm->tm_mday;++ if (year < 0 || year > 129) /* algo only works for 1970-2099 */+ return -1;+ if (month < 0 || month > 11) /* array bounds */+ return -1;+ if (month < 2 || (year + 2) % 4)+ day--;+ if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0)+ return -1;+ return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL ++ tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec;+}++static const char *month_names[] = {+ "January", "February", "March", "April", "May", "June",+ "July", "August", "September", "October", "November", "December"+};++static const char *weekday_names[] = {+ "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays"+};++static time_t gm_time_t(unsigned long time, int tz)+{+ int minutes;++ minutes = tz < 0 ? -tz : tz;+ minutes = (minutes / 100)*60 + (minutes % 100);+ minutes = tz < 0 ? -minutes : minutes;+ return time + minutes * 60;+}++/*+ * The "tz" thing is passed in as this strange "decimal parse of tz"+ * thing, which means that tz -0100 is passed in as the integer -100,+ * even though it means "sixty minutes off"+ */+static struct tm *time_to_tm(unsigned long time, int tz)+{+ time_t t = gm_time_t(time, tz);+ return gmtime(&t);+}++/*+ * What value of "tz" was in effect back then at "time" in the+ * local timezone?+ */+static int local_tzoffset(unsigned long time)+{+ time_t t, t_local;+ struct tm tm;+ int offset, eastwest;++ t = time;+ localtime_r(&t, &tm);+ t_local = tm_to_time_t(&tm);++ if (t_local < t) {+ eastwest = -1;+ offset = t - t_local;+ } else {+ eastwest = 1;+ offset = t_local - t;+ }+ offset /= 60; /* in minutes */+ offset = (offset % 60) + ((offset / 60) * 100);+ return offset * eastwest;+}++const char *show_date_relative(unsigned long time, int tz,+ const struct timeval *now,+ char *timebuf,+ size_t timebuf_size)+{+ unsigned long diff;+ if (now->tv_sec < time)+ return "in the future";+ diff = now->tv_sec - time;+ if (diff < 90) {+ snprintf(timebuf, timebuf_size, "%lu seconds ago", diff);+ return timebuf;+ }+ /* Turn it into minutes */+ diff = (diff + 30) / 60;+ if (diff < 90) {+ snprintf(timebuf, timebuf_size, "%lu minutes ago", diff);+ return timebuf;+ }+ /* Turn it into hours */+ diff = (diff + 30) / 60;+ if (diff < 36) {+ snprintf(timebuf, timebuf_size, "%lu hours ago", diff);+ return timebuf;+ }+ /* We deal with number of days from here on */+ diff = (diff + 12) / 24;+ if (diff < 14) {+ snprintf(timebuf, timebuf_size, "%lu days ago", diff);+ return timebuf;+ }+ /* Say weeks for the past 10 weeks or so */+ if (diff < 70) {+ snprintf(timebuf, timebuf_size, "%lu weeks ago", (diff + 3) / 7);+ return timebuf;+ }+ /* Say months for the past 12 months or so */+ if (diff < 365) {+ snprintf(timebuf, timebuf_size, "%lu months ago", (diff + 15) / 30);+ return timebuf;+ }+ /* Give years and months for 5 years or so */+ if (diff < 1825) {+ unsigned long totalmonths = (diff * 12 * 2 + 365) / (365 * 2);+ unsigned long years = totalmonths / 12;+ unsigned long months = totalmonths % 12;+ int n;+ n = snprintf(timebuf, timebuf_size, "%lu year%s",+ years, (years > 1 ? "s" : ""));+ if (months)+ snprintf(timebuf + n, timebuf_size - n,+ ", %lu month%s ago",+ months, (months > 1 ? "s" : ""));+ else+ snprintf(timebuf + n, timebuf_size - n, " ago");+ return timebuf;+ }+ /* Otherwise, just years. Centuries is probably overkill. */+ snprintf(timebuf, timebuf_size, "%lu years ago", (diff + 183) / 365);+ return timebuf;+}++const char *show_date(unsigned long time, int tz, enum date_mode mode)+{+ struct tm *tm;+ static char timebuf[200];++ if (mode == DATE_RAW) {+ snprintf(timebuf, sizeof(timebuf), "%lu %+05d", time, tz);+ return timebuf;+ }++ if (mode == DATE_RELATIVE) {+ struct timeval now;+ gettimeofday(&now, NULL);+ return show_date_relative(time, tz, &now,+ timebuf, sizeof(timebuf));+ }++ if (mode == DATE_LOCAL)+ tz = local_tzoffset(time);++ tm = time_to_tm(time, tz);+ if (!tm)+ return NULL;+ if (mode == DATE_SHORT)+ sprintf(timebuf, "%04d-%02d-%02d", tm->tm_year + 1900,+ tm->tm_mon + 1, tm->tm_mday);+ else if (mode == DATE_ISO8601)+ sprintf(timebuf, "%04d-%02d-%02d %02d:%02d:%02d %+05d",+ tm->tm_year + 1900,+ tm->tm_mon + 1,+ tm->tm_mday,+ tm->tm_hour, tm->tm_min, tm->tm_sec,+ tz);+ else if (mode == DATE_RFC2822)+ sprintf(timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",+ weekday_names[tm->tm_wday], tm->tm_mday,+ month_names[tm->tm_mon], tm->tm_year + 1900,+ tm->tm_hour, tm->tm_min, tm->tm_sec, tz);+ else+ sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",+ weekday_names[tm->tm_wday],+ month_names[tm->tm_mon],+ tm->tm_mday,+ tm->tm_hour, tm->tm_min, tm->tm_sec,+ tm->tm_year + 1900,+ (mode == DATE_LOCAL) ? 0 : ' ',+ tz);+ return timebuf;+}++/*+ * Check these. And note how it doesn't do the summer-time conversion.+ *+ * In my world, it's always summer, and things are probably a bit off+ * in other ways too.+ */+static const struct {+ const char *name;+ int offset;+ int dst;+} timezone_names[] = {+ { "IDLW", -12, 0, }, /* International Date Line West */+ { "NT", -11, 0, }, /* Nome */+ { "CAT", -10, 0, }, /* Central Alaska */+ { "HST", -10, 0, }, /* Hawaii Standard */+ { "HDT", -10, 1, }, /* Hawaii Daylight */+ { "YST", -9, 0, }, /* Yukon Standard */+ { "YDT", -9, 1, }, /* Yukon Daylight */+ { "PST", -8, 0, }, /* Pacific Standard */+ { "PDT", -8, 1, }, /* Pacific Daylight */+ { "MST", -7, 0, }, /* Mountain Standard */+ { "MDT", -7, 1, }, /* Mountain Daylight */+ { "CST", -6, 0, }, /* Central Standard */+ { "CDT", -6, 1, }, /* Central Daylight */+ { "EST", -5, 0, }, /* Eastern Standard */+ { "EDT", -5, 1, }, /* Eastern Daylight */+ { "AST", -3, 0, }, /* Atlantic Standard */+ { "ADT", -3, 1, }, /* Atlantic Daylight */+ { "WAT", -1, 0, }, /* West Africa */++ { "GMT", 0, 0, }, /* Greenwich Mean */+ { "UTC", 0, 0, }, /* Universal (Coordinated) */+ { "Z", 0, 0, }, /* Zulu, alias for UTC */++ { "WET", 0, 0, }, /* Western European */+ { "BST", 0, 1, }, /* British Summer */+ { "CET", +1, 0, }, /* Central European */+ { "MET", +1, 0, }, /* Middle European */+ { "MEWT", +1, 0, }, /* Middle European Winter */+ { "MEST", +1, 1, }, /* Middle European Summer */+ { "CEST", +1, 1, }, /* Central European Summer */+ { "MESZ", +1, 1, }, /* Middle European Summer */+ { "FWT", +1, 0, }, /* French Winter */+ { "FST", +1, 1, }, /* French Summer */+ { "EET", +2, 0, }, /* Eastern Europe, USSR Zone 1 */+ { "EEST", +2, 1, }, /* Eastern European Daylight */+ { "WAST", +7, 0, }, /* West Australian Standard */+ { "WADT", +7, 1, }, /* West Australian Daylight */+ { "CCT", +8, 0, }, /* China Coast, USSR Zone 7 */+ { "JST", +9, 0, }, /* Japan Standard, USSR Zone 8 */+ { "EAST", +10, 0, }, /* Eastern Australian Standard */+ { "EADT", +10, 1, }, /* Eastern Australian Daylight */+ { "GST", +10, 0, }, /* Guam Standard, USSR Zone 9 */+ { "NZT", +12, 0, }, /* New Zealand */+ { "NZST", +12, 0, }, /* New Zealand Standard */+ { "NZDT", +12, 1, }, /* New Zealand Daylight */+ { "IDLE", +12, 0, }, /* International Date Line East */+};++static int match_string(const char *date, const char *str)+{+ int i = 0;++ for (i = 0; *date; date++, str++, i++) {+ if (*date == *str)+ continue;+ if (toupper(*date) == toupper(*str))+ continue;+ if (!isalnum(*date))+ break;+ return 0;+ }+ return i;+}++static int skip_alpha(const char *date)+{+ int i = 0;+ do {+ i++;+ } while (isalpha(date[i]));+ return i;+}++/*+* Parse month, weekday, or timezone name+*/+static int match_alpha(const char *date, struct tm *tm, int *offset)+{+ int i;++ for (i = 0; i < 12; i++) {+ int match = match_string(date, month_names[i]);+ if (match >= 3) {+ tm->tm_mon = i;+ return match;+ }+ }++ for (i = 0; i < 7; i++) {+ int match = match_string(date, weekday_names[i]);+ if (match >= 3) {+ tm->tm_wday = i;+ return match;+ }+ }++ for (i = 0; i < ARRAY_SIZE(timezone_names); i++) {+ int match = match_string(date, timezone_names[i].name);+ if (match >= 3 || match == strlen(timezone_names[i].name)) {+ int off = timezone_names[i].offset;++ /* This is bogus, but we like summer */+ off += timezone_names[i].dst;++ /* Only use the tz name offset if we don't have anything better */+ if (*offset == -1)+ *offset = 60*off;++ return match;+ }+ }++ if (match_string(date, "PM") == 2) {+ tm->tm_hour = (tm->tm_hour % 12) + 12;+ return 2;+ }++ if (match_string(date, "AM") == 2) {+ tm->tm_hour = (tm->tm_hour % 12) + 0;+ return 2;+ }++ /* BAD CRAP */+ return skip_alpha(date);+}++static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm)+{+ if (month > 0 && month < 13 && day > 0 && day < 32) {+ struct tm check = *tm;+ struct tm *r = (now_tm ? &check : tm);+ time_t specified;++ r->tm_mon = month - 1;+ r->tm_mday = day;+ if (year == -1) {+ if (!now_tm)+ return 1;+ r->tm_year = now_tm->tm_year;+ }+ else if (year >= 1970 && year < 2100)+ r->tm_year = year - 1900;+ else if (year > 70 && year < 100)+ r->tm_year = year;+ else if (year < 38)+ r->tm_year = year + 100;+ else+ return 0;+ if (!now_tm)+ return 1;++ specified = tm_to_time_t(r);++ /* Be it commit time or author time, it does not make+ * sense to specify timestamp way into the future. Make+ * sure it is not later than ten days from now...+ */+ if (now + 10*24*3600 < specified)+ return 0;+ tm->tm_mon = r->tm_mon;+ tm->tm_mday = r->tm_mday;+ if (year != -1)+ tm->tm_year = r->tm_year;+ return 1;+ }+ return 0;+}++static int match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm)+{+ time_t now;+ struct tm now_tm;+ struct tm *refuse_future;+ long num2, num3;++ num2 = strtol(end+1, &end, 10);+ num3 = -1;+ if (*end == c && isdigit(end[1]))+ num3 = strtol(end+1, &end, 10);++ /* Time? Date? */+ switch (c) {+ case ':':+ if (num3 < 0)+ num3 = 0;+ if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) {+ tm->tm_hour = num;+ tm->tm_min = num2;+ tm->tm_sec = num3;+ break;+ }+ return 0;++ case '-':+ case '/':+ case '.':+ now = time(NULL);+ refuse_future = NULL;+ if (gmtime_r(&now, &now_tm))+ refuse_future = &now_tm;++ if (num > 70) {+ /* yyyy-mm-dd? */+ if (is_date(num, num2, num3, refuse_future, now, tm))+ break;+ /* yyyy-dd-mm? */+ if (is_date(num, num3, num2, refuse_future, now, tm))+ break;+ }+ /* Our eastern European friends say dd.mm.yy[yy]+ * is the norm there, so giving precedence to+ * mm/dd/yy[yy] form only when separator is not '.'+ */+ if (c != '.' &&+ is_date(num3, num, num2, refuse_future, now, tm))+ break;+ /* European dd.mm.yy[yy] or funny US dd/mm/yy[yy] */+ if (is_date(num3, num2, num, refuse_future, now, tm))+ break;+ /* Funny European mm.dd.yy */+ if (c == '.' &&+ is_date(num3, num, num2, refuse_future, now, tm))+ break;+ return 0;+ }+ return end - date;+}++/*+ * Have we filled in any part of the time/date yet?+ * We just do a binary 'and' to see if the sign bit+ * is set in all the values.+ */+static inline int nodate(struct tm *tm)+{+ return (tm->tm_year &+ tm->tm_mon &+ tm->tm_mday &+ tm->tm_hour &+ tm->tm_min &+ tm->tm_sec) < 0;+}++/*+ * We've seen a digit. Time? Year? Date?+ */+static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)+{+ int n;+ char *end;+ unsigned long num;++ num = strtoul(date, &end, 10);++ /*+ * Seconds since 1970? We trigger on that for any numbers with+ * more than 8 digits. This is because we don't want to rule out+ * numbers like 20070606 as a YYYYMMDD date.+ */+ if (num >= 100000000 && nodate(tm)) {+ time_t time = num;+ if (gmtime_r(&time, tm)) {+ *tm_gmt = 1;+ return end - date;+ }+ }++ /*+ * Check for special formats: num[-.:/]num[same]num+ */+ switch (*end) {+ case ':':+ case '.':+ case '/':+ case '-':+ if (isdigit(end[1])) {+ int match = match_multi_number(num, *end, date, end, tm);+ if (match)+ return match;+ }+ }++ /*+ * None of the special formats? Try to guess what+ * the number meant. We use the number of digits+ * to make a more educated guess..+ */+ n = 0;+ do {+ n++;+ } while (isdigit(date[n]));++ /* Four-digit year or a timezone? */+ if (n == 4) {+ if (num <= 1400 && *offset == -1) {+ unsigned int minutes = num % 100;+ unsigned int hours = num / 100;+ *offset = hours*60 + minutes;+ } else if (num > 1900 && num < 2100)+ tm->tm_year = num - 1900;+ return n;+ }++ /*+ * Ignore lots of numerals. We took care of 4-digit years above.+ * Days or months must be one or two digits.+ */+ if (n > 2)+ return n;++ /*+ * NOTE! We will give precedence to day-of-month over month or+ * year numbers in the 1-12 range. So 05 is always "mday 5",+ * unless we already have a mday..+ *+ * IOW, 01 Apr 05 parses as "April 1st, 2005".+ */+ if (num > 0 && num < 32 && tm->tm_mday < 0) {+ tm->tm_mday = num;+ return n;+ }++ /* Two-digit year? */+ if (n == 2 && tm->tm_year < 0) {+ if (num < 10 && tm->tm_mday >= 0) {+ tm->tm_year = num + 100;+ return n;+ }+ if (num >= 70) {+ tm->tm_year = num;+ return n;+ }+ }++ if (num > 0 && num < 13 && tm->tm_mon < 0)+ tm->tm_mon = num-1;++ return n;+}++static int match_tz(const char *date, int *offp)+{+ char *end;+ int hour = strtoul(date + 1, &end, 10);+ int n = end - (date + 1);+ int min = 0;++ if (n == 4) {+ /* hhmm */+ min = hour % 100;+ hour = hour / 100;+ } else if (n != 2) {+ min = 99; /* random crap */+ } else if (*end == ':') {+ /* hh:mm? */+ min = strtoul(end + 1, &end, 10);+ if (end - (date + 1) != 5)+ min = 99; /* random crap */+ } /* otherwise we parsed "hh" */++ /*+ * Don't accept any random crap. Even though some places have+ * offset larger than 12 hours (e.g. Pacific/Kiritimati is at+ * UTC+14), there is something wrong if hour part is much+ * larger than that. We might also want to check that the+ * minutes are divisible by 15 or something too. (Offset of+ * Kathmandu, Nepal is UTC+5:45)+ */+ if (min < 60 && hour < 24) {+ int offset = hour * 60 + min;+ if (*date == '-')+ offset = -offset;+ *offp = offset;+ }+ return end - date;+}++static int date_string(unsigned long date, int offset, char *buf, int len)+{+ int sign = '+';++ if (offset < 0) {+ offset = -offset;+ sign = '-';+ }+ return snprintf(buf, len, "%lu %c%02d%02d", date, sign, offset / 60, offset % 60);+}++/*+ * Parse a string like "0 +0000" as ancient timestamp near epoch, but+ * only when it appears not as part of any other string.+ */+static int match_object_header_date(const char *date, unsigned long *timestamp, int *offset)+{+ char *end;+ unsigned long stamp;+ int ofs;++ if (*date < '0' || '9' <= *date)+ return -1;+ stamp = strtoul(date, &end, 10);+ if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-'))+ return -1;+ date = end + 2;+ ofs = strtol(date, &end, 10);+ if ((*end != '\0' && (*end != '\n')) || end != date + 4)+ return -1;+ ofs = (ofs / 100) * 60 + (ofs % 100);+ if (date[-1] == '-')+ ofs = -ofs;+ *timestamp = stamp;+ *offset = ofs;+ return 0;+}++/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822+ (i.e. English) day/month names, and it doesn't work correctly with %z. */+int parse_date_basic(const char *date, unsigned long *timestamp, int *offset)+{+ struct tm tm;+ int tm_gmt;+ unsigned long dummy_timestamp;+ int dummy_offset;++ if (!timestamp)+ timestamp = &dummy_timestamp;+ if (!offset)+ offset = &dummy_offset;++ memset(&tm, 0, sizeof(tm));+ tm.tm_year = -1;+ tm.tm_mon = -1;+ tm.tm_mday = -1;+ tm.tm_isdst = -1;+ tm.tm_hour = -1;+ tm.tm_min = -1;+ tm.tm_sec = -1;+ *offset = -1;+ tm_gmt = 0;++ if (*date == '@' &&+ !match_object_header_date(date + 1, timestamp, offset))+ return 0; /* success */+ for (;;) {+ int match = 0;+ unsigned char c = *date;++ /* Stop at end of string or newline */+ if (!c || c == '\n')+ break;++ if (isalpha(c))+ match = match_alpha(date, &tm, offset);+ else if (isdigit(c))+ match = match_digit(date, &tm, offset, &tm_gmt);+ else if ((c == '-' || c == '+') && isdigit(date[1]))+ match = match_tz(date, offset);++ if (!match) {+ /* BAD CRAP */+ match = 1;+ }++ date += match;+ }++ /* mktime uses local timezone */+ *timestamp = tm_to_time_t(&tm);+ if (*offset == -1)+ *offset = ((time_t)*timestamp - mktime(&tm)) / 60;++ if (*timestamp == -1)+ return -1;++ if (!tm_gmt)+ *timestamp -= *offset * 60;+ return 0; /* success */+}++int parse_date(const char *date, char *result, int maxlen)+{+ unsigned long timestamp;+ int offset;+ if (parse_date_basic(date, ×tamp, &offset))+ return -1;+ return date_string(timestamp, offset, result, maxlen);+}++enum date_mode parse_date_format(const char *format)+{+ if (!strcmp(format, "relative"))+ return DATE_RELATIVE;+ else if (!strcmp(format, "iso8601") ||+ !strcmp(format, "iso"))+ return DATE_ISO8601;+ else if (!strcmp(format, "rfc2822") ||+ !strcmp(format, "rfc"))+ return DATE_RFC2822;+ else if (!strcmp(format, "short"))+ return DATE_SHORT;+ else if (!strcmp(format, "local"))+ return DATE_LOCAL;+ else if (!strcmp(format, "default"))+ return DATE_NORMAL;+ else if (!strcmp(format, "raw"))+ return DATE_RAW;+ else+ return DATE_UNKNOWN;+}++void datestamp(char *buf, int bufsize)+{+ time_t now;+ int offset;++ time(&now);++ offset = tm_to_time_t(localtime(&now)) - now;+ offset /= 60;++ date_string(now, offset, buf, bufsize);+}++/*+ * Relative time update (eg "2 days ago"). If we haven't set the time+ * yet, we need to set it from current time.+ */+static unsigned long update_tm(struct tm *tm, struct tm *now, unsigned long sec)+{+ time_t n;++ if (tm->tm_mday < 0)+ tm->tm_mday = now->tm_mday;+ if (tm->tm_mon < 0)+ tm->tm_mon = now->tm_mon;+ if (tm->tm_year < 0) {+ tm->tm_year = now->tm_year;+ if (tm->tm_mon > now->tm_mon)+ tm->tm_year--;+ }++ n = mktime(tm) - sec;+ localtime_r(&n, tm);+ return n;+}++static void date_now(struct tm *tm, struct tm *now, int *num)+{+ update_tm(tm, now, 0);+}++static void date_yesterday(struct tm *tm, struct tm *now, int *num)+{+ update_tm(tm, now, 24*60*60);+}++static void date_time(struct tm *tm, struct tm *now, int hour)+{+ if (tm->tm_hour < hour)+ date_yesterday(tm, now, NULL);+ tm->tm_hour = hour;+ tm->tm_min = 0;+ tm->tm_sec = 0;+}++static void date_midnight(struct tm *tm, struct tm *now, int *num)+{+ date_time(tm, now, 0);+}++static void date_noon(struct tm *tm, struct tm *now, int *num)+{+ date_time(tm, now, 12);+}++static void date_tea(struct tm *tm, struct tm *now, int *num)+{+ date_time(tm, now, 17);+}++static void date_pm(struct tm *tm, struct tm *now, int *num)+{+ int hour, n = *num;+ *num = 0;++ hour = tm->tm_hour;+ if (n) {+ hour = n;+ tm->tm_min = 0;+ tm->tm_sec = 0;+ }+ tm->tm_hour = (hour % 12) + 12;+}++static void date_am(struct tm *tm, struct tm *now, int *num)+{+ int hour, n = *num;+ *num = 0;++ hour = tm->tm_hour;+ if (n) {+ hour = n;+ tm->tm_min = 0;+ tm->tm_sec = 0;+ }+ tm->tm_hour = (hour % 12);+}++static void date_never(struct tm *tm, struct tm *now, int *num)+{+ time_t n = 0;+ localtime_r(&n, tm);+}++static const struct special {+ const char *name;+ void (*fn)(struct tm *, struct tm *, int *);+} special[] = {+ { "yesterday", date_yesterday },+ { "noon", date_noon },+ { "midnight", date_midnight },+ { "tea", date_tea },+ { "PM", date_pm },+ { "AM", date_am },+ { "never", date_never },+ { "now", date_now },+ { NULL }+};++static const char *number_name[] = {+ "zero", "one", "two", "three", "four",+ "five", "six", "seven", "eight", "nine", "ten",+};++static const struct typelen {+ const char *type;+ int length;+} typelen[] = {+ { "seconds", 1 },+ { "minutes", 60 },+ { "hours", 60*60 },+ { "days", 24*60*60 },+ { "weeks", 7*24*60*60 },+ { NULL }+};++static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num, int *touched)+{+ const struct typelen *tl;+ const struct special *s;+ const char *end = date;+ int i;++ while (isalpha(*++end));+ ;++ for (i = 0; i < 12; i++) {+ int match = match_string(date, month_names[i]);+ if (match >= 3) {+ tm->tm_mon = i;+ *touched = 1;+ return end;+ }+ }++ for (s = special; s->name; s++) {+ int len = strlen(s->name);+ if (match_string(date, s->name) == len) {+ s->fn(tm, now, num);+ *touched = 1;+ return end;+ }+ }++ if (!*num) {+ for (i = 1; i < 11; i++) {+ int len = strlen(number_name[i]);+ if (match_string(date, number_name[i]) == len) {+ *num = i;+ *touched = 1;+ return end;+ }+ }+ if (match_string(date, "last") == 4) {+ *num = 1;+ *touched = 1;+ }+ return end;+ }++ tl = typelen;+ while (tl->type) {+ int len = strlen(tl->type);+ if (match_string(date, tl->type) >= len-1) {+ update_tm(tm, now, tl->length * *num);+ *num = 0;+ *touched = 1;+ return end;+ }+ tl++;+ }++ for (i = 0; i < 7; i++) {+ int match = match_string(date, weekday_names[i]);+ if (match >= 3) {+ int diff, n = *num -1;+ *num = 0;++ diff = tm->tm_wday - i;+ if (diff <= 0)+ n++;+ diff += 7*n;++ update_tm(tm, now, diff * 24 * 60 * 60);+ *touched = 1;+ return end;+ }+ }++ if (match_string(date, "months") >= 5) {+ int n;+ update_tm(tm, now, 0); /* fill in date fields if needed */+ n = tm->tm_mon - *num;+ *num = 0;+ while (n < 0) {+ n += 12;+ tm->tm_year--;+ }+ tm->tm_mon = n;+ *touched = 1;+ return end;+ }++ if (match_string(date, "years") >= 4) {+ update_tm(tm, now, 0); /* fill in date fields if needed */+ tm->tm_year -= *num;+ *num = 0;+ *touched = 1;+ return end;+ }++ return end;+}++static const char *approxidate_digit(const char *date, struct tm *tm, int *num)+{+ char *end;+ unsigned long number = strtoul(date, &end, 10);++ switch (*end) {+ case ':':+ case '.':+ case '/':+ case '-':+ if (isdigit(end[1])) {+ int match = match_multi_number(number, *end, date, end, tm);+ if (match)+ return date + match;+ }+ }++ /* Accept zero-padding only for small numbers ("Dec 02", never "Dec 0002") */+ if (date[0] != '0' || end - date <= 2)+ *num = number;+ return end;+}++/*+ * Do we have a pending number at the end, or when+ * we see a new one? Let's assume it's a month day,+ * as in "Dec 6, 1992"+ */+static void pending_number(struct tm *tm, int *num)+{+ int number = *num;++ if (number) {+ *num = 0;+ if (tm->tm_mday < 0 && number < 32)+ tm->tm_mday = number;+ else if (tm->tm_mon < 0 && number < 13)+ tm->tm_mon = number-1;+ else if (tm->tm_year < 0) {+ if (number > 1969 && number < 2100)+ tm->tm_year = number - 1900;+ else if (number > 69 && number < 100)+ tm->tm_year = number;+ else if (number < 38)+ tm->tm_year = 100 + number;+ /* We screw up for number = 00 ? */+ }+ }+}++static unsigned long approxidate_str(const char *date,+ const struct timeval *tv,+ int *error_ret)+{+ int number = 0;+ int touched = 0;+ struct tm tm, now;+ time_t time_sec;++ time_sec = tv->tv_sec;+ localtime_r(&time_sec, &tm);+ now = tm;++ tm.tm_year = -1;+ tm.tm_mon = -1;+ tm.tm_mday = -1;++ for (;;) {+ unsigned char c = *date;+ if (!c)+ break;+ date++;+ if (isdigit(c)) {+ pending_number(&tm, &number);+ date = approxidate_digit(date-1, &tm, &number);+ touched = 1;+ continue;+ }+ if (isalpha(c))+ date = approxidate_alpha(date-1, &tm, &now, &number, &touched);+ }+ pending_number(&tm, &number);+ if (!touched)+ *error_ret = 1;+ return update_tm(&tm, &now, 0);+}++unsigned long approxidate_relative(const char *date, const struct timeval *tv)+{+ unsigned long timestamp;+ int offset;+ int errors = 0;++ if (!parse_date_basic(date, ×tamp, &offset))+ return timestamp;+ return approxidate_str(date, tv, &errors);+}++unsigned long approxidate_careful(const char *date, int *error_ret)+{+ struct timeval tv;+ unsigned long timestamp;+ int offset;+ int dummy = 0;+ if (!error_ret)+ error_ret = &dummy;++ if (!parse_date_basic(date, ×tamp, &offset)) {+ *error_ret = 0;+ return timestamp;+ }++ gettimeofday(&tv, NULL);+ return approxidate_str(date, &tv, error_ret);+}
+ git-date.cabal view
@@ -0,0 +1,40 @@+name: git-date+version: 0.1+cabal-version: >= 1.8+license: GPL-2+license-file: COPYING+category: Data+copyright: © 2012 Stephen Paul Weber+author: Stephen Paul Weber <singpolyma@singpolyma.net>+maintainer: Stephen Paul Weber <singpolyma@singpolyma.net>+stability: experimental+tested-with: GHC == 7.0.3+synopsis: Bindings to the date parsing from Git.+homepage: https://github.com/singpolyma/git-date-haskell+bug-reports: https://github.com/singpolyma/git-date-haskell/issues+build-type: Simple+description:+ This library implements a simple, pure binding onto the date parsing+ code from git. Why? Because it was the only reasonable implementation+ of a generic date parser I could find.++extra-source-files:+ README,+ ext/date.c++library+ c-sources: ext/date.c+ include-dirs: ext++ exposed-modules:+ Data.Time.Git++ build-depends:+ base == 4.*,+ bytestring,+ utf8-string,+ time++source-repository head+ type: git+ location: git://github.com/singpolyma/git-date-haskell.git