Reputation: 2497
Input
2012-07-24 10:05:08 AM
2012-07-26 10:13:58 AM
2012-07-24 10:13:58 AM
2012-07-24 10:57:50 AM
2012-07-24 11:15:03 AM
2012-07-24 11:26:08 PM
2012-07-25 11:26:08 PM
Desired Output
2012-07-24 10:05:08 AM
2012-07-24 10:13:58 AM
2012-07-24 10:57:50 AM
2012-07-24 11:15:03 AM
2012-07-24 11:26:08 PM
2012-07-25 11:26:08 PM
2012-07-26 10:13:58 AM
Code I tried
sort -t ":" -k 1 -k 2 -k 3 Input.txt | sort -t " " -k 3
But I am not getting desired output.
Can anyone suggest anything?
I wrote a code... but still problem persists...
Code
sed 's/ 12:/00:/g' Input.txt | sort -k 1,1 -k 3,3 -k 2,2 | sed 's/00:/12:/g'
First change 12:43:01 AM to 00:43:01 AM....and then apply sort command.
Upvotes: 4
Views: 2446
Reputation: 2497
Finally I coded without using any external modules. Though its lengthy but working smoothly for any date format.
Technique Used :
Code
my @input = `cat Input.txt`;
open (ts,">","tt.txt");
foreach my $i (@input)
{
chomp($i);
my $timestamp = `date --date "$i" +\%s`;
chomp($timestamp);
push (@time,$timestamp);
print ts "$timestamp\n";
}
close(ts);
open (ts,">","sort_time.txt");
my @sorted_time = join "\n",sort {$a<=>$b} @time;
chomp(@sorted_time);
print ts "@sorted_time\n";
close(ts);
my @input1= `cat sort_time.txt`;
open (ts,">","sort_timestamp.txt");
foreach my $st1 (@input1)
{
chomp($st1);
my $st2 = scalar localtime($st1);
chomp($st2);
print ts "$st2\n";
}
close(ts);
@input2 = `cat sort_timestamp.txt`;
open (ts,">","Output.txt");
foreach my $st2 (@input2)
{
chomp($st2);
$pro_time = `date --date "$st2" +\%Y-\%m-\%d~\%r | sed 's/~/ /g'`;
chomp($pro_time);
print ts "$pro_time\n";
}
close(ts);
`rm tt.txt sort_time.txt sort_timestamp.txt`;
Upvotes: -1
Reputation: 26121
You can use this:
sed 's/ 12:/ 00:/'| LC_ALL="C" sort -k 1,1 -k 3 | sed 's/ 00:/ 12:/'
It should be pretty fast solution.
Upvotes: 1
Reputation: 58473
This might work for you (GNU sed):
sed 's/.*/echo -e "$(date -d"&" +%s)\t&"/e' file | sort -n | sed 's/.*\t//'
or:
date -f file +%s | paste - file | sort -n | sed 's/\S\+\s\+//'
Upvotes: 0
Reputation: 84393
Assuming that your data is stored in /tmp/foo, you can convert the timestamp into a numerically-sortable format with GNU date. For example:
date -f /tmp/foo '+%s' | sort |
while read; do
date -d "@$REPLY" "+%F %I:%M:%S %p"
done
This should correctly handle the sort in all cases, and especially the cases where all AM times should come before all PM times on the same date. For example, 12:01 AM is now listed before 10:00 PM.
Upvotes: 4
Reputation: 91488
Using Schartzian Transform and Date::Parse :
use strict;
use warnings;
use 5.010;
use Date::Parse;
my @data = <DATA>;
chomp @data;
my @sorted =
map { $_->[0] }
sort { $a->[1] <=> $b->[1] }
map { [$_, str2time($_)] } @data;
say for @sorted;
__DATA__
2012-07-24 11:15:03 AM
2012-07-24 11:26:08 PM
2012-07-25 01:26:08 PM
2012-07-25 12:26:08 PM
2012-07-25 01:26:08 AM
2012-07-25 12:26:08 AM
2012-07-25 11:26:08 AM
2012-07-25 11:26:08 PM
output:
2012-07-24 11:15:03 AM
2012-07-24 11:26:08 PM
2012-07-25 12:26:08 AM
2012-07-25 01:26:08 AM
2012-07-25 11:26:08 AM
2012-07-25 12:26:08 PM
2012-07-25 01:26:08 PM
2012-07-25 11:26:08 PM
Upvotes: 2
Reputation: 126742
The strings could simply be sorted lexically except for the 12-hour times.
This solution uses the Schwartzian Transform to change the key that is used to sort the strings. It just adds twelve to the hour field of any string ending with PM
and sorts by that instead.
use strict;
use warnings;
my @data = <DATA>;
chomp @data;
my @sorted = map $_->[0],
sort { $a->[1] cmp $b->[1] }
map { (my $dt = $_) =~ s/(\d\d)(?=:\d\d:\d\d PM)/$1+12/e; [$_, $dt] } @data;
print "$_\n" for @sorted;
__DATA__
2012-07-24 10:05:08 AM
2012-07-26 10:13:58 AM
2012-07-24 10:13:58 AM
2012-07-24 10:57:50 AM
2012-07-24 11:15:03 AM
2012-07-24 11:26:08 PM
2012-07-25 11:26:08 PM
output
2012-07-24 10:05:08 AM
2012-07-24 10:13:58 AM
2012-07-24 10:57:50 AM
2012-07-24 11:15:03 AM
2012-07-24 11:26:08 PM
2012-07-25 11:26:08 PM
2012-07-26 10:13:58 AM
Update
As steffen has pointed out, even after adjusting the hours for am/pm, midnight and midday still prevent a simple string sort from working.
This program uses the core Time::Piece
module to reformat the date/times in ISO 8601 format 2000-02-29T12:34:56
which can be sorted lexically.
use strict;
use warnings;
use Time::Piece;
my @data = <DATA>;
chomp @data;
my @sorted = map $_->[0],
sort { $a->[1] cmp $b->[1] }
map { [ $_, toISO8601($_) ] } @data;
sub toISO8601 {
Time::Piece->strptime(@_, '%Y-%m-%d %I:%M:%S %p')->datetime;
}
print "$_\n" for @sorted;
__DATA__
2012-07-24 10:05:08 AM
2012-07-26 10:13:58 AM
2012-07-24 10:13:58 AM
2012-07-24 10:57:50 AM
2012-07-24 11:15:03 AM
2012-07-24 11:26:08 PM
2012-07-25 11:26:08 PM
2012-08-01 01:00:00 PM
2012-08-01 12:30:00 PM
2012-08-01 12:00:00 PM
2012-08-01 11:30:00 AM
2012-08-01 01:00:00 AM
2012-08-01 12:30:00 AM
2012-08-01 12:00:00 AM
output
2012-07-24 10:05:08 AM
2012-07-24 10:13:58 AM
2012-07-24 10:57:50 AM
2012-07-24 11:15:03 AM
2012-07-24 11:26:08 PM
2012-07-25 11:26:08 PM
2012-07-26 10:13:58 AM
2012-08-01 12:00:00 AM
2012-08-01 12:30:00 AM
2012-08-01 01:00:00 AM
2012-08-01 11:30:00 AM
2012-08-01 12:00:00 PM
2012-08-01 12:30:00 PM
2012-08-01 01:00:00 PM
Upvotes: 3
Reputation: 15214
There is my variant:
$sed 's|\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\) \([0-9]\+\):\([0-9]\+\):\([0-9]\+\) \([A-Z]\+\)|\1 \2 \3 \4 \5 \6 \7 \0|' input.txt | awk '{if($7=="AM"){$7="1";if($4==12){$4 = 0}}else{$7="0"};print}' | sort -n -k1 -k2 -k3 -k4 -k5 -k6 -k7 | cut -d' ' -f 8-
2012-07-24 10:05:08 PM
2012-07-24 10:13:58 AM
2012-07-24 10:57:50 AM
2012-07-24 11:15:03 AM
2012-07-24 11:26:08 PM
2012-07-25 11:26:08 PM
2012-07-26 10:13:58 AM
The main idea to add extra fields, sort by them and after sorted I get rid of them. Sorting numerics is simple, but to sort AM/PM I convert it to 1/0 digits to simplify sorting.
Updated: sed
+ awk
usage may be replaced by awk
:
awk -F'[-: ]' '{printf("%d %d %d %d %d %d %d %s\n", $1, $2, $3, ($4 == 12 && $7 == "AM" ? 0 : $4), $5, $6, $7 == "AM", $0)}' input.txt |
sort -n -k1 -k2 -k3 -k4 -k5 -k6 -k7 |
cut -d' ' -f 8-
Updated: fix AM/PM issue
Upvotes: 0
Reputation: 8968
a little bit awk
ward, I admit...
cat Input.txt | \
awk 'BEGIN{FS="[: -]"}{if($7 == "PM") $4+=12; print $1"-"$2"-"$3" "$4":"$5":"$6" "$7}'|\
sort|\
awk 'BEGIN{FS="[: -]"}{if($7 == "PM") $4-=12; print $1"-"$2"-"$3" "$4":"$5":"$6" "$7}'
edit:
cat Input.txt |\
awk 'BEGIN{FS="[: -]"}{if(length($4)==1) $4="0"$4 ;if($7 == "PM") $4+=12; else if($4 ==12)$4-=12; print $1"-"$2"-"$3" "$4":"$5":"$6" "$7}'|\
sort|\
awk 'BEGIN{FS="[: -]"}{if($7 == "PM") $4-=12; else if($4 ==0)$4+=12; print $1"-"$2"-"$3" "$4":"$5":"$6" "$7}'
But it works...
explanation: I convert the time format to 24 hours using awk
, sort it and convert it back.
edit: I prepend a 0
to hours with only one digit in order to get 1:0:0
and 12:0:0
sorted right. Also for AM.
Upvotes: 2
Reputation: 4456
12:01 AM comes before 1:01 AM, so I can't see how using just sort will help.
You need to convert to some other format, e.g. ISO 8601 or seconds since whenever to get something that can be compared as text or numbers. A perl oneliner will do that.
Upvotes: 1